From 84477867e122251b8c47db240a8d699b0836aa6f Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Thu, 30 Apr 2026 11:02:54 +0200 Subject: [PATCH 001/641] tty: xtensa/iss: use strnlen to improve iss_console_write Use strnlen() to limit scanning 's' to 'count' bytes. Use the length of 's' to decide if simc_write() should be called instead of dereferencing it first and then calling strlen(). With strnlen(), iss_console_write() is further hardened against callers where 's' is not NUL-terminated. Signed-off-by: Thorsten Blum Message-ID: <20260430090253.237654-3-thorsten.blum@linux.dev> Signed-off-by: Max Filippov --- arch/xtensa/platforms/iss/console.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/arch/xtensa/platforms/iss/console.c b/arch/xtensa/platforms/iss/console.c index 8b95221375a8..8e54625cb2ba 100644 --- a/arch/xtensa/platforms/iss/console.c +++ b/arch/xtensa/platforms/iss/console.c @@ -166,8 +166,9 @@ late_initcall(rs_init); static void iss_console_write(struct console *co, const char *s, unsigned count) { - if (s && *s != 0) - simc_write(1, s, min(count, strlen(s))); + count = s ? strnlen(s, count) : 0; + if (count) + simc_write(1, s, count); } static struct tty_driver* iss_console_device(struct console *c, int *index) From d5bbc9b3d20c3f90b31a64c62bb4a800b9a5941c Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 5 May 2026 09:22:29 +0100 Subject: [PATCH 002/641] ARM: 9473/1: kprobes: test: add MODULE_DESCRIPTION All loadable modules must have a description, and since commit 6c6c1fc09de3 ("modpost: require a MODULE_DESCRIPTION()"), the kernel build warns about it missing: WARNING: modpost: missing MODULE_DESCRIPTION() in arch/arm/probes/kprobes/test-kprobes.o Add the missing description tags. Noticing that the existing license tag is unnecessarily hidden in an #ifdef section, remove the #ifdef and use conventional placing of the initcall and module_exit tags as well as the license and description. Tested-by: Randy Dunlap # build-tested Reviewed-by: Randy Dunlap Signed-off-by: Arnd Bergmann Signed-off-by: Russell King --- arch/arm/probes/kprobes/test-core.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/arch/arm/probes/kprobes/test-core.c b/arch/arm/probes/kprobes/test-core.c index 7a2baa135f0f..2de28088dac8 100644 --- a/arch/arm/probes/kprobes/test-core.c +++ b/arch/arm/probes/kprobes/test-core.c @@ -1649,24 +1649,16 @@ out: return ret; } - +late_initcall(run_all_tests); /* * Module setup */ -#ifdef MODULE - static void __exit kprobe_test_exit(void) { } - -module_init(run_all_tests) module_exit(kprobe_test_exit) + +MODULE_DESCRIPTION("Test code for ARM kprobes"); MODULE_LICENSE("GPL"); - -#else /* !MODULE */ - -late_initcall(run_all_tests); - -#endif From a7c958e8721eb2724ee2e2ef13129bc67e1c8a75 Mon Sep 17 00:00:00 2001 From: Ethan Nelson-Moore Date: Mon, 15 Jun 2026 15:49:54 -0700 Subject: [PATCH 003/641] xtensa: correct CONFIG_XTENSA_CALIBRATE_CCOUNT macro name in comment A comment in arch/xtensa/include/asm/platform.h incorrectly refers to CONFIG_XTENSA_CALIBRATE instead of CONFIG_XTENSA_CALIBRATE_CCOUNT. Correct it. Discovered while searching for CONFIG_* symbols referenced in code but not defined in any Kconfig file. Signed-off-by: Ethan Nelson-Moore Message-ID: <20260615224957.21574-1-enelsonmoore@gmail.com> Signed-off-by: Max Filippov --- arch/xtensa/include/asm/platform.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/xtensa/include/asm/platform.h b/arch/xtensa/include/asm/platform.h index 94f13fabf7cd..f2e50fc12b4d 100644 --- a/arch/xtensa/include/asm/platform.h +++ b/arch/xtensa/include/asm/platform.h @@ -33,7 +33,7 @@ extern void platform_setup (char **); extern void platform_idle (void); /* - * platform_calibrate_ccount calibrates cpu clock freq (CONFIG_XTENSA_CALIBRATE) + * platform_calibrate_ccount calibrates cpu clock freq (CONFIG_XTENSA_CALIBRATE_CCOUNT) */ extern void platform_calibrate_ccount (void); From 749c5102487b558cc7d73961a6d0da2fceaa022d Mon Sep 17 00:00:00 2001 From: "Borislav Petkov (AMD)" Date: Fri, 12 Jun 2026 08:35:50 -0700 Subject: [PATCH 004/641] EDAC/mpc85xx: Orphan it Johannes doesn't have the hardware to test patches on it anymore and TTBOMK, no one else has shown interest so orphan the driver, for now at least. Signed-off-by: Borislav Petkov (AMD) Acked-by: Johannes Thumshirn Link: https://patch.msgid.link/20260612153839.GCaiwn_7qOic4KLF8P@fat_crate.local --- MAINTAINERS | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 15011f5752a9..7b37c88143f6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9414,9 +9414,8 @@ S: Maintained F: drivers/edac/igen6_edac.c EDAC-MPC85XX -M: Johannes Thumshirn L: linux-edac@vger.kernel.org -S: Maintained +S: Orphan F: drivers/edac/mpc85xx_edac.[ch] EDAC-NPCM From 90cfd27df4ba5f1c18a3454eb4b454bfe6baaf36 Mon Sep 17 00:00:00 2001 From: Yazen Ghannam Date: Mon, 29 Jun 2026 11:07:29 -0400 Subject: [PATCH 005/641] EDAC/debugfs: Remove the fake_inject debugfs interface The interface has a potential race condition between a real and fake error when updating the memory controller's error descriptor. There doesn't seem to be an active user base for this interface, so remove it. Closes: https://sashiko.dev/#/patchset/20260518160716.171578-1-yazen.ghannam%40amd.com Reported-by: sashiko-bot Suggested-by: Borislav Petkov Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Yazen Ghannam Signed-off-by: Borislav Petkov (AMD) Link: https://lore.kernel.org/linux-edac/20260611012336.GHaioOGB0NBxv5BZXS@fat_crate.local --- drivers/edac/debugfs.c | 65 +----------------------------------------- include/linux/edac.h | 3 -- 2 files changed, 1 insertion(+), 67 deletions(-) diff --git a/drivers/edac/debugfs.c b/drivers/edac/debugfs.c index 8195fc9c9354..447d0c620082 100644 --- a/drivers/edac/debugfs.c +++ b/drivers/edac/debugfs.c @@ -1,50 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-only -#include - #include "edac_module.h" static struct dentry *edac_debugfs; -static ssize_t edac_fake_inject_write(struct file *file, - const char __user *data, - size_t count, loff_t *ppos) -{ - struct device *dev = file->private_data; - struct mem_ctl_info *mci = to_mci(dev); - static enum hw_event_mc_err_type type; - u16 errcount = mci->fake_inject_count; - - if (!errcount) - errcount = 1; - - type = mci->fake_inject_ue ? HW_EVENT_ERR_UNCORRECTED - : HW_EVENT_ERR_CORRECTED; - - printk(KERN_DEBUG - "Generating %d %s fake error%s to %d.%d.%d to test core handling. NOTE: this won't test the driver-specific decoding logic.\n", - errcount, - (type == HW_EVENT_ERR_UNCORRECTED) ? "UE" : "CE", - str_plural(errcount), - mci->fake_inject_layer[0], - mci->fake_inject_layer[1], - mci->fake_inject_layer[2] - ); - edac_mc_handle_error(type, mci, errcount, 0, 0, 0, - mci->fake_inject_layer[0], - mci->fake_inject_layer[1], - mci->fake_inject_layer[2], - "FAKE ERROR", "for EDAC testing only"); - - return count; -} - -static const struct file_operations debug_fake_inject_fops = { - .open = simple_open, - .write = edac_fake_inject_write, - .llseek = generic_file_llseek, -}; - void __init edac_debugfs_init(void) { edac_debugfs = debugfs_create_dir("edac", NULL); @@ -57,29 +16,7 @@ void edac_debugfs_exit(void) void edac_create_debugfs_nodes(struct mem_ctl_info *mci) { - struct dentry *parent; - char name[80]; - int i; - - parent = debugfs_create_dir(mci->dev.kobj.name, edac_debugfs); - - for (i = 0; i < mci->n_layers; i++) { - sprintf(name, "fake_inject_%s", - edac_layer_name[mci->layers[i].type]); - debugfs_create_u8(name, S_IRUGO | S_IWUSR, parent, - &mci->fake_inject_layer[i]); - } - - debugfs_create_bool("fake_inject_ue", S_IRUGO | S_IWUSR, parent, - &mci->fake_inject_ue); - - debugfs_create_u16("fake_inject_count", S_IRUGO | S_IWUSR, parent, - &mci->fake_inject_count); - - debugfs_create_file("fake_inject", S_IWUSR, parent, &mci->dev, - &debug_fake_inject_fops); - - mci->debugfs = parent; + mci->debugfs = debugfs_create_dir(mci->dev.kobj.name, edac_debugfs); } /* Create a toplevel dir under EDAC's debugfs hierarchy */ diff --git a/include/linux/edac.h b/include/linux/edac.h index e6b4e51130e5..f7a8218f9cc0 100644 --- a/include/linux/edac.h +++ b/include/linux/edac.h @@ -598,9 +598,6 @@ struct mem_ctl_info { int op_state; struct dentry *debugfs; - u8 fake_inject_layer[EDAC_MAX_LAYERS]; - bool fake_inject_ue; - u16 fake_inject_count; /* * Memory Controller hierarchy From 07897bdf7a9c60455a175f6eb619c7d95e1d1765 Mon Sep 17 00:00:00 2001 From: Abhinav Ananthu Date: Fri, 20 Jun 2025 01:51:34 +0530 Subject: [PATCH 006/641] EDAC/sysfs: Use sysfs_emit_at() in dimmdev_location_show() Replace the use of scnprintf() with sysfs_emit_at() in dimmdev_location_show() to format the output into the sysfs buffer and thus improve clarity and ensure proper bounds checking in line with the preferred sysfs_emit() API usage for sysfs 'show' functions. No functional change intended. [ bp: Massage commit message. ] Signed-off-by: Abhinav Ananthu Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Qiuxu Zhuo Link: https://patch.msgid.link/20250619202133.11843-1-abhinav.ogl@gmail.com --- drivers/edac/edac_mc_sysfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/edac/edac_mc_sysfs.c b/drivers/edac/edac_mc_sysfs.c index c2ed6c696e54..9b4b5582fa9f 100644 --- a/drivers/edac/edac_mc_sysfs.c +++ b/drivers/edac/edac_mc_sysfs.c @@ -129,7 +129,7 @@ static ssize_t dimmdev_location_show(struct device *dev, ssize_t count; count = edac_dimm_info_location(dimm, data, PAGE_SIZE); - count += scnprintf(data + count, PAGE_SIZE - count, "\n"); + count += sysfs_emit_at(data, count, "\n"); return count; } From 53637506884dbd5c91a89b1a3547d99d80f8ed2c Mon Sep 17 00:00:00 2001 From: Yousef Alhouseen Date: Wed, 24 Jun 2026 19:53:53 +0200 Subject: [PATCH 007/641] ipmi: ipmb: validate write message length ipmb_write() read message fields before validating the length byte. A zero or short write can read uninitialized stack bytes. A length smaller than the SMBus header underflows the block write length. Require a non-empty buffer and the minimum IPMB request length. Also require the length byte plus payload before parsing the message. Fixes: 51bd6f291583 ("Add support for IPMB driver") Cc: stable@vger.kernel.org Signed-off-by: Yousef Alhouseen Message-ID: <20260624175353.8592-1-alhouseenyousef@gmail.com> Signed-off-by: Corey Minyard --- drivers/char/ipmi/ipmb_dev_int.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/char/ipmi/ipmb_dev_int.c b/drivers/char/ipmi/ipmb_dev_int.c index 680ff15c30ab..e4c50d9ae3e1 100644 --- a/drivers/char/ipmi/ipmb_dev_int.c +++ b/drivers/char/ipmi/ipmb_dev_int.c @@ -141,13 +141,14 @@ static ssize_t ipmb_write(struct file *file, const char __user *buf, u8 msg[MAX_MSG_LEN]; ssize_t ret; - if (count > sizeof(msg)) + if (!count || count > sizeof(msg)) return -EINVAL; if (copy_from_user(&msg, buf, count)) return -EFAULT; - if (count < msg[0]) + if (msg[IPMB_MSG_LEN_IDX] < IPMB_REQUEST_LEN_MIN || + count < (size_t)msg[IPMB_MSG_LEN_IDX] + 1) return -EINVAL; rq_sa = GET_7BIT_ADDR(msg[RQ_SA_8BIT_IDX]); From 6d920a75df9a83ab096b3cde7a643b656e4fdfeb Mon Sep 17 00:00:00 2001 From: Seiji Nishikawa Date: Wed, 1 Jul 2026 02:43:48 +0900 Subject: [PATCH 008/641] ipmi: si: Fix NULL pointer dereference after failed registration try_smi_init() allocates new_smi->si_sm and later calls ipmi_register_smi_mod(), which maps to ipmi_add_smi(). During ipmi_add_smi(), the upper IPMI message handler obtains the initial BMC device information through __bmc_get_device_id(). This can fail if the BMC does not return a successful response to the Get Device ID command. When the BMC returns a nonzero completion code, the device-id helper retries the command and eventually returns -EIO if the device ID still cannot be fetched. On this failure path, ipmi_add_smi() logs "Unable to get the device id" and goes to out_err_started, where it invokes the lower driver's shutdown callback. try_smi_init() then logs the returned registration failure: ipmi_si IPI0001:00: IPMI message handler: Unable to get the device id: -5 ipmi_si IPI0001:00: Unable to register device: error -5 For ipmi_si, the shutdown callback is shutdown_smi(), which cleans up the SI state machine data, frees smi_info->si_sm, and sets smi_info->si_sm and smi_info->intf to NULL. However, intf->in_shutdown is not set on this failed-registration rollback path. Therefore, the asynchronous redo_bmc_reg work item can still retry BMC device-id probing after the lower driver has already cleared its SI state machine data. In the observed case, that retry path reached start_next_msg(), which passed the NULL smi_info->si_sm pointer to the selected KCS state machine handler: BUG: unable to handle kernel NULL pointer dereference at 0000000000000000 Workqueue: events redo_bmc_reg [ipmi_msghandler] RIP: start_kcs_transaction+0x2c/0x190 [ipmi_si] Call Trace: start_next_msg+0x50/0x80 [ipmi_si] check_start_timer_thread.part.9+0x3b/0x50 [ipmi_si] sender+0x69/0x80 [ipmi_si] i_ipmi_request+0x2ac/0x9d0 [ipmi_msghandler] __get_device_id.isra.29+0xaa/0x180 [ipmi_msghandler] __bmc_get_device_id+0xef/0x950 [ipmi_msghandler] redo_bmc_reg+0x52/0x60 [ipmi_msghandler] process_one_work+0x1a7/0x360 Set intf->in_shutdown on the out_err_started path before invoking the lower driver's shutdown callback. This prevents later redo_bmc_reg retries from using an interface whose lower driver state has been cleaned up, and applies the same shutdown state to other IPMI interfaces as well. Fixes: 2512e40e48d2 ("ipmi: Rework SMI registration failure") Cc: stable@vger.kernel.org Signed-off-by: Seiji Nishikawa Message-ID: <20260630174348.1483814-1-snishika@redhat.com> Signed-off-by: Corey Minyard --- drivers/char/ipmi/ipmi_msghandler.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/char/ipmi/ipmi_msghandler.c b/drivers/char/ipmi/ipmi_msghandler.c index ab4c85f3d6fe..8d9f2e647d9b 100644 --- a/drivers/char/ipmi/ipmi_msghandler.c +++ b/drivers/char/ipmi/ipmi_msghandler.c @@ -3757,6 +3757,7 @@ int ipmi_add_smi(struct module *owner, out_err_bmc_reg: ipmi_bmc_unregister(intf); out_err_started: + intf->in_shutdown = true; if (intf->handlers->shutdown) intf->handlers->shutdown(intf->send_info); out_err: From 97dfcb871ba776ba0e1ded1cdcbe94a357c2817e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Fri, 3 Jul 2026 19:38:03 +0200 Subject: [PATCH 009/641] MAINTAINERS: Remove Mark Gross from relevant entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sending mail to Mark's Intel address results in the Intel mail server rejecting the mail. Dave Hansen confirmed he left Intel. The kernel.org address seems to work, but there was no reply from Mark on the discussion about broken email settings and his maintainer entries. So drop him from all maintainer entries and move him to credits. Signed-off-by: Uwe Kleine-König Signed-off-by: Borislav Petkov (AMD) Acked-by: Dave Hansen Link: https://patch.msgid.link/20260703173803.3589003-2-ukleinek@kernel.org --- CREDITS | 4 ++++ MAINTAINERS | 7 ++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CREDITS b/CREDITS index 84793a967a0b..091412875a66 100644 --- a/CREDITS +++ b/CREDITS @@ -1503,6 +1503,10 @@ N: Andy Gross E: agross@kernel.org D: Qualcomm SoC subsystem and drivers +N: Mark Gross +E: markgross@kernel.org +D: x86/mellanox platform maintenance and various x86 specific drivers + N: Grant Grundler E: grantgrundler@gmail.com W: http://obmouse.sourceforge.net/ diff --git a/MAINTAINERS b/MAINTAINERS index 7b37c88143f6..f16c09f76e7b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9337,9 +9337,8 @@ S: Supported F: drivers/edac/dmc520_edac.c EDAC-E752X -M: Mark Gross L: linux-edac@vger.kernel.org -S: Maintained +S: Orphan F: drivers/edac/e752x_edac.c EDAC-E7XXX @@ -13201,7 +13200,6 @@ F: drivers/crypto/intel/keembay/ocs-aes.h INTEL KEEM BAY OCS ECC CRYPTO DRIVER M: Prabhjot Khurana -M: Mark Gross S: Maintained F: Documentation/devicetree/bindings/crypto/intel,keembay-ocs-ecc.yaml F: drivers/crypto/intel/keembay/Kconfig @@ -26642,8 +26640,7 @@ S: Maintained F: drivers/net/ethernet/tehuti/tn40* TELECOM CLOCK DRIVER FOR MCPL0010 -M: Mark Gross -S: Supported +S: Orphan F: drivers/char/tlclk.c TEMPO SEMICONDUCTOR DRIVERS From 4c3da04827dc01dc1cfc3d03654b7de656c42d80 Mon Sep 17 00:00:00 2001 From: Yazen Ghannam Date: Mon, 6 Jul 2026 16:21:15 -0500 Subject: [PATCH 010/641] RAS/AMD/ATL, EDAC/amd64: Only load ATL when needed The AMD Address Translation Library (ATL) will attempt to load on all AMD Zen/SMCA systems. However, only systems with DRAM ECC enabled will use the library. Other systems will fail to load the library and produce an unnecessary message to the user. More importantly, that thing is dead code loaded and unused. Remove the ATL module dependency table to prevent autoloading. Request ATL to load from EDAC once all system checks are complete. [ bp: Massage commit message. ] Fixes: 3f3174996be6 ("RAS: Introduce AMD Address Translation Library") Closes: https://lore.kernel.org/20260305154528.1171999-1-mario.limonciello@amd.com Reported-by: Mario Limonciello Signed-off-by: Yazen Ghannam Signed-off-by: Mario Limonciello Signed-off-by: Borislav Petkov (AMD) Tested-by: Deskhmukh Shrirang Link: https://lore.kernel.org/all/20260307144910.GA113343@yaz-khff2.amd.com --- drivers/edac/amd64_edac.c | 2 ++ drivers/ras/amd/atl/core.c | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/edac/amd64_edac.c b/drivers/edac/amd64_edac.c index c6aa69dbd9fb..475235c402e8 100644 --- a/drivers/edac/amd64_edac.c +++ b/drivers/edac/amd64_edac.c @@ -4173,6 +4173,8 @@ static int __init amd64_edac_init(void) goto err_pci; } + request_module_nowait("amd_atl"); + /* register stuff with EDAC MCE */ if (boot_cpu_data.x86 >= 0x17) { amd_register_ecc_decoder(decode_umc_error); diff --git a/drivers/ras/amd/atl/core.c b/drivers/ras/amd/atl/core.c index 0f7cd6dab0b0..d77dacdd4f56 100644 --- a/drivers/ras/amd/atl/core.c +++ b/drivers/ras/amd/atl/core.c @@ -190,7 +190,6 @@ static const struct x86_cpu_id amd_atl_cpuids[] = { X86_MATCH_FEATURE(X86_FEATURE_ZEN, NULL), { } }; -MODULE_DEVICE_TABLE(x86cpu, amd_atl_cpuids); static int __init amd_atl_init(void) { From d4486fc3098e176cb4a29fee037216484761f9ca Mon Sep 17 00:00:00 2001 From: Rounak Das Date: Wed, 8 Jul 2026 13:11:34 +0400 Subject: [PATCH 011/641] EDAC/altera: Use ECC manager compatible to select A10/S10 IRQ layout The SDMMC ECC IRQ layout selection uses CONFIG_64BIT to distinguish between Arria10 and Stratix10 paths. Detect the SoC once at probe via the device match table (.data) store it in struct altr_arria10_edac, and use it instead of CONFIG_64BIT. This keeps the decision correct for every ECC child device (OCRAM, SD/MMC, etc.) and avoids any runtime compatible lookup. Signed-off-by: Rounak Das Signed-off-by: Borislav Petkov (AMD) Acked-by: Dinh Nguyen Link: https://patch.msgid.link/20260708091135.94114-2-rounakdas2025@gmail.com --- drivers/edac/altera_edac.c | 107 +++++++++++++++++++------------------ drivers/edac/altera_edac.h | 1 + 2 files changed, 55 insertions(+), 53 deletions(-) diff --git a/drivers/edac/altera_edac.c b/drivers/edac/altera_edac.c index 4edd2088c2db..24bdf7f5bac6 100644 --- a/drivers/edac/altera_edac.c +++ b/drivers/edac/altera_edac.c @@ -1507,6 +1507,7 @@ static int altr_portb_setup(struct altr_edac_device_dev *device) int edac_idx, rc; struct device_node *np; const struct edac_device_prv_data *prv = &a10_sdmmceccb_data; + bool is_s10 = device->edac->is_s10; rc = altr_check_ecc_deps(device); if (rc) @@ -1548,15 +1549,14 @@ static int altr_portb_setup(struct altr_edac_device_dev *device) /* * Update the PortB IRQs - A10 has 4, S10 has 2, Index accordingly - * - * FIXME: Instead of ifdefs with different architectures the driver - * should properly use compatibles. */ -#ifdef CONFIG_64BIT - altdev->sb_irq = irq_of_parse_and_map(np, 1); -#else - altdev->sb_irq = irq_of_parse_and_map(np, 2); -#endif + + /* Using compatibles to determine the IRQ Index */ + if (is_s10) + altdev->sb_irq = irq_of_parse_and_map(np, 1); + else + altdev->sb_irq = irq_of_parse_and_map(np, 2); + if (!altdev->sb_irq) { edac_printk(KERN_ERR, EDAC_DEVICE, "Error PortB SBIRQ alloc\n"); rc = -ENODEV; @@ -1570,29 +1570,28 @@ static int altr_portb_setup(struct altr_edac_device_dev *device) goto err_release_group_1; } -#ifdef CONFIG_64BIT - /* Use IRQ to determine SError origin instead of assigning IRQ */ - rc = of_property_read_u32_index(np, "interrupts", 1, &altdev->db_irq); - if (rc) { - edac_printk(KERN_ERR, EDAC_DEVICE, - "Error PortB DBIRQ alloc\n"); - goto err_release_group_1; + if (is_s10) { + /* Use IRQ to determine SError origin instead of assigning IRQ */ + rc = of_property_read_u32_index(np, "interrupts", 1, &altdev->db_irq); + if (rc) { + edac_printk(KERN_ERR, EDAC_DEVICE, "Error PortB DBIRQ alloc\n"); + goto err_release_group_1; + } + } else { + altdev->db_irq = irq_of_parse_and_map(np, 3); + if (!altdev->db_irq) { + edac_printk(KERN_ERR, EDAC_DEVICE, "Error PortB DBIRQ alloc\n"); + rc = -ENODEV; + goto err_release_group_1; + } + rc = devm_request_irq(&altdev->ddev, altdev->db_irq, + prv->ecc_irq_handler, IRQF_TRIGGER_HIGH, + ecc_name, altdev); + if (rc) { + edac_printk(KERN_ERR, EDAC_DEVICE, "PortB DBERR IRQ error\n"); + goto err_release_group_1; + } } -#else - altdev->db_irq = irq_of_parse_and_map(np, 3); - if (!altdev->db_irq) { - edac_printk(KERN_ERR, EDAC_DEVICE, "Error PortB DBIRQ alloc\n"); - rc = -ENODEV; - goto err_release_group_1; - } - rc = devm_request_irq(&altdev->ddev, altdev->db_irq, - prv->ecc_irq_handler, IRQF_TRIGGER_HIGH, - ecc_name, altdev); - if (rc) { - edac_printk(KERN_ERR, EDAC_DEVICE, "PortB DBERR IRQ error\n"); - goto err_release_group_1; - } -#endif rc = edac_device_add_device(dci); if (rc) { @@ -1974,29 +1973,29 @@ static int altr_edac_a10_device_add(struct altr_arria10_edac *edac, goto err_release_group1; } -#ifdef CONFIG_64BIT - /* Use IRQ to determine SError origin instead of assigning IRQ */ - rc = of_property_read_u32_index(np, "interrupts", 0, &altdev->db_irq); - if (rc) { - edac_printk(KERN_ERR, EDAC_DEVICE, - "Unable to parse DB IRQ index\n"); - goto err_release_group1; + if (edac->is_s10) { + /* Use IRQ to determine SError origin instead of assigning IRQ */ + rc = of_property_read_u32_index(np, "interrupts", 0, &altdev->db_irq); + if (rc) { + edac_printk(KERN_ERR, EDAC_DEVICE, + "Unable to parse DB IRQ index\n"); + goto err_release_group1; + } + } else { + altdev->db_irq = irq_of_parse_and_map(np, 1); + if (!altdev->db_irq) { + edac_printk(KERN_ERR, EDAC_DEVICE, "Error allocating DBIRQ\n"); + rc = -ENODEV; + goto err_release_group1; + } + rc = devm_request_irq(edac->dev, altdev->db_irq, prv->ecc_irq_handler, + IRQF_TRIGGER_HIGH, + ecc_name, altdev); + if (rc) { + edac_printk(KERN_ERR, EDAC_DEVICE, "No DBERR IRQ resource\n"); + goto err_release_group1; + } } -#else - altdev->db_irq = irq_of_parse_and_map(np, 1); - if (!altdev->db_irq) { - edac_printk(KERN_ERR, EDAC_DEVICE, "Error allocating DBIRQ\n"); - rc = -ENODEV; - goto err_release_group1; - } - rc = devm_request_irq(edac->dev, altdev->db_irq, prv->ecc_irq_handler, - IRQF_TRIGGER_HIGH, - ecc_name, altdev); - if (rc) { - edac_printk(KERN_ERR, EDAC_DEVICE, "No DBERR IRQ resource\n"); - goto err_release_group1; - } -#endif rc = edac_device_add_device(dci); if (rc) { @@ -2122,6 +2121,8 @@ static int altr_edac_a10_probe(struct platform_device *pdev) platform_set_drvdata(pdev, edac); INIT_LIST_HEAD(&edac->a10_ecc_devices); + edac->is_s10 = !!device_get_match_data(&pdev->dev); + edac->ecc_mgr_map = altr_sysmgr_regmap_lookup_by_phandle(pdev->dev.of_node, "altr,sysmgr-syscon"); @@ -2207,7 +2208,7 @@ static int altr_edac_a10_probe(struct platform_device *pdev) static const struct of_device_id altr_edac_a10_of_match[] = { { .compatible = "altr,socfpga-a10-ecc-manager" }, - { .compatible = "altr,socfpga-s10-ecc-manager" }, + { .compatible = "altr,socfpga-s10-ecc-manager", .data = (void *)1 }, {}, }; MODULE_DEVICE_TABLE(of, altr_edac_a10_of_match); diff --git a/drivers/edac/altera_edac.h b/drivers/edac/altera_edac.h index f3e84172caa9..9387056fd65e 100644 --- a/drivers/edac/altera_edac.h +++ b/drivers/edac/altera_edac.h @@ -394,6 +394,7 @@ struct altr_arria10_edac { struct irq_chip irq_chip; struct list_head a10_ecc_devices; struct notifier_block panic_notifier; + bool is_s10; }; #endif /* #ifndef _ALTERA_EDAC_H */ From 11f5fd36076a2ef229ec5062c06954c955d90f9d Mon Sep 17 00:00:00 2001 From: Rounak Das Date: Wed, 8 Jul 2026 13:11:35 +0400 Subject: [PATCH 012/641] EDAC/altera: Remove remaining CONFIG_64BIT ifdefs in the DB-error path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the remaining two CONFIG_64BIT ifdefs with the is_s10 flag, so driver behavior is fully determined by the ECC manager's compatible string rather than the build architecture. These two ifdefs guard the double-bit-error path, where SError handling and the arm_smccc_smc() reboot call are arm64-specific. Switching to is_s10 means s10_edac_dberr_handler() now compiles on 32-bit as well — this is safe because all the symbols it depends on (arm_smccc_smc, INTEL_SIP_SMC_ECC_DBE, and the S10 sysmgr defines) are already available on 32-bit socfpga. Since the function only executes when is_s10 is true, Arria10 behavior is unaffected. This is handled separately from the IRQ-index selection change, as the double-bit-error path is a distinct concern. Signed-off-by: Rounak Das Signed-off-by: Borislav Petkov (AMD) Acked-by: Dinh Nguyen Assisted-by: Claude:claude-sonnet-5 Link: https://patch.msgid.link/20260708091135.94114-3-rounakdas2025@gmail.com --- drivers/edac/altera_edac.c | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/drivers/edac/altera_edac.c b/drivers/edac/altera_edac.c index 24bdf7f5bac6..1d1e2b5ca14c 100644 --- a/drivers/edac/altera_edac.c +++ b/drivers/edac/altera_edac.c @@ -2058,7 +2058,6 @@ static const struct irq_domain_ops a10_eccmgr_ic_ops = { /************** Stratix 10 EDAC Double Bit Error Handler ************/ #define to_a10edac(p, m) container_of(p, struct altr_arria10_edac, m) -#ifdef CONFIG_64BIT /* panic routine issues reboot on non-zero panic_timeout */ extern int panic_timeout; @@ -2105,7 +2104,6 @@ static int s10_edac_dberr_handler(struct notifier_block *this, return NOTIFY_DONE; } -#endif /****************** Arria 10 EDAC Probe Function *********************/ static int altr_edac_a10_probe(struct platform_device *pdev) @@ -2154,8 +2152,7 @@ static int altr_edac_a10_probe(struct platform_device *pdev) irq_set_chained_handler_and_data(edac->sb_irq, altr_edac_a10_irq_handler, edac); - -#ifdef CONFIG_64BIT + if (edac->is_s10) { int dberror, err_addr; @@ -2178,15 +2175,14 @@ static int altr_edac_a10_probe(struct platform_device *pdev) regmap_write(edac->ecc_mgr_map, S10_SYSMGR_UE_ADDR_OFST, 0); } - } -#else - edac->db_irq = platform_get_irq(pdev, 1); - if (edac->db_irq < 0) - return edac->db_irq; + } else { + edac->db_irq = platform_get_irq(pdev, 1); + if (edac->db_irq < 0) + return edac->db_irq; - irq_set_chained_handler_and_data(edac->db_irq, - altr_edac_a10_irq_handler, edac); -#endif + irq_set_chained_handler_and_data(edac->db_irq, + altr_edac_a10_irq_handler, edac); + } for_each_child_of_node(pdev->dev.of_node, child) { if (!of_device_is_available(child)) From e09afa69e3f5d5a304940cf4c6ea17642a1e3993 Mon Sep 17 00:00:00 2001 From: Radhey Shyam Pandey Date: Thu, 25 Jun 2026 16:07:49 +0530 Subject: [PATCH 013/641] MAINTAINERS: Add Radhey Shyam Pandey as Xilinx EDAC reviewer I have volunteered to review Xilinx EDAC related changes. Add myself as a reviewer to stay aligned with ongoing patch activity and actively contribute to this subsystem. Signed-off-by: Radhey Shyam Pandey Signed-off-by: Borislav Petkov (AMD) Link: https://patch.msgid.link/20260625103749.1416083-1-radhey.shyam.pandey@amd.com --- MAINTAINERS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index f16c09f76e7b..d413dac5c8b3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -29589,12 +29589,14 @@ F: include/uapi/linux/xilinx-v4l2-controls.h XILINX VERSAL EDAC DRIVER M: Shubhrajyoti Datta M: Sai Krishna Potthuri +R: Radhey Shyam Pandey S: Maintained F: Documentation/devicetree/bindings/memory-controllers/xlnx,versal-ddrmc-edac.yaml F: drivers/edac/versal_edac.c XILINX VERSALNET EDAC DRIVER M: Shubhrajyoti Datta +R: Radhey Shyam Pandey S: Maintained F: Documentation/devicetree/bindings/memory-controllers/xlnx,versal-net-ddrmc5.yaml F: drivers/edac/versalnet_edac.c @@ -29632,6 +29634,7 @@ F: include/dt-bindings/dma/xlnx-zynqmp-dpdma.h XILINX ZYNQMP OCM EDAC DRIVER M: Shubhrajyoti Datta M: Sai Krishna Potthuri +R: Radhey Shyam Pandey S: Maintained F: Documentation/devicetree/bindings/memory-controllers/xlnx,zynqmp-ocmc-1.0.yaml F: drivers/edac/zynqmp_edac.c From b7088d58dccfba87fe8dd2ab7c493ee1d9d09277 Mon Sep 17 00:00:00 2001 From: Jens Glathe Date: Mon, 8 Jun 2026 06:54:39 +0200 Subject: [PATCH 014/641] drm/msm/dp: add missing drm_edid_connector_update() before add_modes on cached EDID After the refactor to struct drm_edid, the fast path in msm_dp_panel_get_modes() that already held a cached EDID called drm_edid_connector_add_modes() directly without first calling drm_edid_connector_update(). The new API requires the update step to associate the EDID with the connector. Add the missing call. This restores correct behaviour for the cached-EDID path. Fixes: 5bea90ad9743 ("drm/msm/dp: switch to struct drm_edid") Reviewed-by: Dmitry Baryshkov Signed-off-by: Jens Glathe Patchwork: https://patchwork.freedesktop.org/patch/731125/ Link: https://lore.kernel.org/r/20260608-drm_plug_flaky_edid-v3-1-1ca632938e7f@oldschoolsolutions.biz Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_panel.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c index 6bb021820d7c..5b4954e7cb74 100644 --- a/drivers/gpu/drm/msm/dp/dp_panel.c +++ b/drivers/gpu/drm/msm/dp/dp_panel.c @@ -332,8 +332,10 @@ int msm_dp_panel_get_modes(struct msm_dp_panel *msm_dp_panel, return -EINVAL; } - if (msm_dp_panel->drm_edid) + if (msm_dp_panel->drm_edid) { + drm_edid_connector_update(connector, msm_dp_panel->drm_edid); return drm_edid_connector_add_modes(connector); + } return 0; } From 6cd33b6f4155efc20485929fd0b56bb704641db9 Mon Sep 17 00:00:00 2001 From: Kavan Smith Date: Mon, 6 Jul 2026 18:32:40 -0700 Subject: [PATCH 015/641] drm/msm/dsi: round 6G byte clock rate to the PLL-achievable value MSM8916 runtime DSI commands still go through msm_dsi_host_xfer_prepare(), which re-applies the link clock rate before enabling the link clocks. That is fine in principle, but on DSI 6G the requested byte clock rate often does not exactly match the DSI PHY PLL's realizable rate. For example, the driver can request 56250000 Hz while the PLL actually runs at 56246337 Hz. Because the requested and actual rates differ slightly, every later link_clk_set_rate() call is treated as a real clock change and re-locks the PLL. On a video-mode panel without an internal timing generator, such as samsung,s6d7aa0 / lsl080al03 on MSM8916, that live-clock glitch makes the panel lose pixel lock and visibly corrupts scanout on each runtime DCS command, including backlight writes. Fix this by rounding the computed 6G byte clock rate up front, before it is stored in msm_host->byte_clk_rate and reused by later transfers. Once the host carries the PLL-achievable rate instead of the idealized one, repeated link_clk_set_rate() calls become no-ops in the common clock framework and no longer re-lock the PLL. This keeps the normal transfer callback sequencing intact, preserves the OPP vote path in link_clk_set_rate(), and matches the fix direction suggested in the original 2018 discussion. Reported-by: Daniel Mack Closes: https://lore.kernel.org/all/1a682c5b-7fc9-3aaa-120b-64b239a355a3@zonque.org/ Fixes: 6b16f05aa39f ("drm/msm/dsi: Split clk rate setting and enable") Cc: stable@vger.kernel.org Signed-off-by: Kavan Smith Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/738234/ Link: https://lore.kernel.org/r/20260707013240.681012-1-kavansmith82@gmail.com [DB: dropped extra chunk from the patch] Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dsi/dsi_host.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/gpu/drm/msm/dsi/dsi_host.c b/drivers/gpu/drm/msm/dsi/dsi_host.c index 982abaaac00d..5e1b313f04c0 100644 --- a/drivers/gpu/drm/msm/dsi/dsi_host.c +++ b/drivers/gpu/drm/msm/dsi/dsi_host.c @@ -670,12 +670,24 @@ static void dsi_calc_pclk(struct msm_dsi_host *msm_host, bool is_bonded_dsi) int dsi_calc_clk_rate_6g(struct msm_dsi_host *msm_host, bool is_bonded_dsi) { + long rounded_byte_clk_rate; + if (!msm_host->mode) { pr_err("%s: mode not set\n", __func__); return -EINVAL; } dsi_calc_pclk(msm_host, is_bonded_dsi); + + rounded_byte_clk_rate = clk_round_rate(msm_host->byte_clk, + msm_host->byte_clk_rate); + if (rounded_byte_clk_rate < 0) { + pr_err("%s: failed to round byte clock rate, %ld\n", + __func__, rounded_byte_clk_rate); + return rounded_byte_clk_rate; + } + + msm_host->byte_clk_rate = rounded_byte_clk_rate; msm_host->esc_clk_rate = clk_get_rate(msm_host->esc_clk); return 0; } From 14ea7c582879978a7ec0ddbd65d485f2a826053c Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sat, 30 May 2026 13:13:42 -0700 Subject: [PATCH 016/641] drm/msm/dpu: fix parameter name in dpu_core_perf_adjusted_mode_clk kernel-doc The kernel-doc referred to @crtc_clk_rate but the actual parameter is @mode_clk_rate. Assisted-by: Opencode:Big-pickle Signed-off-by: Rosen Penev Fixes: 62b7d6835288 ("drm/msm/dpu: Filter modes based on adjusted mode clock") Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/729413/ Link: https://lore.kernel.org/r/20260530201342.10538-1-rosenp@gmail.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.c index 13cc658065c5..2ff255d7795e 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.c @@ -34,7 +34,7 @@ enum dpu_perf_mode { /** * dpu_core_perf_adjusted_mode_clk - Adjust given mode clock rate according to * the perf clock factor. - * @crtc_clk_rate - Unadjusted mode clock rate + * @mode_clk_rate: unadjusted mode clock rate * @perf_cfg: performance configuration */ u64 dpu_core_perf_adjusted_mode_clk(u64 mode_clk_rate, From 44784327815b2a1ad8bb56b9236770cb538c7c27 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Sun, 12 Jul 2026 14:56:55 +0300 Subject: [PATCH 017/641] Revert "drm/msm: dsi: fix PLL init in bonded mode" Commit 93c97bc8d85d ("drm/msm: dsi: fix PLL init in bonded mode") fixed one of the issues with the DSI bonded mode, but broke non-bonded usecase for DSI as reported by Mohit Dsor. Clock divider is being programmed incorrectly, resultin in the wrong display mode being selected. Revert the offending commit, letting Neil to work on a better fix. Fixes: 93c97bc8d85d ("drm/msm: dsi: fix PLL init in bonded mode") Reported-by: Mohit Dsor Closes: https://lore.kernel.org/r/ae07cef84AmXK43H@hu-mdsor-hyd.qualcomm.com Cc: Neil Armstrong Cc: Thorsten Leemhuis Signed-off-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/739459/ Link: https://lore.kernel.org/r/20260712-msm-revert-dsi-pll-fix-v1-1-40122689ea25@oss.qualcomm.com --- drivers/gpu/drm/msm/dsi/phy/dsi_phy.h | 1 + drivers/gpu/drm/msm/dsi/phy/dsi_phy_7nm.c | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/msm/dsi/phy/dsi_phy.h b/drivers/gpu/drm/msm/dsi/phy/dsi_phy.h index 21a59d66e8dc..f5d3e806f8fd 100644 --- a/drivers/gpu/drm/msm/dsi/phy/dsi_phy.h +++ b/drivers/gpu/drm/msm/dsi/phy/dsi_phy.h @@ -111,6 +111,7 @@ struct msm_dsi_phy { struct msm_dsi_dphy_timing timing; const struct msm_dsi_phy_cfg *cfg; void *tuning_cfg; + void *pll_data; enum msm_dsi_phy_usecase usecase; bool regulator_ldo_mode; diff --git a/drivers/gpu/drm/msm/dsi/phy/dsi_phy_7nm.c b/drivers/gpu/drm/msm/dsi/phy/dsi_phy_7nm.c index 984a66085dfb..5d805a797abd 100644 --- a/drivers/gpu/drm/msm/dsi/phy/dsi_phy_7nm.c +++ b/drivers/gpu/drm/msm/dsi/phy/dsi_phy_7nm.c @@ -426,8 +426,11 @@ static void dsi_pll_enable_pll_bias(struct dsi_pll_7nm *pll) u32 data; spin_lock_irqsave(&pll->pll_enable_lock, flags); - pll->pll_enable_cnt++; - WARN_ON(pll->pll_enable_cnt == INT_MAX); + if (pll->pll_enable_cnt++) { + spin_unlock_irqrestore(&pll->pll_enable_lock, flags); + WARN_ON(pll->pll_enable_cnt == INT_MAX); + return; + } data = readl(pll->phy->base + REG_DSI_7nm_PHY_CMN_CTRL_0); data |= DSI_7nm_PHY_CMN_CTRL_0_PLL_SHUTDOWNB; @@ -873,6 +876,7 @@ static int dsi_pll_7nm_init(struct msm_dsi_phy *phy) spin_lock_init(&pll_7nm->pll_enable_lock); pll_7nm->phy = phy; + phy->pll_data = pll_7nm; ret = pll_7nm_register(pll_7nm, phy->provided_clocks->hws); if (ret) { @@ -961,8 +965,10 @@ static int dsi_7nm_phy_enable(struct msm_dsi_phy *phy, u32 const delay_us = 5; u32 const timeout_us = 1000; struct msm_dsi_dphy_timing *timing = &phy->timing; + struct dsi_pll_7nm *pll = phy->pll_data; void __iomem *base = phy->base; bool less_than_1500_mhz; + unsigned long flags; u32 vreg_ctrl_0, vreg_ctrl_1, lane_ctrl0; u32 glbl_pemph_ctrl_0; u32 glbl_str_swi_cal_sel_ctrl, glbl_hstx_str_ctrl_0; @@ -1084,10 +1090,13 @@ static int dsi_7nm_phy_enable(struct msm_dsi_phy *phy, glbl_rescode_bot_ctrl = 0x3c; } + spin_lock_irqsave(&pll->pll_enable_lock, flags); + pll->pll_enable_cnt = 1; /* de-assert digital and pll power down */ data = DSI_7nm_PHY_CMN_CTRL_0_DIGTOP_PWRDN_B | DSI_7nm_PHY_CMN_CTRL_0_PLL_SHUTDOWNB; writel(data, base + REG_DSI_7nm_PHY_CMN_CTRL_0); + spin_unlock_irqrestore(&pll->pll_enable_lock, flags); /* Assert PLL core reset */ writel(0x00, base + REG_DSI_7nm_PHY_CMN_PLL_CNTRL); @@ -1200,7 +1209,9 @@ static bool dsi_7nm_set_continuous_clock(struct msm_dsi_phy *phy, bool enable) static void dsi_7nm_phy_disable(struct msm_dsi_phy *phy) { + struct dsi_pll_7nm *pll = phy->pll_data; void __iomem *base = phy->base; + unsigned long flags; u32 data; DBG(""); @@ -1227,8 +1238,11 @@ static void dsi_7nm_phy_disable(struct msm_dsi_phy *phy) writel(data, base + REG_DSI_7nm_PHY_CMN_CTRL_0); writel(0, base + REG_DSI_7nm_PHY_CMN_LANE_CTRL0); + spin_lock_irqsave(&pll->pll_enable_lock, flags); + pll->pll_enable_cnt = 0; /* Turn off all PHY blocks */ writel(0x00, base + REG_DSI_7nm_PHY_CMN_CTRL_0); + spin_unlock_irqrestore(&pll->pll_enable_lock, flags); /* make sure phy is turned off */ wmb(); From b8a9c9c5787bed1243e5364c89ca66c26b4e4d83 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 11 Jun 2026 13:27:30 +0300 Subject: [PATCH 018/641] drm/msm/adreno: fix use after free on error path in a6xx_gpu_init() The a6xx_destroy() function frees "a6xx_gpu" and so "adreno_gpu" points to freed memory. Preserve the error code before freeing the memory to avoid a use after free. Fixes: d158886cba08 ("drm/msm/adreno: Trust the SSoT UBWC config") Signed-off-by: Dan Carpenter Reviewed-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/732275/ Message-ID: Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/a6xx_gpu.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gpu.c b/drivers/gpu/drm/msm/adreno/a6xx_gpu.c index 8b3bb2fd433b..a44380316aaa 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gpu.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gpu.c @@ -2770,8 +2770,9 @@ static struct msm_gpu *a6xx_gpu_init(struct drm_device *dev) adreno_gpu->ubwc_config = qcom_ubwc_config_get_data(); if (IS_ERR(adreno_gpu->ubwc_config)) { + ret = PTR_ERR(adreno_gpu->ubwc_config); a6xx_destroy(&(a6xx_gpu->base.base)); - return ERR_CAST(adreno_gpu->ubwc_config); + return ERR_PTR(ret); } /* Set up the preemption specific bits and pieces for each ringbuffer */ From be0e82b8e0c96649b8bc77a99ab0d185243b7659 Mon Sep 17 00:00:00 2001 From: Alexandre MINETTE Date: Wed, 10 Jun 2026 09:49:17 +0200 Subject: [PATCH 019/641] drm/msm/a3xx: Drain VBIF before GPU suspend A3xx hangs after every runtime suspend on the Samsung Galaxy S4 GT-I9505. Even simple GPU workloads, such as drawing a single triangle, hang reliably once the GPU has been suspended by runtime PM. The generic MSM GPU suspend path disables clocks/power, but A3xx also needs to ensure that pending VBIF transactions are drained before that happens. Add an A3xx-specific pm_suspend callback. Wait for the GPU to become idle, halt all VBIF XIN clients, wait for the corresponding acknowledgment, and only then enter the generic MSM GPU suspend path. This fixes reliable A3xx GPU hangs observed after runtime PM on the Samsung Galaxy S4 GT-I9505, codename jflte. The failure is reported as: mdp4 5100000.display-controller: [drm:hangcheck_handler] *ERROR* 3.2.0.2: hangcheck detected gpu lockup rb 0! mdp4 5100000.display-controller: [drm:hangcheck_handler] *ERROR* 3.2.0.2: completed fence: 4294967041 mdp4 5100000.display-controller: [drm:hangcheck_handler] *ERROR* 3.2.0.2: submitted fence: 4294967049 mdp4 5100000.display-controller: [drm:recover_worker] *ERROR* 3.2.0.2: hangcheck recover! Link: https://github.com/freedreno-zz/freedreno/issues/12 Signed-off-by: Alexandre MINETTE Patchwork: https://patchwork.freedesktop.org/patch/731919/ Message-ID: <20260610-mainline-fix-a3xx-gpu-hang-sending-v1-1-9282182840b5@alex-min.fr> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/a3xx_gpu.c | 36 ++++++++++++++++++- drivers/gpu/drm/msm/registers/adreno/a3xx.xml | 2 ++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/adreno/a3xx_gpu.c b/drivers/gpu/drm/msm/adreno/a3xx_gpu.c index c17e9777beae..0259e60b8011 100644 --- a/drivers/gpu/drm/msm/adreno/a3xx_gpu.c +++ b/drivers/gpu/drm/msm/adreno/a3xx_gpu.c @@ -25,6 +25,8 @@ extern bool hang_debug; +#define A3XX_VBIF_XIN_HALT_CTRL0_MASK GENMASK(5, 0) + static void a3xx_dump(struct msm_gpu *gpu); static bool a3xx_idle(struct msm_gpu *gpu); @@ -496,6 +498,38 @@ static u64 a3xx_gpu_busy(struct msm_gpu *gpu, unsigned long *out_sample_rate) return busy_cycles; } +static int a3xx_vbif_halt(struct msm_gpu *gpu) +{ + u32 ack; + int ret; + + gpu_write(gpu, REG_A3XX_VBIF_XIN_HALT_CTRL0, + A3XX_VBIF_XIN_HALT_CTRL0_MASK); + ret = spin_until(((ack = gpu_read(gpu, REG_A3XX_VBIF_XIN_HALT_CTRL1)) & + A3XX_VBIF_XIN_HALT_CTRL0_MASK) == + A3XX_VBIF_XIN_HALT_CTRL0_MASK); + gpu_write(gpu, REG_A3XX_VBIF_XIN_HALT_CTRL0, 0); + + if (ret) + return -EBUSY; + + return 0; +} + +static int a3xx_pm_suspend(struct msm_gpu *gpu) +{ + int ret; + + if (!a3xx_idle(gpu)) + return -EBUSY; + + ret = a3xx_vbif_halt(gpu); + if (ret) + return ret; + + return msm_gpu_pm_suspend(gpu); +} + static u32 a3xx_get_rptr(struct msm_gpu *gpu, struct msm_ringbuffer *ring) { ring->memptrs->rptr = gpu_read(gpu, REG_AXXX_CP_RB_RPTR); @@ -581,7 +615,7 @@ const struct adreno_gpu_funcs a3xx_gpu_funcs = { .get_param = adreno_get_param, .set_param = adreno_set_param, .hw_init = a3xx_hw_init, - .pm_suspend = msm_gpu_pm_suspend, + .pm_suspend = a3xx_pm_suspend, .pm_resume = msm_gpu_pm_resume, .recover = a3xx_recover, .submit = a3xx_submit, diff --git a/drivers/gpu/drm/msm/registers/adreno/a3xx.xml b/drivers/gpu/drm/msm/registers/adreno/a3xx.xml index 09c9606fc3e1..21b00a17ef3e 100644 --- a/drivers/gpu/drm/msm/registers/adreno/a3xx.xml +++ b/drivers/gpu/drm/msm/registers/adreno/a3xx.xml @@ -1495,6 +1495,8 @@ xsi:schemaLocation="https://gitlab.freedesktop.org/freedreno/ rules-fd.xsd"> + + From d9108bfdb746edacdb05bd27959a4ae63c6c7f3f Mon Sep 17 00:00:00 2001 From: Shivam Rawat Date: Fri, 5 Jun 2026 01:38:17 +0530 Subject: [PATCH 020/641] drm/msm/a6xx: Fix stale rpmh votes after suspend There are stale RPMH votes (BCM votes) observed after GMU suspend. This is because the rpmh stop sequences are skipped during gmu suspend. Fix this and also move GMU to reset state to avoid any further activity. Fixes: f248d5d5159a ("drm/msm/a6xx: Fix PDC sleep sequence") Signed-off-by: Shivam Rawat Signed-off-by: Akhil P Oommen Tested-by: Neil Armstrong # on SM8650-HDK Reviewed-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Patchwork: https://patchwork.freedesktop.org/patch/730652/ Message-ID: <20260605-assorted-fixes-june-v1-1-2caa04f7287c@oss.qualcomm.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/a6xx_gmu.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gmu.c b/drivers/gpu/drm/msm/adreno/a6xx_gmu.c index 2e5d7b53a0c3..a2f6918c4f7f 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gmu.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gmu.c @@ -642,7 +642,7 @@ static void a6xx_rpmh_stop(struct a6xx_gmu *gmu) int ret; u32 val; - if (test_and_clear_bit(GMU_STATUS_FW_START, &gmu->status)) + if (!test_and_clear_bit(GMU_STATUS_FW_START, &gmu->status)) return; if (adreno_is_a840(adreno_gpu)) @@ -1465,6 +1465,9 @@ static void a6xx_gmu_shutdown(struct a6xx_gmu *gmu) /* Stop the interrupts and mask the hardware */ a6xx_gmu_irq_disable(gmu); + /* Halt the gmu cm3 core */ + gmu_write(gmu, REG_A6XX_GMU_CM3_SYSRESET, 1); + /* Tell RPMh to power off the GPU */ a6xx_rpmh_stop(gmu); From b303e1d52811de7d1bcf793560754d4df68d4a1c Mon Sep 17 00:00:00 2001 From: Jie Zhang Date: Fri, 5 Jun 2026 01:38:18 +0530 Subject: [PATCH 021/641] drm/msm: Recover HW before retire hung submit During recovery, it is not safe to retire the hung submit before we recover the GPU. Retiring the submit triggers BO free and that can result in GPU pagefaults since the GPU may be actively accessing those BOs. To fix this, retire the submits after gpu recovery is complete in recover_worker(). Fixes: 1a370be9ac51 ("drm/msm: restart queued submits after hang") Signed-off-by: Jie Zhang Signed-off-by: Akhil P Oommen Acked-by: Konrad Dybcio Patchwork: https://patchwork.freedesktop.org/patch/730655/ Message-ID: <20260605-assorted-fixes-june-v1-2-2caa04f7287c@oss.qualcomm.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/msm_gpu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_gpu.c b/drivers/gpu/drm/msm/msm_gpu.c index 18ed00e5f143..9ac7740a87f0 100644 --- a/drivers/gpu/drm/msm/msm_gpu.c +++ b/drivers/gpu/drm/msm/msm_gpu.c @@ -552,11 +552,11 @@ static void recover_worker(struct kthread_work *work) msm_update_fence(ring->fctx, fence); } + gpu->funcs->recover(gpu); + /* retire completed submits, plus the one that hung: */ retire_submits(gpu); - gpu->funcs->recover(gpu); - /* * Replay all remaining submits starting with highest priority * ring From fc7ccbc6174b79ffab5be5dca5b6e253df22f030 Mon Sep 17 00:00:00 2001 From: Jie Zhang Date: Fri, 5 Jun 2026 01:38:19 +0530 Subject: [PATCH 022/641] drm/msm/a6xx: Fix A663 GPUCC register list for state capture The GPUCC register list for A663 is incorrect, which can cause out-of-bounds register access during GPU state capture. Update it to use the correct register ranges. Fixes: 5773cce8615c ("drm/msm/a6xx: Add support for A663") Signed-off-by: Jie Zhang Signed-off-by: Akhil P Oommen Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/730656/ Message-ID: <20260605-assorted-fixes-june-v1-3-2caa04f7287c@oss.qualcomm.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c b/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c index 166365359fa6..2a62a22077f9 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c @@ -1244,7 +1244,9 @@ static void a6xx_get_gmu_registers(struct msm_gpu *gpu, _a6xx_get_gmu_registers(gpu, a6xx_state, &a6xx_gmu_reglist[1], &a6xx_state->gmu_registers[1], true); - if (adreno_is_a621(adreno_gpu) || adreno_is_a623(adreno_gpu)) + if (adreno_is_a621(adreno_gpu) || + adreno_is_a623(adreno_gpu) || + adreno_is_a663(adreno_gpu)) _a6xx_get_gmu_registers(gpu, a6xx_state, &a621_gpucc_reg, &a6xx_state->gmu_registers[2], false); else From d052d0358fb89b59718b9c24871d72006d4b89b0 Mon Sep 17 00:00:00 2001 From: Jie Zhang Date: Fri, 5 Jun 2026 01:38:20 +0530 Subject: [PATCH 023/641] drm/msm/a6xx: Fix A621 GPUCC register list for state capture A621 uses an incorrect GPUCC register list during state capture. The existing list matches A623/A663. Rename it accordingly and add a dedicated A621 GPUCC register list. Fixes: 11cdb81b3c1b ("drm/msm/a6xx: Fix gpucc register block for A621") Signed-off-by: Jie Zhang Signed-off-by: Akhil P Oommen Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/730659/ Message-ID: <20260605-assorted-fixes-june-v1-4-2caa04f7287c@oss.qualcomm.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c | 7 ++++--- drivers/gpu/drm/msm/adreno/a6xx_gpu_state.h | 12 ++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c b/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c index 2a62a22077f9..3ea8ff8c7404 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c @@ -1244,11 +1244,12 @@ static void a6xx_get_gmu_registers(struct msm_gpu *gpu, _a6xx_get_gmu_registers(gpu, a6xx_state, &a6xx_gmu_reglist[1], &a6xx_state->gmu_registers[1], true); - if (adreno_is_a621(adreno_gpu) || - adreno_is_a623(adreno_gpu) || - adreno_is_a663(adreno_gpu)) + if (adreno_is_a621(adreno_gpu)) _a6xx_get_gmu_registers(gpu, a6xx_state, &a621_gpucc_reg, &a6xx_state->gmu_registers[2], false); + else if (adreno_is_a623(adreno_gpu) || adreno_is_a663(adreno_gpu)) + _a6xx_get_gmu_registers(gpu, a6xx_state, &a623_gpucc_reg, + &a6xx_state->gmu_registers[2], false); else _a6xx_get_gmu_registers(gpu, a6xx_state, &a6xx_gpucc_reg, &a6xx_state->gmu_registers[2], false); diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.h b/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.h index b49d8427b59e..0a13a65f89ac 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.h +++ b/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.h @@ -377,6 +377,17 @@ static const u32 a6xx_gmu_gpucc_registers[] = { }; static const u32 a621_gmu_gpucc_registers[] = { + /* GPU CC */ + 0x24000, 0x2400e, 0x24400, 0x2440e, 0x24800, 0x24805, 0x24c00, 0x24cff, + 0x25800, 0x25804, 0x25c00, 0x25c04, 0x26000, 0x26004, 0x26400, 0x26405, + 0x26414, 0x2641d, 0x2642a, 0x26430, 0x26432, 0x26432, 0x26441, 0x26455, + 0x26466, 0x26468, 0x26478, 0x2647a, 0x26489, 0x2648a, 0x2649c, 0x2649e, + 0x264a0, 0x264a3, 0x264b3, 0x264b5, 0x264c5, 0x264c7, 0x264d6, 0x264d8, + 0x264e8, 0x264e9, 0x264f9, 0x264fc, 0x2650b, 0x2650c, 0x2651c, 0x2651e, + 0x26540, 0x26570, 0x26600, 0x26616, 0x26620, 0x2662d, +}; + +static const u32 a623_gmu_gpucc_registers[] = { /* GPU CC */ 0x24000, 0x2400e, 0x24400, 0x2440e, 0x25800, 0x25804, 0x25c00, 0x25c04, 0x26000, 0x26004, 0x26400, 0x26405, 0x26414, 0x2641d, 0x2642a, 0x26430, @@ -402,6 +413,7 @@ static const struct a6xx_registers a6xx_gmu_reglist[] = { static const struct a6xx_registers a6xx_gpucc_reg = REGS(a6xx_gmu_gpucc_registers, 0, 0); static const struct a6xx_registers a621_gpucc_reg = REGS(a621_gmu_gpucc_registers, 0, 0); +static const struct a6xx_registers a623_gpucc_reg = REGS(a623_gmu_gpucc_registers, 0, 0); static u32 a6xx_get_cp_roq_size(struct msm_gpu *gpu); static u32 a7xx_get_cp_roq_size(struct msm_gpu *gpu); From bc024d325e98b6b2806e00455e030622fb8e1820 Mon Sep 17 00:00:00 2001 From: Jie Zhang Date: Fri, 5 Jun 2026 01:38:21 +0530 Subject: [PATCH 024/641] drm/msm/a6xx: Fix IRQ storm during msm_recovery test Once a hang is triggered by the msm_recovery test, the gpu error irq remains asserted and triggers an interrupt storm. In the worst case, this IRQ storm lands on the CPU core where the hangcheck timer is scheduled, blocking it from running. This eventually leads to CPU watchdog timeouts. To fix this, mask the gpu error irqs during msm_recovery test and enable them back during the recovery. Fixes: 5edf2750d998 ("drm/msm: Add debugfs to disable hw err handling") Signed-off-by: Jie Zhang Signed-off-by: Akhil P Oommen Patchwork: https://patchwork.freedesktop.org/patch/730660/ Message-ID: <20260605-assorted-fixes-june-v1-5-2caa04f7287c@oss.qualcomm.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/a5xx_gpu.c | 5 +++++ drivers/gpu/drm/msm/adreno/a6xx_gpu.c | 5 ++++- drivers/gpu/drm/msm/adreno/a8xx_gpu.c | 5 ++++- drivers/gpu/drm/msm/msm_gpu.c | 2 ++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/msm/adreno/a5xx_gpu.c b/drivers/gpu/drm/msm/adreno/a5xx_gpu.c index 2c0bbac43c52..f1df2514c613 100644 --- a/drivers/gpu/drm/msm/adreno/a5xx_gpu.c +++ b/drivers/gpu/drm/msm/adreno/a5xx_gpu.c @@ -1275,6 +1275,11 @@ static irqreturn_t a5xx_irq(struct msm_gpu *gpu) status & ~A5XX_RBBM_INT_0_MASK_RBBM_AHB_ERROR); if (priv->disable_err_irq) { + /* Turn off interrupts to avoid interrupt storm */ + gpu_write(gpu, REG_A5XX_RBBM_INT_0_MASK, + A5XX_RBBM_INT_0_MASK_CP_CACHE_FLUSH_TS | + A5XX_RBBM_INT_0_MASK_CP_SW); + status &= A5XX_RBBM_INT_0_MASK_CP_CACHE_FLUSH_TS | A5XX_RBBM_INT_0_MASK_CP_SW; } diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gpu.c b/drivers/gpu/drm/msm/adreno/a6xx_gpu.c index a44380316aaa..e293b4ca808a 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gpu.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gpu.c @@ -1911,8 +1911,11 @@ static irqreturn_t a6xx_irq(struct msm_gpu *gpu) gpu_write(gpu, REG_A6XX_RBBM_INT_CLEAR_CMD, status); - if (priv->disable_err_irq) + if (priv->disable_err_irq) { + /* Turn off interrupts to avoid interrupt storm */ + gpu_write(gpu, REG_A6XX_RBBM_INT_0_MASK, A6XX_RBBM_INT_0_MASK_CP_CACHE_FLUSH_TS); status &= A6XX_RBBM_INT_0_MASK_CP_CACHE_FLUSH_TS; + } if (status & A6XX_RBBM_INT_0_MASK_RBBM_HANG_DETECT) a6xx_fault_detect_irq(gpu); diff --git a/drivers/gpu/drm/msm/adreno/a8xx_gpu.c b/drivers/gpu/drm/msm/adreno/a8xx_gpu.c index 9e44fd1ae634..0f6fd35bd587 100644 --- a/drivers/gpu/drm/msm/adreno/a8xx_gpu.c +++ b/drivers/gpu/drm/msm/adreno/a8xx_gpu.c @@ -1211,8 +1211,11 @@ irqreturn_t a8xx_irq(struct msm_gpu *gpu) gpu_write(gpu, REG_A8XX_RBBM_INT_CLEAR_CMD, status); - if (priv->disable_err_irq) + if (priv->disable_err_irq) { + /* Turn off interrupts to avoid interrupt storm */ + gpu_write(gpu, REG_A8XX_RBBM_INT_0_MASK, A6XX_RBBM_INT_0_MASK_CP_CACHE_FLUSH_TS); status &= A6XX_RBBM_INT_0_MASK_CP_CACHE_FLUSH_TS; + } if (status & A6XX_RBBM_INT_0_MASK_RBBM_HANG_DETECT) a8xx_fault_detect_irq(gpu); diff --git a/drivers/gpu/drm/msm/msm_gpu.c b/drivers/gpu/drm/msm/msm_gpu.c index 9ac7740a87f0..48ac51f4119b 100644 --- a/drivers/gpu/drm/msm/msm_gpu.c +++ b/drivers/gpu/drm/msm/msm_gpu.c @@ -552,6 +552,8 @@ static void recover_worker(struct kthread_work *work) msm_update_fence(ring->fctx, fence); } + priv->disable_err_irq = false; + gpu->funcs->recover(gpu); /* retire completed submits, plus the one that hung: */ From 40b793714ad8f393ab3d469f9d00b20ebda46257 Mon Sep 17 00:00:00 2001 From: Jie Zhang Date: Fri, 5 Jun 2026 01:38:22 +0530 Subject: [PATCH 025/641] drm/msm: Fix task_struct reference leak in recover_worker get_pid_task() increments the task reference count, but the corresponding put_task_struct() was missing in the else branch, leaking a reference on every GPU hang recovery. Fixes: 25654a1756a4 ("drm/msm: Update global fault counter when faulty process has already ended") Signed-off-by: Jie Zhang Signed-off-by: Akhil P Oommen Reviewed-by: Konrad Dybcio Patchwork: https://patchwork.freedesktop.org/patch/730662/ Message-ID: <20260605-assorted-fixes-june-v1-6-2caa04f7287c@oss.qualcomm.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/msm_gpu.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/msm/msm_gpu.c b/drivers/gpu/drm/msm/msm_gpu.c index 48ac51f4119b..03c057856065 100644 --- a/drivers/gpu/drm/msm/msm_gpu.c +++ b/drivers/gpu/drm/msm/msm_gpu.c @@ -505,6 +505,8 @@ static void recover_worker(struct kthread_work *work) */ if (!vm->managed) msm_gem_vm_unusable(submit->vm); + + put_task_struct(task); } noreclaim_flag = memalloc_noreclaim_save(); From e2332abed2a4d3caa59052095dc16e4ce44791ea Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Thu, 9 Jul 2026 14:23:09 +0800 Subject: [PATCH 026/641] drm/msm: Only fini scheduler after successful init msm_ringbuffer_new() destroys a partially initialized ring through msm_ringbuffer_destroy() when an allocation or scheduler setup step fails. If drm_sched_init() fails before it finishes initializing the scheduler, the failure path still calls drm_sched_fini(). That teardown path assumes the scheduler work items, lists, and workqueue state were initialized. Track successful scheduler initialization and call drm_sched_fini() only after drm_sched_init() returned 0. This issue was found by a static analysis checker and confirmed by manual source review. Fixes: 1d8a5ca436ee ("drm/msm: Conversion to drm scheduler") Signed-off-by: Ruoyu Wang Patchwork: https://patchwork.freedesktop.org/patch/738905/ Message-ID: <20260709062309.4168362-1-ruoyuw560@gmail.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/msm_ringbuffer.c | 7 ++++--- drivers/gpu/drm/msm/msm_ringbuffer.h | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_ringbuffer.c b/drivers/gpu/drm/msm/msm_ringbuffer.c index 30ddb5351e98..f060e6362ae4 100644 --- a/drivers/gpu/drm/msm/msm_ringbuffer.c +++ b/drivers/gpu/drm/msm/msm_ringbuffer.c @@ -109,9 +109,9 @@ struct msm_ringbuffer *msm_ringbuffer_new(struct msm_gpu *gpu, int id, ring->memptrs_iova = memptrs_iova; ret = drm_sched_init(&ring->sched, &args); - if (ret) { + if (ret) goto fail; - } + ring->sched_initialized = true; INIT_LIST_HEAD(&ring->submits); spin_lock_init(&ring->submit_lock); @@ -133,7 +133,8 @@ void msm_ringbuffer_destroy(struct msm_ringbuffer *ring) if (IS_ERR_OR_NULL(ring)) return; - drm_sched_fini(&ring->sched); + if (ring->sched_initialized) + drm_sched_fini(&ring->sched); msm_fence_context_free(ring->fctx); diff --git a/drivers/gpu/drm/msm/msm_ringbuffer.h b/drivers/gpu/drm/msm/msm_ringbuffer.h index 28ca8c9f7463..3631ec283c6e 100644 --- a/drivers/gpu/drm/msm/msm_ringbuffer.h +++ b/drivers/gpu/drm/msm/msm_ringbuffer.h @@ -56,6 +56,7 @@ struct msm_ringbuffer { * The job scheduler for this ring. */ struct drm_gpu_scheduler sched; + bool sched_initialized; /* * List of in-flight submits on this ring. Protected by submit_lock. From e44580b601d44238ac615f441ce1179bc88a47ad Mon Sep 17 00:00:00 2001 From: Akhil P Oommen Date: Tue, 7 Jul 2026 03:10:19 +0530 Subject: [PATCH 027/641] dt-bindings: display/msm: gpu: Document Adreno 840 Adreno 840 GPU found in Kaanapali chipsets belongs to the A8x family. It is a new IP which features the new slice architecture with 3 slices, raytracing support, and the highest GMEM size seen so far on a Snapdragon mobile chipsets. Update the dt bindings documentation to describe this GPU. Acked-by: Krzysztof Kozlowski Signed-off-by: Akhil P Oommen Patchwork: https://patchwork.freedesktop.org/patch/738143/ Message-ID: <20260707-kaana-gpu-dt-v3-1-8dac9a60dd5c@oss.qualcomm.com> Signed-off-by: Rob Clark --- Documentation/devicetree/bindings/display/msm/gpu.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/display/msm/gpu.yaml b/Documentation/devicetree/bindings/display/msm/gpu.yaml index dbbd8b814189..d0b7304c17af 100644 --- a/Documentation/devicetree/bindings/display/msm/gpu.yaml +++ b/Documentation/devicetree/bindings/display/msm/gpu.yaml @@ -413,6 +413,7 @@ allOf: contains: enum: - qcom,adreno-44010000 + - qcom,adreno-44050a01 - qcom,adreno-44070001 then: properties: @@ -448,6 +449,7 @@ allOf: - qcom,adreno-43050c01 - qcom,adreno-43051401 - qcom,adreno-44010000 + - qcom,adreno-44050a01 - qcom,adreno-44070001 then: # Starting with A6xx, the clocks are usually defined in the GMU node From 1f69339da06255a7f8560b4768fff83992c03deb Mon Sep 17 00:00:00 2001 From: Aditya Sherawat Date: Fri, 10 Jul 2026 00:12:00 +0530 Subject: [PATCH 028/641] drm/msm/adreno: Add support for A704 GPU Adreno A704 GPU found in Shikra is an IP reuse of A702 GPU with very minimal changes. The only KMD facing difference is the chipid and the zap firmware which is specified via devicetree. Just add the new chipid to enable support for A704 GPU in Shikra. Signed-off-by: Aditya Sherawat Reviewed-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Signed-off-by: Akhil P Oommen Patchwork: https://patchwork.freedesktop.org/patch/738931/ Message-ID: <20260710-shikra-gpu-v6-1-b388ec5dce77@oss.qualcomm.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/a6xx_catalog.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/adreno/a6xx_catalog.c b/drivers/gpu/drm/msm/adreno/a6xx_catalog.c index 3e6f409d13a2..2de3ab010135 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_catalog.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_catalog.c @@ -1454,7 +1454,7 @@ DECLARE_ADRENO_REGLIST_PIPE_LIST(a7xx_dyn_pwrup_reglist); static const struct adreno_info a7xx_gpus[] = { { - .chip_ids = ADRENO_CHIP_IDS(0x07000200), + .chip_ids = ADRENO_CHIP_IDS(0x07000200, 0x07000400), .family = ADRENO_6XX_GEN1, /* NOT a mistake! */ .fw = { [ADRENO_FW_SQE] = "a702_sqe.fw", From 9479a45e5f59c1fab40e0d43410ac7193d662624 Mon Sep 17 00:00:00 2001 From: Aditya Sherawat Date: Fri, 10 Jul 2026 00:12:01 +0530 Subject: [PATCH 029/641] dt-bindings: display/msm/gpu: Add support for A704 GPU Adreno A704 GPU found Shikra SoC is an IP reuse of A702 GPU with very minimal changes. Signed-off-by: Aditya Sherawat Reviewed-by: Krzysztof Kozlowski Signed-off-by: Akhil P Oommen Patchwork: https://patchwork.freedesktop.org/patch/738933/ Message-ID: <20260710-shikra-gpu-v6-2-b388ec5dce77@oss.qualcomm.com> Signed-off-by: Rob Clark --- Documentation/devicetree/bindings/display/msm/gpu.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/display/msm/gpu.yaml b/Documentation/devicetree/bindings/display/msm/gpu.yaml index d0b7304c17af..992d3de7c117 100644 --- a/Documentation/devicetree/bindings/display/msm/gpu.yaml +++ b/Documentation/devicetree/bindings/display/msm/gpu.yaml @@ -353,6 +353,7 @@ allOf: - qcom,adreno-610.0 - qcom,adreno-619.1 - qcom,adreno-07000200 + - qcom,adreno-07000400 then: properties: clocks: From 504a65121cf15b07c9af9f04e0ec723db531c2fa Mon Sep 17 00:00:00 2001 From: Bibek Kumar Patro Date: Fri, 10 Jul 2026 00:12:02 +0530 Subject: [PATCH 030/641] dt-bindings: arm-smmu: Document GPU SMMU for Shikra SoC Add specific compatible strings to document the GPU SMMU present in the Shikra SoC. Signed-off-by: Bibek Kumar Patro Reviewed-by: Krzysztof Kozlowski Signed-off-by: Akhil P Oommen Patchwork: https://patchwork.freedesktop.org/patch/738935/ Message-ID: <20260710-shikra-gpu-v6-3-b388ec5dce77@oss.qualcomm.com> Signed-off-by: Rob Clark --- Documentation/devicetree/bindings/iommu/arm,smmu.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/iommu/arm,smmu.yaml b/Documentation/devicetree/bindings/iommu/arm,smmu.yaml index a701dec2fa0a..ad15fda5c25e 100644 --- a/Documentation/devicetree/bindings/iommu/arm,smmu.yaml +++ b/Documentation/devicetree/bindings/iommu/arm,smmu.yaml @@ -108,6 +108,7 @@ properties: - qcom,sc7280-smmu-500 - qcom,sc8180x-smmu-500 - qcom,sc8280xp-smmu-500 + - qcom,shikra-smmu-500 - qcom,sm6115-smmu-500 - qcom,sm6125-smmu-500 - qcom,sm8150-smmu-500 @@ -543,6 +544,7 @@ allOf: - enum: - qcom,milos-smmu-500 - qcom,sar2130p-smmu-500 + - qcom,shikra-smmu-500 - qcom,sm8550-smmu-500 - qcom,sm8650-smmu-500 - qcom,x1e80100-smmu-500 From 01bcc0398f43099acb407a6067481e635c3e1b84 Mon Sep 17 00:00:00 2001 From: Puranam V G Tejaswi Date: Sat, 18 Jul 2026 02:11:26 +0530 Subject: [PATCH 031/641] drm/msm/a6xx: Fix RBBM_CLOCK_CNTL3_TP0 value in a730_hwcg The RBBM_CLOCK_CNTL3_TP0 entry in a730_hwcg has bits[19:16] set to 2 (clock gating enabled for that TP0 stage). As per the latest recommendation, clear this nibble to disable clock gating for this particular stage. Fixes: 9588d2f860a4 ("drm/msm/a6xx: Add A730 support") Signed-off-by: Puranam V G Tejaswi Reviewed-by: Konrad Dybcio Signed-off-by: Akhil P Oommen Patchwork: https://patchwork.freedesktop.org/patch/740955/ Message-ID: <20260718-eliza-gpu-v2-1-64379dbebd7a@oss.qualcomm.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/a6xx_catalog.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/adreno/a6xx_catalog.c b/drivers/gpu/drm/msm/adreno/a6xx_catalog.c index 2de3ab010135..8f74b2558035 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_catalog.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_catalog.c @@ -1199,7 +1199,7 @@ static const struct adreno_reglist a730_hwcg[] = { { REG_A6XX_RBBM_CLOCK_DELAY_SP0, 0x00000080 }, { REG_A6XX_RBBM_CLOCK_CNTL_TP0, 0x22222220 }, { REG_A6XX_RBBM_CLOCK_CNTL2_TP0, 0x22222222 }, - { REG_A6XX_RBBM_CLOCK_CNTL3_TP0, 0x22222222 }, + { REG_A6XX_RBBM_CLOCK_CNTL3_TP0, 0x22220222 }, { REG_A6XX_RBBM_CLOCK_CNTL4_TP0, 0x00222222 }, { REG_A6XX_RBBM_CLOCK_HYST_TP0, 0x77777777 }, { REG_A6XX_RBBM_CLOCK_HYST2_TP0, 0x77777777 }, From 352116dbc4fc61ae22eae748da36e46a3f45f65c Mon Sep 17 00:00:00 2001 From: Akhil P Oommen Date: Sat, 18 Jul 2026 02:11:27 +0530 Subject: [PATCH 032/641] drm/msm/a6xx: Rename GBIF_CX_CONFIG to a A6XX- variant register The GBIF_CX_CONFIG register exists on GPUs prior to A8XX (it is used on A722, for example), so it should be tagged as an A6XX variant to match the register spec. Widen its variant range from "A8XX-" to "A6XX-" in the register XML and rename the generated macro accordingly at all existing usage sites. Reviewed-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Signed-off-by: Akhil P Oommen Patchwork: https://patchwork.freedesktop.org/patch/740957/ Message-ID: <20260718-eliza-gpu-v2-2-64379dbebd7a@oss.qualcomm.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/a6xx_catalog.c | 2 +- drivers/gpu/drm/msm/adreno/a6xx_gmu.c | 2 +- drivers/gpu/drm/msm/adreno/a8xx_gpu.c | 2 +- drivers/gpu/drm/msm/registers/adreno/a6xx.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/msm/adreno/a6xx_catalog.c b/drivers/gpu/drm/msm/adreno/a6xx_catalog.c index 8f74b2558035..b89da68145bc 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_catalog.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_catalog.c @@ -2180,7 +2180,7 @@ static const struct adreno_reglist a840_gbif[] = { { REG_A6XX_GBIF_QSB_SIDE1, 0x00071e20 }, { REG_A6XX_GBIF_QSB_SIDE2, 0x00071e20 }, { REG_A6XX_GBIF_QSB_SIDE3, 0x00071e20 }, - { REG_A8XX_GBIF_CX_CONFIG, 0x20023000 }, + { REG_A6XX_GBIF_CX_CONFIG, 0x20023000 }, { }, }; diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gmu.c b/drivers/gpu/drm/msm/adreno/a6xx_gmu.c index a2f6918c4f7f..a5a5f3534fc0 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gmu.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gmu.c @@ -1032,7 +1032,7 @@ static int a6xx_gmu_fw_start(struct a6xx_gmu *gmu, unsigned int state) gpu_write(gpu, gbif_cx[i].offset, gbif_cx[i].value); if (adreno_is_a8xx(adreno_gpu)) { - gpu_write(gpu, REG_A8XX_GBIF_CX_CONFIG, 0x20023000); + gpu_write(gpu, REG_A6XX_GBIF_CX_CONFIG, 0x20023000); gmu_write(gmu, REG_A6XX_GMU_MRC_GBIF_QOS_CTRL, 0x33); } diff --git a/drivers/gpu/drm/msm/adreno/a8xx_gpu.c b/drivers/gpu/drm/msm/adreno/a8xx_gpu.c index 0f6fd35bd587..add93c1fa039 100644 --- a/drivers/gpu/drm/msm/adreno/a8xx_gpu.c +++ b/drivers/gpu/drm/msm/adreno/a8xx_gpu.c @@ -228,7 +228,7 @@ static void a8xx_set_hwcg(struct msm_gpu *gpu, bool state) * GMU enables clk gating in GBIF during boot up. So, * override that here when hwcg feature is disabled */ - gpu_rmw(gpu, REG_A8XX_GBIF_CX_CONFIG, BIT(0), 0); + gpu_rmw(gpu, REG_A6XX_GBIF_CX_CONFIG, BIT(0), 0); } } diff --git a/drivers/gpu/drm/msm/registers/adreno/a6xx.xml b/drivers/gpu/drm/msm/registers/adreno/a6xx.xml index 3349c01646e1..69dd0446f8d2 100644 --- a/drivers/gpu/drm/msm/registers/adreno/a6xx.xml +++ b/drivers/gpu/drm/msm/registers/adreno/a6xx.xml @@ -1268,7 +1268,7 @@ by a particular renderpass/blit. - + From a25fd4227f59b9147ae6d23336878c28e5437a4d Mon Sep 17 00:00:00 2001 From: Puranam V G Tejaswi Date: Sat, 18 Jul 2026 02:11:28 +0530 Subject: [PATCH 033/641] drm/msm/a6xx: Add Adreno 722 support Add support for Adreno A722, a member of the GEN1 A7xx family. It is derived from A730 and shares the same IP-level configurations: HWCG registers, protected registers, GBIF CX registers and gmu_cgc_mode. Major differences include lower cache/core counts, 1MB GMEM, no Concurrent Binning & LPAC support. Some of the peripheral blocks like RSCC are from A740 that resulted in updates to RSC layout. Add a new entry to the catalog to describe the usual configuration and few additional fixup mainly due to missing CB/LPAC features and updated RSC layout. Signed-off-by: Puranam V G Tejaswi Signed-off-by: Akhil P Oommen Patchwork: https://patchwork.freedesktop.org/patch/740959/ Message-ID: <20260718-eliza-gpu-v2-3-64379dbebd7a@oss.qualcomm.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/a6xx_catalog.c | 34 ++ drivers/gpu/drm/msm/adreno/a6xx_gmu.c | 15 +- drivers/gpu/drm/msm/adreno/a6xx_gpu.c | 4 +- drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c | 81 +++- drivers/gpu/drm/msm/adreno/a6xx_gpu_state.h | 5 + .../msm/adreno/adreno_gen7_17_0_snapshot.h | 428 ++++++++++++++++++ drivers/gpu/drm/msm/adreno/adreno_gpu.h | 5 + 7 files changed, 549 insertions(+), 23 deletions(-) create mode 100644 drivers/gpu/drm/msm/adreno/adreno_gen7_17_0_snapshot.h diff --git a/drivers/gpu/drm/msm/adreno/a6xx_catalog.c b/drivers/gpu/drm/msm/adreno/a6xx_catalog.c index b89da68145bc..90a0c7ffb3c8 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_catalog.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_catalog.c @@ -1500,6 +1500,40 @@ static const struct adreno_info a7xx_gpus[] = { .gmu_cgc_mode = 0x00020000, }, .preempt_record_size = 2860 * SZ_1K, + }, { + .chip_ids = ADRENO_CHIP_IDS(0x43020100), + .family = ADRENO_7XX_GEN1, + .fw = { + [ADRENO_FW_SQE] = "qcom/gen71700_sqe.fw", + [ADRENO_FW_GMU] = "qcom/gen71700_gmu.bin", + }, + .gmem = SZ_1M, + .inactive_period = DRM_MSM_INACTIVE_PERIOD, + .quirks = ADRENO_QUIRK_HAS_CACHED_COHERENT | + ADRENO_QUIRK_HAS_HW_APRIV | + ADRENO_QUIRK_PREEMPTION, + .funcs = &a7xx_gpu_funcs, + .a6xx = &(const struct a6xx_info) { + .hwcg = a730_hwcg, + .protect = &a730_protect, + .pwrup_reglist = &a7xx_pwrup_reglist, + .dyn_pwrup_reglist = &a7xx_dyn_pwrup_reglist, + .gbif_cx = a640_gbif, + .gmu_chipid = 0x07110000, + .gmu_cgc_mode = 0x00020000, + .bcms = (const struct a6xx_bcm[]) { + { .name = "SH0", .buswidth = 16 }, + { .name = "MC0", .buswidth = 4 }, + { + .name = "ACV", + .fixed = true, + .perfmode = BIT(3), + .perfmode_bw = 16500000, + }, + { /* sentinel */ }, + }, + }, + .preempt_record_size = 1536 * SZ_1K, }, { .chip_ids = ADRENO_CHIP_IDS(0x43050a01), /* "C510v2" */ .family = ADRENO_7XX_GEN2, diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gmu.c b/drivers/gpu/drm/msm/adreno/a6xx_gmu.c index a5a5f3534fc0..e97d8d8ee65a 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gmu.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gmu.c @@ -710,7 +710,8 @@ static void a6xx_gmu_rpmh_init(struct a6xx_gmu *gmu) gmu_write_rscc(gmu, REG_A6XX_RSCC_HIDDEN_TCS_CMD0_DATA + 2, 0); gmu_write_rscc(gmu, REG_A6XX_RSCC_HIDDEN_TCS_CMD0_ADDR + 2, 0); gmu_write_rscc(gmu, REG_A6XX_RSCC_HIDDEN_TCS_CMD0_DATA + 4, - adreno_is_a740_family(adreno_gpu) ? 0x80000021 : 0x80000000); + (adreno_is_a740_family(adreno_gpu) || + adreno_is_a722(adreno_gpu)) ? 0x80000021 : 0x80000000); gmu_write_rscc(gmu, REG_A6XX_RSCC_HIDDEN_TCS_CMD0_ADDR + 4, 0); gmu_write_rscc(gmu, REG_A6XX_RSCC_OVERRIDE_START_ADDR, 0); gmu_write_rscc(gmu, REG_A6XX_RSCC_PDC_SEQ_START_ADDR, 0x4520); @@ -718,7 +719,7 @@ static void a6xx_gmu_rpmh_init(struct a6xx_gmu *gmu) gmu_write_rscc(gmu, REG_A6XX_RSCC_PDC_MATCH_VALUE_HI, 0x4514); /* The second spin of A7xx GPUs messed with some register offsets.. */ - if (adreno_is_a740_family(adreno_gpu)) + if (adreno_is_a740_family(adreno_gpu) || adreno_is_a722(adreno_gpu)) seqmem0_drv0_reg = REG_A7XX_RSCC_SEQ_MEM_0_DRV0_A740; /* Load RSC sequencer uCode for sleep and wakeup */ @@ -1034,7 +1035,9 @@ static int a6xx_gmu_fw_start(struct a6xx_gmu *gmu, unsigned int state) if (adreno_is_a8xx(adreno_gpu)) { gpu_write(gpu, REG_A6XX_GBIF_CX_CONFIG, 0x20023000); gmu_write(gmu, REG_A6XX_GMU_MRC_GBIF_QOS_CTRL, 0x33); - } + } else if (adreno_is_a722(adreno_gpu)) + gpu_rmw(gpu, REG_A6XX_GBIF_CX_CONFIG, GENMASK(31, 29), + FIELD_PREP(GENMASK(31, 29), 2)); /* Set up the lowest idle level on the GMU */ a6xx_gmu_power_config(gmu); @@ -1087,7 +1090,8 @@ static void a6xx_gmu_rpmh_off(struct a6xx_gmu *gmu) u32 val, seqmem_off = 0; /* The second spin of A7xx GPUs messed with some register offsets.. */ - if (adreno_is_a740_family(adreno_gpu) || adreno_is_a8xx(adreno_gpu)) + if (adreno_is_a740_family(adreno_gpu) || adreno_is_a722(adreno_gpu) || + adreno_is_a8xx(adreno_gpu)) seqmem_off = 4; /* Make sure there are no outstanding RPMh votes */ @@ -1100,7 +1104,8 @@ static void a6xx_gmu_rpmh_off(struct a6xx_gmu *gmu) gmu_poll_timeout_rscc(gmu, REG_A6XX_RSCC_TCS3_DRV0_STATUS + seqmem_off, val, (val & 1), 100, 1000); - if (!adreno_is_a740_family(adreno_gpu) && !adreno_is_a8xx(adreno_gpu)) + if (!adreno_is_a740_family(adreno_gpu) && !adreno_is_a722(adreno_gpu) && + !adreno_is_a8xx(adreno_gpu)) return; gmu_poll_timeout_rscc(gmu, REG_A7XX_RSCC_TCS4_DRV0_STATUS + seqmem_off, diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gpu.c b/drivers/gpu/drm/msm/adreno/a6xx_gpu.c index e293b4ca808a..2c5faee881b3 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gpu.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gpu.c @@ -1273,7 +1273,8 @@ static int hw_init(struct msm_gpu *gpu) if (!(adreno_is_a650_family(adreno_gpu) || adreno_is_a702(adreno_gpu) || adreno_is_a730(adreno_gpu))) { - gmem_range_min = adreno_is_a740_family(adreno_gpu) ? SZ_16M : SZ_1M; + gmem_range_min = (adreno_is_a740_family(adreno_gpu) || + adreno_is_a722(adreno_gpu)) ? SZ_16M : SZ_1M; /* Set the GMEM VA range [0x100000:0x100000 + gpu->gmem - 1] */ gpu_write64(gpu, REG_A6XX_UCHE_GMEM_RANGE_MIN, gmem_range_min); @@ -1338,6 +1339,7 @@ static int hw_init(struct msm_gpu *gpu) /* Enable fault detection */ if (adreno_is_a612(adreno_gpu) || + adreno_is_a722(adreno_gpu) || adreno_is_a730(adreno_gpu) || adreno_is_a740_family(adreno_gpu)) gpu_write(gpu, REG_A6XX_RBBM_INTERFACE_HANG_INT_CNTL, (1 << 30) | 0xcfffff); diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c b/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c index 3ea8ff8c7404..26c17861cd5d 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c @@ -11,12 +11,14 @@ static const unsigned int *gen7_0_0_external_core_regs[] __always_unused; static const unsigned int *gen7_2_0_external_core_regs[] __always_unused; static const unsigned int *gen7_9_0_external_core_regs[] __always_unused; +static const unsigned int *gen7_17_0_external_core_regs[] __always_unused; static const struct gen7_sptp_cluster_registers gen7_9_0_sptp_clusters[] __always_unused; static const u32 gen7_9_0_cx_debugbus_blocks[] __always_unused; #include "adreno_gen7_0_0_snapshot.h" #include "adreno_gen7_2_0_snapshot.h" #include "adreno_gen7_9_0_snapshot.h" +#include "adreno_gen7_17_0_snapshot.h" struct a6xx_gpu_state_obj { const void *handle; @@ -404,8 +406,13 @@ static void a7xx_get_debugbus_blocks(struct msm_gpu *gpu, int i; if (adreno_gpu->info->family == ADRENO_7XX_GEN1) { - debugbus_blocks = gen7_0_0_debugbus_blocks; - debugbus_blocks_count = ARRAY_SIZE(gen7_0_0_debugbus_blocks); + if (adreno_is_a722(adreno_gpu)) { + debugbus_blocks = gen7_17_0_debugbus_blocks; + debugbus_blocks_count = ARRAY_SIZE(gen7_17_0_debugbus_blocks); + } else { + debugbus_blocks = gen7_0_0_debugbus_blocks; + debugbus_blocks_count = ARRAY_SIZE(gen7_0_0_debugbus_blocks); + } gbif_debugbus_blocks = a7xx_gbif_debugbus_blocks; gbif_debugbus_blocks_count = ARRAY_SIZE(a7xx_gbif_debugbus_blocks); } else if (adreno_gpu->info->family == ADRENO_7XX_GEN2) { @@ -678,8 +685,13 @@ static void a7xx_get_dbgahb_clusters(struct msm_gpu *gpu, unsigned dbgahb_clusters_size; if (adreno_gpu->info->family == ADRENO_7XX_GEN1) { - dbgahb_clusters = gen7_0_0_sptp_clusters; - dbgahb_clusters_size = ARRAY_SIZE(gen7_0_0_sptp_clusters); + if (adreno_is_a722(adreno_gpu)) { + dbgahb_clusters = gen7_17_0_sptp_clusters; + dbgahb_clusters_size = ARRAY_SIZE(gen7_17_0_sptp_clusters); + } else { + dbgahb_clusters = gen7_0_0_sptp_clusters; + dbgahb_clusters_size = ARRAY_SIZE(gen7_0_0_sptp_clusters); + } } else if (adreno_gpu->info->family == ADRENO_7XX_GEN2) { dbgahb_clusters = gen7_2_0_sptp_clusters; dbgahb_clusters_size = ARRAY_SIZE(gen7_2_0_sptp_clusters); @@ -839,8 +851,13 @@ static void a7xx_get_clusters(struct msm_gpu *gpu, unsigned clusters_size; if (adreno_gpu->info->family == ADRENO_7XX_GEN1) { - clusters = gen7_0_0_clusters; - clusters_size = ARRAY_SIZE(gen7_0_0_clusters); + if (adreno_is_a722(adreno_gpu)) { + clusters = gen7_17_0_clusters; + clusters_size = ARRAY_SIZE(gen7_17_0_clusters); + } else { + clusters = gen7_0_0_clusters; + clusters_size = ARRAY_SIZE(gen7_0_0_clusters); + } } else if (adreno_gpu->info->family == ADRENO_7XX_GEN2) { clusters = gen7_2_0_clusters; clusters_size = ARRAY_SIZE(gen7_2_0_clusters); @@ -977,8 +994,13 @@ static void a7xx_get_shaders(struct msm_gpu *gpu, int i; if (adreno_gpu->info->family == ADRENO_7XX_GEN1) { - shader_blocks = gen7_0_0_shader_blocks; - num_shader_blocks = ARRAY_SIZE(gen7_0_0_shader_blocks); + if (adreno_is_a722(adreno_gpu)) { + shader_blocks = gen7_17_0_shader_blocks; + num_shader_blocks = ARRAY_SIZE(gen7_17_0_shader_blocks); + } else { + shader_blocks = gen7_0_0_shader_blocks; + num_shader_blocks = ARRAY_SIZE(gen7_0_0_shader_blocks); + } } else if (adreno_gpu->info->family == ADRENO_7XX_GEN2) { shader_blocks = gen7_2_0_shader_blocks; num_shader_blocks = ARRAY_SIZE(gen7_2_0_shader_blocks); @@ -1379,8 +1401,13 @@ static void a7xx_get_registers(struct msm_gpu *gpu, const struct gen7_reg_list *reglist; if (adreno_gpu->info->family == ADRENO_7XX_GEN1) { - reglist = gen7_0_0_reg_list; - pre_crashdumper_regs = gen7_0_0_pre_crashdumper_gpu_registers; + if (adreno_is_a722(adreno_gpu)) { + reglist = gen7_17_0_reg_list; + pre_crashdumper_regs = gen7_9_0_pre_crashdumper_gpu_registers; + } else { + reglist = gen7_0_0_reg_list; + pre_crashdumper_regs = gen7_0_0_pre_crashdumper_gpu_registers; + } } else if (adreno_gpu->info->family == ADRENO_7XX_GEN2) { reglist = gen7_2_0_reg_list; pre_crashdumper_regs = gen7_0_0_pre_crashdumper_gpu_registers; @@ -1436,7 +1463,9 @@ static void a7xx_get_post_crashdumper_registers(struct msm_gpu *gpu, const u32 *regs; BUG_ON(adreno_gpu->info->family > ADRENO_7XX_GEN3); - regs = gen7_0_0_post_crashdumper_registers; + regs = adreno_is_a722(adreno_gpu) ? + gen7_17_0_post_crashdumper_registers : + gen7_0_0_post_crashdumper_registers; a7xx_get_ahb_gpu_registers(gpu, a6xx_state, regs, @@ -1543,19 +1572,35 @@ static void a7xx_get_indexed_registers(struct msm_gpu *gpu, { struct adreno_gpu *adreno_gpu = to_adreno_gpu(gpu); const struct a6xx_indexed_registers *indexed_regs; + const struct a6xx_indexed_registers *mempool_regs; int i, indexed_count, mempool_count; + bool concurrent_binning; - if (adreno_gpu->info->family <= ADRENO_7XX_GEN2) { + if (adreno_is_a722(adreno_gpu)) { + /* + * Eliza has no BV or LPAC SQE — skip the BV/LPAC indexed + * registers and the BV mempool + */ + indexed_regs = gen7_17_0_cp_indexed_reglist; + indexed_count = ARRAY_SIZE(gen7_17_0_cp_indexed_reglist); + mempool_regs = a7xx_cp_mempool_indexed; + mempool_count = ARRAY_SIZE(a7xx_cp_mempool_indexed); + concurrent_binning = false; + } else if (adreno_gpu->info->family <= ADRENO_7XX_GEN2) { indexed_regs = a7xx_indexed_reglist; indexed_count = ARRAY_SIZE(a7xx_indexed_reglist); + mempool_regs = a7xx_cp_bv_mempool_indexed; + mempool_count = ARRAY_SIZE(a7xx_cp_bv_mempool_indexed); + concurrent_binning = true; } else { BUG_ON(adreno_gpu->info->family != ADRENO_7XX_GEN3); indexed_regs = gen7_9_0_cp_indexed_reg_list; indexed_count = ARRAY_SIZE(gen7_9_0_cp_indexed_reg_list); + mempool_regs = a7xx_cp_bv_mempool_indexed; + mempool_count = ARRAY_SIZE(a7xx_cp_bv_mempool_indexed); + concurrent_binning = true; } - mempool_count = ARRAY_SIZE(a7xx_cp_bv_mempool_indexed); - a6xx_state->indexed_regs = state_kcalloc(a6xx_state, indexed_count + mempool_count, sizeof(*a6xx_state->indexed_regs)); @@ -1570,15 +1615,17 @@ static void a7xx_get_indexed_registers(struct msm_gpu *gpu, &a6xx_state->indexed_regs[i]); gpu_rmw(gpu, REG_A6XX_CP_CHICKEN_DBG, 0, BIT(2)); - gpu_rmw(gpu, REG_A7XX_CP_BV_CHICKEN_DBG, 0, BIT(2)); + if (concurrent_binning) + gpu_rmw(gpu, REG_A7XX_CP_BV_CHICKEN_DBG, 0, BIT(2)); /* Get the contents of the CP_BV mempool */ for (i = 0; i < mempool_count; i++) - a6xx_get_indexed_regs(gpu, a6xx_state, &a7xx_cp_bv_mempool_indexed[i], + a6xx_get_indexed_regs(gpu, a6xx_state, &mempool_regs[i], &a6xx_state->indexed_regs[indexed_count + i]); gpu_rmw(gpu, REG_A6XX_CP_CHICKEN_DBG, BIT(2), 0); - gpu_rmw(gpu, REG_A7XX_CP_BV_CHICKEN_DBG, BIT(2), 0); + if (concurrent_binning) + gpu_rmw(gpu, REG_A7XX_CP_BV_CHICKEN_DBG, BIT(2), 0); return; } diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.h b/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.h index 0a13a65f89ac..6f592394f866 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.h +++ b/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.h @@ -474,6 +474,11 @@ static const struct a6xx_indexed_registers a7xx_cp_bv_mempool_indexed[] = { REG_A7XX_CP_BV_MEM_POOL_DBG_DATA, 0x2200, NULL }, }; +static const struct a6xx_indexed_registers a7xx_cp_mempool_indexed[] = { + { "CP_MEM_POOL_DBG", REG_A6XX_CP_MEM_POOL_DBG_ADDR, + REG_A6XX_CP_MEM_POOL_DBG_DATA, 0x2200, NULL }, +}; + #define DEBUGBUS(_id, _count) { .id = _id, .name = #_id, .count = _count } static const struct a6xx_debugbus_block { diff --git a/drivers/gpu/drm/msm/adreno/adreno_gen7_17_0_snapshot.h b/drivers/gpu/drm/msm/adreno/adreno_gen7_17_0_snapshot.h new file mode 100644 index 000000000000..00a4a0fc97d2 --- /dev/null +++ b/drivers/gpu/drm/msm/adreno/adreno_gen7_17_0_snapshot.h @@ -0,0 +1,428 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) Qualcomm Innovation Center, Inc. All rights reserved. + */ +#ifndef __ADRENO_GEN7_17_0_SNAPSHOT_H +#define __ADRENO_GEN7_17_0_SNAPSHOT_H + +#include "a6xx_gpu_state.h" + +/* + * Snapshot tables for Adreno A722 (Eliza). + * Cluster sub-arrays that are identical to A730 reference gen7_0_0_* + * symbols; adreno_gen7_0_0_snapshot.h is included first in the TU. + */ + +static const u32 gen7_17_0_rscc_registers[] = { + 0x14000, 0x14034, 0x14036, 0x14036, 0x14040, 0x14042, 0x14044, 0x14045, + 0x14047, 0x14047, 0x14080, 0x14084, 0x14089, 0x1408c, 0x14091, 0x14094, + 0x14099, 0x1409c, 0x140a1, 0x140a4, 0x140a9, 0x140ac, 0x140b1, 0x140b4, + 0x140b9, 0x140bc, 0x14100, 0x14104, 0x14114, 0x14119, 0x14124, 0x14132, + 0x14154, 0x1416b, 0x14340, 0x14341, 0x14344, 0x14344, 0x14346, 0x1437c, + 0x143f0, 0x143f8, 0x143fa, 0x143fe, 0x14400, 0x14404, 0x14406, 0x1440a, + 0x1440c, 0x14410, 0x14412, 0x14416, 0x14418, 0x1441c, 0x1441e, 0x14422, + 0x14424, 0x14424, 0x14498, 0x144a0, 0x144a2, 0x144a6, 0x144a8, 0x144ac, + 0x144ae, 0x144b2, 0x144b4, 0x144b8, 0x144ba, 0x144be, 0x144c0, 0x144c4, + 0x144c6, 0x144ca, 0x144cc, 0x144cc, 0x14540, 0x14548, 0x1454a, 0x1454e, + 0x14550, 0x14554, 0x14556, 0x1455a, 0x1455c, 0x14560, 0x14562, 0x14566, + 0x14568, 0x1456c, 0x1456e, 0x14572, 0x14574, 0x14574, 0x145e8, 0x145f0, + 0x145f2, 0x145f6, 0x145f8, 0x145fc, 0x145fe, 0x14602, 0x14604, 0x14608, + 0x1460a, 0x1460e, 0x14610, 0x14614, 0x14616, 0x1461a, 0x1461c, 0x1461c, + 0x14690, 0x14698, 0x1469a, 0x1469e, 0x146a0, 0x146a4, 0x146a6, 0x146aa, + 0x146ac, 0x146b0, 0x146b2, 0x146b6, 0x146b8, 0x146bc, 0x146be, 0x146c2, + 0x146c4, 0x146c4, 0x14738, 0x14740, 0x14742, 0x14746, 0x14748, 0x1474c, + 0x1474e, 0x14752, 0x14754, 0x14758, 0x1475a, 0x1475e, 0x14760, 0x14764, + 0x14766, 0x1476a, 0x1476c, 0x1476c, 0x147e0, 0x147e8, 0x147ea, 0x147ee, + 0x147f0, 0x147f4, 0x147f6, 0x147fa, 0x147fc, 0x14800, 0x14802, 0x14806, + 0x14808, 0x1480c, 0x1480e, 0x14812, 0x14814, 0x14814, 0x14888, 0x14890, + 0x14892, 0x14896, 0x14898, 0x1489c, 0x1489e, 0x148a2, 0x148a4, 0x148a8, + 0x148aa, 0x148ae, 0x148b0, 0x148b4, 0x148b6, 0x148ba, 0x148bc, 0x148bc, + 0x14930, 0x14938, 0x1493a, 0x1493e, 0x14940, 0x14944, 0x14946, 0x1494a, + 0x1494c, 0x14950, 0x14952, 0x14956, 0x14958, 0x1495c, 0x1495e, 0x14962, + 0x14964, 0x14964, + UINT_MAX, UINT_MAX, +}; +static_assert(IS_ALIGNED(sizeof(gen7_17_0_rscc_registers), 8)); + +static const u32 gen7_17_0_cpr_registers[] = { + 0x26800, 0x26805, 0x26808, 0x2680c, 0x26814, 0x26814, 0x2681c, 0x2681c, + 0x26820, 0x26838, 0x26840, 0x26840, 0x26848, 0x26848, 0x26850, 0x26850, + 0x26880, 0x2688e, 0x26980, 0x269b0, 0x269c0, 0x269c2, 0x269c6, 0x269c8, + 0x269e0, 0x269ee, 0x269fb, 0x269ff, 0x26a02, 0x26a07, 0x26a09, 0x26a0b, + 0x26a10, 0x26b0f, 0x27440, 0x27441, 0x27444, 0x27444, 0x27480, 0x274a2, + 0x274ac, 0x274c4, 0x274c8, 0x274da, + UINT_MAX, UINT_MAX, +}; +static_assert(IS_ALIGNED(sizeof(gen7_17_0_cpr_registers), 8)); + +static const u32 gen7_17_0_gpucc_registers[] = { + 0x24000, 0x2400f, 0x24400, 0x2440f, 0x24800, 0x24805, 0x24c00, 0x24cff, + 0x25400, 0x25404, 0x25800, 0x25804, 0x25c00, 0x25c04, 0x26000, 0x26004, + 0x26400, 0x26405, 0x26414, 0x2641d, 0x2642a, 0x2642c, 0x2642e, 0x26432, + 0x26434, 0x26434, 0x26443, 0x26457, 0x26459, 0x2645d, 0x2645f, 0x26464, + 0x26477, 0x26479, 0x26489, 0x2648b, 0x2649a, 0x2649b, 0x264ad, 0x264af, + 0x264b1, 0x264b5, 0x264d6, 0x264d8, 0x264e7, 0x264e9, 0x264f9, 0x264fa, + 0x2650a, 0x2650d, 0x2651f, 0x26520, 0x2652d, 0x2652f, 0x2653e, 0x2653e, + 0x26540, 0x2654e, 0x26554, 0x26573, 0x26576, 0x26576, 0x26593, 0x26593, + 0x26600, 0x26616, 0x26620, 0x2662d, 0x26630, 0x26631, 0x26635, 0x26635, + 0x26637, 0x26637, 0x2663a, 0x2663a, 0x26642, 0x26642, 0x26656, 0x26658, + 0x2665b, 0x2665d, 0x2665f, 0x26662, + UINT_MAX, UINT_MAX, +}; +static_assert(IS_ALIGNED(sizeof(gen7_17_0_gpucc_registers), 8)); + +static const u32 *gen7_17_0_external_core_regs[] = { + gen7_17_0_gpucc_registers, + gen7_17_0_cpr_registers, +}; + +static const u32 gen7_17_0_debugbus_blocks[] = { + A7XX_DBGBUS_CP_0_0, + A7XX_DBGBUS_CP_0_1, + A7XX_DBGBUS_RBBM, + A7XX_DBGBUS_HLSQ, + A7XX_DBGBUS_UCHE_0, + A7XX_DBGBUS_TESS_BR, + A7XX_DBGBUS_PC_BR, + A7XX_DBGBUS_VFDP_BR, + A7XX_DBGBUS_VPC_BR, + A7XX_DBGBUS_TSE_BR, + A7XX_DBGBUS_RAS_BR, + A7XX_DBGBUS_VSC, + A7XX_DBGBUS_COM_0, + A7XX_DBGBUS_LRZ_BR, + A7XX_DBGBUS_UFC_0, + A7XX_DBGBUS_UFC_1, + A7XX_DBGBUS_GMU_GX, + A7XX_DBGBUS_DBGC, + A7XX_DBGBUS_GPC_BR, + A7XX_DBGBUS_LARC, + A7XX_DBGBUS_HLSQ_SPTP, + A7XX_DBGBUS_RB_0, + A7XX_DBGBUS_RB_1, + A7XX_DBGBUS_UCHE_WRAPPER, + A7XX_DBGBUS_CCU_0, + A7XX_DBGBUS_CCU_1, + A7XX_DBGBUS_VFD_BR_0, + A7XX_DBGBUS_VFD_BR_1, + A7XX_DBGBUS_VFD_BR_2, + A7XX_DBGBUS_VFD_BR_3, + A7XX_DBGBUS_USP_0, + A7XX_DBGBUS_USP_1, + A7XX_DBGBUS_TP_0, + A7XX_DBGBUS_TP_1, + A7XX_DBGBUS_TP_2, + A7XX_DBGBUS_TP_3, + A7XX_DBGBUS_USPTP_0, + A7XX_DBGBUS_USPTP_1, + A7XX_DBGBUS_USPTP_2, + A7XX_DBGBUS_USPTP_3, +}; + +static const struct gen7_sel_reg gen7_17_0_rb_rac_sel = { + .host_reg = REG_A6XX_RB_SUB_BLOCK_SEL_CNTL_HOST, + .cd_reg = REG_A6XX_RB_SUB_BLOCK_SEL_CNTL_CD, + .val = 0x0, +}; + +static const struct gen7_sel_reg gen7_17_0_rb_rbp_sel = { + .host_reg = REG_A6XX_RB_SUB_BLOCK_SEL_CNTL_HOST, + .cd_reg = REG_A6XX_RB_SUB_BLOCK_SEL_CNTL_CD, + .val = 0x9, +}; + +static const u32 gen7_17_0_post_crashdumper_registers[] = { + 0x00535, 0x00535, + UINT_MAX, UINT_MAX, +}; +static_assert(IS_ALIGNED(sizeof(gen7_17_0_post_crashdumper_registers), 8)); + +static const u32 gen7_17_0_gpu_registers[] = { + 0x00000, 0x00000, 0x00002, 0x00002, 0x00011, 0x00012, 0x00016, 0x0001b, + 0x0001f, 0x00032, 0x00038, 0x0003c, 0x00042, 0x00042, 0x00044, 0x00044, + 0x00047, 0x00047, 0x00049, 0x0004a, 0x0004c, 0x0004c, 0x00050, 0x00050, + 0x00056, 0x00056, 0x00073, 0x00075, 0x000ad, 0x000ae, 0x000b0, 0x000b0, + 0x000b4, 0x000b4, 0x000b8, 0x000b8, 0x000bc, 0x000bc, 0x000c0, 0x000c0, + 0x000c4, 0x000c4, 0x000c8, 0x000c8, 0x000cc, 0x000cc, 0x000d0, 0x000d0, + 0x000d4, 0x000d4, 0x000d8, 0x000d8, 0x000dc, 0x000dc, 0x000e0, 0x000e0, + 0x000e4, 0x000e4, 0x000e8, 0x000e8, 0x000ec, 0x000ec, 0x000f0, 0x000f0, + 0x000f4, 0x000f4, 0x000f8, 0x000f8, 0x00100, 0x00100, 0x00104, 0x0010b, + 0x0010f, 0x0011d, 0x0012f, 0x0012f, 0x00200, 0x0020d, 0x00215, 0x00243, + 0x00260, 0x00268, 0x00272, 0x00274, 0x00286, 0x00286, 0x0028a, 0x0028a, + 0x0028c, 0x0028c, 0x00300, 0x00401, 0x00500, 0x00500, 0x00507, 0x0050b, + 0x0050f, 0x0050f, 0x00511, 0x00511, 0x00533, 0x00534, 0x00540, 0x00555, + 0x00564, 0x00567, 0x00800, 0x00808, 0x00810, 0x00813, 0x00820, 0x00821, + 0x00823, 0x00827, 0x00830, 0x00834, 0x00840, 0x00841, 0x00843, 0x00847, + 0x0084f, 0x00886, 0x008a0, 0x008ab, 0x008c0, 0x008c0, 0x008c4, 0x008c5, + 0x008d0, 0x008dd, 0x008f0, 0x008f3, 0x00900, 0x00903, 0x00908, 0x00911, + 0x00928, 0x0093e, 0x00942, 0x0094d, 0x00980, 0x00984, 0x0098d, 0x0098f, + 0x009b0, 0x009b4, 0x009c2, 0x009c9, 0x009ce, 0x009d7, 0x00a00, 0x00a00, + 0x00a02, 0x00a03, 0x00a10, 0x00a4f, 0x00a67, 0x00a6c, 0x00a9c, 0x00a9f, + 0x00c00, 0x00c00, 0x00c02, 0x00c04, 0x00c06, 0x00c06, 0x00c10, 0x00cd9, + 0x00ce0, 0x00d0c, 0x00df0, 0x00df4, 0x00e01, 0x00e02, 0x00e07, 0x00e0e, + 0x00e10, 0x00e12, 0x00e17, 0x00e17, 0x00e19, 0x00e19, 0x00e1b, 0x00e2b, + 0x00e30, 0x00e32, 0x00e38, 0x00e3c, + UINT_MAX, UINT_MAX, +}; +static_assert(IS_ALIGNED(sizeof(gen7_17_0_gpu_registers), 8)); + +static const u32 gen7_17_0_dbgc_registers[] = { + 0x00600, 0x0061c, 0x0061e, 0x00634, 0x00640, 0x0065a, 0x00679, 0x0067a, + 0x00699, 0x00699, 0x0069b, 0x0069e, 0x18400, 0x1841c, 0x1841e, 0x18434, + 0x18440, 0x1845c, 0x18479, 0x1847c, 0x18580, 0x18581, + UINT_MAX, UINT_MAX, +}; +static_assert(IS_ALIGNED(sizeof(gen7_17_0_dbgc_registers), 8)); + +static const u32 gen7_17_0_noncontext_pipe_br_registers[] = { + 0x00887, 0x0088c, 0x08600, 0x08600, 0x08602, 0x08602, 0x08610, 0x0861b, + 0x08620, 0x08620, 0x08630, 0x08630, 0x08637, 0x08639, 0x08640, 0x08640, + 0x09600, 0x09600, 0x09602, 0x09603, 0x0960a, 0x09616, 0x09624, 0x0963a, + 0x09640, 0x09640, 0x09e00, 0x09e00, 0x09e02, 0x09e07, 0x09e0a, 0x09e16, + 0x09e19, 0x09e19, 0x09e1c, 0x09e1c, 0x09e20, 0x09e25, 0x09e30, 0x09e31, + 0x09e40, 0x09e51, 0x09e64, 0x09e64, 0x09e70, 0x09e72, 0x09e78, 0x09e79, + 0x09e80, 0x09fff, 0x0a600, 0x0a600, 0x0a603, 0x0a603, 0x0a610, 0x0a61f, + 0x0a630, 0x0a631, 0x0a638, 0x0a638, + UINT_MAX, UINT_MAX, +}; +static_assert(IS_ALIGNED(sizeof(gen7_17_0_noncontext_pipe_br_registers), 8)); + +static const u32 gen7_17_0_noncontext_rb_rac_pipe_br_registers[] = { + 0x08e10, 0x08e1c, 0x08e20, 0x08e25, 0x08e51, 0x08e54, + UINT_MAX, UINT_MAX, +}; +static_assert(IS_ALIGNED(sizeof(gen7_17_0_noncontext_rb_rac_pipe_br_registers), 8)); + +static const u32 gen7_17_0_noncontext_rb_rbp_pipe_br_registers[] = { + 0x08e01, 0x08e01, 0x08e04, 0x08e04, 0x08e06, 0x08e09, 0x08e0c, 0x08e0c, + 0x08e28, 0x08e28, 0x08e2c, 0x08e35, 0x08e3b, 0x08e3f, 0x08e50, 0x08e50, + 0x08e5b, 0x08e5d, 0x08e5f, 0x08e5f, 0x08e61, 0x08e61, 0x08e63, 0x08e65, + 0x08e68, 0x08e68, 0x08e70, 0x08e79, 0x08e80, 0x08e8f, + UINT_MAX, UINT_MAX, +}; +static_assert(IS_ALIGNED(sizeof(gen7_17_0_noncontext_rb_rbp_pipe_br_registers), 8)); + +static const u32 gen7_17_0_pc_cluster_fe_pipe_br_registers[] = { + 0x09800, 0x09804, 0x09806, 0x0980a, 0x09810, 0x09811, 0x09884, 0x09886, + 0x09970, 0x09972, 0x09b00, 0x09b08, + UINT_MAX, UINT_MAX, +}; +static_assert(IS_ALIGNED(sizeof(gen7_17_0_pc_cluster_fe_pipe_br_registers), 8)); + +static const u32 gen7_17_0_sp_cluster_sp_ps_pipe_lpac_hlsq_state_registers[] = { + 0x0aa40, 0x0aabf, + UINT_MAX, UINT_MAX, +}; +static_assert(IS_ALIGNED(sizeof(gen7_17_0_sp_cluster_sp_ps_pipe_lpac_hlsq_state_registers), 8)); + +static const u32 gen7_17_0_sp_cluster_sp_ps_pipe_lpac_usptp_registers[] = { + 0x0aa40, 0x0aabf, + UINT_MAX, UINT_MAX, +}; +static_assert(IS_ALIGNED(sizeof(gen7_17_0_sp_cluster_sp_ps_pipe_lpac_usptp_registers), 8)); + +static const u32 gen7_17_0_non_context_tpl1_pipe_none_usptp_registers[] = { + 0x0b602, 0x0b602, 0x0b604, 0x0b604, 0x0b608, 0x0b60c, 0x0b60f, 0x0b621, + 0x0b630, 0x0b633, + UINT_MAX, UINT_MAX, +}; +static_assert(IS_ALIGNED(sizeof(gen7_17_0_non_context_tpl1_pipe_none_usptp_registers), 8)); + +static const u32 gen7_17_0_non_context_tpl1_pipe_br_usptp_registers[] = { + 0x0b600, 0x0b600, + UINT_MAX, UINT_MAX, +}; +static_assert(IS_ALIGNED(sizeof(gen7_17_0_non_context_tpl1_pipe_br_usptp_registers), 8)); + +static const u32 gen7_17_0_tpl1_cluster_sp_vs_pipe_br_usptp_registers[] = { + 0x0b300, 0x0b307, 0x0b309, 0x0b309, 0x0b310, 0x0b310, + UINT_MAX, UINT_MAX, +}; +static_assert(IS_ALIGNED(sizeof(gen7_17_0_tpl1_cluster_sp_vs_pipe_br_usptp_registers), 8)); + +static const u32 gen7_17_0_tpl1_cluster_sp_ps_pipe_br_usptp_registers[] = { + 0x0b180, 0x0b183, 0x0b190, 0x0b195, 0x0b2c0, 0x0b2d5, 0x0b300, 0x0b307, + 0x0b309, 0x0b309, 0x0b310, 0x0b310, + UINT_MAX, UINT_MAX, +}; +static_assert(IS_ALIGNED(sizeof(gen7_17_0_tpl1_cluster_sp_ps_pipe_br_usptp_registers), 8)); + +/* No BV pipe — gen7_0_0_* sub-arrays are shared from adreno_gen7_0_0_snapshot.h */ +static struct gen7_cluster_registers gen7_17_0_clusters[] = { + { A7XX_CLUSTER_NONE, PIPE_BR, STATE_NON_CONTEXT, + gen7_17_0_noncontext_pipe_br_registers, }, + { A7XX_CLUSTER_NONE, PIPE_BR, STATE_NON_CONTEXT, + gen7_17_0_noncontext_rb_rac_pipe_br_registers, &gen7_17_0_rb_rac_sel, }, + { A7XX_CLUSTER_NONE, PIPE_BR, STATE_NON_CONTEXT, + gen7_17_0_noncontext_rb_rbp_pipe_br_registers, &gen7_17_0_rb_rbp_sel, }, + { A7XX_CLUSTER_PS, PIPE_BR, STATE_FORCE_CTXT_0, + gen7_0_0_rb_rac_cluster_ps_pipe_br_registers, &gen7_17_0_rb_rac_sel, }, + { A7XX_CLUSTER_PS, PIPE_BR, STATE_FORCE_CTXT_1, + gen7_0_0_rb_rac_cluster_ps_pipe_br_registers, &gen7_17_0_rb_rac_sel, }, + { A7XX_CLUSTER_PS, PIPE_BR, STATE_FORCE_CTXT_0, + gen7_0_0_rb_rbp_cluster_ps_pipe_br_registers, &gen7_17_0_rb_rbp_sel, }, + { A7XX_CLUSTER_PS, PIPE_BR, STATE_FORCE_CTXT_1, + gen7_0_0_rb_rbp_cluster_ps_pipe_br_registers, &gen7_17_0_rb_rbp_sel, }, + { A7XX_CLUSTER_GRAS, PIPE_BR, STATE_FORCE_CTXT_0, + gen7_0_0_gras_cluster_gras_pipe_br_registers, }, + { A7XX_CLUSTER_GRAS, PIPE_BR, STATE_FORCE_CTXT_1, + gen7_0_0_gras_cluster_gras_pipe_br_registers, }, + { A7XX_CLUSTER_FE, PIPE_BR, STATE_FORCE_CTXT_0, + gen7_17_0_pc_cluster_fe_pipe_br_registers, }, + { A7XX_CLUSTER_FE, PIPE_BR, STATE_FORCE_CTXT_1, + gen7_17_0_pc_cluster_fe_pipe_br_registers, }, + { A7XX_CLUSTER_FE, PIPE_BR, STATE_FORCE_CTXT_0, + gen7_0_0_vfd_cluster_fe_pipe_bv_registers, }, + { A7XX_CLUSTER_FE, PIPE_BR, STATE_FORCE_CTXT_1, + gen7_0_0_vfd_cluster_fe_pipe_bv_registers, }, + { A7XX_CLUSTER_FE, PIPE_BR, STATE_FORCE_CTXT_0, + gen7_0_0_vpc_cluster_fe_pipe_br_registers, }, + { A7XX_CLUSTER_FE, PIPE_BR, STATE_FORCE_CTXT_1, + gen7_0_0_vpc_cluster_fe_pipe_br_registers, }, + { A7XX_CLUSTER_PC_VS, PIPE_BR, STATE_FORCE_CTXT_0, + gen7_0_0_vpc_cluster_pc_vs_pipe_br_registers, }, + { A7XX_CLUSTER_PC_VS, PIPE_BR, STATE_FORCE_CTXT_1, + gen7_0_0_vpc_cluster_pc_vs_pipe_br_registers, }, + { A7XX_CLUSTER_VPC_PS, PIPE_BR, STATE_FORCE_CTXT_0, + gen7_0_0_vpc_cluster_vpc_ps_pipe_br_registers, }, + { A7XX_CLUSTER_VPC_PS, PIPE_BR, STATE_FORCE_CTXT_1, + gen7_0_0_vpc_cluster_vpc_ps_pipe_br_registers, }, +}; + +/* No BV pipe; 2 SPs, 2 USPTPs */ +static struct gen7_sptp_cluster_registers gen7_17_0_sptp_clusters[] = { + { A7XX_CLUSTER_NONE, A7XX_SP_NCTX_REG, PIPE_BR, 0, A7XX_HLSQ_STATE, + gen7_0_0_sp_noncontext_pipe_br_hlsq_state_registers, 0xae00 }, + { A7XX_CLUSTER_NONE, A7XX_SP_NCTX_REG, PIPE_BR, 0, A7XX_SP_TOP, + gen7_0_0_sp_noncontext_pipe_br_sp_top_registers, 0xae00 }, + { A7XX_CLUSTER_NONE, A7XX_SP_NCTX_REG, PIPE_BR, 0, A7XX_USPTP, + gen7_0_0_sp_noncontext_pipe_br_usptp_registers, 0xae00 }, + { A7XX_CLUSTER_SP_VS, A7XX_SP_CTX0_3D_CVS_REG, PIPE_BR, 0, A7XX_HLSQ_STATE, + gen7_0_0_sp_cluster_sp_vs_pipe_br_hlsq_state_registers, 0xa800 }, + { A7XX_CLUSTER_SP_VS, A7XX_SP_CTX0_3D_CVS_REG, PIPE_BR, 0, A7XX_SP_TOP, + gen7_0_0_sp_cluster_sp_vs_pipe_br_sp_top_registers, 0xa800 }, + { A7XX_CLUSTER_SP_VS, A7XX_SP_CTX0_3D_CVS_REG, PIPE_BR, 0, A7XX_USPTP, + gen7_0_0_sp_cluster_sp_vs_pipe_br_usptp_registers, 0xa800 }, + { A7XX_CLUSTER_SP_VS, A7XX_SP_CTX1_3D_CVS_REG, PIPE_BR, 1, A7XX_HLSQ_STATE, + gen7_0_0_sp_cluster_sp_vs_pipe_br_hlsq_state_registers, 0xa800 }, + { A7XX_CLUSTER_SP_VS, A7XX_SP_CTX1_3D_CVS_REG, PIPE_BR, 1, A7XX_SP_TOP, + gen7_0_0_sp_cluster_sp_vs_pipe_br_sp_top_registers, 0xa800 }, + { A7XX_CLUSTER_SP_VS, A7XX_SP_CTX1_3D_CVS_REG, PIPE_BR, 1, A7XX_USPTP, + gen7_0_0_sp_cluster_sp_vs_pipe_br_usptp_registers, 0xa800 }, + { A7XX_CLUSTER_SP_PS, A7XX_SP_CTX0_3D_CPS_REG, PIPE_BR, 0, A7XX_HLSQ_STATE, + gen7_0_0_sp_cluster_sp_ps_pipe_br_hlsq_state_registers, 0xa800 }, + { A7XX_CLUSTER_SP_PS, A7XX_SP_CTX0_3D_CPS_REG, PIPE_BR, 0, A7XX_HLSQ_DP, + gen7_0_0_sp_cluster_sp_ps_pipe_br_hlsq_dp_registers, 0xa800 }, + { A7XX_CLUSTER_SP_PS, A7XX_SP_CTX0_3D_CPS_REG, PIPE_BR, 0, A7XX_SP_TOP, + gen7_0_0_sp_cluster_sp_ps_pipe_br_sp_top_registers, 0xa800 }, + { A7XX_CLUSTER_SP_PS, A7XX_SP_CTX0_3D_CPS_REG, PIPE_BR, 0, A7XX_USPTP, + gen7_0_0_sp_cluster_sp_ps_pipe_br_usptp_registers, 0xa800 }, + { A7XX_CLUSTER_SP_PS, A7XX_SP_CTX0_3D_CPS_REG, PIPE_LPAC, 0, A7XX_HLSQ_STATE, + gen7_17_0_sp_cluster_sp_ps_pipe_lpac_hlsq_state_registers, 0xa800 }, + { A7XX_CLUSTER_SP_PS, A7XX_SP_CTX0_3D_CPS_REG, PIPE_LPAC, 0, A7XX_USPTP, + gen7_17_0_sp_cluster_sp_ps_pipe_lpac_usptp_registers, 0xa800 }, + { A7XX_CLUSTER_SP_PS, A7XX_SP_CTX1_3D_CPS_REG, PIPE_BR, 1, A7XX_HLSQ_STATE, + gen7_0_0_sp_cluster_sp_ps_pipe_br_hlsq_state_registers, 0xa800 }, + { A7XX_CLUSTER_SP_PS, A7XX_SP_CTX1_3D_CPS_REG, PIPE_BR, 1, A7XX_HLSQ_DP, + gen7_0_0_sp_cluster_sp_ps_pipe_br_hlsq_dp_registers, 0xa800 }, + { A7XX_CLUSTER_SP_PS, A7XX_SP_CTX1_3D_CPS_REG, PIPE_BR, 1, A7XX_SP_TOP, + gen7_0_0_sp_cluster_sp_ps_pipe_br_sp_top_registers, 0xa800 }, + { A7XX_CLUSTER_SP_PS, A7XX_SP_CTX1_3D_CPS_REG, PIPE_BR, 1, A7XX_USPTP, + gen7_0_0_sp_cluster_sp_ps_pipe_br_usptp_registers, 0xa800 }, + { A7XX_CLUSTER_SP_PS, A7XX_SP_CTX2_3D_CPS_REG, PIPE_BR, 2, A7XX_HLSQ_DP, + gen7_0_0_sp_cluster_sp_ps_pipe_br_hlsq_dp_registers, 0xa800 }, + { A7XX_CLUSTER_SP_PS, A7XX_SP_CTX2_3D_CPS_REG, PIPE_BR, 2, A7XX_SP_TOP, + gen7_0_0_sp_cluster_sp_ps_pipe_br_sp_top_registers, 0xa800 }, + { A7XX_CLUSTER_SP_PS, A7XX_SP_CTX2_3D_CPS_REG, PIPE_BR, 2, A7XX_USPTP, + gen7_0_0_sp_cluster_sp_ps_pipe_br_usptp_registers, 0xa800 }, + { A7XX_CLUSTER_SP_PS, A7XX_SP_CTX3_3D_CPS_REG, PIPE_BR, 3, A7XX_HLSQ_DP, + gen7_0_0_sp_cluster_sp_ps_pipe_br_hlsq_dp_registers, 0xa800 }, + { A7XX_CLUSTER_SP_PS, A7XX_SP_CTX3_3D_CPS_REG, PIPE_BR, 3, A7XX_SP_TOP, + gen7_0_0_sp_cluster_sp_ps_pipe_br_sp_top_registers, 0xa800 }, + { A7XX_CLUSTER_SP_PS, A7XX_SP_CTX3_3D_CPS_REG, PIPE_BR, 3, A7XX_USPTP, + gen7_0_0_sp_cluster_sp_ps_pipe_br_usptp_registers, 0xa800 }, + { A7XX_CLUSTER_NONE, A7XX_TP0_NCTX_REG, PIPE_NONE, 0, A7XX_USPTP, + gen7_17_0_non_context_tpl1_pipe_none_usptp_registers, 0xb600 }, + { A7XX_CLUSTER_NONE, A7XX_TP0_NCTX_REG, PIPE_BR, 0, A7XX_USPTP, + gen7_17_0_non_context_tpl1_pipe_br_usptp_registers, 0xb600 }, + { A7XX_CLUSTER_SP_VS, A7XX_TP0_CTX0_3D_CVS_REG, PIPE_BR, 0, A7XX_USPTP, + gen7_17_0_tpl1_cluster_sp_vs_pipe_br_usptp_registers, 0xb000 }, + { A7XX_CLUSTER_SP_VS, A7XX_TP0_CTX1_3D_CVS_REG, PIPE_BR, 1, A7XX_USPTP, + gen7_17_0_tpl1_cluster_sp_vs_pipe_br_usptp_registers, 0xb000 }, + { A7XX_CLUSTER_SP_PS, A7XX_TP0_CTX0_3D_CPS_REG, PIPE_BR, 0, A7XX_USPTP, + gen7_17_0_tpl1_cluster_sp_ps_pipe_br_usptp_registers, 0xb000 }, + { A7XX_CLUSTER_SP_PS, A7XX_TP0_CTX1_3D_CPS_REG, PIPE_BR, 1, A7XX_USPTP, + gen7_17_0_tpl1_cluster_sp_ps_pipe_br_usptp_registers, 0xb000 }, + { A7XX_CLUSTER_SP_PS, A7XX_TP0_CTX2_3D_CPS_REG, PIPE_BR, 2, A7XX_USPTP, + gen7_17_0_tpl1_cluster_sp_ps_pipe_br_usptp_registers, 0xb000 }, + { A7XX_CLUSTER_SP_PS, A7XX_TP0_CTX3_3D_CPS_REG, PIPE_BR, 3, A7XX_USPTP, + gen7_17_0_tpl1_cluster_sp_ps_pipe_br_usptp_registers, 0xb000 }, +}; + +static struct gen7_shader_block gen7_17_0_shader_blocks[] = { + { A7XX_TP0_TMO_DATA, 0x0200, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_TP0_SMO_DATA, 0x0080, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_TP0_MIPMAP_BASE_DATA, 0x03c0, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_SP_INST_DATA, 0x0800, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_SP_INST_DATA_1, 0x0800, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_SP_LB_0_DATA, 0x0800, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_SP_LB_1_DATA, 0x0800, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_SP_LB_2_DATA, 0x0800, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_SP_LB_3_DATA, 0x0800, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_SP_LB_4_DATA, 0x0800, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_SP_LB_5_DATA, 0x0800, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_SP_CB_RAM, 0x0390, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_SP_INST_TAG, 0x0090, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_SP_TMO_TAG, 0x0080, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_SP_SMO_TAG, 0x0080, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_SP_STATE_DATA, 0x0040, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_SP_HWAVE_RAM, 0x0100, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_SP_L0_INST_BUF, 0x0050, 2, 2, PIPE_BR, A7XX_USPTP }, + { A7XX_HLSQ_CVS_BE_CTXT_BUF_RAM_TAG, 0x0010, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_CPS_BE_CTXT_BUF_RAM_TAG, 0x0010, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_GFX_CVS_BE_CTXT_BUF_RAM, 0x0300, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_GFX_CPS_BE_CTXT_BUF_RAM, 0x0300, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_CHUNK_CVS_RAM, 0x01c0, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_CHUNK_CPS_RAM, 0x0300, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_CHUNK_CVS_RAM_TAG, 0x0040, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_CHUNK_CPS_RAM_TAG, 0x0040, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_ICB_CVS_CB_BASE_TAG, 0x0010, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_ICB_CPS_CB_BASE_TAG, 0x0010, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_CVS_MISC_RAM, 0x0280, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_CPS_MISC_RAM, 0x0800, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_CPS_MISC_RAM_1, 0x0200, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_INST_RAM, 0x0800, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_GFX_CVS_CONST_RAM, 0x0800, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_GFX_CPS_CONST_RAM, 0x0800, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_CVS_MISC_RAM_TAG, 0x0010, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_CPS_MISC_RAM_TAG, 0x0010, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_INST_RAM_TAG, 0x0080, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_GFX_CVS_CONST_RAM_TAG, 0x0064, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_GFX_CPS_CONST_RAM_TAG, 0x0064, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_INST_RAM_1, 0x0800, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_STPROC_META, 0x0010, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_BV_BE_META, 0x0010, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_DATAPATH_META, 0x0020, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_FRONTEND_META, 0x0040, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_INDIRECT_META, 0x0010, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, + { A7XX_HLSQ_BACKEND_META, 0x0040, 1, 1, PIPE_BR, A7XX_HLSQ_STATE }, +}; + +static struct gen7_reg_list gen7_17_0_reg_list[] = { + { gen7_17_0_gpu_registers, NULL }, + { gen7_17_0_dbgc_registers, NULL }, + { NULL, NULL }, +}; + +static const struct a6xx_indexed_registers gen7_17_0_cp_indexed_reglist[] = { + { "CP_SQE_STAT", REG_A6XX_CP_SQE_STAT_ADDR, + REG_A6XX_CP_SQE_STAT_DATA, 0x40, NULL }, + { "CP_DRAW_STATE", REG_A6XX_CP_DRAW_STATE_ADDR, + REG_A6XX_CP_DRAW_STATE_DATA, 0x100, NULL }, + { "CP_SQE_UCODE_DBG", REG_A6XX_CP_SQE_UCODE_DBG_ADDR, + REG_A6XX_CP_SQE_UCODE_DBG_DATA, 0x8000, NULL }, + { "CP_ROQ_DBG", REG_A6XX_CP_ROQ_DBG_ADDR, + REG_A6XX_CP_ROQ_DBG_DATA, 0, a7xx_get_cp_roq_size }, +}; + +#endif /* __ADRENO_GEN7_17_0_SNAPSHOT_H */ diff --git a/drivers/gpu/drm/msm/adreno/adreno_gpu.h b/drivers/gpu/drm/msm/adreno/adreno_gpu.h index 1f201322cb6e..114a40f79ef3 100644 --- a/drivers/gpu/drm/msm/adreno/adreno_gpu.h +++ b/drivers/gpu/drm/msm/adreno/adreno_gpu.h @@ -562,6 +562,11 @@ static inline int adreno_is_x185(struct adreno_gpu *gpu) return gpu->info->chip_ids[0] == 0x43050c01; } +static inline int adreno_is_a722(struct adreno_gpu *gpu) +{ + return gpu->info->chip_ids[0] == 0x43020100; +} + static inline int adreno_is_a740_family(struct adreno_gpu *gpu) { if (WARN_ON_ONCE(!gpu->info)) From cee19f08f460bc7a9b531472f25137aa8626cd51 Mon Sep 17 00:00:00 2001 From: Akhil P Oommen Date: Sat, 18 Jul 2026 02:11:29 +0530 Subject: [PATCH 034/641] dt-bindings: arm-smmu: Document GPU SMMU for Eliza SoC Add specific compatible strings to document the GPU SMMU present in the Eliza SoC. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Akhil P Oommen Patchwork: https://patchwork.freedesktop.org/patch/740962/ Message-ID: <20260718-eliza-gpu-v2-4-64379dbebd7a@oss.qualcomm.com> Signed-off-by: Rob Clark --- Documentation/devicetree/bindings/iommu/arm,smmu.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/iommu/arm,smmu.yaml b/Documentation/devicetree/bindings/iommu/arm,smmu.yaml index ad15fda5c25e..d87281026a71 100644 --- a/Documentation/devicetree/bindings/iommu/arm,smmu.yaml +++ b/Documentation/devicetree/bindings/iommu/arm,smmu.yaml @@ -95,6 +95,7 @@ properties: - description: Qcom Adreno GPUs implementing "qcom,smmu-500" and "arm,mmu-500" items: - enum: + - qcom,eliza-smmu-500 - qcom,glymur-smmu-500 - qcom,hawi-smmu-500 - qcom,kaanapali-smmu-500 @@ -572,6 +573,7 @@ allOf: compatible: items: - enum: + - qcom,eliza-smmu-500 - qcom,glymur-smmu-500 - qcom,hawi-smmu-500 - qcom,kaanapali-smmu-500 From 0b69e4f4ee325ee0b50396657fd19d4d7b0a6294 Mon Sep 17 00:00:00 2001 From: Puranam V G Tejaswi Date: Sat, 18 Jul 2026 02:11:30 +0530 Subject: [PATCH 035/641] dt-bindings: display/msm: Document Adreno 722 GPU and GMU Adreno 722 found in Eliza chipset belongs to the A7x Gen1 family. It is derived from A730 and shares the same IP-level configurations: HWCG registers, protected registers, GBIF CX registers and gmu_cgc_mode. Major differences include lower cache/core counts, 1MB GMEM, no Concurrent Binning & LPAC support. Some of the peripheral blocks like RSCC are from A740 that resulted in updates to RSC layout. Update the dt-binding docs to document this GPU and GMU. Signed-off-by: Puranam V G Tejaswi Acked-by: Krzysztof Kozlowski Signed-off-by: Akhil P Oommen Patchwork: https://patchwork.freedesktop.org/patch/740964/ Message-ID: <20260718-eliza-gpu-v2-5-64379dbebd7a@oss.qualcomm.com> Signed-off-by: Rob Clark --- Documentation/devicetree/bindings/display/msm/gmu.yaml | 1 + Documentation/devicetree/bindings/display/msm/gpu.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/display/msm/gmu.yaml b/Documentation/devicetree/bindings/display/msm/gmu.yaml index 8578c2f8122e..9e459f12ce3f 100644 --- a/Documentation/devicetree/bindings/display/msm/gmu.yaml +++ b/Documentation/devicetree/bindings/display/msm/gmu.yaml @@ -262,6 +262,7 @@ allOf: compatible: contains: enum: + - qcom,adreno-gmu-722.0 - qcom,adreno-gmu-730.1 - qcom,adreno-gmu-740.1 - qcom,adreno-gmu-750.1 diff --git a/Documentation/devicetree/bindings/display/msm/gpu.yaml b/Documentation/devicetree/bindings/display/msm/gpu.yaml index 992d3de7c117..e5efe2d35ddc 100644 --- a/Documentation/devicetree/bindings/display/msm/gpu.yaml +++ b/Documentation/devicetree/bindings/display/msm/gpu.yaml @@ -445,6 +445,7 @@ allOf: - qcom,adreno-680.1 - qcom,adreno-690.0 - qcom,adreno-730.1 + - qcom,adreno-43020100 - qcom,adreno-43030c00 - qcom,adreno-43050a01 - qcom,adreno-43050c01 From 36a6518e746dcd2e30391c61ce6a8c4bcafd7bb7 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Mon, 13 Jul 2026 21:15:10 +0800 Subject: [PATCH 036/641] EDAC: Remove redundant dev_err() Since 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() and devm_request_threaded_irq() automatically log detailed error messages on failure. Remove the now-redundant driver-specific dev_err() calls. Signed-off-by: Pan Chuang Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Andrew Jeffery # aspeed Link: https://patch.msgid.link/20260713131510.332386-1-panchuang@vivo.com --- drivers/edac/al_mc_edac.c | 12 ++---------- drivers/edac/aspeed_edac.c | 4 +--- drivers/edac/highbank_mc_edac.c | 4 +--- drivers/edac/thunderx_edac.c | 4 +--- drivers/edac/xgene_edac.c | 5 +---- 5 files changed, 6 insertions(+), 23 deletions(-) diff --git a/drivers/edac/al_mc_edac.c b/drivers/edac/al_mc_edac.c index 178b9e581a72..bf6921d8890b 100644 --- a/drivers/edac/al_mc_edac.c +++ b/drivers/edac/al_mc_edac.c @@ -302,12 +302,8 @@ static int al_mc_edac_probe(struct platform_device *pdev) IRQF_SHARED, pdev->name, pdev); - if (ret != 0) { - dev_err(&pdev->dev, - "failed to request UE IRQ %d (%d)\n", - al_mc->irq_ue, ret); + if (ret != 0) return ret; - } } if (al_mc->irq_ce > 0) { @@ -317,12 +313,8 @@ static int al_mc_edac_probe(struct platform_device *pdev) IRQF_SHARED, pdev->name, pdev); - if (ret != 0) { - dev_err(&pdev->dev, - "failed to request CE IRQ %d (%d)\n", - al_mc->irq_ce, ret); + if (ret != 0) return ret; - } } return 0; diff --git a/drivers/edac/aspeed_edac.c b/drivers/edac/aspeed_edac.c index dadb8acbee3d..6e069b255595 100644 --- a/drivers/edac/aspeed_edac.c +++ b/drivers/edac/aspeed_edac.c @@ -214,10 +214,8 @@ static int config_irq(void *ctx, struct platform_device *pdev) rc = devm_request_irq(&pdev->dev, irq, mcr_isr, IRQF_TRIGGER_HIGH, DRV_NAME, ctx); - if (rc) { - dev_err(&pdev->dev, "unable to request irq %d\n", irq); + if (rc) return rc; - } /* enable interrupts */ regmap_update_bits(aspeed_regmap, ASPEED_MCR_INTR_CTRL, diff --git a/drivers/edac/highbank_mc_edac.c b/drivers/edac/highbank_mc_edac.c index a8879d72d064..68d16cc8298d 100644 --- a/drivers/edac/highbank_mc_edac.c +++ b/drivers/edac/highbank_mc_edac.c @@ -235,10 +235,8 @@ static int highbank_mc_probe(struct platform_device *pdev) irq = platform_get_irq(pdev, 0); res = devm_request_irq(&pdev->dev, irq, highbank_mc_err_handler, 0, dev_name(&pdev->dev), mci); - if (res < 0) { - dev_err(&pdev->dev, "Unable to request irq %d\n", irq); + if (res < 0) goto err2; - } devres_close_group(&pdev->dev, NULL); return 0; diff --git a/drivers/edac/thunderx_edac.c b/drivers/edac/thunderx_edac.c index 75c04dfc3962..9c0a1e48f96f 100644 --- a/drivers/edac/thunderx_edac.c +++ b/drivers/edac/thunderx_edac.c @@ -729,10 +729,8 @@ static int thunderx_lmc_probe(struct pci_dev *pdev, thunderx_lmc_err_isr, thunderx_lmc_threaded_isr, 0, "[EDAC] ThunderX LMC", mci); - if (ret) { - dev_err(&pdev->dev, "Cannot set ISR: %d\n", ret); + if (ret) goto err_free; - } lmc->node = FIELD_GET(THUNDERX_NODE, pci_resource_start(pdev, 0)); diff --git a/drivers/edac/xgene_edac.c b/drivers/edac/xgene_edac.c index 9955396c9a52..62b8166dc287 100644 --- a/drivers/edac/xgene_edac.c +++ b/drivers/edac/xgene_edac.c @@ -1924,11 +1924,8 @@ static int xgene_edac_probe(struct platform_device *pdev) rc = devm_request_irq(&pdev->dev, irq, xgene_edac_isr, IRQF_SHARED, dev_name(&pdev->dev), edac); - if (rc) { - dev_err(&pdev->dev, - "Could not request IRQ %d\n", irq); + if (rc) goto out_err; - } } } From 0c4775d3a756b923c815327dc585ee167057ed52 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Fri, 24 Jul 2026 03:45:29 +0900 Subject: [PATCH 037/641] RAS/AMD/ATL: Remove conditional return with no effect Both branches of the check return the same value, so the check has no effect. Remove it and return the value directly. This is the result of running the Coccinelle script from scripts/coccinelle/misc/cond_return_no_effect.cocci. Signed-off-by: Sang-Heon Jeon Signed-off-by: Borislav Petkov (AMD) Link: https://patch.msgid.link/20260723184538.3888637-28-ekffu200098@gmail.com --- drivers/ras/amd/atl/map.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/ras/amd/atl/map.c b/drivers/ras/amd/atl/map.c index 24a05af747d5..4ec9333ef745 100644 --- a/drivers/ras/amd/atl/map.c +++ b/drivers/ras/amd/atl/map.c @@ -771,9 +771,5 @@ int get_address_map(struct addr_ctx *ctx) dump_address_map(&ctx->map); - ret = validate_address_map(ctx); - if (ret) - return ret; - - return ret; + return validate_address_map(ctx); } From 35e5554ec14b12d31886d03de1e9fbbb13073564 Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Tue, 7 Jul 2026 19:39:34 +0800 Subject: [PATCH 038/641] f2fs: drop FGP_NOFS in f2fs_write_begin() This patch proposes to drop FGP_NOFS from f2fs_filemap_get_folio() in f2fs_write_begin(), I don't see there is potential deadlock issue when __filemap_get_folio() calling into filesystem reclaim interfaces, e.g. .writepages, evict_inode, shrinker. Cc: Matthew Wilcox Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/data.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c index a765fda71536..62ea74e65db8 100644 --- a/fs/f2fs/data.c +++ b/fs/f2fs/data.c @@ -3975,7 +3975,7 @@ repeat: * Will wait that below with our IO control. */ folio = f2fs_filemap_get_folio(mapping, index, - FGP_LOCK | FGP_WRITE | FGP_CREAT | FGP_NOFS, + FGP_LOCK | FGP_WRITE | FGP_CREAT, mapping_gfp_mask(mapping)); if (IS_ERR(folio)) { err = PTR_ERR(folio); From b41d9ff965f70f88b2c33089fdb26865914ca92d Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Tue, 7 Jul 2026 19:39:33 +0800 Subject: [PATCH 039/641] f2fs: quota: do not use GFP_NOFS in f2fs_quota_read() Commit 02117b8ae9c0 ("f2fs: Set GF_NOFS in read_cache_page_gfp while doing f2fs_quota_read") adds GFP_NOFS in f2fs_quota_read() to avoid below deadlock: - do_sys_open - vfs_open - dquot_file_open - dquot_initialize - dqget - dquot_acquire : locks &dqopt->dqio_mutex (VFS Quota Mutex) - qtree_read_dquot - f2fs_quota_read - read_mapping_page (GFP_KERNEL / allows GFP_FS) - __alloc_pages_nodemask - try_to_free_pages (Direct Reclaim) - prune_icache_sb - evict - f2fs_evict_inode - dquot_drop - dqput - dquot_commit : tries to lock &dqopt->dqio_mutex again ==> DEADLOCK (waiting for itself) As Jan Kara mentioned, quota system has fixed this issue w/ commit 537e11cdc7a6 ("quota: Prevent memory allocation recursion while holding dq_lock"), so this GFP_NOFS flag should be relic, let's use GFP_KERNEL instead. Cc: Jan Kara Cc: Matthew Wilcox Signed-off-by: Chao Yu Reviewed-by: Jan Kara Signed-off-by: Jaegeuk Kim --- fs/f2fs/super.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index 9760e4efeffe..f41a23828508 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -3168,7 +3168,7 @@ static ssize_t f2fs_quota_read(struct super_block *sb, int type, char *data, repeat: folio = mapping_read_folio_gfp(mapping, off >> PAGE_SHIFT, - GFP_NOFS); + GFP_KERNEL); if (IS_ERR(folio)) { if (PTR_ERR(folio) == -ENOMEM) { memalloc_retry_wait(GFP_NOFS); From 3d7bca9d583793bb7d0bac0d95a24ddd2e129eed Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Mon, 6 Jul 2026 12:33:21 +0000 Subject: [PATCH 040/641] f2fs: embed f2fs_gc_kthread in f2fs_sb_info Instead of allocating f2fs_gc_kthread dynamically, embed it in f2fs_sb_info. This simplifies lifetime management and prepares for fixing race conditions during teardown. - __sbi_store - remount|shutdown - f2fs_stop_gc_thread - access sbi->gc_thread - sbi->gc_thread = NULL - access sbi->gc_thread->f2fs_gc_task Fixes: 52190933c37a ("f2fs: sysfs: introduce critical_task_priority") Fixes: 7950e9ac638e ("f2fs: stop gc/discard thread after fs shutdown") Cc: stable@kernel.org Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/debug.c | 4 ---- fs/f2fs/f2fs.h | 29 ++++++++++++++++++++++++++++- fs/f2fs/gc.c | 36 +++++++++++++++--------------------- fs/f2fs/gc.h | 27 +-------------------------- fs/f2fs/segment.c | 9 ++++----- fs/f2fs/super.c | 4 ++-- fs/f2fs/sysfs.c | 22 +++++++++++----------- 7 files changed, 61 insertions(+), 70 deletions(-) diff --git a/fs/f2fs/debug.c b/fs/f2fs/debug.c index af88db8fdb71..ff379aff4472 100644 --- a/fs/f2fs/debug.c +++ b/fs/f2fs/debug.c @@ -352,10 +352,6 @@ static void update_mem_info(struct f2fs_sb_info *sbi) get_cache: si->cache_mem = 0; - /* build gc */ - if (sbi->gc_thread) - si->cache_mem += sizeof(struct f2fs_gc_kthread); - /* build merge flush thread */ if (SM_I(sbi)->fcc_info) si->cache_mem += sizeof(struct flush_cmd_control); diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index 8f3e632f315c..8011bbdf2c68 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -1748,6 +1748,33 @@ struct decompress_io_ctx { #define MAX_COMPRESS_LOG_SIZE 8 #define MAX_COMPRESS_WINDOW_SIZE(log_size) ((PAGE_SIZE) << (log_size)) +struct f2fs_gc_kthread { + struct task_struct *f2fs_gc_task; + wait_queue_head_t gc_wait_queue_head; + + /* for gc sleep time */ + unsigned int urgent_sleep_time; + unsigned int min_sleep_time; + unsigned int max_sleep_time; + unsigned int no_gc_sleep_time; + + /* for changing gc mode */ + bool gc_wake; + + /* for GC_MERGE mount option */ + wait_queue_head_t fggc_wq; /* + * caller of f2fs_balance_fs() + * will wait on this wait queue. + */ + + /* for gc control for zoned devices */ + unsigned int no_zoned_gc_percent; + unsigned int boost_zoned_gc_percent; + unsigned int valid_thresh_ratio; + unsigned int boost_gc_multiple; + unsigned int boost_gc_greedy; +}; + struct f2fs_sb_info { struct super_block *sb; /* pointer to VFS super block */ struct proc_dir_entry *s_proc; /* proc entry */ @@ -1883,7 +1910,7 @@ struct f2fs_sb_info { * semaphore for GC, avoid * race between GC and GC or CP */ - struct f2fs_gc_kthread *gc_thread; /* GC thread */ + struct f2fs_gc_kthread gc_thread; /* GC thread */ struct atgc_management am; /* atgc management */ unsigned int cur_victim_sec; /* current victim section num */ unsigned int gc_mode; /* current GC state */ diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c index ffaa7ba76a1b..d04633f872ef 100644 --- a/fs/f2fs/gc.c +++ b/fs/f2fs/gc.c @@ -31,9 +31,9 @@ static unsigned int count_bits(const unsigned long *addr, static int gc_thread_func(void *data) { struct f2fs_sb_info *sbi = data; - struct f2fs_gc_kthread *gc_th = sbi->gc_thread; - wait_queue_head_t *wq = &sbi->gc_thread->gc_wait_queue_head; - wait_queue_head_t *fggc_wq = &sbi->gc_thread->fggc_wq; + struct f2fs_gc_kthread *gc_th = &sbi->gc_thread; + wait_queue_head_t *wq = &sbi->gc_thread.gc_wait_queue_head; + wait_queue_head_t *fggc_wq = &sbi->gc_thread.fggc_wq; unsigned int wait_ms; struct f2fs_gc_control gc_control = { .victim_segno = NULL_SEGNO, @@ -193,13 +193,9 @@ next: int f2fs_start_gc_thread(struct f2fs_sb_info *sbi) { - struct f2fs_gc_kthread *gc_th; + struct f2fs_gc_kthread *gc_th = &sbi->gc_thread; dev_t dev = sbi->sb->s_bdev->bd_dev; - gc_th = f2fs_kmalloc(sbi, sizeof(struct f2fs_gc_kthread), GFP_KERNEL); - if (!gc_th) - return -ENOMEM; - gc_th->urgent_sleep_time = DEF_GC_THREAD_URGENT_SLEEP_TIME; gc_th->valid_thresh_ratio = DEF_GC_THREAD_VALID_THRESH_RATIO; gc_th->boost_gc_multiple = BOOST_GC_MULTIPLE; @@ -221,16 +217,14 @@ int f2fs_start_gc_thread(struct f2fs_sb_info *sbi) gc_th->gc_wake = false; - sbi->gc_thread = gc_th; - init_waitqueue_head(&sbi->gc_thread->gc_wait_queue_head); - init_waitqueue_head(&sbi->gc_thread->fggc_wq); - sbi->gc_thread->f2fs_gc_task = kthread_run(gc_thread_func, sbi, + init_waitqueue_head(&gc_th->gc_wait_queue_head); + init_waitqueue_head(&gc_th->fggc_wq); + gc_th->f2fs_gc_task = kthread_run(gc_thread_func, sbi, "f2fs_gc-%u:%u", MAJOR(dev), MINOR(dev)); if (IS_ERR(gc_th->f2fs_gc_task)) { int err = PTR_ERR(gc_th->f2fs_gc_task); - kfree(gc_th); - sbi->gc_thread = NULL; + gc_th->f2fs_gc_task = NULL; return err; } @@ -241,14 +235,14 @@ int f2fs_start_gc_thread(struct f2fs_sb_info *sbi) void f2fs_stop_gc_thread(struct f2fs_sb_info *sbi) { - struct f2fs_gc_kthread *gc_th = sbi->gc_thread; + struct f2fs_gc_kthread *gc_th = &sbi->gc_thread; - if (!gc_th) + if (!gc_th->f2fs_gc_task) return; + kthread_stop(gc_th->f2fs_gc_task); + gc_th->f2fs_gc_task = NULL; wake_up_all(&gc_th->fggc_wq); - kfree(gc_th); - sbi->gc_thread = NULL; } static int select_gc_type(struct f2fs_sb_info *sbi, int gc_type) @@ -796,7 +790,7 @@ int f2fs_get_victim(struct f2fs_sb_info *sbi, unsigned int *result, if (one_time) { p.one_time_gc = one_time; if (has_enough_free_secs(sbi, 0, NR_PERSISTENT_LOG)) - valid_thresh_ratio = sbi->gc_thread->valid_thresh_ratio; + valid_thresh_ratio = sbi->gc_thread.valid_thresh_ratio; } retry: @@ -1807,9 +1801,9 @@ static int do_garbage_collect(struct f2fs_sb_info *sbi, if (f2fs_sb_has_blkzoned(sbi) && !has_enough_free_blocks(sbi, - sbi->gc_thread->boost_zoned_gc_percent)) + sbi->gc_thread.boost_zoned_gc_percent)) window_granularity *= - sbi->gc_thread->boost_gc_multiple; + sbi->gc_thread.boost_gc_multiple; end_segno = start_segno + window_granularity; } diff --git a/fs/f2fs/gc.h b/fs/f2fs/gc.h index 6c4d4567571e..b015742fb455 100644 --- a/fs/f2fs/gc.h +++ b/fs/f2fs/gc.h @@ -45,32 +45,7 @@ #define NR_GC_CHECKPOINT_SECS (3) /* data/node/dentry sections */ -struct f2fs_gc_kthread { - struct task_struct *f2fs_gc_task; - wait_queue_head_t gc_wait_queue_head; - /* for gc sleep time */ - unsigned int urgent_sleep_time; - unsigned int min_sleep_time; - unsigned int max_sleep_time; - unsigned int no_gc_sleep_time; - - /* for changing gc mode */ - bool gc_wake; - - /* for GC_MERGE mount option */ - wait_queue_head_t fggc_wq; /* - * caller of f2fs_balance_fs() - * will wait on this wait queue. - */ - - /* for gc control for zoned devices */ - unsigned int no_zoned_gc_percent; - unsigned int boost_zoned_gc_percent; - unsigned int valid_thresh_ratio; - unsigned int boost_gc_multiple; - unsigned int boost_gc_greedy; -}; struct gc_inode_list { struct list_head ilist; @@ -197,6 +172,6 @@ static inline bool need_to_boost_gc(struct f2fs_sb_info *sbi) { if (f2fs_sb_has_blkzoned(sbi)) return !has_enough_free_blocks(sbi, - sbi->gc_thread->boost_zoned_gc_percent); + sbi->gc_thread.boost_zoned_gc_percent); return has_enough_invalid_blocks(sbi); } diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c index d71ddb3ee918..0b706568b034 100644 --- a/fs/f2fs/segment.c +++ b/fs/f2fs/segment.c @@ -452,15 +452,14 @@ void f2fs_balance_fs(struct f2fs_sb_info *sbi, bool need) f2fs_submit_merged_write(sbi, DATA); f2fs_submit_all_merged_ipu_writes(sbi); - if (test_opt(sbi, GC_MERGE) && sbi->gc_thread && - sbi->gc_thread->f2fs_gc_task) { + if (test_opt(sbi, GC_MERGE) && sbi->gc_thread.f2fs_gc_task) { DEFINE_WAIT(wait); - prepare_to_wait(&sbi->gc_thread->fggc_wq, &wait, + prepare_to_wait(&sbi->gc_thread.fggc_wq, &wait, TASK_UNINTERRUPTIBLE); - wake_up(&sbi->gc_thread->gc_wait_queue_head); + wake_up(&sbi->gc_thread.gc_wait_queue_head); io_schedule(); - finish_wait(&sbi->gc_thread->fggc_wq, &wait); + finish_wait(&sbi->gc_thread.fggc_wq, &wait); } else { struct f2fs_gc_control gc_control = { .victim_segno = NULL_SEGNO, diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index f41a23828508..90a47cf86378 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -2943,11 +2943,11 @@ static int __f2fs_remount(struct fs_context *fc, struct super_block *sb) if ((flags & SB_RDONLY) || (F2FS_OPTION(sbi).bggc_mode == BGGC_MODE_OFF && !test_opt(sbi, GC_MERGE))) { - if (sbi->gc_thread) { + if (sbi->gc_thread.f2fs_gc_task) { f2fs_stop_gc_thread(sbi); need_restart_gc = true; } - } else if (!sbi->gc_thread) { + } else if (!sbi->gc_thread.f2fs_gc_task) { err = f2fs_start_gc_thread(sbi); if (err) goto restore_opts; diff --git a/fs/f2fs/sysfs.c b/fs/f2fs/sysfs.c index 665687244c93..be92c05a5420 100644 --- a/fs/f2fs/sysfs.c +++ b/fs/f2fs/sysfs.c @@ -75,7 +75,7 @@ static ssize_t f2fs_sbi_show(struct f2fs_attr *a, static unsigned char *__struct_ptr(struct f2fs_sb_info *sbi, int struct_type) { if (struct_type == GC_THREAD) - return (unsigned char *)sbi->gc_thread; + return (unsigned char *)&sbi->gc_thread; else if (struct_type == SM_INFO) return (unsigned char *)SM_I(sbi); else if (struct_type == DCC_INFO) @@ -664,20 +664,20 @@ out: sbi->gc_mode = GC_NORMAL; } else if (t == 1) { sbi->gc_mode = GC_URGENT_HIGH; - if (sbi->gc_thread) { - sbi->gc_thread->gc_wake = true; + if (sbi->gc_thread.f2fs_gc_task) { + sbi->gc_thread.gc_wake = true; wake_up_interruptible_all( - &sbi->gc_thread->gc_wait_queue_head); + &sbi->gc_thread.gc_wait_queue_head); wake_up_discard_thread(sbi, true); } } else if (t == 2) { sbi->gc_mode = GC_URGENT_LOW; } else if (t == 3) { sbi->gc_mode = GC_URGENT_MID; - if (sbi->gc_thread) { - sbi->gc_thread->gc_wake = true; + if (sbi->gc_thread.f2fs_gc_task) { + sbi->gc_thread.gc_wake = true; wake_up_interruptible_all( - &sbi->gc_thread->gc_wait_queue_head); + &sbi->gc_thread.gc_wait_queue_head); } } else { return -EINVAL; @@ -934,14 +934,14 @@ out: if (!strcmp(a->attr.name, "gc_boost_gc_multiple")) { if (t < 1 || t > SEGS_PER_SEC(sbi)) return -EINVAL; - sbi->gc_thread->boost_gc_multiple = (unsigned int)t; + sbi->gc_thread.boost_gc_multiple = (unsigned int)t; return count; } if (!strcmp(a->attr.name, "gc_boost_gc_greedy")) { if (t > GC_GREEDY) return -EINVAL; - sbi->gc_thread->boost_gc_greedy = (unsigned int)t; + sbi->gc_thread.boost_gc_greedy = (unsigned int)t; return count; } @@ -989,8 +989,8 @@ out: if (sbi->cprc_info.f2fs_issue_ckpt) set_user_nice(sbi->cprc_info.f2fs_issue_ckpt, PRIO_TO_NICE(sbi->critical_task_priority)); - if (sbi->gc_thread && sbi->gc_thread->f2fs_gc_task) - set_user_nice(sbi->gc_thread->f2fs_gc_task, + if (sbi->gc_thread.f2fs_gc_task) + set_user_nice(sbi->gc_thread.f2fs_gc_task, PRIO_TO_NICE(sbi->critical_task_priority)); return count; } From d0a481fad5c7a3a56ecf54a099651216869f4d0a Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Mon, 29 Jun 2026 11:49:18 +0000 Subject: [PATCH 041/641] f2fs: fix to avoid potential section-unaligned pinfile Blocks of pinfile may not aligned to section size due to wrong use on pinfile, result in heavy overhead of GC, let avoid this by adding additional check condition in f2fs_setattr(). - truncate -s 8mb pinfile : random checkpoint may persist filesize w/ inode - fallocate -o 0 -l 8mb pinfile - f2fs_fallocate - f2fs_expand_inode_data - f2fs_allocate_pinning_section - f2fs_map_blocks - f2fs_map_lock - __allocate_data_block - file_need_truncate : w/ FADVISE_TRUNC_BIT, we can expect unaligned mapping can be truncated while open() if f2fs is not umount abnormally - f2fs_map_unlock : following f2fs checkpoint and sudden power-cut - mount - open pinfile - f2fs_file_open - finish_preallocate_blocks - truncate_setsize : filesize is 8mb - f2fs_truncate : can only truncate block outside filesize, rather than truncating unaligned blocks inside filesize Fixes: f5a53edcf01e ("f2fs: support aligned pinned file") Cc: stable@kernel.org Cc: Daeho Jeong Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/file.c | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index 4b52c56d71f0..c54897a25981 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -1107,17 +1107,23 @@ int f2fs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, !IS_ALIGNED(attr->ia_size, F2FS_BLK_TO_BYTES(fi->i_cluster_size))) return -EINVAL; - /* - * To prevent scattered pin block generation, we don't allow - * smaller/equal size unaligned truncation for pinned file. - * We only support overwrite IO to pinned file, so don't - * care about larger size truncation. - */ - if (f2fs_is_pinned_file(inode) && - attr->ia_size <= i_size_read(inode) && - !IS_ALIGNED(attr->ia_size, - F2FS_BLK_TO_BYTES(CAP_BLKS_PER_SEC(sbi)))) - return -EINVAL; + + if (f2fs_is_pinned_file(inode)) { + /* + * It may break section-aligned fallocate recovery + * mechanism, so do not allow larger size truncation. + */ + if (attr->ia_size > i_size_read(inode)) + return -EINVAL; + /* + * To prevent scattered pin block generation, we don't + * allow smaller/equal size unaligned truncation for + * pinned file. + */ + else if (!IS_ALIGNED(attr->ia_size, + F2FS_BLK_TO_BYTES(CAP_BLKS_PER_SEC(sbi)))) + return -EINVAL; + } } if (is_quota_modification(idmap, inode, attr)) { From 0dab71381f1b4d12dc2056f8bd5aaa9d93ce9082 Mon Sep 17 00:00:00 2001 From: Zhaoyang Huang Date: Mon, 13 Jul 2026 19:19:44 +0800 Subject: [PATCH 042/641] f2fs: fix folio_nr_pages() race after put in large folio invalidate Our v6.18 based Android system is continuely suffering livelock and bad page stat as shown in[1] which related to broken xarray slot status. By investigating big folio operations within f2fs, we find below races and fix it by get the nr_pages before drop the refcount and folio_lock. f2fs_get_read_data_folio() calls f2fs_folio_put() before folio_nr_pages() when invalidating a large folio from the page cache. That unlocks the folio and drops the caller reference, leaving a window where a concurrent truncate or folio split can shrink the compound folio or free it before the invalidate range is computed. An undersized range then leaves split sub-folios in mapping->i_pages, which can later interact badly with truncate and reclaim (stale xarray entries and bad page state when folio->mapping no longer matches the mapping being truncated). [1] PID: 2594 TASK: ffffff8169b81580 CPU: 7 COMMAND: "Thread-3" #0 [ffffffc08ef2b8a0] xas_load at ffffffe52d1f42a4 #1 [ffffffc08ef2b900] find_get_entries at ffffffe52c185798 #2 [ffffffc08ef2bb60] truncate_inode_pages_range at ffffffe52c19e83c #3 [ffffffc08ef2bbc0] truncate_inode_pages_final at ffffffe52c19ec2c #4 [ffffffc08ef2bc20] f2fs_evict_inode at ffffffe52c4c8400 #5 [ffffffc08ef2bcc0] evict at ffffffe52c2de9f4 #6 [ffffffc08ef2bd00] iput at ffffffe52c2db1b4 #7 [ffffffc08ef2bd30] dentry_unlink_inode at ffffffe52c2d7204 #8 [ffffffc08ef2bd50] __dentry_kill at ffffffe52c2d3dcc #9 [ffffffc08ef2bd80] dput at ffffffe52c2d3c3c #10 [ffffffc08ef2bda0] __fput at ffffffe52c2b0a7c #11 [ffffffc08ef2bde0] ____fput at ffffffe52c2b1034 #12 [ffffffc08ef2bdf0] task_work_run at ffffffe52beea200 #13 [ffffffc08ef2be20] exit_to_user_mode_loop at ffffffe52bfbc17c #14 [ffffffc08ef2be80] el0_svc at ffffffe52d1f8e54 #15 [ffffffc08ef2beb0] el0t_64_sync_handler at ffffffe52d1f8d10 Cc: stable@kernel.org Fixes: 05e65c14ea59 ("f2fs: support large folio for immutable non-compressed case") Reviewed-by: Chao Yu Signed-off-by: Zhaoyang Huang Signed-off-by: Jaegeuk Kim --- fs/f2fs/data.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c index 62ea74e65db8..042ed8ad9cc3 100644 --- a/fs/f2fs/data.c +++ b/fs/f2fs/data.c @@ -1323,10 +1323,11 @@ retry: if (folio_test_large(folio)) { pgoff_t folio_index = mapping_align_index(mapping, index); + unsigned long nr_pages = folio_nr_pages(folio); f2fs_folio_put(folio, true); invalidate_inode_pages2_range(mapping, folio_index, - folio_index + folio_nr_pages(folio) - 1); + folio_index + nr_pages - 1); f2fs_schedule_timeout(DEFAULT_SCHEDULE_TIMEOUT); goto retry; } From cca7d3e30bf30333314e31bc70b9a739f1342167 Mon Sep 17 00:00:00 2001 From: Guanghui Yang <3497809730@qq.com> Date: Sun, 19 Jul 2026 16:45:14 +0800 Subject: [PATCH 043/641] f2fs: fix dentry folio leak in find_in_level find_in_level() gets a dentry folio with f2fs_find_data_folio() before calling find_in_block(). If find_in_block() returns an error, the function stores the error in res_folio and breaks out of the loop without dropping the dentry folio. This leaks the folio reference on the find_in_block() error path. Drop the dentry folio before returning the error to the caller. Fixes: 7ad08a58bf67 ("f2fs: Handle casefolding with Encryption") Cc: stable@vger.kernel.org Reviewed-by: Chao Yu Signed-off-by: Guanghui Yang <3497809730@qq.com> Signed-off-by: Jaegeuk Kim --- fs/f2fs/dir.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/f2fs/dir.c b/fs/f2fs/dir.c index a9563f7fcd88..0562ad211351 100644 --- a/fs/f2fs/dir.c +++ b/fs/f2fs/dir.c @@ -320,6 +320,7 @@ start_find_bucket: de = find_in_block(dir, dentry_folio, fname, &max_slots, use_hash); if (IS_ERR(de)) { + f2fs_folio_put(dentry_folio, false); *res_folio = ERR_CAST(de); de = NULL; break; From c08b809d8c2cd32ce14715f6c5712dbcdd920b45 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 28 Jul 2026 11:26:09 +0200 Subject: [PATCH 044/641] drm/msm: Fix stale comments in uapi/drm/msm_drm.h At some point drm_msm_gem_syncobj was renamed to drm_msm_syncobj and MSM_SUBMIT_SYNCOBJ_FLAGS was renamed to MSM_SYNCOBJ_FLAGS but some comments still refer to the old names. Update the comments with the new names. Signed-off-by: Hans de Goede Patchwork: https://patchwork.freedesktop.org/patch/742717/ Message-ID: <20260728092609.22049-1-johannes.goede@oss.qualcomm.com> Signed-off-by: Rob Clark --- include/uapi/drm/msm_drm.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/uapi/drm/msm_drm.h b/include/uapi/drm/msm_drm.h index 7f2e594be4eb..3d363f12f66a 100644 --- a/include/uapi/drm/msm_drm.h +++ b/include/uapi/drm/msm_drm.h @@ -228,7 +228,7 @@ struct drm_msm_gem_cpu_fini { struct drm_msm_syncobj { __u32 handle; /* in, syncobj handle. */ - __u32 flags; /* in, from MSM_SUBMIT_SYNCOBJ_FLAGS */ + __u32 flags; /* in, from MSM_SYNCOBJ_FLAGS */ __u64 point; /* in, timepoint for timeline syncobjs. */ }; @@ -393,9 +393,9 @@ struct drm_msm_vm_bind { __s32 fence_fd; /** @queue_id: in, submitqueue id */ __u32 queue_id; - /** @in_syncobjs: in, ptr to array of drm_msm_gem_syncobj */ + /** @in_syncobjs: in, ptr to array of drm_msm_syncobj */ __u64 in_syncobjs; - /** @out_syncobjs: in, ptr to array of drm_msm_gem_syncobj */ + /** @out_syncobjs: in, ptr to array of drm_msm_syncobj */ __u64 out_syncobjs; /** @nr_in_syncobjs: in, number of entries in in_syncobj */ __u32 nr_in_syncobjs; From 83723f32cb3de23d45c1ac09241b5e0cfb32cc9b Mon Sep 17 00:00:00 2001 From: Anna Maniscalco Date: Thu, 23 Jul 2026 21:23:22 +0200 Subject: [PATCH 045/641] drm/msm: remove objects from evit list after pinning them Once objects are pinned they should not be kept in the evict list as that will cause drm_gpuvm_validate to keep ieterating a growing list of objects needlessly. Once an object is pinned remove it from the list. Fixes: 2e6a8a1fe2b2 ("drm/msm: Add VM_BIND ioctl") Signed-off-by: Anna Maniscalco Patchwork: https://patchwork.freedesktop.org/patch/742166/ Message-ID: <20260723-evict_list_fix-v2-1-bd0725e56253@gmail.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/msm_gem_vma.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/msm/msm_gem_vma.c b/drivers/gpu/drm/msm/msm_gem_vma.c index 3ed05ab0eeef..50d4812bdb4c 100644 --- a/drivers/gpu/drm/msm/msm_gem_vma.c +++ b/drivers/gpu/drm/msm/msm_gem_vma.c @@ -458,6 +458,8 @@ msm_gem_vm_bo_validate(struct drm_gpuvm_bo *vm_bo, struct drm_exec *exec) return ret; } + drm_gpuvm_bo_evict(vm_bo, false); + return 0; } From ae88499d71ce80ecd25c055eddddace108266321 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 29 Jul 2026 08:55:38 -0700 Subject: [PATCH 046/641] drm/msm: Fix barriers accessing ctx vm Don't rely on store ordering to protect us from caller seeing a partially initialized vm. Reported-by: Sashiko Signed-off-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/743079/ Message-ID: <20260729155609.20190-2-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/msm_drv.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c index ac86b427c0e5..29075aafc5cc 100644 --- a/drivers/gpu/drm/msm/msm_drv.c +++ b/drivers/gpu/drm/msm/msm_drv.c @@ -224,18 +224,19 @@ struct drm_gpuvm *msm_context_vm(struct drm_device *dev, struct msm_context *ctx { static DEFINE_MUTEX(init_lock); struct msm_drm_private *priv = dev->dev_private; + struct drm_gpuvm *vm = smp_load_acquire(&ctx->vm); /* Once ctx->vm is created it is valid for the lifetime of the context: */ - if (ctx->vm) - return ctx->vm; + if (vm) + return vm; + + guard(mutex)(&init_lock); - mutex_lock(&init_lock); if (!ctx->vm) { - ctx->vm = msm_gpu_create_private_vm( + vm = msm_gpu_create_private_vm( priv->gpu, current, !ctx->userspace_managed_vm); - + smp_store_release(&ctx->vm, vm); } - mutex_unlock(&init_lock); return ctx->vm; } From ea69d489d3a6822e2cf10d6806a889dd2e3271f0 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 29 Jul 2026 08:55:39 -0700 Subject: [PATCH 047/641] drm/msm: Rework queuelock Rename to ctxlock, and use cleanup guards to manage releasing the lock. This will let us re-use it for other per-context read/write serial- ization, such as VM creation. Signed-off-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/743080/ Message-ID: <20260729155609.20190-3-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/msm_drv.c | 2 +- drivers/gpu/drm/msm/msm_gpu.h | 4 ++-- drivers/gpu/drm/msm/msm_submitqueue.c | 14 +++----------- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c index 29075aafc5cc..77681cb4fdc2 100644 --- a/drivers/gpu/drm/msm/msm_drv.c +++ b/drivers/gpu/drm/msm/msm_drv.c @@ -251,7 +251,7 @@ static int context_init(struct drm_device *dev, struct drm_file *file) return -ENOMEM; INIT_LIST_HEAD(&ctx->submitqueues); - rwlock_init(&ctx->queuelock); + init_rwsem(&ctx->ctxlock); kref_init(&ctx->ref); msm_submitqueue_init(dev, ctx); diff --git a/drivers/gpu/drm/msm/msm_gpu.h b/drivers/gpu/drm/msm/msm_gpu.h index 6c83b8cbbb90..d27d54bdb7a7 100644 --- a/drivers/gpu/drm/msm/msm_gpu.h +++ b/drivers/gpu/drm/msm/msm_gpu.h @@ -392,8 +392,8 @@ msm_gpu_sysprof_no_ifpc(struct msm_gpu *gpu) * struct msm_context - per-drm_file context */ struct msm_context { - /** @queuelock: synchronizes access to submitqueues list */ - rwlock_t queuelock; + /** @ctxlock: synchronizes access to submitqueues list, etc */ + struct rw_semaphore ctxlock; /** @submitqueues: list of &msm_gpu_submitqueue created by userspace */ struct list_head submitqueues; /** diff --git a/drivers/gpu/drm/msm/msm_submitqueue.c b/drivers/gpu/drm/msm/msm_submitqueue.c index 1a5a77b28016..8b5980d5a6a7 100644 --- a/drivers/gpu/drm/msm/msm_submitqueue.c +++ b/drivers/gpu/drm/msm/msm_submitqueue.c @@ -93,18 +93,15 @@ struct msm_gpu_submitqueue *msm_submitqueue_get(struct msm_context *ctx, if (!ctx) return NULL; - read_lock(&ctx->queuelock); + guard(rwsem_read)(&ctx->ctxlock); list_for_each_entry(entry, &ctx->submitqueues, node) { if (entry->id == id) { kref_get(&entry->ref); - read_unlock(&ctx->queuelock); - return entry; } } - read_unlock(&ctx->queuelock); return NULL; } @@ -237,7 +234,7 @@ int msm_submitqueue_create(struct drm_device *drm, struct msm_context *ctx, return ret; } - write_lock(&ctx->queuelock); + guard(rwsem_write)(&ctx->ctxlock); queue->ctx = msm_context_get(ctx); queue->id = ctx->queueid++; @@ -251,8 +248,6 @@ int msm_submitqueue_create(struct drm_device *drm, struct msm_context *ctx, list_add_tail(&queue->node, &ctx->submitqueues); - write_unlock(&ctx->queuelock); - return 0; } @@ -335,19 +330,16 @@ int msm_submitqueue_remove(struct msm_context *ctx, u32 id) if (!id) return -ENOENT; - write_lock(&ctx->queuelock); + guard(rwsem_write)(&ctx->ctxlock); list_for_each_entry(entry, &ctx->submitqueues, node) { if (entry->id == id) { list_del(&entry->node); - write_unlock(&ctx->queuelock); - msm_submitqueue_put(entry); return 0; } } - write_unlock(&ctx->queuelock); return -ENOENT; } From 2c26f9e46d39571a11ad8a6c3cdf150af70e4f12 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 29 Jul 2026 08:55:40 -0700 Subject: [PATCH 048/641] drm/msm: Synchronize VM creation on ctxlock And serialize setting EN_VM_BIND against VM creation. Signed-off-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/743083/ Message-ID: <20260729155609.20190-4-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/adreno/adreno_gpu.c | 5 ++++- drivers/gpu/drm/msm/msm_drv.c | 3 +-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/msm/adreno/adreno_gpu.c b/drivers/gpu/drm/msm/adreno/adreno_gpu.c index c62c45bb0ddb..0d54141ff089 100644 --- a/drivers/gpu/drm/msm/adreno/adreno_gpu.c +++ b/drivers/gpu/drm/msm/adreno/adreno_gpu.c @@ -503,7 +503,9 @@ int adreno_set_param(struct msm_gpu *gpu, struct msm_context *ctx, if (!perfmon_capable()) return UERR(EPERM, drm, "invalid permissions"); return msm_context_set_sysprof(ctx, gpu, value); - case MSM_PARAM_EN_VM_BIND: + case MSM_PARAM_EN_VM_BIND: { + guard(rwsem_read)(&ctx->ctxlock); + /* We can only support VM_BIND with per-process pgtables: */ if (ctx->vm == gpu->vm) return UERR(EINVAL, drm, "requires per-process pgtables"); @@ -518,6 +520,7 @@ int adreno_set_param(struct msm_gpu *gpu, struct msm_context *ctx, ctx->userspace_managed_vm = value; return 0; + } default: return UERR(EINVAL, drm, "%s: invalid param: %u", gpu->name, param); } diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c index 77681cb4fdc2..a5714e24fcc6 100644 --- a/drivers/gpu/drm/msm/msm_drv.c +++ b/drivers/gpu/drm/msm/msm_drv.c @@ -222,7 +222,6 @@ static void load_gpu(struct drm_device *dev) */ struct drm_gpuvm *msm_context_vm(struct drm_device *dev, struct msm_context *ctx) { - static DEFINE_MUTEX(init_lock); struct msm_drm_private *priv = dev->dev_private; struct drm_gpuvm *vm = smp_load_acquire(&ctx->vm); @@ -230,7 +229,7 @@ struct drm_gpuvm *msm_context_vm(struct drm_device *dev, struct msm_context *ctx if (vm) return vm; - guard(mutex)(&init_lock); + guard(rwsem_write)(&ctx->ctxlock); if (!ctx->vm) { vm = msm_gpu_create_private_vm( From 2b1bfcb59d3f9bd4ab37eb6747fc783c67a7f82b Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 29 Jul 2026 08:55:41 -0700 Subject: [PATCH 049/641] drm/msm: Synchronize set_sysprof on ctxlock A user that was perfmon_capable() could try to race setting SYSPROF param on multiple threads to trigger a reference leak. Signed-off-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/743091/ Message-ID: <20260729155609.20190-5-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/msm_submitqueue.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/msm/msm_submitqueue.c b/drivers/gpu/drm/msm/msm_submitqueue.c index 8b5980d5a6a7..233c4f35ab12 100644 --- a/drivers/gpu/drm/msm/msm_submitqueue.c +++ b/drivers/gpu/drm/msm/msm_submitqueue.c @@ -9,6 +9,8 @@ int msm_context_set_sysprof(struct msm_context *ctx, struct msm_gpu *gpu, int sysprof) { + guard(rwsem_write)(&ctx->ctxlock); + /* * Since pm_runtime and sysprof_active are both refcounts, we * call apply the new value first, and then unwind the previous From 9ee2884ed58e6b39da35732146446d5930e4746b Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 29 Jul 2026 08:55:42 -0700 Subject: [PATCH 050/641] drm/msm: Move nr_cmds initialization Previously if we entered an error path between these two points, we could leak the relocs tables due to submit->nr_cmds still being zero. In practice, relocs are disallowed on a6xx+, and non-ancient userspace will not use relocs on earlier gens unless running on an ancient kernel. But userspace could use this to trigger a memory leak. Reported-by: Sashiko Signed-off-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/743085/ Message-ID: <20260729155609.20190-6-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/msm_gem_submit.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_gem_submit.c b/drivers/gpu/drm/msm/msm_gem_submit.c index 3c6bc90c3d48..aa48ea4e7f58 100644 --- a/drivers/gpu/drm/msm/msm_gem_submit.c +++ b/drivers/gpu/drm/msm/msm_gem_submit.c @@ -260,6 +260,9 @@ static int submit_lookup_cmds(struct msm_gem_submit *submit, ret = -ENOMEM; goto out; } + + submit->nr_cmds = i + 1; + ret = copy_from_user(submit->cmd[i].relocs, userptr, sz); if (ret) { ret = -EFAULT; @@ -719,8 +722,6 @@ int msm_ioctl_gem_submit(struct drm_device *dev, void *data, goto out; } - submit->nr_cmds = args->nr_cmds; - idr_preload(GFP_KERNEL); spin_lock(&queue->idr_lock); From 0369a2619c50bab5e9ef33e13f1def0c8aca25e8 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 29 Jul 2026 08:55:43 -0700 Subject: [PATCH 051/641] drm/msm: Remove redundant SIZE_MAX check kmalloc() will already fail and return NULL if passed SIZE_MAX. Signed-off-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/743082/ Message-ID: <20260729155609.20190-7-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/msm_gem_submit.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_gem_submit.c b/drivers/gpu/drm/msm/msm_gem_submit.c index aa48ea4e7f58..040354ee16d1 100644 --- a/drivers/gpu/drm/msm/msm_gem_submit.c +++ b/drivers/gpu/drm/msm/msm_gem_submit.c @@ -250,11 +250,6 @@ static int submit_lookup_cmds(struct msm_gem_submit *submit, sz = array_size(submit_cmd.nr_relocs, sizeof(struct drm_msm_gem_submit_reloc)); - /* check for overflow: */ - if (sz == SIZE_MAX) { - ret = -ENOMEM; - goto out; - } submit->cmd[i].relocs = kmalloc(sz, GFP_KERNEL | __GFP_NOWARN); if (!submit->cmd[i].relocs) { ret = -ENOMEM; From b87c50d2cabc48a74b2d0d79d9ff852eb8b73b58 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 29 Jul 2026 08:55:44 -0700 Subject: [PATCH 052/641] drm/msm/a6xx: Access VM directly in submit path The GEM_SUBMIT ioctl has already ensured that the VM is created, so we aren't expecting to lazily create the VM this deep into the ioctl. Signed-off-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/743101/ Message-ID: <20260729155609.20190-8-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/adreno/a6xx_gpu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gpu.c b/drivers/gpu/drm/msm/adreno/a6xx_gpu.c index 2c5faee881b3..f9de9329dee3 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gpu.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gpu.c @@ -229,7 +229,7 @@ static void a6xx_set_pagetable(struct a6xx_gpu *a6xx_gpu, { bool sysprof = msm_gpu_sysprof_no_perfcntr_zap(&a6xx_gpu->base.base); struct msm_context *ctx = submit->queue->ctx; - struct drm_gpuvm *vm = msm_context_vm(submit->dev, ctx); + struct drm_gpuvm *vm = ctx->vm; struct adreno_gpu *adreno_gpu = &a6xx_gpu->base; phys_addr_t ttbr; u32 asid; From 517ca9a86a4aaed7a79e5133c9a3e399cec6cdf5 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 29 Jul 2026 08:55:45 -0700 Subject: [PATCH 053/641] drm/msm: Add helper to check for per-process pgtables VM This will simplify a following commit to allow lazy VM creation to fail. Signed-off-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/743103/ Message-ID: <20260729155609.20190-9-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/adreno/adreno_gpu.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/msm/adreno/adreno_gpu.c b/drivers/gpu/drm/msm/adreno/adreno_gpu.c index 0d54141ff089..5738eac9aebd 100644 --- a/drivers/gpu/drm/msm/adreno/adreno_gpu.c +++ b/drivers/gpu/drm/msm/adreno/adreno_gpu.c @@ -356,6 +356,12 @@ int adreno_fault_handler(struct msm_gpu *gpu, unsigned long iova, int flags, return 0; } +static bool +valid_per_process_vm(struct msm_gpu *gpu, struct drm_gpuvm *vm) +{ + return (vm != gpu->vm); +} + int adreno_get_param(struct msm_gpu *gpu, struct msm_context *ctx, uint32_t param, uint64_t *value, uint32_t *len) { @@ -414,12 +420,12 @@ int adreno_get_param(struct msm_gpu *gpu, struct msm_context *ctx, *value = gpu->suspend_count; return 0; case MSM_PARAM_VA_START: - if (vm == gpu->vm) + if (!valid_per_process_vm(gpu, vm)) return UERR(EINVAL, drm, "requires per-process pgtables"); *value = vm->mm_start; return 0; case MSM_PARAM_VA_SIZE: - if (vm == gpu->vm) + if (!valid_per_process_vm(gpu, vm)) return UERR(EINVAL, drm, "requires per-process pgtables"); *value = vm->mm_range; return 0; From e6863b085606f632082192d847d8d578013d4a66 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 29 Jul 2026 08:55:46 -0700 Subject: [PATCH 054/641] drm/msm/gem: Fix dma_buf import error paths Set import_attach early, so that if we hit an error path msm_gem_free_object() goes down the drm_gem_is_imported() path. Set sgt late so _free_object() skips drm_prime_gem_destroy() as this is done by drm_gem_prime_import_dev(). Reported-by: Sashiko Signed-off-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/743084/ Message-ID: <20260729155609.20190-10-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/msm_gem.c | 17 ++++++++++++++--- drivers/gpu/drm/msm/msm_gem.h | 3 ++- drivers/gpu/drm/msm/msm_gem_prime.c | 2 +- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_gem.c b/drivers/gpu/drm/msm/msm_gem.c index efd3d3c9a449..74c3728f45bd 100644 --- a/drivers/gpu/drm/msm/msm_gem.c +++ b/drivers/gpu/drm/msm/msm_gem.c @@ -1093,7 +1093,9 @@ static void msm_gem_free_object(struct drm_gem_object *obj) */ kvfree(msm_obj->pages); - drm_prime_gem_destroy(obj, msm_obj->sgt); + /* In msm_gem_import() error path, sgt won't be set yet: */ + if (msm_obj->sgt) + drm_prime_gem_destroy(obj, msm_obj->sgt); } else { msm_gem_vunmap(obj); put_pages(obj); @@ -1282,11 +1284,13 @@ fail: } struct drm_gem_object *msm_gem_import(struct drm_device *dev, - struct dma_buf *dmabuf, struct sg_table *sgt) + struct dma_buf_attachment *attach, + struct sg_table *sgt) { struct msm_drm_private *priv = dev->dev_private; struct msm_gem_object *msm_obj; struct drm_gem_object *obj; + struct dma_buf *dmabuf = attach->dmabuf; size_t size, npages; int ret; @@ -1296,13 +1300,17 @@ struct drm_gem_object *msm_gem_import(struct drm_device *dev, if (ret) return ERR_PTR(ret); + /* + * Set import_attach here in case we hit an error path that ends + * up in drm_gem_object_put() -> msm_gem_free_object() + */ + obj->import_attach = attach; drm_gem_private_object_init(dev, obj, size); npages = size / PAGE_SIZE; msm_obj = to_msm_bo(obj); msm_gem_lock(obj); - msm_obj->sgt = sgt; msm_obj->pages = kvmalloc_objs(struct page *, npages); if (!msm_obj->pages) { msm_gem_unlock(obj); @@ -1328,6 +1336,9 @@ struct drm_gem_object *msm_gem_import(struct drm_device *dev, if (ret) goto fail; + /* Now that we are past potential failure points, set sgt: */ + msm_obj->sgt = sgt; + return obj; fail: diff --git a/drivers/gpu/drm/msm/msm_gem.h b/drivers/gpu/drm/msm/msm_gem.h index 762e546d25ef..86dc93cc1804 100644 --- a/drivers/gpu/drm/msm/msm_gem.h +++ b/drivers/gpu/drm/msm/msm_gem.h @@ -302,7 +302,8 @@ void *msm_gem_kernel_new(struct drm_device *dev, size_t size, uint32_t flags, uint64_t *iova); void msm_gem_kernel_put(struct drm_gem_object *bo, struct drm_gpuvm *vm); struct drm_gem_object *msm_gem_import(struct drm_device *dev, - struct dma_buf *dmabuf, struct sg_table *sgt); + struct dma_buf_attachment *attach, + struct sg_table *sgt); __printf(2, 3) void msm_gem_object_set_name(struct drm_gem_object *bo, const char *fmt, ...); diff --git a/drivers/gpu/drm/msm/msm_gem_prime.c b/drivers/gpu/drm/msm/msm_gem_prime.c index 036d34c674d9..beb7f22fd694 100644 --- a/drivers/gpu/drm/msm/msm_gem_prime.c +++ b/drivers/gpu/drm/msm/msm_gem_prime.c @@ -83,7 +83,7 @@ struct drm_gem_object *msm_gem_prime_import(struct drm_device *dev, struct drm_gem_object *msm_gem_prime_import_sg_table(struct drm_device *dev, struct dma_buf_attachment *attach, struct sg_table *sg) { - return msm_gem_import(dev, attach->dmabuf, sg); + return msm_gem_import(dev, attach, sg); } struct dma_buf *msm_gem_prime_export(struct drm_gem_object *obj, int flags) From 4e67b8fc55c4389652321355ff71ae8d4cb39783 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 29 Jul 2026 08:55:47 -0700 Subject: [PATCH 055/641] drm/msm/gem: Remove useless locking in GEM import The locking has changed a few times over the years, and this extra locking was the mistake of evolution. Harmless but useless. Signed-off-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/743087/ Message-ID: <20260729155609.20190-11-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/msm_gem.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_gem.c b/drivers/gpu/drm/msm/msm_gem.c index 74c3728f45bd..cc45392691e5 100644 --- a/drivers/gpu/drm/msm/msm_gem.c +++ b/drivers/gpu/drm/msm/msm_gem.c @@ -1310,22 +1310,17 @@ struct drm_gem_object *msm_gem_import(struct drm_device *dev, npages = size / PAGE_SIZE; msm_obj = to_msm_bo(obj); - msm_gem_lock(obj); msm_obj->pages = kvmalloc_objs(struct page *, npages); if (!msm_obj->pages) { - msm_gem_unlock(obj); ret = -ENOMEM; goto fail; } ret = drm_prime_sg_to_page_array(sgt, msm_obj->pages, npages); if (ret) { - msm_gem_unlock(obj); goto fail; } - msm_gem_unlock(obj); - drm_gem_lru_move_tail(&priv->lru.pinned, obj); mutex_lock(&priv->obj_lock); From 695d2b042af032bdbbfe4a1db475de2a2024e320 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 29 Jul 2026 08:55:48 -0700 Subject: [PATCH 056/641] drm/msm/gem: Extract bookkeeping init helper Clean up duplicated logic between import and new paths. Signed-off-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/743089/ Message-ID: <20260729155609.20190-12-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/msm_gem.c | 38 +++++++++++++++++------------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_gem.c b/drivers/gpu/drm/msm/msm_gem.c index cc45392691e5..3c36b1c4a4d5 100644 --- a/drivers/gpu/drm/msm/msm_gem.c +++ b/drivers/gpu/drm/msm/msm_gem.c @@ -1234,10 +1234,25 @@ static int msm_gem_new_impl(struct drm_device *dev, uint32_t flags, return 0; } +static int msm_gem_init_bookkeeping(struct drm_gem_object *obj) +{ + struct msm_drm_private *priv = obj->dev->dev_private; + + if (drm_gem_is_imported(obj)) { + drm_gem_lru_move_tail(&priv->lru.pinned, obj); + } else { + drm_gem_lru_move_tail(&priv->lru.unbacked, obj); + } + + mutex_lock(&priv->obj_lock); + list_add_tail(&to_msm_bo(obj)->node, &priv->objects); + mutex_unlock(&priv->obj_lock); + + return drm_gem_create_mmap_offset(obj); +} + struct drm_gem_object *msm_gem_new(struct drm_device *dev, size_t size, uint32_t flags) { - struct msm_drm_private *priv = dev->dev_private; - struct msm_gem_object *msm_obj; struct drm_gem_object *obj = NULL; int ret; @@ -1253,8 +1268,6 @@ struct drm_gem_object *msm_gem_new(struct drm_device *dev, size_t size, uint32_t if (ret) return ERR_PTR(ret); - msm_obj = to_msm_bo(obj); - ret = drm_gem_object_init(dev, obj, size); if (ret) goto fail; @@ -1266,13 +1279,7 @@ struct drm_gem_object *msm_gem_new(struct drm_device *dev, size_t size, uint32_t */ mapping_set_gfp_mask(obj->filp->f_mapping, GFP_HIGHUSER); - drm_gem_lru_move_tail(&priv->lru.unbacked, obj); - - mutex_lock(&priv->obj_lock); - list_add_tail(&msm_obj->node, &priv->objects); - mutex_unlock(&priv->obj_lock); - - ret = drm_gem_create_mmap_offset(obj); + ret = msm_gem_init_bookkeeping(obj); if (ret) goto fail; @@ -1287,7 +1294,6 @@ struct drm_gem_object *msm_gem_import(struct drm_device *dev, struct dma_buf_attachment *attach, struct sg_table *sgt) { - struct msm_drm_private *priv = dev->dev_private; struct msm_gem_object *msm_obj; struct drm_gem_object *obj; struct dma_buf *dmabuf = attach->dmabuf; @@ -1321,13 +1327,7 @@ struct drm_gem_object *msm_gem_import(struct drm_device *dev, goto fail; } - drm_gem_lru_move_tail(&priv->lru.pinned, obj); - - mutex_lock(&priv->obj_lock); - list_add_tail(&msm_obj->node, &priv->objects); - mutex_unlock(&priv->obj_lock); - - ret = drm_gem_create_mmap_offset(obj); + ret = msm_gem_init_bookkeeping(obj); if (ret) goto fail; From df68029e639b490e8f5f7715135a8af29e1b6cda Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 29 Jul 2026 08:55:49 -0700 Subject: [PATCH 057/641] drm/msm/gem: Set resv before exposing obj Don't swap the resv object _after_ exposing the newly created obj in LRU or global objects list, as that creates a race condition where another thread could lock the object using the original (per-obj) resv, but then unlock after the resv is replaced. Signed-off-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/743109/ Message-ID: <20260729155609.20190-13-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/adreno/a6xx_gmu.c | 2 +- drivers/gpu/drm/msm/disp/mdp4/mdp4_kms.c | 2 +- drivers/gpu/drm/msm/msm_fb.c | 4 +-- drivers/gpu/drm/msm/msm_gem.c | 32 ++++++++++++++---------- drivers/gpu/drm/msm/msm_gem.h | 2 +- 5 files changed, 24 insertions(+), 18 deletions(-) diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gmu.c b/drivers/gpu/drm/msm/adreno/a6xx_gmu.c index e97d8d8ee65a..27cac853975f 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gmu.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gmu.c @@ -1554,7 +1554,7 @@ static int a6xx_gmu_memory_alloc(struct a6xx_gmu *gmu, struct a6xx_gmu_bo *bo, flags |= MSM_BO_MAP_PRIV; } - bo->obj = msm_gem_new(dev, size, flags); + bo->obj = msm_gem_new(dev, size, flags, NULL); if (IS_ERR(bo->obj)) return PTR_ERR(bo->obj); diff --git a/drivers/gpu/drm/msm/disp/mdp4/mdp4_kms.c b/drivers/gpu/drm/msm/disp/mdp4/mdp4_kms.c index 7726edb0d4ed..c289dff78cd5 100644 --- a/drivers/gpu/drm/msm/disp/mdp4/mdp4_kms.c +++ b/drivers/gpu/drm/msm/disp/mdp4/mdp4_kms.c @@ -463,7 +463,7 @@ static int mdp4_kms_init(struct drm_device *dev) goto fail; } - mdp4_kms->blank_cursor_bo = msm_gem_new(dev, SZ_16K, MSM_BO_WC | MSM_BO_SCANOUT); + mdp4_kms->blank_cursor_bo = msm_gem_new(dev, SZ_16K, MSM_BO_WC | MSM_BO_SCANOUT, NULL); if (IS_ERR(mdp4_kms->blank_cursor_bo)) { ret = PTR_ERR(mdp4_kms->blank_cursor_bo); DRM_DEV_ERROR(dev->dev, "could not allocate blank-cursor bo: %d\n", ret); diff --git a/drivers/gpu/drm/msm/msm_fb.c b/drivers/gpu/drm/msm/msm_fb.c index 9b681e144c07..ee17a47f31c4 100644 --- a/drivers/gpu/drm/msm/msm_fb.c +++ b/drivers/gpu/drm/msm/msm_fb.c @@ -267,11 +267,11 @@ msm_alloc_stolen_fb(struct drm_device *dev, int w, int h, int p, uint32_t format /* allocate backing bo */ size = mode_cmd.pitches[0] * mode_cmd.height; DBG("allocating %d bytes for fb %d", size, dev->primary->index); - bo = msm_gem_new(dev, size, MSM_BO_SCANOUT | MSM_BO_WC | MSM_BO_STOLEN); + bo = msm_gem_new(dev, size, MSM_BO_SCANOUT | MSM_BO_WC | MSM_BO_STOLEN, NULL); if (IS_ERR(bo)) { dev_warn(dev->dev, "could not allocate stolen bo\n"); /* try regular bo: */ - bo = msm_gem_new(dev, size, MSM_BO_SCANOUT | MSM_BO_WC); + bo = msm_gem_new(dev, size, MSM_BO_SCANOUT | MSM_BO_WC, NULL); } if (IS_ERR(bo)) { DRM_DEV_ERROR(dev->dev, "failed to allocate buffer object\n"); diff --git a/drivers/gpu/drm/msm/msm_gem.c b/drivers/gpu/drm/msm/msm_gem.c index 3c36b1c4a4d5..535c6e1acb3c 100644 --- a/drivers/gpu/drm/msm/msm_gem.c +++ b/drivers/gpu/drm/msm/msm_gem.c @@ -1136,10 +1136,17 @@ int msm_gem_new_handle(struct drm_device *dev, struct drm_file *file, size_t size, uint32_t flags, uint32_t *handle, char *name) { - struct drm_gem_object *obj; + struct drm_gem_object *obj, *r_obj = NULL; int ret; - obj = msm_gem_new(dev, size, flags); + if (flags & MSM_BO_NO_SHARE) { + struct msm_context *ctx = file->driver_priv; + struct drm_gpuvm *vm = msm_context_vm(dev, ctx); + + r_obj = drm_gpuvm_resv_obj(vm); + } + + obj = msm_gem_new(dev, size, flags, r_obj); if (IS_ERR(obj)) return PTR_ERR(obj); @@ -1147,15 +1154,6 @@ int msm_gem_new_handle(struct drm_device *dev, struct drm_file *file, if (name) msm_gem_object_set_name(obj, "%s", name); - if (flags & MSM_BO_NO_SHARE) { - struct msm_context *ctx = file->driver_priv; - struct drm_gem_object *r_obj = drm_gpuvm_resv_obj(ctx->vm); - - drm_gem_object_get(r_obj); - - obj->resv = r_obj->resv; - } - ret = drm_gem_handle_create(file, obj, handle); /* drop reference from allocate - handle holds it now */ @@ -1251,7 +1249,9 @@ static int msm_gem_init_bookkeeping(struct drm_gem_object *obj) return drm_gem_create_mmap_offset(obj); } -struct drm_gem_object *msm_gem_new(struct drm_device *dev, size_t size, uint32_t flags) +struct drm_gem_object * +msm_gem_new(struct drm_device *dev, size_t size, uint32_t flags, + struct drm_gem_object *r_obj) { struct drm_gem_object *obj = NULL; int ret; @@ -1268,6 +1268,11 @@ struct drm_gem_object *msm_gem_new(struct drm_device *dev, size_t size, uint32_t if (ret) return ERR_PTR(ret); + if (flags & MSM_BO_NO_SHARE) { + drm_gem_object_get(r_obj); + obj->resv = r_obj->resv; + } + ret = drm_gem_object_init(dev, obj, size); if (ret) goto fail; @@ -1311,6 +1316,7 @@ struct drm_gem_object *msm_gem_import(struct drm_device *dev, * up in drm_gem_object_put() -> msm_gem_free_object() */ obj->import_attach = attach; + obj->resv = dmabuf->resv; drm_gem_private_object_init(dev, obj, size); npages = size / PAGE_SIZE; @@ -1346,7 +1352,7 @@ void *msm_gem_kernel_new(struct drm_device *dev, size_t size, uint32_t flags, uint64_t *iova) { void *vaddr; - struct drm_gem_object *obj = msm_gem_new(dev, size, flags); + struct drm_gem_object *obj = msm_gem_new(dev, size, flags, NULL); int ret; if (IS_ERR(obj)) diff --git a/drivers/gpu/drm/msm/msm_gem.h b/drivers/gpu/drm/msm/msm_gem.h index 86dc93cc1804..dff60cbc9d95 100644 --- a/drivers/gpu/drm/msm/msm_gem.h +++ b/drivers/gpu/drm/msm/msm_gem.h @@ -296,7 +296,7 @@ int msm_gem_cpu_fini(struct drm_gem_object *obj); int msm_gem_new_handle(struct drm_device *dev, struct drm_file *file, size_t size, uint32_t flags, uint32_t *handle, char *name); struct drm_gem_object *msm_gem_new(struct drm_device *dev, - size_t size, uint32_t flags); + size_t size, uint32_t flags, struct drm_gem_object *r_obj); void *msm_gem_kernel_new(struct drm_device *dev, size_t size, uint32_t flags, struct drm_gpuvm *vm, struct drm_gem_object **bo, uint64_t *iova); From a6d87a272b2c8fe1366ea9a8e2e4cccddbb4157f Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 29 Jul 2026 08:55:50 -0700 Subject: [PATCH 058/641] drm/msm/gem: Validate lazy VM in GEM_NEW Otherwise creating a _NO_SHARE BO before any BOs are mapped could cause a NPE. Reported-by: Sashiko Signed-off-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/743104/ Message-ID: <20260729155609.20190-14-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/msm_gem.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/msm/msm_gem.c b/drivers/gpu/drm/msm/msm_gem.c index 535c6e1acb3c..de4e60e28a71 100644 --- a/drivers/gpu/drm/msm/msm_gem.c +++ b/drivers/gpu/drm/msm/msm_gem.c @@ -1140,9 +1140,13 @@ int msm_gem_new_handle(struct drm_device *dev, struct drm_file *file, int ret; if (flags & MSM_BO_NO_SHARE) { + struct msm_drm_private *priv = dev->dev_private; struct msm_context *ctx = file->driver_priv; struct drm_gpuvm *vm = msm_context_vm(dev, ctx); + if (!priv->gpu || !vm) + return UERR(EINVAL, dev, "not supported with shared VM"); + r_obj = drm_gpuvm_resv_obj(vm); } From 00dfa76bdfc2fa807881df260f30053e2515c665 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 29 Jul 2026 08:55:51 -0700 Subject: [PATCH 059/641] drm/msm: Allow lazy VM creation to fail In the next commit, we'll stop falling back to shared VM if private VM creation fails. This isn't expected to happen in practice, it would either require small memory allocations to fail, or missing support in arm-smmu-qcom for setting up per-process pgtable support (ie. missing patch during bringup). Signed-off-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/743088/ Message-ID: <20260729155609.20190-15-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/adreno/adreno_gpu.c | 2 +- drivers/gpu/drm/msm/msm_drv.c | 9 ++++++++- drivers/gpu/drm/msm/msm_gem_submit.c | 12 ++++++++---- drivers/gpu/drm/msm/msm_gem_vma.c | 12 ++++++++---- drivers/gpu/drm/msm/msm_submitqueue.c | 8 +++++++- 5 files changed, 32 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/msm/adreno/adreno_gpu.c b/drivers/gpu/drm/msm/adreno/adreno_gpu.c index 5738eac9aebd..84101e534ebe 100644 --- a/drivers/gpu/drm/msm/adreno/adreno_gpu.c +++ b/drivers/gpu/drm/msm/adreno/adreno_gpu.c @@ -359,7 +359,7 @@ int adreno_fault_handler(struct msm_gpu *gpu, unsigned long iova, int flags, static bool valid_per_process_vm(struct msm_gpu *gpu, struct drm_gpuvm *vm) { - return (vm != gpu->vm); + return vm && (vm != gpu->vm); } int adreno_get_param(struct msm_gpu *gpu, struct msm_context *ctx, diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c index a5714e24fcc6..84b126c6347f 100644 --- a/drivers/gpu/drm/msm/msm_drv.c +++ b/drivers/gpu/drm/msm/msm_drv.c @@ -422,10 +422,14 @@ static int msm_ioctl_gem_info_iova(struct drm_device *dev, { struct msm_drm_private *priv = dev->dev_private; struct msm_context *ctx = file->driver_priv; + struct drm_gpuvm *vm = msm_context_vm(dev, ctx); if (!priv->gpu) return -EINVAL; + if (!vm) + return UERR(ENOMEM, dev, "no VM"); + if (msm_context_is_vmbind(ctx)) return UERR(EINVAL, dev, "VM_BIND is enabled"); @@ -436,7 +440,7 @@ static int msm_ioctl_gem_info_iova(struct drm_device *dev, * Don't pin the memory here - just get an address so that userspace can * be productive */ - return msm_gem_get_iova(obj, msm_context_vm(dev, ctx), iova); + return msm_gem_get_iova(obj, vm, iova); } static int msm_ioctl_gem_info_set_iova(struct drm_device *dev, @@ -450,6 +454,9 @@ static int msm_ioctl_gem_info_set_iova(struct drm_device *dev, if (!priv->gpu) return -EINVAL; + if (!vm) + return UERR(ENOMEM, dev, "no VM"); + if (msm_context_is_vmbind(ctx)) return UERR(EINVAL, dev, "VM_BIND is enabled"); diff --git a/drivers/gpu/drm/msm/msm_gem_submit.c b/drivers/gpu/drm/msm/msm_gem_submit.c index 040354ee16d1..6b0bee6c39bc 100644 --- a/drivers/gpu/drm/msm/msm_gem_submit.c +++ b/drivers/gpu/drm/msm/msm_gem_submit.c @@ -30,7 +30,7 @@ */ static struct msm_gem_submit *submit_create(struct drm_device *dev, - struct msm_gpu *gpu, + struct msm_gpu *gpu, struct drm_gpuvm *vm, struct msm_gpu_submitqueue *queue, uint32_t nr_bos, uint32_t nr_cmds, u64 drm_client_id) { @@ -66,7 +66,7 @@ static struct msm_gem_submit *submit_create(struct drm_device *dev, kref_init(&submit->ref); submit->dev = dev; - submit->vm = msm_context_vm(dev, queue->ctx); + submit->vm = vm; submit->gpu = gpu; submit->cmd = (void *)&submit->bos[nr_bos]; submit->queue = queue; @@ -552,6 +552,7 @@ int msm_ioctl_gem_submit(struct drm_device *dev, void *data, struct msm_drm_private *priv = dev->dev_private; struct drm_msm_gem_submit *args = data; struct msm_context *ctx = file->driver_priv; + struct drm_gpuvm *vm = msm_context_vm(dev, ctx); struct msm_gem_submit *submit = NULL; struct msm_gpu *gpu = priv->gpu; struct msm_gpu_submitqueue *queue; @@ -567,10 +568,13 @@ int msm_ioctl_gem_submit(struct drm_device *dev, void *data, if (!gpu) return -ENXIO; + if (!vm) + return UERR(ENOMEM, dev, "no VM"); + if (args->pad) return -EINVAL; - if (to_msm_vm(ctx->vm)->unusable) + if (to_msm_vm(vm)->unusable) return UERR(EPIPE, dev, "context is unusable"); /* for now, we just have 3d pipe.. eventually this would need to @@ -607,7 +611,7 @@ int msm_ioctl_gem_submit(struct drm_device *dev, void *data, } } - submit = submit_create(dev, gpu, queue, args->nr_bos, args->nr_cmds, + submit = submit_create(dev, gpu, vm, queue, args->nr_bos, args->nr_cmds, file->client_id); if (IS_ERR(submit)) { ret = PTR_ERR(submit); diff --git a/drivers/gpu/drm/msm/msm_gem_vma.c b/drivers/gpu/drm/msm/msm_gem_vma.c index 50d4812bdb4c..1f9b75fc12fd 100644 --- a/drivers/gpu/drm/msm/msm_gem_vma.c +++ b/drivers/gpu/drm/msm/msm_gem_vma.c @@ -956,7 +956,7 @@ msm_gem_vm_close(struct drm_gpuvm *gpuvm) static struct msm_vm_bind_job * -vm_bind_job_create(struct drm_device *dev, struct drm_file *file, +vm_bind_job_create(struct drm_device *dev, struct drm_file *file, struct drm_gpuvm *vm, struct msm_gpu_submitqueue *queue, uint32_t nr_ops) { struct msm_vm_bind_job *job; @@ -973,7 +973,7 @@ vm_bind_job_create(struct drm_device *dev, struct drm_file *file, return ERR_PTR(ret); } - job->vm = msm_context_vm(dev, queue->ctx); + job->vm = vm; job->queue = queue; INIT_LIST_HEAD(&job->vm_ops); @@ -1432,6 +1432,7 @@ msm_ioctl_vm_bind(struct drm_device *dev, void *data, struct drm_file *file) struct msm_drm_private *priv = dev->dev_private; struct drm_msm_vm_bind *args = data; struct msm_context *ctx = file->driver_priv; + struct drm_gpuvm *vm = msm_context_vm(dev, ctx); struct msm_vm_bind_job *job = NULL; struct msm_gpu *gpu = priv->gpu; struct msm_gpu_submitqueue *queue; @@ -1446,11 +1447,14 @@ msm_ioctl_vm_bind(struct drm_device *dev, void *data, struct drm_file *file) if (!gpu) return -ENXIO; + if (!vm) + return UERR(ENOMEM, dev, "no VM"); + /* * Maybe we could allow just UNMAP ops? OTOH userspace should just * immediately close the device file and all will be torn down. */ - if (to_msm_vm(msm_context_vm(dev, ctx))->unusable) + if (to_msm_vm(vm)->unusable) return UERR(EPIPE, dev, "context is unusable"); /* @@ -1481,7 +1485,7 @@ msm_ioctl_vm_bind(struct drm_device *dev, void *data, struct drm_file *file) } } - job = vm_bind_job_create(dev, file, queue, args->nr_ops); + job = vm_bind_job_create(dev, file, vm, queue, args->nr_ops); if (IS_ERR(job)) { ret = PTR_ERR(job); goto out_post_unlock; diff --git a/drivers/gpu/drm/msm/msm_submitqueue.c b/drivers/gpu/drm/msm/msm_submitqueue.c index 233c4f35ab12..d307cdd17d90 100644 --- a/drivers/gpu/drm/msm/msm_submitqueue.c +++ b/drivers/gpu/drm/msm/msm_submitqueue.c @@ -174,6 +174,7 @@ int msm_submitqueue_create(struct drm_device *drm, struct msm_context *ctx, struct msm_drm_private *priv = drm->dev_private; struct msm_gpu_submitqueue *queue; enum drm_sched_priority sched_prio; + struct drm_gpuvm *vm = NULL; unsigned ring_nr; int ret; @@ -186,6 +187,11 @@ int msm_submitqueue_create(struct drm_device *drm, struct msm_context *ctx, if (flags & MSM_SUBMITQUEUE_VM_BIND) { unsigned sz; + vm = msm_context_vm(drm, ctx); + + if (!vm) + return UERR(ENOMEM, drm, "no VM"); + /* Not allowed for kernel managed VMs (ie. kernel allocs VA) */ if (!msm_context_is_vmbind(ctx)) return -EINVAL; @@ -217,7 +223,7 @@ int msm_submitqueue_create(struct drm_device *drm, struct msm_context *ctx, queue->flags = flags; if (flags & MSM_SUBMITQUEUE_VM_BIND) { - struct drm_gpu_scheduler *sched = &to_msm_vm(msm_context_vm(drm, ctx))->sched; + struct drm_gpu_scheduler *sched = &to_msm_vm(vm)->sched; queue->entity = &queue->_vm_bind_entity[0]; From 3b35a5c528baf3c8cff89b44ae1c5174007b127a Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 29 Jul 2026 08:55:52 -0700 Subject: [PATCH 060/641] drm/msm: Don't fallback to shared VM for VM_BIND If the user wants a userspace managed VM (EN_VM_BIND) don't silently fall back to shared VM. Signed-off-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/743110/ Message-ID: <20260729155609.20190-16-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/msm_drv.c | 3 ++- drivers/gpu/drm/msm/msm_gpu.c | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c index 84b126c6347f..db1b655dd055 100644 --- a/drivers/gpu/drm/msm/msm_drv.c +++ b/drivers/gpu/drm/msm/msm_drv.c @@ -234,7 +234,8 @@ struct drm_gpuvm *msm_context_vm(struct drm_device *dev, struct msm_context *ctx if (!ctx->vm) { vm = msm_gpu_create_private_vm( priv->gpu, current, !ctx->userspace_managed_vm); - smp_store_release(&ctx->vm, vm); + if (!IS_ERR_OR_NULL(vm)) + smp_store_release(&ctx->vm, vm); } return ctx->vm; diff --git a/drivers/gpu/drm/msm/msm_gpu.c b/drivers/gpu/drm/msm/msm_gpu.c index 03c057856065..0c2c35636251 100644 --- a/drivers/gpu/drm/msm/msm_gpu.c +++ b/drivers/gpu/drm/msm/msm_gpu.c @@ -879,7 +879,7 @@ msm_gpu_create_private_vm(struct msm_gpu *gpu, struct task_struct *task, to_msm_vm(vm)->pid = get_pid(task_pid(task)); } - if (IS_ERR_OR_NULL(vm)) + if (IS_ERR_OR_NULL(vm) && kernel_managed) vm = drm_gpuvm_get(gpu->vm); return vm; From 1b8029394fb77adde9c494a3acd40b0b39793b55 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 29 Jul 2026 08:55:53 -0700 Subject: [PATCH 061/641] drm/msm: Fix per-process-pgtables check ctx->vm should not be inialized yet (or if it has, an error is returned immediately following this check), so this isn't a valid way to check for per-process-pgtable support. Instead just check if create_private_vm() is supported. Reported-by: Sashiko Signed-off-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/743096/ Message-ID: <20260729155609.20190-17-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/adreno/adreno_gpu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/adreno/adreno_gpu.c b/drivers/gpu/drm/msm/adreno/adreno_gpu.c index 84101e534ebe..3370cd44382f 100644 --- a/drivers/gpu/drm/msm/adreno/adreno_gpu.c +++ b/drivers/gpu/drm/msm/adreno/adreno_gpu.c @@ -513,7 +513,7 @@ int adreno_set_param(struct msm_gpu *gpu, struct msm_context *ctx, guard(rwsem_read)(&ctx->ctxlock); /* We can only support VM_BIND with per-process pgtables: */ - if (ctx->vm == gpu->vm) + if (!gpu->funcs->create_private_vm) return UERR(EINVAL, drm, "requires per-process pgtables"); /* From 7aeada642ebfd52ddb87aaf1f0324a7fcf636610 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 29 Jul 2026 08:55:54 -0700 Subject: [PATCH 062/641] drm/msm: Fixup invalid overflow check On overflow struct_size() would return SIZE_MAX. But kzalloc() (and friends) check this already, so we can just remove the check. On the other hand, we should be using the overflow helpers to calculate the cmd array size. Reported-by: Sashiko Signed-off-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/743111/ Message-ID: <20260729155609.20190-18-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/msm_gem_submit.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_gem_submit.c b/drivers/gpu/drm/msm/msm_gem_submit.c index 6b0bee6c39bc..5862db05297a 100644 --- a/drivers/gpu/drm/msm/msm_gem_submit.c +++ b/drivers/gpu/drm/msm/msm_gem_submit.c @@ -36,14 +36,11 @@ static struct msm_gem_submit *submit_create(struct drm_device *dev, { static atomic_t ident = ATOMIC_INIT(0); struct msm_gem_submit *submit; - uint64_t sz; + size_t sz; int ret; - sz = struct_size(submit, bos, nr_bos) + - ((u64)nr_cmds * sizeof(submit->cmd[0])); - - if (sz > SIZE_MAX) - return ERR_PTR(-ENOMEM); + sz = size_add(struct_size(submit, bos, nr_bos), + array_size(sizeof(submit->cmd[0]), nr_cmds)); submit = kzalloc(sz, GFP_KERNEL | __GFP_NOWARN); if (!submit) From 141556543c9917d7c3d527f7eca6e288ec6bb58b Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Thu, 30 Jul 2026 10:42:29 +0800 Subject: [PATCH 063/641] EDAC/ie31200: Decouple DIMM width decoding from enum order The current method to get DIMM width relied on DEV_* enum ordering via a linear offset (+ DEV_X8), tightly coupling hardware encoding to enum layout. Replace it with explicit decoding to remove this dependency, as the enum is expected to grow with additional device widths. Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20260730024238.4096623-2-qiuxu.zhuo@intel.com --- drivers/edac/ie31200_edac.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/drivers/edac/ie31200_edac.c b/drivers/edac/ie31200_edac.c index e3bd6436669b..bfd54012ae47 100644 --- a/drivers/edac/ie31200_edac.c +++ b/drivers/edac/ie31200_edac.c @@ -416,7 +416,23 @@ static void populate_dimm_info(struct dimm_data *dd, u32 addr_decode, int dimm, { dd->size = field_get(cfg->reg_mad_dimm_size_mask[dimm], addr_decode) * cfg->reg_mad_dimm_size_granularity; dd->ranks = field_get(cfg->reg_mad_dimm_rank_mask[dimm], addr_decode) + 1; - dd->dtype = field_get(cfg->reg_mad_dimm_width_mask[dimm], addr_decode) + DEV_X8; + + switch (field_get(cfg->reg_mad_dimm_width_mask[dimm], addr_decode)) { + case 0: + dd->dtype = DEV_X8; + break; + case 1: + dd->dtype = DEV_X16; + break; + case 2: + dd->dtype = DEV_X32; + break; + case 3: + dd->dtype = DEV_X64; + break; + default: + dd->dtype = DEV_UNKNOWN; + } } static void ie31200_get_dimm_config(struct mem_ctl_info *mci, void __iomem *window, From f4008169bd320eedb9ddf2b39eeb21370ddac278 Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Thu, 30 Jul 2026 10:42:30 +0800 Subject: [PATCH 064/641] EDAC/igen6: Fix interleave boundary condition The address translation logic splits the memory space into interleaved and non-interleaved regions using a boundary at 2 * s_size. The current check uses '>' and incorrectly classifies the boundary address (2 * s_size) as part of the interleaved region. This leads to incorrect channel/sub-channel selection at the region boundary. Fix the classification by using '>=' so that the boundary address is handled in the non-interleaved region, matching the hardware layout. Fixes: 10590a9d4f23 ("EDAC/igen6: Add EDAC driver for Intel client SoCs using IBECC") Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20260730024238.4096623-3-qiuxu.zhuo@intel.com --- drivers/edac/igen6_edac.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/edac/igen6_edac.c b/drivers/edac/igen6_edac.c index f1fc20d4ebf6..43b56a2eb547 100644 --- a/drivers/edac/igen6_edac.c +++ b/drivers/edac/igen6_edac.c @@ -1035,7 +1035,7 @@ static void decode_addr(u64 addr, u32 hash, u64 s_size, int l_map, { int intlv_bit = CHANNEL_HASH_LSB_MASK_BIT(hash) + 6; - if (addr > 2 * s_size) { + if (addr >= 2 * s_size) { *sub_addr = addr - s_size; *idx = l_map; return; From 540b79536f3a89a66c5b6c490110298d43025618 Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Thu, 30 Jul 2026 10:42:31 +0800 Subject: [PATCH 065/641] EDAC/igen6: Fix channel selection hash In channel selection hash mode, the hardware decoding logic always includes the channel interleave bit in XOR operations. However, the hash mask may or may not include this channel interleave bit. When the mask does include this bit, the current igen6_edac code performs XOR on the interleave bit twice, effectively ignoring it - which is incorrect. Fix this issue by ensuring the hash mask always includes the interleave bit, so XOR is performed on the interleave bit exactly once. Fixes: 10590a9d4f23 ("EDAC/igen6: Add EDAC driver for Intel client SoCs using IBECC") Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20260730024238.4096623-4-qiuxu.zhuo@intel.com --- drivers/edac/igen6_edac.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/drivers/edac/igen6_edac.c b/drivers/edac/igen6_edac.c index 43b56a2eb547..f1fb644154ae 100644 --- a/drivers/edac/igen6_edac.c +++ b/drivers/edac/igen6_edac.c @@ -1009,14 +1009,22 @@ static void set_dimm_params(struct igen6_imc *imc, int chan) static int decode_chan_idx(u64 addr, u64 mask, int intlv_bit) { - u64 hash_addr = addr & mask, hash = 0; - u64 intlv = (addr >> intlv_bit) & 1; + u64 hash_addr, hash = 0; int i; + /* + * In hash mode, the @intlv_bit is the lowest selected bit of @addr + * to be XORed. While @mask may or may not include this @intlv_bit, + * we enforce that @mask includes @intlv_bit to ensure @intlv_bit is + * XORed exactly once. + */ + mask |= 1 << intlv_bit; + hash_addr = addr & mask; + for (i = 6; i < 20; i++) hash ^= (hash_addr >> i) & 1; - return (int)hash ^ intlv; + return (int)hash; } static u64 decode_channel_addr(u64 addr, int intlv_bit) From 7b348d0d401d478f1923ba20a34a61681d1f7971 Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Thu, 30 Jul 2026 10:42:32 +0800 Subject: [PATCH 066/641] EDAC/igen6: Fix channel address decode for non-hash mode In non-hash mode, decode_channel_addr() and channel index extraction used a hardcoded interleave bit position 6 instead of the actual intlv_bit parameter, causing incorrect channel address decoding. Fix this by using intlv_bit consistently in both hash and non-hash modes. Fixes: 10590a9d4f23 ("EDAC/igen6: Add EDAC driver for Intel client SoCs using IBECC") Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20260730024238.4096623-5-qiuxu.zhuo@intel.com --- drivers/edac/igen6_edac.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/edac/igen6_edac.c b/drivers/edac/igen6_edac.c index f1fb644154ae..ea5628d780eb 100644 --- a/drivers/edac/igen6_edac.c +++ b/drivers/edac/igen6_edac.c @@ -1049,13 +1049,12 @@ static void decode_addr(u64 addr, u32 hash, u64 s_size, int l_map, return; } - if (CHANNEL_HASH_MODE(hash)) { - *sub_addr = decode_channel_addr(addr, intlv_bit); + *sub_addr = decode_channel_addr(addr, intlv_bit); + + if (CHANNEL_HASH_MODE(hash)) *idx = decode_chan_idx(addr, CHANNEL_HASH_MASK(hash), intlv_bit); - } else { - *sub_addr = decode_channel_addr(addr, 6); - *idx = GET_BITFIELD(addr, 6, 6); - } + else + *idx = GET_BITFIELD(addr, intlv_bit, intlv_bit); } static int igen6_decode(struct decoded_addr *res) From 0361f576ec0dffca13edc94580c8666146a91e02 Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Thu, 30 Jul 2026 10:42:33 +0800 Subject: [PATCH 067/641] EDAC/igen6: Fix Raptor Lake-P logged error address Raptor Lake-P was treated as using a different IBECC (In-Band ECC) error address format and therefore had a dedicated extraction path that shifted the logged address. However, Raptor Lake-P uses the same cache-line-granularity error address format as other IBECC platforms. The special handling causes the logged address to be decoded incorrectly. Fix the issue by removing Raptor Lake-P specific extraction logic and using the common path instead. This also allows reusing Alder Lake resource configuration data. Fixes: d23627a7688f ("EDAC/igen6: Add Intel Raptor Lake-P SoCs support") Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20260730024238.4096623-6-qiuxu.zhuo@intel.com --- drivers/edac/igen6_edac.c | 39 ++++++--------------------------------- 1 file changed, 6 insertions(+), 33 deletions(-) diff --git a/drivers/edac/igen6_edac.c b/drivers/edac/igen6_edac.c index ea5628d780eb..12d718a50e1c 100644 --- a/drivers/edac/igen6_edac.c +++ b/drivers/edac/igen6_edac.c @@ -175,8 +175,6 @@ static struct res_config { /* Set imc->dimm_{l_size,s_size,l_map}[chan]. */ void (*set_dimm_params)(struct igen6_imc *imc, int chan); bool (*ibecc_available)(struct pci_dev *pdev); - /* Extract error address logged in IBECC */ - u64 (*err_addr)(u64 ecclog); /* Convert error address logged in IBECC to system physical address */ u64 (*err_addr_to_sys_addr)(u64 eaddr, int mc); /* Convert error address logged in IBECC to integrated memory controller address */ @@ -522,11 +520,6 @@ static u64 adl_err_addr_to_imc_addr(u64 eaddr, int mc) return imc_addr; } -static u64 rpl_p_err_addr(u64 ecclog) -{ - return field_get(res_cfg->reg_eccerrlog_addr_mask, ecclog); -} - static enum mem_type ptl_h_get_mem_type(struct igen6_imc *imc) { u32 mtype, val; @@ -716,22 +709,6 @@ static struct res_config adl_n_cfg = { .err_addr_to_imc_addr = adl_err_addr_to_imc_addr, }; -static struct res_config rpl_p_cfg = { - .machine_check = true, - .num_imc = 2, - .reg_mchbar_mask = GENMASK_ULL(41, 17), - .reg_tom_mask = GENMASK_ULL(41, 20), - .reg_touud_mask = GENMASK_ULL(41, 20), - .reg_eccerrlog_addr_mask = GENMASK_ULL(45, 5), - .imc_base = 0xd800, - .ibecc_base = 0xd400, - .ibecc_error_log_offset = 0x68, - .ibecc_available = tgl_ibecc_available, - .err_addr = rpl_p_err_addr, - .err_addr_to_sys_addr = adl_err_addr_to_sys_addr, - .err_addr_to_imc_addr = adl_err_addr_to_imc_addr, -}; - static struct res_config mtl_ps_cfg = { .machine_check = true, .num_imc = 2, @@ -877,11 +854,11 @@ static struct pci_device_id igen6_pci_tbl[] = { { PCI_VDEVICE(INTEL, DID_ASL_SKU1), .driver_data = (kernel_ulong_t)&adl_n_cfg }, { PCI_VDEVICE(INTEL, DID_ASL_SKU2), .driver_data = (kernel_ulong_t)&adl_n_cfg }, { PCI_VDEVICE(INTEL, DID_ASL_SKU3), .driver_data = (kernel_ulong_t)&adl_n_cfg }, - { PCI_VDEVICE(INTEL, DID_RPL_P_SKU1), .driver_data = (kernel_ulong_t)&rpl_p_cfg }, - { PCI_VDEVICE(INTEL, DID_RPL_P_SKU2), .driver_data = (kernel_ulong_t)&rpl_p_cfg }, - { PCI_VDEVICE(INTEL, DID_RPL_P_SKU3), .driver_data = (kernel_ulong_t)&rpl_p_cfg }, - { PCI_VDEVICE(INTEL, DID_RPL_P_SKU4), .driver_data = (kernel_ulong_t)&rpl_p_cfg }, - { PCI_VDEVICE(INTEL, DID_RPL_P_SKU5), .driver_data = (kernel_ulong_t)&rpl_p_cfg }, + { PCI_VDEVICE(INTEL, DID_RPL_P_SKU1), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_RPL_P_SKU2), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_RPL_P_SKU3), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_RPL_P_SKU4), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_RPL_P_SKU5), .driver_data = (kernel_ulong_t)&adl_cfg }, { PCI_VDEVICE(INTEL, DID_MTL_PS_SKU1), .driver_data = (kernel_ulong_t)&mtl_ps_cfg }, { PCI_VDEVICE(INTEL, DID_MTL_PS_SKU2), .driver_data = (kernel_ulong_t)&mtl_ps_cfg }, { PCI_VDEVICE(INTEL, DID_MTL_PS_SKU3), .driver_data = (kernel_ulong_t)&mtl_ps_cfg }, @@ -1237,11 +1214,7 @@ static void ecclog_work_cb(struct work_struct *work) llist_for_each_entry_safe(node, tmp, head, llnode) { memset(&res, 0, sizeof(res)); - if (res_cfg->err_addr) - eaddr = res_cfg->err_addr(node->ecclog); - else - eaddr = node->ecclog & res_cfg->reg_eccerrlog_addr_mask; - + eaddr = node->ecclog & res_cfg->reg_eccerrlog_addr_mask; res.mc = node->mc; res.sys_addr = res_cfg->err_addr_to_sys_addr(eaddr, res.mc); res.imc_addr = res_cfg->err_addr_to_imc_addr(eaddr, res.mc); From a118a5e2f172a5122d387022e5d1d41d30750738 Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Thu, 30 Jul 2026 10:42:34 +0800 Subject: [PATCH 068/641] EDAC/igen6: Remove unnecessary XOR on the zero-valued interleave bit When reconstructing the removed interleave bit from an inflated memory slice address, where a zero was inserted at the interleave bit position, it's unnecessary to XOR this zero-valued interleave bit. Remove this unnecessary XOR operation. No functional changes intended. Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20260730024238.4096623-7-qiuxu.zhuo@intel.com --- drivers/edac/igen6_edac.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/edac/igen6_edac.c b/drivers/edac/igen6_edac.c index 12d718a50e1c..71ed50daeb19 100644 --- a/drivers/edac/igen6_edac.c +++ b/drivers/edac/igen6_edac.c @@ -448,16 +448,16 @@ static u64 mem_addr_to_sys_addr(u64 maddr) return maddr; } -static u64 mem_slice_hash(u64 addr, u64 mask, u64 hash_init, int intlv_bit) +static u64 mem_slice_hash(u64 addr, u64 mask, u64 hash_init) { + /* The interleave bit in @addr is a zero. */ u64 hash_addr = addr & mask, hash = hash_init; - u64 intlv = (addr >> intlv_bit) & 1; int i; for (i = 6; i < 20; i++) hash ^= (hash_addr >> i) & 1; - return hash ^ intlv; + return hash; } static u64 tgl_err_addr_to_mem_addr(u64 eaddr, int mc) @@ -478,7 +478,7 @@ static u64 tgl_err_addr_to_mem_addr(u64 eaddr, int mc) maddr = GET_BITFIELD(eaddr, intlv_bit, 63) << (intlv_bit + 1) | GET_BITFIELD(eaddr, 0, intlv_bit - 1); - hash = mem_slice_hash(maddr, mask, mc, intlv_bit); + hash = mem_slice_hash(maddr, mask, mc); return maddr | (hash << intlv_bit); } From 8ac9136d79e960b8b8b9a41b9d4076e4afe6de5e Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Thu, 30 Jul 2026 10:42:35 +0800 Subject: [PATCH 069/641] EDAC/igen6: Simplify compute die ID comments The existing comments repeat information already implied by the code structure. Shorten them to SoC names only to reduce clutter and improve readability. No functional changes intended. Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20260730024238.4096623-8-qiuxu.zhuo@intel.com --- drivers/edac/igen6_edac.c | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/drivers/edac/igen6_edac.c b/drivers/edac/igen6_edac.c index 71ed50daeb19..2960af172c57 100644 --- a/drivers/edac/igen6_edac.c +++ b/drivers/edac/igen6_edac.c @@ -223,7 +223,8 @@ static char ecclog_buf[ECCLOG_POOL_SIZE]; static struct irq_work ecclog_irq_work; static struct work_struct ecclog_work; -/* Compute die IDs for Elkhart Lake with IBECC */ +/* SoC compute die IDs with IBECC capability. */ +/* Elkhart Lake */ #define DID_EHL_SKU5 0x4514 #define DID_EHL_SKU6 0x4528 #define DID_EHL_SKU7 0x452a @@ -236,22 +237,22 @@ static struct work_struct ecclog_work; #define DID_EHL_SKU14 0x4534 #define DID_EHL_SKU15 0x4536 -/* Compute die IDs for ICL-NNPI with IBECC */ +/* ICL-NNPI */ #define DID_ICL_SKU8 0x4581 #define DID_ICL_SKU10 0x4585 #define DID_ICL_SKU11 0x4589 #define DID_ICL_SKU12 0x458d -/* Compute die IDs for Tiger Lake with IBECC */ +/* Tiger Lake */ #define DID_TGL_SKU 0x9a14 -/* Compute die IDs for Alder Lake with IBECC */ +/* Alder Lake */ #define DID_ADL_SKU1 0x4601 #define DID_ADL_SKU2 0x4602 #define DID_ADL_SKU3 0x4621 #define DID_ADL_SKU4 0x4641 -/* Compute die IDs for Alder Lake-N with IBECC */ +/* Alder Lake-N */ #define DID_ADL_N_SKU1 0x4614 #define DID_ADL_N_SKU2 0x4617 #define DID_ADL_N_SKU3 0x461b @@ -265,38 +266,38 @@ static struct work_struct ecclog_work; #define DID_ADL_N_SKU11 0x467c #define DID_ADL_N_SKU12 0x4632 -/* Compute die IDs for Arizona Beach with IBECC */ +/* Arizona Beach */ #define DID_AZB_SKU1 0x4676 -/* Compute did IDs for Amston Lake with IBECC */ +/* Amston Lake */ #define DID_ASL_SKU1 0x464a #define DID_ASL_SKU2 0x4646 #define DID_ASL_SKU3 0x4652 -/* Compute die IDs for Raptor Lake-P with IBECC */ +/* Raptor Lake-P */ #define DID_RPL_P_SKU1 0xa706 #define DID_RPL_P_SKU2 0xa707 #define DID_RPL_P_SKU3 0xa708 #define DID_RPL_P_SKU4 0xa716 #define DID_RPL_P_SKU5 0xa718 -/* Compute die IDs for Meteor Lake-PS with IBECC */ +/* Meteor Lake-PS */ #define DID_MTL_PS_SKU1 0x7d21 #define DID_MTL_PS_SKU2 0x7d22 #define DID_MTL_PS_SKU3 0x7d23 #define DID_MTL_PS_SKU4 0x7d24 -/* Compute die IDs for Meteor Lake-P with IBECC */ +/* Meteor Lake-P */ #define DID_MTL_P_SKU1 0x7d01 #define DID_MTL_P_SKU2 0x7d02 #define DID_MTL_P_SKU3 0x7d14 -/* Compute die IDs for Arrow Lake-UH with IBECC */ +/* Arrow Lake-UH */ #define DID_ARL_UH_SKU1 0x7d06 #define DID_ARL_UH_SKU2 0x7d20 #define DID_ARL_UH_SKU3 0x7d30 -/* Compute die IDs for Panther Lake-H with IBECC */ +/* Panther Lake-H */ #define DID_PTL_H_SKU1 0xb000 #define DID_PTL_H_SKU2 0xb001 #define DID_PTL_H_SKU3 0xb002 @@ -312,10 +313,10 @@ static struct work_struct ecclog_work; #define DID_PTL_H_SKU13 0xb02a #define DID_PTL_H_SKU14 0xb00a -/* Compute die IDs for Wildcat Lake with IBECC */ +/* Wildcat Lake */ #define DID_WCL_SKU1 0xfd00 -/* Compute die IDs for Nova Lake-H/HX with IBECC */ +/* Nova Lake-H/HX */ #define DID_NVL_H_SKU1 0xd701 #define DID_NVL_H_SKU2 0xd702 #define DID_NVL_H_SKU3 0xd704 From e492449e39b7aee9bfcb1bda3181fa942d24cdd2 Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Thu, 30 Jul 2026 10:42:36 +0800 Subject: [PATCH 070/641] EDAC/igen6: Detect present memory controllers at runtime The igen6_edac currently relies on res_config::num_imc to describe the number of memory controllers supported by each SoC. As a result, adding support for a new platform requires updating this configuration even though the hardware can be discovered at runtime. Instead, detect the number of present memory controllers at runtime and size the driver state accordingly. This eliminates the need to update res_config whenever a new SoC variant is added. Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20260730024238.4096623-9-qiuxu.zhuo@intel.com --- drivers/edac/igen6_edac.c | 97 ++++++++++++++++++++++++++------------- 1 file changed, 64 insertions(+), 33 deletions(-) diff --git a/drivers/edac/igen6_edac.c b/drivers/edac/igen6_edac.c index 2960af172c57..d468c655348c 100644 --- a/drivers/edac/igen6_edac.c +++ b/drivers/edac/igen6_edac.c @@ -42,7 +42,8 @@ #define GET_BITFIELD(v, lo, hi) (((v) & GENMASK_ULL(hi, lo)) >> (lo)) -#define NUM_IMC 2 /* Max memory controllers */ +/* Probing upper bound, not a hardware capability limit. */ +#define MAX_IMC_TO_PROBE 8 #define NUM_CHANNELS 2 /* Max channels */ #define NUM_DIMMS 2 /* Max DIMMs per channel */ @@ -182,11 +183,11 @@ static struct res_config { } *res_cfg; static struct igen6_pvt { - struct igen6_imc imc[NUM_IMC]; void __iomem *memss_pma_cr; u64 ms_hash; u64 ms_s_size; int ms_l_map; + struct igen6_imc imc[]; } *igen6_pvt; /* The top of low usable DRAM */ @@ -353,6 +354,46 @@ static int get_mchbar(struct pci_dev *pdev, u64 *mchbar) return 0; } +/* Check whether the memory controller is absent. */ +static bool imc_absent(void __iomem *window) +{ + return readl(window + MAD_INTER_CHANNEL_OFFSET) == ~0; +} + +/* Return MMIO base address of the memory controller if it's present, otherwise return NULL. */ +static void __iomem *map_imc_window(u64 mchbar, int pmc) +{ + void __iomem *window; + + window = ioremap(mchbar + pmc * MCHBAR_SIZE, MCHBAR_SIZE); + if (!window) + return NULL; + + if (imc_absent(window)) { + iounmap(window); + return NULL; + } + + return window; +} + +/* Return the number of present memory controllers. */ +static int get_imc_num(u64 mchbar) +{ + void __iomem *window; + int lmc, pmc; + + for (lmc = 0, pmc = 0; pmc < MAX_IMC_TO_PROBE; pmc++) { + window = map_imc_window(mchbar, pmc); + if (window) { + iounmap(window); + lmc++; + } + } + + return lmc; +} + static bool ehl_ibecc_available(struct pci_dev *pdev) { u32 v; @@ -1457,18 +1498,27 @@ static struct igen6_pvt *igen6_pvt_setup(struct pci_dev *pdev) { void __iomem *memss_pma_cr; struct igen6_pvt *pvt; + int imc_num, rc; u64 mchbar; - int rc; - - pvt = kzalloc_obj(*igen6_pvt); - if (!pvt) - return NULL; rc = get_mchbar(pdev, &mchbar); - if (rc) { - kfree(pvt); + if (rc) + return NULL; + + imc_num = get_imc_num(mchbar); + if (!imc_num) { + igen6_printk(KERN_ERR, "No mc found.\n"); return NULL; } + edac_dbg(2, "%d mcs found.\n", imc_num); + + /* Use the runtime detected IMC count. */ + if (res_cfg->num_imc != imc_num) + res_cfg->num_imc = imc_num; + + pvt = kzalloc_flex(*pvt, imc, imc_num); + if (!pvt) + return NULL; memss_pma_cr = ioremap(mchbar, MCHBAR_SIZE * 2); if (!memss_pma_cr) { @@ -1553,12 +1603,6 @@ static void igen6_check(struct mem_ctl_info *mci) irq_work_queue(&ecclog_irq_work); } -/* Check whether the memory controller is absent. */ -static bool igen6_imc_absent(void __iomem *window) -{ - return readl(window + MAD_INTER_CHANNEL_OFFSET) == ~0; -} - static void imc_release(struct device *dev) { /* Nothing to do, the 'imc' owns the 'dev' and will also release it. */ @@ -1670,26 +1714,15 @@ static int igen6_register_mcis(struct pci_dev *pdev, u64 mchbar) { void __iomem *window; int lmc, pmc, rc; - u64 base; - for (lmc = 0, pmc = 0; pmc < NUM_IMC; pmc++) { - base = mchbar + pmc * MCHBAR_SIZE; - window = ioremap(base, MCHBAR_SIZE); - if (!window) { - igen6_printk(KERN_ERR, "Failed to ioremap 0x%llx for mc%d\n", base, pmc); - rc = -ENOMEM; - goto out_unregister_mcis; - } - - if (igen6_imc_absent(window)) { - iounmap(window); - edac_dbg(2, "Skip absent mc%d\n", pmc); + for (lmc = 0, pmc = 0; pmc < MAX_IMC_TO_PROBE; pmc++) { + window = map_imc_window(mchbar, pmc); + if (!window) continue; - } rc = igen6_register_mci(lmc, window, pdev); if (rc) - goto out_iounmap; + goto err_unregister; /* Done, if all present MCs are detected and registered. */ if (++lmc >= res_cfg->num_imc) @@ -1709,10 +1742,8 @@ static int igen6_register_mcis(struct pci_dev *pdev, u64 mchbar) return 0; -out_iounmap: +err_unregister: iounmap(window); - -out_unregister_mcis: igen6_unregister_mcis(); return rc; From 1f43c17ce550e8ed628afd7fb3226c904076f864 Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Thu, 30 Jul 2026 10:42:37 +0800 Subject: [PATCH 071/641] EDAC/igen6: Remove redundant resource configuration tables Several resource configuration tables differ only in their num_imc value, while all other fields are identical. Their only purpose is to describe the number of memory controllers supported by a platform. Since IMC count is now detected at runtime, these duplicate tables no longer carry any unique platform information. Reuse the shared configurations and remove the redundant tables. Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20260730024238.4096623-10-qiuxu.zhuo@intel.com --- drivers/edac/igen6_edac.c | 66 +++++++++++---------------------------- 1 file changed, 19 insertions(+), 47 deletions(-) diff --git a/drivers/edac/igen6_edac.c b/drivers/edac/igen6_edac.c index d468c655348c..e10e29f1a5f5 100644 --- a/drivers/edac/igen6_edac.c +++ b/drivers/edac/igen6_edac.c @@ -721,6 +721,7 @@ static struct res_config tgl_cfg = { .err_addr_to_imc_addr = tgl_err_addr_to_imc_addr, }; +/* Shared by Alder Lake, Alder Lake-N, Arizona Beach, Amston Lake, and Raptor Lake-P */ static struct res_config adl_cfg = { .machine_check = true, .num_imc = 2, @@ -736,21 +737,6 @@ static struct res_config adl_cfg = { .err_addr_to_imc_addr = adl_err_addr_to_imc_addr, }; -static struct res_config adl_n_cfg = { - .machine_check = true, - .num_imc = 1, - .reg_mchbar_mask = GENMASK_ULL(41, 17), - .reg_tom_mask = GENMASK_ULL(41, 20), - .reg_touud_mask = GENMASK_ULL(41, 20), - .reg_eccerrlog_addr_mask = GENMASK_ULL(45, 5), - .imc_base = 0xd800, - .ibecc_base = 0xd400, - .ibecc_error_log_offset = 0x68, - .ibecc_available = tgl_ibecc_available, - .err_addr_to_sys_addr = adl_err_addr_to_sys_addr, - .err_addr_to_imc_addr = adl_err_addr_to_imc_addr, -}; - static struct res_config mtl_ps_cfg = { .machine_check = true, .num_imc = 2, @@ -768,6 +754,7 @@ static struct res_config mtl_ps_cfg = { .err_addr_to_imc_addr = adl_err_addr_to_imc_addr, }; +/* Shared by Meteor Lake-P, Arrow Lake-UH, and Wildcat Lake */ static struct res_config mtl_p_cfg = { .machine_check = true, .num_imc = 2, @@ -813,21 +800,6 @@ static struct res_config ptl_h_cfg = { .err_addr_to_imc_addr = adl_err_addr_to_imc_addr, }; -static struct res_config wcl_cfg = { - .machine_check = true, - .num_imc = 1, - .reg_mchbar_mask = GENMASK_ULL(41, 17), - .reg_tom_mask = GENMASK_ULL(41, 20), - .reg_touud_mask = GENMASK_ULL(41, 20), - .reg_eccerrlog_addr_mask = GENMASK_ULL(38, 5), - .imc_base = 0xd800, - .ibecc_base = 0xd400, - .ibecc_error_log_offset = 0x170, - .ibecc_available = mtl_p_ibecc_available, - .err_addr_to_sys_addr = adl_err_addr_to_sys_addr, - .err_addr_to_imc_addr = adl_err_addr_to_imc_addr, -}; - static struct res_config nvl_h_cfg = { .machine_check = true, .num_imc = 2, @@ -880,22 +852,22 @@ static struct pci_device_id igen6_pci_tbl[] = { { PCI_VDEVICE(INTEL, DID_ADL_SKU2), .driver_data = (kernel_ulong_t)&adl_cfg }, { PCI_VDEVICE(INTEL, DID_ADL_SKU3), .driver_data = (kernel_ulong_t)&adl_cfg }, { PCI_VDEVICE(INTEL, DID_ADL_SKU4), .driver_data = (kernel_ulong_t)&adl_cfg }, - { PCI_VDEVICE(INTEL, DID_ADL_N_SKU1), .driver_data = (kernel_ulong_t)&adl_n_cfg }, - { PCI_VDEVICE(INTEL, DID_ADL_N_SKU2), .driver_data = (kernel_ulong_t)&adl_n_cfg }, - { PCI_VDEVICE(INTEL, DID_ADL_N_SKU3), .driver_data = (kernel_ulong_t)&adl_n_cfg }, - { PCI_VDEVICE(INTEL, DID_ADL_N_SKU4), .driver_data = (kernel_ulong_t)&adl_n_cfg }, - { PCI_VDEVICE(INTEL, DID_ADL_N_SKU5), .driver_data = (kernel_ulong_t)&adl_n_cfg }, - { PCI_VDEVICE(INTEL, DID_ADL_N_SKU6), .driver_data = (kernel_ulong_t)&adl_n_cfg }, - { PCI_VDEVICE(INTEL, DID_ADL_N_SKU7), .driver_data = (kernel_ulong_t)&adl_n_cfg }, - { PCI_VDEVICE(INTEL, DID_ADL_N_SKU8), .driver_data = (kernel_ulong_t)&adl_n_cfg }, - { PCI_VDEVICE(INTEL, DID_ADL_N_SKU9), .driver_data = (kernel_ulong_t)&adl_n_cfg }, - { PCI_VDEVICE(INTEL, DID_ADL_N_SKU10), .driver_data = (kernel_ulong_t)&adl_n_cfg }, - { PCI_VDEVICE(INTEL, DID_ADL_N_SKU11), .driver_data = (kernel_ulong_t)&adl_n_cfg }, - { PCI_VDEVICE(INTEL, DID_ADL_N_SKU12), .driver_data = (kernel_ulong_t)&adl_n_cfg }, - { PCI_VDEVICE(INTEL, DID_AZB_SKU1), .driver_data = (kernel_ulong_t)&adl_n_cfg }, - { PCI_VDEVICE(INTEL, DID_ASL_SKU1), .driver_data = (kernel_ulong_t)&adl_n_cfg }, - { PCI_VDEVICE(INTEL, DID_ASL_SKU2), .driver_data = (kernel_ulong_t)&adl_n_cfg }, - { PCI_VDEVICE(INTEL, DID_ASL_SKU3), .driver_data = (kernel_ulong_t)&adl_n_cfg }, + { PCI_VDEVICE(INTEL, DID_ADL_N_SKU1), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_ADL_N_SKU2), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_ADL_N_SKU3), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_ADL_N_SKU4), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_ADL_N_SKU5), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_ADL_N_SKU6), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_ADL_N_SKU7), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_ADL_N_SKU8), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_ADL_N_SKU9), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_ADL_N_SKU10), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_ADL_N_SKU11), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_ADL_N_SKU12), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_AZB_SKU1), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_ASL_SKU1), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_ASL_SKU2), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_ASL_SKU3), .driver_data = (kernel_ulong_t)&adl_cfg }, { PCI_VDEVICE(INTEL, DID_RPL_P_SKU1), .driver_data = (kernel_ulong_t)&adl_cfg }, { PCI_VDEVICE(INTEL, DID_RPL_P_SKU2), .driver_data = (kernel_ulong_t)&adl_cfg }, { PCI_VDEVICE(INTEL, DID_RPL_P_SKU3), .driver_data = (kernel_ulong_t)&adl_cfg }, @@ -911,6 +883,7 @@ static struct pci_device_id igen6_pci_tbl[] = { { PCI_VDEVICE(INTEL, DID_ARL_UH_SKU1), .driver_data = (kernel_ulong_t)&mtl_p_cfg }, { PCI_VDEVICE(INTEL, DID_ARL_UH_SKU2), .driver_data = (kernel_ulong_t)&mtl_p_cfg }, { PCI_VDEVICE(INTEL, DID_ARL_UH_SKU3), .driver_data = (kernel_ulong_t)&mtl_p_cfg }, + { PCI_VDEVICE(INTEL, DID_WCL_SKU1), .driver_data = (kernel_ulong_t)&mtl_p_cfg }, { PCI_VDEVICE(INTEL, DID_PTL_H_SKU1), .driver_data = (kernel_ulong_t)&ptl_h_cfg }, { PCI_VDEVICE(INTEL, DID_PTL_H_SKU2), .driver_data = (kernel_ulong_t)&ptl_h_cfg }, { PCI_VDEVICE(INTEL, DID_PTL_H_SKU3), .driver_data = (kernel_ulong_t)&ptl_h_cfg }, @@ -925,7 +898,6 @@ static struct pci_device_id igen6_pci_tbl[] = { { PCI_VDEVICE(INTEL, DID_PTL_H_SKU12), .driver_data = (kernel_ulong_t)&ptl_h_cfg }, { PCI_VDEVICE(INTEL, DID_PTL_H_SKU13), .driver_data = (kernel_ulong_t)&ptl_h_cfg }, { PCI_VDEVICE(INTEL, DID_PTL_H_SKU14), .driver_data = (kernel_ulong_t)&ptl_h_cfg }, - { PCI_VDEVICE(INTEL, DID_WCL_SKU1), .driver_data = (kernel_ulong_t)&wcl_cfg }, { PCI_VDEVICE(INTEL, DID_NVL_H_SKU1), .driver_data = (kernel_ulong_t)&nvl_h_cfg }, { PCI_VDEVICE(INTEL, DID_NVL_H_SKU2), .driver_data = (kernel_ulong_t)&nvl_h_cfg }, { PCI_VDEVICE(INTEL, DID_NVL_H_SKU3), .driver_data = (kernel_ulong_t)&nvl_h_cfg }, From 377c68b988d9c36beb6dceedf81ed0ff55331aa9 Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Thu, 30 Jul 2026 10:42:38 +0800 Subject: [PATCH 072/641] EDAC/igen6: Refactor address translation logic The igen6 EDAC driver implements similar interleave and hash translation logic at multiple levels of the memory hierarchy. The separate implementations duplicate decoding logic, making future changes harder and increasing the risk of behavior diverging. Consolidate the common address translation operations into shared helpers so all decoding paths use a single implementation. No functional changes intended. Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20260730024238.4096623-11-qiuxu.zhuo@intel.com --- drivers/edac/igen6_edac.c | 244 +++++++++++++++++++++++++------------- 1 file changed, 159 insertions(+), 85 deletions(-) diff --git a/drivers/edac/igen6_edac.c b/drivers/edac/igen6_edac.c index e10e29f1a5f5..6abc9b203748 100644 --- a/drivers/edac/igen6_edac.c +++ b/drivers/edac/igen6_edac.c @@ -123,6 +123,43 @@ #define MEM_SLICE_HASH_MASK(v) (GET_BITFIELD(v, 6, 19) << 6) #define MEM_SLICE_HASH_LSB_MASK_BIT(v) GET_BITFIELD(v, 24, 26) +/* + * A slice represents a portion of memory space participating in an + * interleave relationship within the memory hierarchy. + * + * It can represent in different levels such as: + * + * - a pair of memory controllers + * - a memory controller + * - a memory channel + * - a memory sub-channel / DIMM + * + * +--------+ + * | | + * | Zone 1 | + * | | + * +--------+ +--------+ + * | | | | + * | | | | + * | Zone 0 | | Zone 0 | + * | | | | + * | | | | + * +--------+ +--------+ + * + * Slice L Slice S + * + * Memory space is divided into: + * + * - Zone 0 : Interleaved region + * - Zone 1 : Non-interleaved region (upper part of the large slice). + */ +struct slice { + /* Slice address. */ + u64 addr; + /* Slice that @addr belongs to. */ + int id; +}; + struct igen6_imc { int mc; struct mem_ctl_info *mci; @@ -323,6 +360,102 @@ static struct work_struct ecclog_work; #define DID_NVL_H_SKU3 0xd704 #define DID_NVL_H_SKU4 0xd705 +/* Remove the interleave bit and shift upper part down to fill gap. */ +static u64 squeeze_addr(u64 addr, int intlv_bit) +{ + u64 slice_addr; + + slice_addr = GET_BITFIELD(addr, intlv_bit + 1, 63) << intlv_bit; + slice_addr |= GET_BITFIELD(addr, 0, intlv_bit - 1); + + return slice_addr; +} + +/* Shift the upper bits up and insert a zero at the @intlv_bit bit position. */ +static u64 inflate_addr(u64 addr, int intlv_bit) +{ + u64 inflated_addr; + + /* Insert a zero at @intlv_bit position. */ + inflated_addr = GET_BITFIELD(addr, intlv_bit, 63) << (intlv_bit + 1); + inflated_addr |= GET_BITFIELD(addr, 0, intlv_bit - 1); + + return inflated_addr; +} + +static u64 compute_hash(u64 addr, u64 hash_mask, u64 hash_base, int intlv_bit) +{ + u64 hash_addr; + int i; + + /* + * In hash mode, @intlv_bit is the lowest selected bit of @addr + * to be XORed. While @mask may or may not include this @intlv_bit, + * we enforce that @mask includes @intlv_bit to ensure @intlv_bit is + * XORed exactly once. + */ + hash_mask |= BIT_ULL(intlv_bit); + hash_addr = addr & hash_mask; + + for (i = 6; i < 20; i++) + hash_base ^= (hash_addr >> i) & 1; + + return hash_base; +} + +/* + * Converts a higher-level address (system / IMC / channel) into a lower-level + * slice address and identifier. + */ +static void translate_to_lower_level(u64 addr, u64 hash_mask, u64 hash_base, + int intlv_bit, u64 s_size, int l_map, + struct slice *slice) +{ + /* In non-interleave zone. */ + if (addr >= 2 * s_size) { + slice->addr = addr - s_size; + slice->id = l_map; + return; + } + + /* In interleave zone. */ + slice->addr = squeeze_addr(addr, intlv_bit); + + /* Non-hash mode. */ + if (!hash_mask) { + slice->id = GET_BITFIELD(addr, intlv_bit, intlv_bit); + return; + } + + /* Hash mode. */ + slice->id = compute_hash(addr, hash_mask, hash_base, intlv_bit); +} + +/* Reconstruct address for upper memory hierarchy level. */ +static u64 translate_to_upper_level(u64 addr, u64 hash_mask, u64 hash_base, + int intlv_bit, u64 s_size) +{ + u64 inflated_addr, hash_val; + + /* In non-interleave zone. */ + if (addr >= s_size) + return addr + s_size; + + /* + * In interleave zone. + * + * Insert a zero at @intlv_bit position. + */ + inflated_addr = inflate_addr(addr, intlv_bit); + + /* + * Reconstruct the removed interleave bit and use it to replace + * the zero at @intlv_bit position. + */ + hash_val = compute_hash(inflated_addr, hash_mask, hash_base, intlv_bit); + return inflated_addr | (hash_val << intlv_bit); +} + static int get_mchbar(struct pci_dev *pdev, u64 *mchbar) { union { @@ -490,21 +623,9 @@ static u64 mem_addr_to_sys_addr(u64 maddr) return maddr; } -static u64 mem_slice_hash(u64 addr, u64 mask, u64 hash_init) -{ - /* The interleave bit in @addr is a zero. */ - u64 hash_addr = addr & mask, hash = hash_init; - int i; - - for (i = 6; i < 20; i++) - hash ^= (hash_addr >> i) & 1; - - return hash; -} - static u64 tgl_err_addr_to_mem_addr(u64 eaddr, int mc) { - u64 maddr, hash, mask, ms_s_size; + u64 mask, ms_s_size; int intlv_bit; u32 ms_hash; @@ -517,12 +638,7 @@ static u64 tgl_err_addr_to_mem_addr(u64 eaddr, int mc) mask = MEM_SLICE_HASH_MASK(ms_hash); intlv_bit = MEM_SLICE_HASH_LSB_MASK_BIT(ms_hash) + 6; - maddr = GET_BITFIELD(eaddr, intlv_bit, 63) << (intlv_bit + 1) | - GET_BITFIELD(eaddr, 0, intlv_bit - 1); - - hash = mem_slice_hash(maddr, mask, mc); - - return maddr | (hash << intlv_bit); + return translate_to_upper_level(eaddr, mask, mc, intlv_bit, ms_s_size); } static u64 tgl_err_addr_to_sys_addr(u64 eaddr, int mc) @@ -544,8 +660,9 @@ static u64 adl_err_addr_to_sys_addr(u64 eaddr, int mc) static u64 adl_err_addr_to_imc_addr(u64 eaddr, int mc) { - u64 imc_addr, ms_s_size = igen6_pvt->ms_s_size; + u64 ms_s_size = igen6_pvt->ms_s_size; struct igen6_imc *imc = &igen6_pvt->imc[mc]; + struct slice slice; int intlv_bit; u32 mc_hash; @@ -556,10 +673,8 @@ static u64 adl_err_addr_to_imc_addr(u64 eaddr, int mc) intlv_bit = MAC_MC_HASH_LSB(mc_hash) + 6; - imc_addr = GET_BITFIELD(eaddr, intlv_bit + 1, 63) << intlv_bit | - GET_BITFIELD(eaddr, 0, intlv_bit - 1); - - return imc_addr; + translate_to_lower_level(eaddr, 0, 0, intlv_bit, ms_s_size, 0, &slice); + return slice.addr; } static enum mem_type ptl_h_get_mem_type(struct igen6_imc *imc) @@ -998,62 +1113,13 @@ static void set_dimm_params(struct igen6_imc *imc, int chan) imc->dimm_s_size[chan] = MAD_DIMM_CH_DIMM_S_SIZE(val); } -static int decode_chan_idx(u64 addr, u64 mask, int intlv_bit) -{ - u64 hash_addr, hash = 0; - int i; - - /* - * In hash mode, the @intlv_bit is the lowest selected bit of @addr - * to be XORed. While @mask may or may not include this @intlv_bit, - * we enforce that @mask includes @intlv_bit to ensure @intlv_bit is - * XORed exactly once. - */ - mask |= 1 << intlv_bit; - hash_addr = addr & mask; - - for (i = 6; i < 20; i++) - hash ^= (hash_addr >> i) & 1; - - return (int)hash; -} - -static u64 decode_channel_addr(u64 addr, int intlv_bit) -{ - u64 channel_addr; - - /* Remove the interleave bit and shift upper part down to fill gap */ - channel_addr = GET_BITFIELD(addr, intlv_bit + 1, 63) << intlv_bit; - channel_addr |= GET_BITFIELD(addr, 0, intlv_bit - 1); - - return channel_addr; -} - -static void decode_addr(u64 addr, u32 hash, u64 s_size, int l_map, - int *idx, u64 *sub_addr) -{ - int intlv_bit = CHANNEL_HASH_LSB_MASK_BIT(hash) + 6; - - if (addr >= 2 * s_size) { - *sub_addr = addr - s_size; - *idx = l_map; - return; - } - - *sub_addr = decode_channel_addr(addr, intlv_bit); - - if (CHANNEL_HASH_MODE(hash)) - *idx = decode_chan_idx(addr, CHANNEL_HASH_MASK(hash), intlv_bit); - else - *idx = GET_BITFIELD(addr, intlv_bit, intlv_bit); -} - static int igen6_decode(struct decoded_addr *res) { struct igen6_imc *imc = &igen6_pvt->imc[res->mc]; - u64 addr = res->imc_addr, sub_addr, s_size; - int idx, l_map; - u32 hash; + u64 addr = res->imc_addr, s_size; + int intlv_bit, l_map; + u32 hash, hash_mask; + struct slice slice; if (addr >= igen6_tom) { edac_dbg(0, "Address 0x%llx out of range\n", addr); @@ -1064,17 +1130,25 @@ static int igen6_decode(struct decoded_addr *res) hash = readl(imc->window + CHANNEL_HASH_OFFSET); s_size = imc->ch_s_size; l_map = imc->ch_l_map; - decode_addr(addr, hash, s_size, l_map, &idx, &sub_addr); - res->channel_idx = idx; - res->channel_addr = sub_addr; + hash_mask = CHANNEL_HASH_MODE(hash) ? CHANNEL_HASH_MASK(hash) : 0; + intlv_bit = CHANNEL_HASH_LSB_MASK_BIT(hash) + 6; + + translate_to_lower_level(addr, hash_mask, 0, intlv_bit, s_size, l_map, &slice); + + res->channel_idx = slice.id; + res->channel_addr = slice.addr; /* Decode sub-channel/DIMM */ hash = readl(imc->window + CHANNEL_EHASH_OFFSET); - s_size = imc->dimm_s_size[idx]; - l_map = imc->dimm_l_map[idx]; - decode_addr(res->channel_addr, hash, s_size, l_map, &idx, &sub_addr); - res->sub_channel_idx = idx; - res->sub_channel_addr = sub_addr; + s_size = imc->dimm_s_size[res->channel_idx]; + l_map = imc->dimm_l_map[res->channel_idx]; + hash_mask = CHANNEL_HASH_MODE(hash) ? CHANNEL_HASH_MASK(hash) : 0; + intlv_bit = CHANNEL_HASH_LSB_MASK_BIT(hash) + 6; + + translate_to_lower_level(res->channel_addr, hash_mask, 0, intlv_bit, s_size, l_map, &slice); + + res->sub_channel_idx = slice.id; + res->sub_channel_addr = slice.addr; return 0; } From 1713cc6b0e1904cf2c2b477ff25faf163c43cbdf Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Thu, 30 Jul 2026 10:54:54 +0800 Subject: [PATCH 073/641] EDAC/igen6: Add Intel Starfire SoCs support Starfire is a derivative of Panther Lake SoC and shares a similar memory subsystem architecture. Add Starfire compute die ID and reuse Panther Lake's configuration data for EDAC support. Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Tested-by: Jie Wang Link: https://patch.msgid.link/20260730025454.4099934-1-qiuxu.zhuo@intel.com --- drivers/edac/igen6_edac.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/edac/igen6_edac.c b/drivers/edac/igen6_edac.c index 6abc9b203748..776c5db2f598 100644 --- a/drivers/edac/igen6_edac.c +++ b/drivers/edac/igen6_edac.c @@ -351,6 +351,9 @@ static struct work_struct ecclog_work; #define DID_PTL_H_SKU13 0xb02a #define DID_PTL_H_SKU14 0xb00a +/* Starfire */ +#define DID_STF_SKU1 0xb02b + /* Wildcat Lake */ #define DID_WCL_SKU1 0xfd00 @@ -885,6 +888,7 @@ static struct res_config mtl_p_cfg = { .err_addr_to_imc_addr = adl_err_addr_to_imc_addr, }; +/* Shared by Panther Lake-H and Starfire */ static struct res_config ptl_h_cfg = { .machine_check = true, .num_imc = 2, @@ -1013,6 +1017,7 @@ static struct pci_device_id igen6_pci_tbl[] = { { PCI_VDEVICE(INTEL, DID_PTL_H_SKU12), .driver_data = (kernel_ulong_t)&ptl_h_cfg }, { PCI_VDEVICE(INTEL, DID_PTL_H_SKU13), .driver_data = (kernel_ulong_t)&ptl_h_cfg }, { PCI_VDEVICE(INTEL, DID_PTL_H_SKU14), .driver_data = (kernel_ulong_t)&ptl_h_cfg }, + { PCI_VDEVICE(INTEL, DID_STF_SKU1), .driver_data = (kernel_ulong_t)&ptl_h_cfg }, { PCI_VDEVICE(INTEL, DID_NVL_H_SKU1), .driver_data = (kernel_ulong_t)&nvl_h_cfg }, { PCI_VDEVICE(INTEL, DID_NVL_H_SKU2), .driver_data = (kernel_ulong_t)&nvl_h_cfg }, { PCI_VDEVICE(INTEL, DID_NVL_H_SKU3), .driver_data = (kernel_ulong_t)&nvl_h_cfg }, From c8ec4b10864ee5cfd4ea88bca4238bf7df24b10b Mon Sep 17 00:00:00 2001 From: Nabige Aala Date: Mon, 8 Jun 2026 12:31:42 +0530 Subject: [PATCH 074/641] dt-bindings: display: msm: qcm2290: Add Shikra MDSS Shikra reuses the same MDSS/DPU 6.5 hardware as QCM2290. Extend the existing qcm2290 bindings to cover Shikra by adding fallback compatible chains for MDSS, DPU and DSI controller nodes rather than introducing a separate binding file. Signed-off-by: Nabige Aala Reviewed-by: Dmitry Baryshkov Reviewed-by: Krzysztof Kozlowski Reviewed-by: Loic Poulain Patchwork: https://patchwork.freedesktop.org/patch/731152/ Link: https://lore.kernel.org/r/20260608-shikra-display-v4-1-88a846afdd5d@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- .../display/msm/dsi-controller-main.yaml | 4 ++++ .../display/msm/qcom,qcm2290-dpu.yaml | 6 +++++- .../display/msm/qcom,qcm2290-mdss.yaml | 19 ++++++++++++------- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/Documentation/devicetree/bindings/display/msm/dsi-controller-main.yaml b/Documentation/devicetree/bindings/display/msm/dsi-controller-main.yaml index dbc0613e427e..fd0834d09ad6 100644 --- a/Documentation/devicetree/bindings/display/msm/dsi-controller-main.yaml +++ b/Documentation/devicetree/bindings/display/msm/dsi-controller-main.yaml @@ -47,6 +47,10 @@ properties: - qcom,sm8650-dsi-ctrl - qcom,sm8750-dsi-ctrl - const: qcom,mdss-dsi-ctrl + - items: + - const: qcom,shikra-dsi-ctrl + - const: qcom,qcm2290-dsi-ctrl + - const: qcom,mdss-dsi-ctrl - items: - enum: - qcom,qcs8300-dsi-ctrl diff --git a/Documentation/devicetree/bindings/display/msm/qcom,qcm2290-dpu.yaml b/Documentation/devicetree/bindings/display/msm/qcom,qcm2290-dpu.yaml index be6cd8adb3b6..034d3df8d247 100644 --- a/Documentation/devicetree/bindings/display/msm/qcom,qcm2290-dpu.yaml +++ b/Documentation/devicetree/bindings/display/msm/qcom,qcm2290-dpu.yaml @@ -13,7 +13,11 @@ $ref: /schemas/display/msm/dpu-common.yaml# properties: compatible: - const: qcom,qcm2290-dpu + oneOf: + - const: qcom,qcm2290-dpu + - items: + - const: qcom,shikra-dpu + - const: qcom,qcm2290-dpu reg: items: diff --git a/Documentation/devicetree/bindings/display/msm/qcom,qcm2290-mdss.yaml b/Documentation/devicetree/bindings/display/msm/qcom,qcm2290-mdss.yaml index bb09ecd1a5b4..49a7b5c4c678 100644 --- a/Documentation/devicetree/bindings/display/msm/qcom,qcm2290-mdss.yaml +++ b/Documentation/devicetree/bindings/display/msm/qcom,qcm2290-mdss.yaml @@ -4,7 +4,7 @@ $id: http://devicetree.org/schemas/display/msm/qcom,qcm2290-mdss.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Qualcomm QCM220 Display MDSS +title: Qualcomm QCM2290 and Shikra Display MDSS maintainers: - Loic Poulain @@ -12,13 +12,18 @@ maintainers: description: Device tree bindings for MSM Mobile Display Subsystem(MDSS) that encapsulates sub-blocks like DPU display controller and DSI. Device tree bindings of MDSS - are mentioned for QCM2290 target. + are mentioned for QCM2290 and Shikra targets. Shikra uses the same MDSS/DPU/DSI + hardware as QCM2290 (DPU 6.5) and shares the same register layout. $ref: /schemas/display/msm/mdss-common.yaml# properties: compatible: - const: qcom,qcm2290-mdss + oneOf: + - const: qcom,qcm2290-mdss + - items: + - const: qcom,shikra-mdss + - const: qcom,qcm2290-mdss clocks: items: @@ -52,7 +57,8 @@ patternProperties: properties: compatible: - const: qcom,qcm2290-dpu + contains: + const: qcom,qcm2290-dpu "^dsi@[0-9a-f]+$": type: object @@ -60,9 +66,8 @@ patternProperties: properties: compatible: - items: - - const: qcom,qcm2290-dsi-ctrl - - const: qcom,mdss-dsi-ctrl + contains: + const: qcom,qcm2290-dsi-ctrl "^phy@[0-9a-f]+$": type: object From d858770e86e75bf78429b9f5210b7556ef431033 Mon Sep 17 00:00:00 2001 From: Kumar Anurag Date: Tue, 16 Jun 2026 08:12:52 -0700 Subject: [PATCH 075/641] drm/msm/dp: return 0 from audio_prepare when cable is disconnected PipeWire treats a non-zero return from prepare as fatal, marking the DP audio device as a dummy sink when the cable is unplugged. The active_stream_cnt guard already prevents any unclocked hardware access, so return success instead of -EINVAL when the link is not active. Signed-off-by: Kumar Anurag Suggested-by: Srinivas Kandagatla Reviewed-by: Dmitry Baryshkov # same behaviour Patchwork: https://patchwork.freedesktop.org/patch/733663/ Link: https://lore.kernel.org/r/20260616151252.3599089-2-kumar.singh@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_audio.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_audio.c b/drivers/gpu/drm/msm/dp/dp_audio.c index 41018e82efa1..df222943ae57 100644 --- a/drivers/gpu/drm/msm/dp/dp_audio.c +++ b/drivers/gpu/drm/msm/dp/dp_audio.c @@ -284,10 +284,8 @@ int msm_dp_audio_prepare(struct drm_bridge *bridge, * such cases check for connection status and bail out if not * connected. */ - if (!msm_dp_display->power_on) { - rc = -EINVAL; + if (!msm_dp_display->power_on) goto end; - } audio = msm_dp_audio_get_data(msm_dp_display); if (IS_ERR(audio)) { From 4f2b89b0341126ddddcfcf8a5a9895d226930e73 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Thu, 18 Jun 2026 16:11:32 +0200 Subject: [PATCH 076/641] drm/msm: Do not declare msm_framebuffer_init() as static Declare msm_framebuffer_init() in msm_drv.h and remove the static qualifier. The function will be required in msm_fbdev.c after inlining msm_alloc_stolen_fb(). Also move msm_framebuffer_init() before msm_framebuffer_create(), so that it can later be made static again. Prepares msm's fbdev emulation for using client buffers. Signed-off-by: Thomas Zimmermann Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/733883/ Link: https://lore.kernel.org/r/20260618141249.151338-2-tzimmermann@suse.de Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/msm_drv.h | 4 ++ drivers/gpu/drm/msm/msm_fb.c | 72 +++++++++++++++++------------------ 2 files changed, 39 insertions(+), 37 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h index 3787db8770ad..7dccf0713b02 100644 --- a/drivers/gpu/drm/msm/msm_drv.h +++ b/drivers/gpu/drm/msm/msm_drv.h @@ -258,6 +258,10 @@ void msm_framebuffer_cleanup(struct drm_framebuffer *fb, bool needed_dirtyfb); uint32_t msm_framebuffer_iova(struct drm_framebuffer *fb, int plane); struct drm_gem_object *msm_framebuffer_bo(struct drm_framebuffer *fb, int plane); const struct msm_format *msm_framebuffer_format(struct drm_framebuffer *fb); +struct drm_framebuffer *msm_framebuffer_init(struct drm_device *dev, + const struct drm_format_info *info, + const struct drm_mode_fb_cmd2 *mode_cmd, + struct drm_gem_object **bos); struct drm_framebuffer *msm_framebuffer_create(struct drm_device *dev, struct drm_file *file, const struct drm_format_info *info, const struct drm_mode_fb_cmd2 *mode_cmd); diff --git a/drivers/gpu/drm/msm/msm_fb.c b/drivers/gpu/drm/msm/msm_fb.c index ee17a47f31c4..c98710569a2e 100644 --- a/drivers/gpu/drm/msm/msm_fb.c +++ b/drivers/gpu/drm/msm/msm_fb.c @@ -29,10 +29,6 @@ struct msm_framebuffer { }; #define to_msm_framebuffer(x) container_of(x, struct msm_framebuffer, base) -static struct drm_framebuffer *msm_framebuffer_init(struct drm_device *dev, - const struct drm_format_info *info, - const struct drm_mode_fb_cmd2 *mode_cmd, struct drm_gem_object **bos); - static int msm_framebuffer_dirtyfb(struct drm_framebuffer *fb, struct drm_file *file_priv, unsigned int flags, unsigned int color, struct drm_clip_rect *clips, @@ -139,39 +135,10 @@ const struct msm_format *msm_framebuffer_format(struct drm_framebuffer *fb) return msm_fb->format; } -struct drm_framebuffer *msm_framebuffer_create(struct drm_device *dev, - struct drm_file *file, const struct drm_format_info *info, - const struct drm_mode_fb_cmd2 *mode_cmd) -{ - struct drm_gem_object *bos[4] = {0}; - struct drm_framebuffer *fb; - int ret, i, n = info->num_planes; - - for (i = 0; i < n; i++) { - bos[i] = drm_gem_object_lookup(file, mode_cmd->handles[i]); - if (!bos[i]) { - ret = -ENXIO; - goto out_unref; - } - } - - fb = msm_framebuffer_init(dev, info, mode_cmd, bos); - if (IS_ERR(fb)) { - ret = PTR_ERR(fb); - goto out_unref; - } - - return fb; - -out_unref: - for (i = 0; i < n; i++) - drm_gem_object_put(bos[i]); - return ERR_PTR(ret); -} - -static struct drm_framebuffer *msm_framebuffer_init(struct drm_device *dev, - const struct drm_format_info *info, - const struct drm_mode_fb_cmd2 *mode_cmd, struct drm_gem_object **bos) +struct drm_framebuffer *msm_framebuffer_init(struct drm_device *dev, + const struct drm_format_info *info, + const struct drm_mode_fb_cmd2 *mode_cmd, + struct drm_gem_object **bos) { struct msm_drm_private *priv = dev->dev_private; struct msm_kms *kms = priv->kms; @@ -251,6 +218,37 @@ fail: return ERR_PTR(ret); } +struct drm_framebuffer *msm_framebuffer_create(struct drm_device *dev, + struct drm_file *file, + const struct drm_format_info *info, + const struct drm_mode_fb_cmd2 *mode_cmd) +{ + struct drm_gem_object *bos[4] = {0}; + struct drm_framebuffer *fb; + int ret, i, n = info->num_planes; + + for (i = 0; i < n; i++) { + bos[i] = drm_gem_object_lookup(file, mode_cmd->handles[i]); + if (!bos[i]) { + ret = -ENXIO; + goto out_unref; + } + } + + fb = msm_framebuffer_init(dev, info, mode_cmd, bos); + if (IS_ERR(fb)) { + ret = PTR_ERR(fb); + goto out_unref; + } + + return fb; + +out_unref: + for (i = 0; i < n; i++) + drm_gem_object_put(bos[i]); + return ERR_PTR(ret); +} + struct drm_framebuffer * msm_alloc_stolen_fb(struct drm_device *dev, int w, int h, int p, uint32_t format) { From 0cfd468c995644a512c3fe82f391814f7ea30e77 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Thu, 18 Jun 2026 16:11:33 +0200 Subject: [PATCH 077/641] drm/msm: fbdev: Inline msm_alloc_stolen_fb() Inline msm_alloc_stolen_fb() into its only caller. This is necessary for converting fbdev emulation to use client buffers. There are some minor changes: - Handle errors for the non-stolen BO in the respective branch. - Fill mode_cmd right before using it with msm_framebuffer_init(). Both will later be replaced with client-buffer interfaces. - Set the modifier[0] to DRM_FORMAT_MOD_LINEAR. No functional change. - Integrate the error handling with the existing clean-up. Signed-off-by: Thomas Zimmermann Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/733884/ Link: https://lore.kernel.org/r/20260618141249.151338-3-tzimmermann@suse.de Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/msm_drv.h | 2 -- drivers/gpu/drm/msm/msm_fb.c | 46 --------------------------------- drivers/gpu/drm/msm/msm_fbdev.c | 46 ++++++++++++++++++++++++++------- 3 files changed, 37 insertions(+), 57 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h index 7dccf0713b02..01a64a32b843 100644 --- a/drivers/gpu/drm/msm/msm_drv.h +++ b/drivers/gpu/drm/msm/msm_drv.h @@ -265,8 +265,6 @@ struct drm_framebuffer *msm_framebuffer_init(struct drm_device *dev, struct drm_framebuffer *msm_framebuffer_create(struct drm_device *dev, struct drm_file *file, const struct drm_format_info *info, const struct drm_mode_fb_cmd2 *mode_cmd); -struct drm_framebuffer * msm_alloc_stolen_fb(struct drm_device *dev, - int w, int h, int p, uint32_t format); #ifdef CONFIG_DRM_MSM_KMS_FBDEV int msm_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, diff --git a/drivers/gpu/drm/msm/msm_fb.c b/drivers/gpu/drm/msm/msm_fb.c index c98710569a2e..dec550e6cf75 100644 --- a/drivers/gpu/drm/msm/msm_fb.c +++ b/drivers/gpu/drm/msm/msm_fb.c @@ -6,7 +6,6 @@ #include #include -#include #include #include #include @@ -248,48 +247,3 @@ out_unref: drm_gem_object_put(bos[i]); return ERR_PTR(ret); } - -struct drm_framebuffer * -msm_alloc_stolen_fb(struct drm_device *dev, int w, int h, int p, uint32_t format) -{ - struct drm_mode_fb_cmd2 mode_cmd = { - .pixel_format = format, - .width = w, - .height = h, - .pitches = { p }, - }; - struct drm_gem_object *bo; - struct drm_framebuffer *fb; - int size; - - /* allocate backing bo */ - size = mode_cmd.pitches[0] * mode_cmd.height; - DBG("allocating %d bytes for fb %d", size, dev->primary->index); - bo = msm_gem_new(dev, size, MSM_BO_SCANOUT | MSM_BO_WC | MSM_BO_STOLEN, NULL); - if (IS_ERR(bo)) { - dev_warn(dev->dev, "could not allocate stolen bo\n"); - /* try regular bo: */ - bo = msm_gem_new(dev, size, MSM_BO_SCANOUT | MSM_BO_WC, NULL); - } - if (IS_ERR(bo)) { - DRM_DEV_ERROR(dev->dev, "failed to allocate buffer object\n"); - return ERR_CAST(bo); - } - - msm_gem_object_set_name(bo, "stolenfb"); - - fb = msm_framebuffer_init(dev, - drm_get_format_info(dev, mode_cmd.pixel_format, - mode_cmd.modifier[0]), - &mode_cmd, &bo); - if (IS_ERR(fb)) { - DRM_DEV_ERROR(dev->dev, "failed to allocate fb\n"); - /* note: if fb creation failed, we can't rely on fb destroy - * to unref the bo: - */ - drm_gem_object_put(bo); - return ERR_CAST(fb); - } - - return fb; -} diff --git a/drivers/gpu/drm/msm/msm_fbdev.c b/drivers/gpu/drm/msm/msm_fbdev.c index fd19995b12b5..f834a595e55e 100644 --- a/drivers/gpu/drm/msm/msm_fbdev.c +++ b/drivers/gpu/drm/msm/msm_fbdev.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -92,11 +93,13 @@ int msm_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, struct drm_device *dev = helper->dev; struct msm_drm_private *priv = dev->dev_private; struct fb_info *fbi = helper->info; + struct drm_mode_fb_cmd2 mode_cmd = { }; struct drm_framebuffer *fb = NULL; struct drm_gem_object *bo; uint64_t paddr; uint32_t format; int ret, pitch; + int size; format = drm_mode_legacy_fb_format(sizes->surface_bpp, sizes->surface_depth); @@ -105,15 +108,38 @@ int msm_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, sizes->fb_width, sizes->fb_height); pitch = align_pitch(sizes->surface_width, sizes->surface_bpp); - fb = msm_alloc_stolen_fb(dev, sizes->surface_width, - sizes->surface_height, pitch, format); - if (IS_ERR(fb)) { - DRM_DEV_ERROR(dev->dev, "failed to allocate fb\n"); - return PTR_ERR(fb); + /* allocate backing bo */ + size = pitch * sizes->surface_height; + DBG("allocating %d bytes for fb %d", size, dev->primary->index); + bo = msm_gem_new(dev, size, MSM_BO_SCANOUT | MSM_BO_WC | MSM_BO_STOLEN, NULL); + if (IS_ERR(bo)) { + dev_warn(dev->dev, "could not allocate stolen bo\n"); + /* try regular bo: */ + bo = msm_gem_new(dev, size, MSM_BO_SCANOUT | MSM_BO_WC, NULL); + if (IS_ERR(bo)) { + DRM_DEV_ERROR(dev->dev, "failed to allocate buffer object\n"); + return PTR_ERR(bo); + } } - bo = msm_framebuffer_bo(fb, 0); + msm_gem_object_set_name(bo, "stolenfb"); + + mode_cmd.pixel_format = format; + mode_cmd.width = sizes->surface_width; + mode_cmd.height = sizes->surface_height; + mode_cmd.pitches[0] = pitch; + mode_cmd.modifier[0] = DRM_FORMAT_MOD_LINEAR; + + fb = msm_framebuffer_init(dev, + drm_get_format_info(dev, mode_cmd.pixel_format, + mode_cmd.modifier[0]), + &mode_cmd, &bo); + if (IS_ERR(fb)) { + DRM_DEV_ERROR(dev->dev, "failed to allocate fb\n"); + ret = PTR_ERR(fb); + goto err_drm_gem_object_put; + } /* * NOTE: if we can be guaranteed to be able to map buffer @@ -123,7 +149,7 @@ int msm_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, ret = msm_gem_get_and_pin_iova(bo, priv->kms->vm, &paddr); if (ret) { DRM_DEV_ERROR(dev->dev, "failed to get buffer obj iova: %d\n", ret); - goto fail; + goto err_drm_framebuffer_remove; } DBG("fbi=%p, dev=%p", fbi, dev); @@ -138,7 +164,7 @@ int msm_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, fbi->screen_buffer = msm_gem_get_vaddr(bo); if (IS_ERR(fbi->screen_buffer)) { ret = PTR_ERR(fbi->screen_buffer); - goto fail; + goto err_drm_framebuffer_remove; } fbi->screen_size = bo->size; fbi->fix.smem_start = paddr; @@ -149,7 +175,9 @@ int msm_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, return 0; -fail: +err_drm_framebuffer_remove: drm_framebuffer_remove(fb); +err_drm_gem_object_put: + drm_gem_object_put(bo); return ret; } From e78d1bc9e4d062d1c95cd262ffe0c6fd66c40e1f Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Thu, 18 Jun 2026 16:11:34 +0200 Subject: [PATCH 078/641] drm/msm: fbdev: Fix error reporting Replace deprecated error reporting in msm_fbdev_driver_fbdev_probe(). Use drm_warn() and drm_err() instead. Signed-off-by: Thomas Zimmermann Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/733887/ Link: https://lore.kernel.org/r/20260618141249.151338-4-tzimmermann@suse.de Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/msm_fbdev.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_fbdev.c b/drivers/gpu/drm/msm/msm_fbdev.c index f834a595e55e..6af08b7b8082 100644 --- a/drivers/gpu/drm/msm/msm_fbdev.c +++ b/drivers/gpu/drm/msm/msm_fbdev.c @@ -114,11 +114,11 @@ int msm_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, DBG("allocating %d bytes for fb %d", size, dev->primary->index); bo = msm_gem_new(dev, size, MSM_BO_SCANOUT | MSM_BO_WC | MSM_BO_STOLEN, NULL); if (IS_ERR(bo)) { - dev_warn(dev->dev, "could not allocate stolen bo\n"); + drm_warn(dev, "could not allocate stolen bo\n"); /* try regular bo: */ bo = msm_gem_new(dev, size, MSM_BO_SCANOUT | MSM_BO_WC, NULL); if (IS_ERR(bo)) { - DRM_DEV_ERROR(dev->dev, "failed to allocate buffer object\n"); + drm_err(dev, "failed to allocate buffer object\n"); return PTR_ERR(bo); } } @@ -136,7 +136,7 @@ int msm_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, mode_cmd.modifier[0]), &mode_cmd, &bo); if (IS_ERR(fb)) { - DRM_DEV_ERROR(dev->dev, "failed to allocate fb\n"); + drm_err(dev, "failed to allocate fb\n"); ret = PTR_ERR(fb); goto err_drm_gem_object_put; } @@ -148,7 +148,7 @@ int msm_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, */ ret = msm_gem_get_and_pin_iova(bo, priv->kms->vm, &paddr); if (ret) { - DRM_DEV_ERROR(dev->dev, "failed to get buffer obj iova: %d\n", ret); + drm_err(dev, "failed to get buffer obj iova: %d\n", ret); goto err_drm_framebuffer_remove; } From 6cf3fb14f33ea73ee4a05fe4c406a6623c72d353 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Thu, 18 Jun 2026 16:11:35 +0200 Subject: [PATCH 079/641] drm/msm: fbdev: Calculate buffer geometry with format helpers Replace the geometry and size calculation in msm's fbdev emulation with DRM format helpers. This consists of a 4CC lookup from the fbdev parameters, format lookup, pitch calculation and size calculation. Then allocate the GEM buffer object for the framebuffer memory from the calculated size. Explicitly align the size of the allocated GEM buffer object to full pages. The contained memory is the framebuffer memory as seen by fbdev. The page alignment is required for mmap. v2: - clarify the page alignment of the buffer size (Dmitry) Signed-off-by: Thomas Zimmermann Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/733885/ Link: https://lore.kernel.org/r/20260618141249.151338-5-tzimmermann@suse.de Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/msm_drv.h | 7 ------- drivers/gpu/drm/msm/msm_fbdev.c | 26 ++++++++++++-------------- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h index 01a64a32b843..b21f8971dfa1 100644 --- a/drivers/gpu/drm/msm/msm_drv.h +++ b/drivers/gpu/drm/msm/msm_drv.h @@ -505,13 +505,6 @@ void msm_hrtimer_work_init(struct msm_hrtimer_work *work, #define DBG(fmt, ...) DRM_DEBUG_DRIVER(fmt"\n", ##__VA_ARGS__) #define VERB(fmt, ...) if (0) DRM_DEBUG_DRIVER(fmt"\n", ##__VA_ARGS__) -static inline int align_pitch(int width, int bpp) -{ - int bytespp = (bpp + 7) / 8; - /* adreno needs pitch aligned to 32 pixels: */ - return bytespp * ALIGN(width, 32); -} - /* for the generated headers: */ #define INVALID_IDX(idx) ({BUG(); 0;}) #define fui(x) ({BUG(); 0;}) diff --git a/drivers/gpu/drm/msm/msm_fbdev.c b/drivers/gpu/drm/msm/msm_fbdev.c index 6af08b7b8082..35b26830afb4 100644 --- a/drivers/gpu/drm/msm/msm_fbdev.c +++ b/drivers/gpu/drm/msm/msm_fbdev.c @@ -95,23 +95,25 @@ int msm_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, struct fb_info *fbi = helper->info; struct drm_mode_fb_cmd2 mode_cmd = { }; struct drm_framebuffer *fb = NULL; + const struct drm_format_info *format; + u32 fourcc, pitch; + u64 size; struct drm_gem_object *bo; uint64_t paddr; - uint32_t format; - int ret, pitch; - int size; - - format = drm_mode_legacy_fb_format(sizes->surface_bpp, sizes->surface_depth); + int ret; DBG("create fbdev: %dx%d@%d (%dx%d)", sizes->surface_width, sizes->surface_height, sizes->surface_bpp, sizes->fb_width, sizes->fb_height); - pitch = align_pitch(sizes->surface_width, sizes->surface_bpp); + fourcc = drm_mode_legacy_fb_format(sizes->surface_bpp, sizes->surface_depth); + format = drm_get_format_info(dev, fourcc, DRM_FORMAT_MOD_LINEAR); + /* adreno needs pitch aligned to 32 pixels: */ + pitch = drm_format_info_min_pitch(format, 0, ALIGN(sizes->surface_width, 32)); + size = ALIGN(pitch * sizes->surface_height, PAGE_SIZE); /* allocate backing bo */ - size = pitch * sizes->surface_height; - DBG("allocating %d bytes for fb %d", size, dev->primary->index); + DBG("allocating %llu bytes for fb %d", size, dev->primary->index); bo = msm_gem_new(dev, size, MSM_BO_SCANOUT | MSM_BO_WC | MSM_BO_STOLEN, NULL); if (IS_ERR(bo)) { drm_warn(dev, "could not allocate stolen bo\n"); @@ -125,16 +127,12 @@ int msm_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, msm_gem_object_set_name(bo, "stolenfb"); - mode_cmd.pixel_format = format; + mode_cmd.pixel_format = fourcc; mode_cmd.width = sizes->surface_width; mode_cmd.height = sizes->surface_height; mode_cmd.pitches[0] = pitch; - mode_cmd.modifier[0] = DRM_FORMAT_MOD_LINEAR; - fb = msm_framebuffer_init(dev, - drm_get_format_info(dev, mode_cmd.pixel_format, - mode_cmd.modifier[0]), - &mode_cmd, &bo); + fb = msm_framebuffer_init(dev, format, &mode_cmd, &bo); if (IS_ERR(fb)) { drm_err(dev, "failed to allocate fb\n"); ret = PTR_ERR(fb); From eb714953c44122fa4d9cb74cf98f22b46d3a5941 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Thu, 18 Jun 2026 16:11:36 +0200 Subject: [PATCH 080/641] drm/msm: fbdev: Use a DRM client buffer Replace the internal DRM framebuffer with a DRM client buffer. The client buffer allocates the DRM framebuffer on a file and also uses GEM object handles via the regular ADDFB2 interfaces. Using client-buffer interfaces unifies framebuffer allocation for DRM clients in user space and msm's internal fbdev emulation. It also simplifies the clean-up side of the fbdev emulation. Signed-off-by: Thomas Zimmermann Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/733891/ Link: https://lore.kernel.org/r/20260618141249.151338-6-tzimmermann@suse.de [DB: fixed error handling in msm_fbdev_driver_fbdev_probe] Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/msm_fbdev.c | 56 +++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_fbdev.c b/drivers/gpu/drm/msm/msm_fbdev.c index 35b26830afb4..89ca9da3e1f2 100644 --- a/drivers/gpu/drm/msm/msm_fbdev.c +++ b/drivers/gpu/drm/msm/msm_fbdev.c @@ -41,17 +41,15 @@ static int msm_fbdev_mmap(struct fb_info *info, struct vm_area_struct *vma) static void msm_fbdev_fb_destroy(struct fb_info *info) { struct drm_fb_helper *helper = (struct drm_fb_helper *)info->par; - struct drm_framebuffer *fb = helper->fb; - struct drm_gem_object *bo = msm_framebuffer_bo(fb, 0); + struct drm_gem_object *bo = msm_framebuffer_bo(helper->fb, 0); DBG(); drm_fb_helper_fini(helper); - /* this will free the backing object */ msm_gem_put_vaddr(bo); - drm_framebuffer_remove(fb); + drm_client_buffer_delete(helper->buffer); drm_client_release(&helper->client); } @@ -90,15 +88,16 @@ static const struct drm_fb_helper_funcs msm_fbdev_helper_funcs = { int msm_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, struct drm_fb_helper_surface_size *sizes) { - struct drm_device *dev = helper->dev; + struct drm_client_dev *client = &helper->client; + struct drm_device *dev = client->dev; + struct drm_file *file = client->file; struct msm_drm_private *priv = dev->dev_private; struct fb_info *fbi = helper->info; - struct drm_mode_fb_cmd2 mode_cmd = { }; - struct drm_framebuffer *fb = NULL; const struct drm_format_info *format; - u32 fourcc, pitch; + u32 fourcc, pitch, handle; u64 size; struct drm_gem_object *bo; + struct drm_client_buffer *buffer; uint64_t paddr; int ret; @@ -127,16 +126,15 @@ int msm_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, msm_gem_object_set_name(bo, "stolenfb"); - mode_cmd.pixel_format = fourcc; - mode_cmd.width = sizes->surface_width; - mode_cmd.height = sizes->surface_height; - mode_cmd.pitches[0] = pitch; - - fb = msm_framebuffer_init(dev, format, &mode_cmd, &bo); - if (IS_ERR(fb)) { - drm_err(dev, "failed to allocate fb\n"); - ret = PTR_ERR(fb); + ret = drm_gem_handle_create(file, bo, &handle); + if (ret) goto err_drm_gem_object_put; + + buffer = drm_client_buffer_create(client, sizes->surface_width, sizes->surface_height, + fourcc, handle, pitch); + if (IS_ERR(buffer)) { + ret = PTR_ERR(buffer); + goto err_drm_gem_handle_delete; } /* @@ -147,13 +145,14 @@ int msm_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, ret = msm_gem_get_and_pin_iova(bo, priv->kms->vm, &paddr); if (ret) { drm_err(dev, "failed to get buffer obj iova: %d\n", ret); - goto err_drm_framebuffer_remove; + goto err_drm_client_buffer_delete; } DBG("fbi=%p, dev=%p", fbi, dev); helper->funcs = &msm_fbdev_helper_funcs; - helper->fb = fb; + helper->buffer = buffer; + helper->fb = buffer->fb; fbi->fbops = &msm_fb_ops; @@ -162,19 +161,30 @@ int msm_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, fbi->screen_buffer = msm_gem_get_vaddr(bo); if (IS_ERR(fbi->screen_buffer)) { ret = PTR_ERR(fbi->screen_buffer); - goto err_drm_framebuffer_remove; + goto err_msm_gem_unpin; } fbi->screen_size = bo->size; fbi->fix.smem_start = paddr; fbi->fix.smem_len = bo->size; DBG("par=%p, %dx%d", fbi->par, fbi->var.xres, fbi->var.yres); - DBG("allocated %dx%d fb", fb->width, fb->height); + DBG("allocated %dx%d fb", buffer->fb->width, buffer->fb->height); + + /* The handle is only needed for creating the framebuffer. */ + drm_gem_handle_delete(file, handle); + + /* The framebuffer still holds a reference on the GEM object. */ + drm_gem_object_put(bo); return 0; -err_drm_framebuffer_remove: - drm_framebuffer_remove(fb); +err_msm_gem_unpin: + msm_gem_unpin_iova(bo, priv->kms->vm); + msm_gem_vma_put(bo); +err_drm_client_buffer_delete: + drm_client_buffer_delete(buffer); +err_drm_gem_handle_delete: + drm_gem_handle_delete(file, handle); err_drm_gem_object_put: drm_gem_object_put(bo); return ret; From 1a2bfb1cf7dff01646657606f5a8bddb8b3ea868 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Thu, 18 Jun 2026 16:11:37 +0200 Subject: [PATCH 081/641] drm/msm: Make msm_framebuffer_init() an internal interface again The only caller of msm_framebuffer_init() is msm_framebuffer_create() from the same source file. Declare the former as static. Signed-off-by: Thomas Zimmermann Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/733889/ Link: https://lore.kernel.org/r/20260618141249.151338-7-tzimmermann@suse.de Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/msm_drv.h | 4 ---- drivers/gpu/drm/msm/msm_fb.c | 8 ++++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h index b21f8971dfa1..2a3ce6afca1c 100644 --- a/drivers/gpu/drm/msm/msm_drv.h +++ b/drivers/gpu/drm/msm/msm_drv.h @@ -258,10 +258,6 @@ void msm_framebuffer_cleanup(struct drm_framebuffer *fb, bool needed_dirtyfb); uint32_t msm_framebuffer_iova(struct drm_framebuffer *fb, int plane); struct drm_gem_object *msm_framebuffer_bo(struct drm_framebuffer *fb, int plane); const struct msm_format *msm_framebuffer_format(struct drm_framebuffer *fb); -struct drm_framebuffer *msm_framebuffer_init(struct drm_device *dev, - const struct drm_format_info *info, - const struct drm_mode_fb_cmd2 *mode_cmd, - struct drm_gem_object **bos); struct drm_framebuffer *msm_framebuffer_create(struct drm_device *dev, struct drm_file *file, const struct drm_format_info *info, const struct drm_mode_fb_cmd2 *mode_cmd); diff --git a/drivers/gpu/drm/msm/msm_fb.c b/drivers/gpu/drm/msm/msm_fb.c index dec550e6cf75..60c108d35d2a 100644 --- a/drivers/gpu/drm/msm/msm_fb.c +++ b/drivers/gpu/drm/msm/msm_fb.c @@ -134,10 +134,10 @@ const struct msm_format *msm_framebuffer_format(struct drm_framebuffer *fb) return msm_fb->format; } -struct drm_framebuffer *msm_framebuffer_init(struct drm_device *dev, - const struct drm_format_info *info, - const struct drm_mode_fb_cmd2 *mode_cmd, - struct drm_gem_object **bos) +static struct drm_framebuffer * +msm_framebuffer_init(struct drm_device *dev, const struct drm_format_info *info, + const struct drm_mode_fb_cmd2 *mode_cmd, + struct drm_gem_object **bos) { struct msm_drm_private *priv = dev->dev_private; struct msm_kms *kms = priv->kms; From eecba818a962b74f569cef5f388f41d7a889122f Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 2 Jul 2026 11:36:40 +0200 Subject: [PATCH 082/641] drm/msm/hdmi_bridge: Simplify register bit updates Simplify reister updates (read, apply mask, write) with a wrapper to make code more obvious and avoid possible errors of reading and writing to different registers. Reviewed-by: Dmitry Baryshkov Signed-off-by: Krzysztof Kozlowski Patchwork: https://patchwork.freedesktop.org/patch/737201/ Link: https://lore.kernel.org/r/20260702-drm-msm-hdmi-cleanup-v2-1-a4a4f0e8895b@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/hdmi/hdmi.h | 19 +++++ drivers/gpu/drm/msm/hdmi/hdmi_bridge.c | 98 +++++++++++--------------- 2 files changed, 60 insertions(+), 57 deletions(-) diff --git a/drivers/gpu/drm/msm/hdmi/hdmi.h b/drivers/gpu/drm/msm/hdmi/hdmi.h index 49433f7727c3..436d4f9fe346 100644 --- a/drivers/gpu/drm/msm/hdmi/hdmi.h +++ b/drivers/gpu/drm/msm/hdmi/hdmi.h @@ -112,6 +112,25 @@ static inline u32 hdmi_read(struct hdmi *hdmi, u32 reg) return readl(hdmi->mmio + reg); } +static inline void hdmi_clear_bits(struct hdmi *hdmi, u32 reg, u32 mask) +{ + u32 val; + + val = hdmi_read(hdmi, reg); + val &= ~mask; + hdmi_write(hdmi, reg, val); +} + +static inline void hdmi_update_bits(struct hdmi *hdmi, u32 reg, u32 mask, u32 data) +{ + u32 val; + + val = hdmi_read(hdmi, reg); + val &= ~mask; + val |= data & mask; + hdmi_write(hdmi, reg, val); +} + static inline u32 hdmi_qfprom_read(struct hdmi *hdmi, u32 reg) { return readl(hdmi->qfprom_mmio + reg); diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c b/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c index 7abb9243dba5..157f19bd90b4 100644 --- a/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c +++ b/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c @@ -58,16 +58,13 @@ static int msm_hdmi_bridge_clear_avi_infoframe(struct drm_bridge *bridge) { struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge); struct hdmi *hdmi = hdmi_bridge->hdmi; - u32 val; - val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL0); - val &= ~(HDMI_INFOFRAME_CTRL0_AVI_SEND | - HDMI_INFOFRAME_CTRL0_AVI_CONT); - hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL0, val); + hdmi_clear_bits(hdmi, REG_HDMI_INFOFRAME_CTRL0, + HDMI_INFOFRAME_CTRL0_AVI_SEND | + HDMI_INFOFRAME_CTRL0_AVI_CONT); - val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL1); - val &= ~HDMI_INFOFRAME_CTRL1_AVI_INFO_LINE__MASK; - hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL1, val); + hdmi_clear_bits(hdmi, REG_HDMI_INFOFRAME_CTRL1, + HDMI_INFOFRAME_CTRL1_AVI_INFO_LINE__MASK); return 0; } @@ -76,18 +73,15 @@ static int msm_hdmi_bridge_clear_audio_infoframe(struct drm_bridge *bridge) { struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge); struct hdmi *hdmi = hdmi_bridge->hdmi; - u32 val; - val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL0); - val &= ~(HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SEND | - HDMI_INFOFRAME_CTRL0_AUDIO_INFO_CONT | - HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SOURCE | - HDMI_INFOFRAME_CTRL0_AUDIO_INFO_UPDATE); - hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL0, val); + hdmi_clear_bits(hdmi, REG_HDMI_INFOFRAME_CTRL0, + HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SEND | + HDMI_INFOFRAME_CTRL0_AUDIO_INFO_CONT | + HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SOURCE | + HDMI_INFOFRAME_CTRL0_AUDIO_INFO_UPDATE); - val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL1); - val &= ~HDMI_INFOFRAME_CTRL1_AUDIO_INFO_LINE__MASK; - hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL1, val); + hdmi_clear_bits(hdmi, REG_HDMI_INFOFRAME_CTRL1, + HDMI_INFOFRAME_CTRL1_AUDIO_INFO_LINE__MASK); return 0; } @@ -96,13 +90,11 @@ static int msm_hdmi_bridge_clear_spd_infoframe(struct drm_bridge *bridge) { struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge); struct hdmi *hdmi = hdmi_bridge->hdmi; - u32 val; - val = hdmi_read(hdmi, REG_HDMI_GEN_PKT_CTRL); - val &= ~(HDMI_GEN_PKT_CTRL_GENERIC1_SEND | - HDMI_GEN_PKT_CTRL_GENERIC1_CONT | - HDMI_GEN_PKT_CTRL_GENERIC1_LINE__MASK); - hdmi_write(hdmi, REG_HDMI_GEN_PKT_CTRL, val); + hdmi_clear_bits(hdmi, REG_HDMI_GEN_PKT_CTRL, + HDMI_GEN_PKT_CTRL_GENERIC1_SEND | + HDMI_GEN_PKT_CTRL_GENERIC1_CONT | + HDMI_GEN_PKT_CTRL_GENERIC1_LINE__MASK); return 0; } @@ -111,14 +103,12 @@ static int msm_hdmi_bridge_clear_hdmi_infoframe(struct drm_bridge *bridge) { struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge); struct hdmi *hdmi = hdmi_bridge->hdmi; - u32 val; - val = hdmi_read(hdmi, REG_HDMI_GEN_PKT_CTRL); - val &= ~(HDMI_GEN_PKT_CTRL_GENERIC0_SEND | - HDMI_GEN_PKT_CTRL_GENERIC0_CONT | - HDMI_GEN_PKT_CTRL_GENERIC0_UPDATE | - HDMI_GEN_PKT_CTRL_GENERIC0_LINE__MASK); - hdmi_write(hdmi, REG_HDMI_GEN_PKT_CTRL, val); + hdmi_clear_bits(hdmi, REG_HDMI_GEN_PKT_CTRL, + HDMI_GEN_PKT_CTRL_GENERIC0_SEND | + HDMI_GEN_PKT_CTRL_GENERIC0_CONT | + HDMI_GEN_PKT_CTRL_GENERIC0_UPDATE | + HDMI_GEN_PKT_CTRL_GENERIC0_LINE__MASK); return 0; } @@ -129,7 +119,6 @@ static int msm_hdmi_bridge_write_avi_infoframe(struct drm_bridge *bridge, struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge); struct hdmi *hdmi = hdmi_bridge->hdmi; u32 buf[4] = {}; - u32 val; int i; if (len != HDMI_INFOFRAME_SIZE(AVI) || len - 3 > sizeof(buf)) { @@ -153,15 +142,13 @@ static int msm_hdmi_bridge_write_avi_infoframe(struct drm_bridge *bridge, for (i = 0; i < ARRAY_SIZE(buf); i++) hdmi_write(hdmi, REG_HDMI_AVI_INFO(i), buf[i]); - val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL0); - val |= HDMI_INFOFRAME_CTRL0_AVI_SEND | - HDMI_INFOFRAME_CTRL0_AVI_CONT; - hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL0, val); + hdmi_update_bits(hdmi, REG_HDMI_INFOFRAME_CTRL0, + HDMI_INFOFRAME_CTRL0_AVI_SEND | HDMI_INFOFRAME_CTRL0_AVI_CONT, + HDMI_INFOFRAME_CTRL0_AVI_SEND | HDMI_INFOFRAME_CTRL0_AVI_CONT); - val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL1); - val &= ~HDMI_INFOFRAME_CTRL1_AVI_INFO_LINE__MASK; - val |= HDMI_INFOFRAME_CTRL1_AVI_INFO_LINE(AVI_IFRAME_LINE_NUMBER); - hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL1, val); + hdmi_update_bits(hdmi, REG_HDMI_INFOFRAME_CTRL1, + HDMI_INFOFRAME_CTRL1_AVI_INFO_LINE__MASK, + HDMI_INFOFRAME_CTRL1_AVI_INFO_LINE(AVI_IFRAME_LINE_NUMBER)); return 0; } @@ -193,12 +180,11 @@ static int msm_hdmi_bridge_write_audio_infoframe(struct drm_bridge *bridge, buffer[9] << 16 | buffer[10] << 24); - val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL0); - val |= HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SEND | - HDMI_INFOFRAME_CTRL0_AUDIO_INFO_CONT | - HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SOURCE | - HDMI_INFOFRAME_CTRL0_AUDIO_INFO_UPDATE; - hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL0, val); + val = HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SEND | + HDMI_INFOFRAME_CTRL0_AUDIO_INFO_CONT | + HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SOURCE | + HDMI_INFOFRAME_CTRL0_AUDIO_INFO_UPDATE; + hdmi_update_bits(hdmi, REG_HDMI_INFOFRAME_CTRL0, val, val); return 0; } @@ -231,11 +217,10 @@ static int msm_hdmi_bridge_write_spd_infoframe(struct drm_bridge *bridge, for (i = 0; i < ARRAY_SIZE(buf); i++) hdmi_write(hdmi, REG_HDMI_GENERIC1(i), buf[i]); - val = hdmi_read(hdmi, REG_HDMI_GEN_PKT_CTRL); - val |= HDMI_GEN_PKT_CTRL_GENERIC1_SEND | - HDMI_GEN_PKT_CTRL_GENERIC1_CONT | - HDMI_GEN_PKT_CTRL_GENERIC1_LINE(SPD_IFRAME_LINE_NUMBER); - hdmi_write(hdmi, REG_HDMI_GEN_PKT_CTRL, val); + val = HDMI_GEN_PKT_CTRL_GENERIC1_SEND | + HDMI_GEN_PKT_CTRL_GENERIC1_CONT | + HDMI_GEN_PKT_CTRL_GENERIC1_LINE(SPD_IFRAME_LINE_NUMBER); + hdmi_update_bits(hdmi, REG_HDMI_GEN_PKT_CTRL, val, val); return 0; } @@ -269,12 +254,11 @@ static int msm_hdmi_bridge_write_hdmi_infoframe(struct drm_bridge *bridge, for (i = 0; i < ARRAY_SIZE(buf); i++) hdmi_write(hdmi, REG_HDMI_GENERIC0(i), buf[i]); - val = hdmi_read(hdmi, REG_HDMI_GEN_PKT_CTRL); - val |= HDMI_GEN_PKT_CTRL_GENERIC0_SEND | - HDMI_GEN_PKT_CTRL_GENERIC0_CONT | - HDMI_GEN_PKT_CTRL_GENERIC0_UPDATE | - HDMI_GEN_PKT_CTRL_GENERIC0_LINE(VENSPEC_IFRAME_LINE_NUMBER); - hdmi_write(hdmi, REG_HDMI_GEN_PKT_CTRL, val); + val = HDMI_GEN_PKT_CTRL_GENERIC0_SEND | + HDMI_GEN_PKT_CTRL_GENERIC0_CONT | + HDMI_GEN_PKT_CTRL_GENERIC0_UPDATE | + HDMI_GEN_PKT_CTRL_GENERIC0_LINE(VENSPEC_IFRAME_LINE_NUMBER); + hdmi_update_bits(hdmi, REG_HDMI_GEN_PKT_CTRL, val, val); return 0; } From c5f6b1abca25ab00f9ad99bfeff5c45b1557ad2c Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 2 Jul 2026 11:36:41 +0200 Subject: [PATCH 083/641] drm/msm/hdmi_hdcp: Simplify register bit updates Simplify reister updates (read, apply mask, write) with a wrapper to make code more obvious and avoid possible errors of reading and writing to different registers. Reviewed-by: Dmitry Baryshkov Signed-off-by: Krzysztof Kozlowski Patchwork: https://patchwork.freedesktop.org/patch/737203/ Link: https://lore.kernel.org/r/20260702-drm-msm-hdmi-cleanup-v2-2-a4a4f0e8895b@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c | 85 +++++++++------------------- 1 file changed, 28 insertions(+), 57 deletions(-) diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c b/drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c index 8fb5497aac9f..7862bd67d154 100644 --- a/drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c +++ b/drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c @@ -306,9 +306,9 @@ static int msm_reset_hdcp_ddc_failures(struct hdmi_hdcp_ctrl *hdcp_ctrl) HDMI_HDCP_DDC_CTRL_0_DISABLE); /* ACK the Failure to Clear it */ - reg_val = hdmi_read(hdmi, REG_HDMI_HDCP_DDC_CTRL_1); - reg_val |= HDMI_HDCP_DDC_CTRL_1_FAILED_ACK; - hdmi_write(hdmi, REG_HDMI_HDCP_DDC_CTRL_1, reg_val); + hdmi_update_bits(hdmi, REG_HDMI_HDCP_DDC_CTRL_1, + HDMI_HDCP_DDC_CTRL_1_FAILED_ACK, + HDMI_HDCP_DDC_CTRL_1_FAILED_ACK); /* Check if the FAILURE got Cleared */ reg_val = hdmi_read(hdmi, REG_HDMI_HDCP_DDC_STATUS); @@ -324,28 +324,22 @@ static int msm_reset_hdcp_ddc_failures(struct hdmi_hdcp_ctrl *hdcp_ctrl) DBG("Before: HDMI_DDC_SW_STATUS=0x%08x", hdmi_read(hdmi, REG_HDMI_DDC_SW_STATUS)); /* Reset HDMI DDC software status */ - reg_val = hdmi_read(hdmi, REG_HDMI_DDC_CTRL); - reg_val |= HDMI_DDC_CTRL_SW_STATUS_RESET; - hdmi_write(hdmi, REG_HDMI_DDC_CTRL, reg_val); + hdmi_update_bits(hdmi, REG_HDMI_DDC_CTRL, HDMI_DDC_CTRL_SW_STATUS_RESET, + HDMI_DDC_CTRL_SW_STATUS_RESET); rc = msm_hdmi_hdcp_msleep(hdcp_ctrl, 20, AUTH_ABORT_EV); - reg_val = hdmi_read(hdmi, REG_HDMI_DDC_CTRL); - reg_val &= ~HDMI_DDC_CTRL_SW_STATUS_RESET; - hdmi_write(hdmi, REG_HDMI_DDC_CTRL, reg_val); + hdmi_clear_bits(hdmi, REG_HDMI_DDC_CTRL, HDMI_DDC_CTRL_SW_STATUS_RESET); /* Reset HDMI DDC Controller */ - reg_val = hdmi_read(hdmi, REG_HDMI_DDC_CTRL); - reg_val |= HDMI_DDC_CTRL_SOFT_RESET; - hdmi_write(hdmi, REG_HDMI_DDC_CTRL, reg_val); + hdmi_update_bits(hdmi, REG_HDMI_DDC_CTRL, HDMI_DDC_CTRL_SOFT_RESET, + HDMI_DDC_CTRL_SOFT_RESET); /* If previous msleep is aborted, skip this msleep */ if (!rc) rc = msm_hdmi_hdcp_msleep(hdcp_ctrl, 20, AUTH_ABORT_EV); - reg_val = hdmi_read(hdmi, REG_HDMI_DDC_CTRL); - reg_val &= ~HDMI_DDC_CTRL_SOFT_RESET; - hdmi_write(hdmi, REG_HDMI_DDC_CTRL, reg_val); + hdmi_clear_bits(hdmi, REG_HDMI_DDC_CTRL, HDMI_DDC_CTRL_SOFT_RESET); DBG("After: HDMI_DDC_SW_STATUS=0x%08x", hdmi_read(hdmi, REG_HDMI_DDC_SW_STATUS)); } @@ -399,7 +393,6 @@ static void msm_hdmi_hdcp_reauth_work(struct work_struct *work) struct hdmi_hdcp_ctrl, hdcp_reauth_work); struct hdmi *hdmi = hdcp_ctrl->hdmi; unsigned long flags; - u32 reg_val; DBG("HDCP REAUTH WORK"); /* @@ -409,9 +402,7 @@ static void msm_hdmi_hdcp_reauth_work(struct work_struct *work) * AN1_READY bits in HDMI_HDCP_LINK0_STATUS register */ spin_lock_irqsave(&hdmi->reg_lock, flags); - reg_val = hdmi_read(hdmi, REG_HDMI_HPD_CTRL); - reg_val &= ~HDMI_HPD_CTRL_ENABLE; - hdmi_write(hdmi, REG_HDMI_HPD_CTRL, reg_val); + hdmi_clear_bits(hdmi, REG_HDMI_HPD_CTRL, HDMI_HPD_CTRL_ENABLE); /* Disable HDCP interrupts */ hdmi_write(hdmi, REG_HDMI_HDCP_INT_CTRL, 0); @@ -431,9 +422,8 @@ static void msm_hdmi_hdcp_reauth_work(struct work_struct *work) /* Enable HPD circuitry */ spin_lock_irqsave(&hdmi->reg_lock, flags); - reg_val = hdmi_read(hdmi, REG_HDMI_HPD_CTRL); - reg_val |= HDMI_HPD_CTRL_ENABLE; - hdmi_write(hdmi, REG_HDMI_HPD_CTRL, reg_val); + hdmi_update_bits(hdmi, REG_HDMI_HPD_CTRL, HDMI_HPD_CTRL_ENABLE, + HDMI_HPD_CTRL_ENABLE); spin_unlock_irqrestore(&hdmi->reg_lock, flags); /* @@ -456,7 +446,6 @@ static int msm_hdmi_hdcp_auth_prepare(struct hdmi_hdcp_ctrl *hdcp_ctrl) { struct hdmi *hdmi = hdcp_ctrl->hdmi; u32 link0_status; - u32 reg_val; unsigned long flags; int rc; @@ -472,14 +461,11 @@ static int msm_hdmi_hdcp_auth_prepare(struct hdmi_hdcp_ctrl *hdcp_ctrl) spin_lock_irqsave(&hdmi->reg_lock, flags); /* disable HDMI Encrypt */ - reg_val = hdmi_read(hdmi, REG_HDMI_CTRL); - reg_val &= ~HDMI_CTRL_ENCRYPTED; - hdmi_write(hdmi, REG_HDMI_CTRL, reg_val); + hdmi_clear_bits(hdmi, REG_HDMI_CTRL, HDMI_CTRL_ENCRYPTED); /* Enabling Software DDC */ - reg_val = hdmi_read(hdmi, REG_HDMI_DDC_ARBITRATION); - reg_val &= ~HDMI_DDC_ARBITRATION_HW_ARBITRATION; - hdmi_write(hdmi, REG_HDMI_DDC_ARBITRATION, reg_val); + hdmi_clear_bits(hdmi, REG_HDMI_DDC_ARBITRATION, + HDMI_DDC_ARBITRATION_HW_ARBITRATION); spin_unlock_irqrestore(&hdmi->reg_lock, flags); /* @@ -498,9 +484,8 @@ static int msm_hdmi_hdcp_auth_prepare(struct hdmi_hdcp_ctrl *hdcp_ctrl) hdmi_write(hdmi, REG_HDMI_HDCP_ENTROPY_CTRL1, 0xF00DFACE); /* Disable the RngCipher state */ - reg_val = hdmi_read(hdmi, REG_HDMI_HDCP_DEBUG_CTRL); - reg_val &= ~HDMI_HDCP_DEBUG_CTRL_RNG_CIPHER; - hdmi_write(hdmi, REG_HDMI_HDCP_DEBUG_CTRL, reg_val); + hdmi_clear_bits(hdmi, REG_HDMI_HDCP_DEBUG_CTRL, + HDMI_HDCP_DEBUG_CTRL_RNG_CIPHER); DBG("HDCP_DEBUG_CTRL=0x%08x", hdmi_read(hdmi, REG_HDMI_HDCP_DEBUG_CTRL)); @@ -537,15 +522,12 @@ static int msm_hdmi_hdcp_auth_prepare(struct hdmi_hdcp_ctrl *hdcp_ctrl) static void msm_hdmi_hdcp_auth_fail(struct hdmi_hdcp_ctrl *hdcp_ctrl) { struct hdmi *hdmi = hdcp_ctrl->hdmi; - u32 reg_val; unsigned long flags; DBG("hdcp auth failed, queue reauth work"); /* clear HDMI Encrypt */ spin_lock_irqsave(&hdmi->reg_lock, flags); - reg_val = hdmi_read(hdmi, REG_HDMI_CTRL); - reg_val &= ~HDMI_CTRL_ENCRYPTED; - hdmi_write(hdmi, REG_HDMI_CTRL, reg_val); + hdmi_clear_bits(hdmi, REG_HDMI_CTRL, HDMI_CTRL_ENCRYPTED); spin_unlock_irqrestore(&hdmi->reg_lock, flags); hdcp_ctrl->hdcp_state = HDCP_STATE_AUTH_FAILED; @@ -555,7 +537,6 @@ static void msm_hdmi_hdcp_auth_fail(struct hdmi_hdcp_ctrl *hdcp_ctrl) static void msm_hdmi_hdcp_auth_done(struct hdmi_hdcp_ctrl *hdcp_ctrl) { struct hdmi *hdmi = hdcp_ctrl->hdmi; - u32 reg_val; unsigned long flags; /* @@ -563,16 +544,15 @@ static void msm_hdmi_hdcp_auth_done(struct hdmi_hdcp_ctrl *hdcp_ctrl) * there is no Arbitration between software and hardware for DDC */ spin_lock_irqsave(&hdmi->reg_lock, flags); - reg_val = hdmi_read(hdmi, REG_HDMI_DDC_ARBITRATION); - reg_val |= HDMI_DDC_ARBITRATION_HW_ARBITRATION; - hdmi_write(hdmi, REG_HDMI_DDC_ARBITRATION, reg_val); + hdmi_update_bits(hdmi, REG_HDMI_DDC_ARBITRATION, + HDMI_DDC_ARBITRATION_HW_ARBITRATION, + HDMI_DDC_ARBITRATION_HW_ARBITRATION); spin_unlock_irqrestore(&hdmi->reg_lock, flags); /* enable HDMI Encrypt */ spin_lock_irqsave(&hdmi->reg_lock, flags); - reg_val = hdmi_read(hdmi, REG_HDMI_CTRL); - reg_val |= HDMI_CTRL_ENCRYPTED; - hdmi_write(hdmi, REG_HDMI_CTRL, reg_val); + hdmi_update_bits(hdmi, REG_HDMI_CTRL, HDMI_CTRL_ENCRYPTED, + HDMI_CTRL_ENCRYPTED); spin_unlock_irqrestore(&hdmi->reg_lock, flags); hdcp_ctrl->hdcp_state = HDCP_STATE_AUTHENTICATED; @@ -1304,7 +1284,6 @@ end: void msm_hdmi_hdcp_on(struct hdmi_hdcp_ctrl *hdcp_ctrl) { struct hdmi *hdmi = hdcp_ctrl->hdmi; - u32 reg_val; unsigned long flags; if ((HDCP_STATE_INACTIVE != hdcp_ctrl->hdcp_state) || @@ -1315,9 +1294,7 @@ void msm_hdmi_hdcp_on(struct hdmi_hdcp_ctrl *hdcp_ctrl) /* clear HDMI Encrypt */ spin_lock_irqsave(&hdmi->reg_lock, flags); - reg_val = hdmi_read(hdmi, REG_HDMI_CTRL); - reg_val &= ~HDMI_CTRL_ENCRYPTED; - hdmi_write(hdmi, REG_HDMI_CTRL, reg_val); + hdmi_clear_bits(hdmi, REG_HDMI_CTRL, HDMI_CTRL_ENCRYPTED); spin_unlock_irqrestore(&hdmi->reg_lock, flags); hdcp_ctrl->auth_event = 0; @@ -1330,7 +1307,6 @@ void msm_hdmi_hdcp_off(struct hdmi_hdcp_ctrl *hdcp_ctrl) { struct hdmi *hdmi = hdcp_ctrl->hdmi; unsigned long flags; - u32 reg_val; if ((HDCP_STATE_INACTIVE == hdcp_ctrl->hdcp_state) || (HDCP_STATE_NO_AKSV == hdcp_ctrl->hdcp_state)) { @@ -1345,9 +1321,7 @@ void msm_hdmi_hdcp_off(struct hdmi_hdcp_ctrl *hdcp_ctrl) * AN1_READY bits in HDMI_HDCP_LINK0_STATUS register */ spin_lock_irqsave(&hdmi->reg_lock, flags); - reg_val = hdmi_read(hdmi, REG_HDMI_HPD_CTRL); - reg_val &= ~HDMI_HPD_CTRL_ENABLE; - hdmi_write(hdmi, REG_HDMI_HPD_CTRL, reg_val); + hdmi_clear_bits(hdmi, REG_HDMI_HPD_CTRL, HDMI_HPD_CTRL_ENABLE); /* * Disable HDCP interrupts. @@ -1375,14 +1349,11 @@ void msm_hdmi_hdcp_off(struct hdmi_hdcp_ctrl *hdcp_ctrl) hdmi_write(hdmi, REG_HDMI_HDCP_CTRL, 0); spin_lock_irqsave(&hdmi->reg_lock, flags); - reg_val = hdmi_read(hdmi, REG_HDMI_CTRL); - reg_val &= ~HDMI_CTRL_ENCRYPTED; - hdmi_write(hdmi, REG_HDMI_CTRL, reg_val); + hdmi_clear_bits(hdmi, REG_HDMI_CTRL, HDMI_CTRL_ENCRYPTED); /* Enable HPD circuitry */ - reg_val = hdmi_read(hdmi, REG_HDMI_HPD_CTRL); - reg_val |= HDMI_HPD_CTRL_ENABLE; - hdmi_write(hdmi, REG_HDMI_HPD_CTRL, reg_val); + hdmi_update_bits(hdmi, REG_HDMI_HPD_CTRL, HDMI_HPD_CTRL_ENABLE, + HDMI_HPD_CTRL_ENABLE); spin_unlock_irqrestore(&hdmi->reg_lock, flags); hdcp_ctrl->hdcp_state = HDCP_STATE_INACTIVE; From 8834f5494ab5e169b1c0fb4cbcb5464fd81d1ed0 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Mon, 20 Jul 2026 15:56:08 +0800 Subject: [PATCH 084/641] drm/msm/dpu: Fix DMA SSPP REC block offsets on DPU v13 On DPU v13, the DMA SSPP REC0 and REC1 blocks are located at offsets 0x1000 and 0x3000 from the SSPP common base. The existing DMA SSPP sub-block descriptor does not initialize sspp_rec0_blk and sspp_rec1_blk, causing REC register accesses to be performed at offset 0 instead of the corresponding REC block. As a result, DMA SSPP pipes are not programmed correctly and fail to produce output. Introduce a DPU v13 specific DMA SSPP descriptor with the correct REC block offsets and use it for all DMA SSPPs in the Kaanapali catalog. Signed-off-by: Yongxing Mou Fixes: 83fe2cd56b1d ("drm/msm/dpu: Add support for Kaanapali DPU") Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/741230/ Link: https://lore.kernel.org/r/20260720-dpu-v13-dma-sspp-rec-fix-v1-1-10d69b4875e7@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- .../drm/msm/disp/dpu1/catalog/dpu_13_0_kaanapali.h | 12 ++++++------ drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.c | 12 ++++++++++++ 2 files changed, 18 insertions(+), 6 deletions(-) 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 06da1583fb1e..85f455a9f29c 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 @@ -86,42 +86,42 @@ static const struct dpu_sspp_cfg kaanapali_sspp[] = { .name = "sspp_8", .id = SSPP_DMA0, .base = 0x97000, .len = 0x84, .features = DMA_SDM845_MASK_SDMA, - .sblk = &dpu_dma_sblk, + .sblk = &dpu_dma_sblk_v13, .xin_id = 1, .type = SSPP_TYPE_DMA, }, { .name = "sspp_9", .id = SSPP_DMA1, .base = 0xa0000, .len = 0x84, .features = DMA_SDM845_MASK_SDMA, - .sblk = &dpu_dma_sblk, + .sblk = &dpu_dma_sblk_v13, .xin_id = 5, .type = SSPP_TYPE_DMA, }, { .name = "sspp_10", .id = SSPP_DMA2, .base = 0xa9000, .len = 0x84, .features = DMA_SDM845_MASK_SDMA, - .sblk = &dpu_dma_sblk, + .sblk = &dpu_dma_sblk_v13, .xin_id = 9, .type = SSPP_TYPE_DMA, }, { .name = "sspp_11", .id = SSPP_DMA3, .base = 0xb2000, .len = 0x84, .features = DMA_SDM845_MASK_SDMA, - .sblk = &dpu_dma_sblk, + .sblk = &dpu_dma_sblk_v13, .xin_id = 13, .type = SSPP_TYPE_DMA, }, { .name = "sspp_12", .id = SSPP_DMA4, .base = 0xbb000, .len = 0x84, .features = DMA_CURSOR_SDM845_MASK_SDMA, - .sblk = &dpu_dma_sblk, + .sblk = &dpu_dma_sblk_v13, .xin_id = 14, .type = SSPP_TYPE_DMA, }, { .name = "sspp_13", .id = SSPP_DMA5, .base = 0xc4000, .len = 0x84, .features = DMA_CURSOR_SDM845_MASK_SDMA, - .sblk = &dpu_dma_sblk, + .sblk = &dpu_dma_sblk_v13, .xin_id = 15, .type = SSPP_TYPE_DMA, }, diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.c index 2e10add84fd7..9a993cdfab85 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.c @@ -303,6 +303,16 @@ static const u32 wb2_formats_rgb_yuv[] = { .num_formats = ARRAY_SIZE(plane_formats), \ } +#define _DMA_SBLK_V13() \ + { \ + .sspp_rec0_blk = {.name = "sspp_rec0", \ + .base = 0x1000, .len = 0x180,}, \ + .sspp_rec1_blk = {.name = "sspp_rec1", \ + .base = 0x3000, .len = 0x180,}, \ + .format_list = plane_formats, \ + .num_formats = ARRAY_SIZE(plane_formats), \ + } + static const struct dpu_rotation_cfg dpu_rot_sc7280_cfg_v2 = { .rot_maxheight = 1088, .rot_num_formats = ARRAY_SIZE(rotation_v2_formats), @@ -353,6 +363,8 @@ static const struct dpu_sspp_sub_blks dpu_rgb_sblk = _RGB_SBLK(); static const struct dpu_sspp_sub_blks dpu_dma_sblk = _DMA_SBLK(); +static const struct dpu_sspp_sub_blks dpu_dma_sblk_v13 = _DMA_SBLK_V13(); + /************************************************************* * MIXER sub blocks config *************************************************************/ From 93c125e4ea98fb25f927ba5a334d85845127d667 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Thu, 23 Jul 2026 12:52:12 +0300 Subject: [PATCH 085/641] drm/msm: don't tear down KMS twice when KMS init fails When priv->kms_init() (mdp4_kms_init() / mdp5_kms_init()) fails partway through, both display drivers already tear their KMS state down via mdp4_destroy() / mdp5_kms_destroy() before returning the error. The common error path in msm_drm_init() then runs msm_drm_uninit() -> msm_drm_kms_uninit(), which tries to destroy the very same KMS a second time, which causes a use-after-free crash. Bring MDP4/MDP5 in line with the DPU driver whose dpu_kms_init() doesn't perform error cleanup on the failure. Let the common path own the cleanup, instead of freeing the KMS from their error paths. The crash trace for the reference: __lock_acquire from lock_acquire (kernel/locking/lockdep.c:5906 kernel/locking/lockdep.c:5863) lock_acquire from touch_wq_lockdep_map (kernel/workqueue.c:4094 (discriminator 1)) touch_wq_lockdep_map from __flush_workqueue (kernel/workqueue.c:4136) __flush_workqueue from msm_drm_kms_uninit (drivers/gpu/drm/msm/msm_kms.c:243 (discriminator 33)) msm_drm_kms_uninit from msm_drm_uninit (drivers/gpu/drm/msm/msm_drv.c:93) msm_drm_uninit from msm_drm_init (drivers/gpu/drm/msm/msm_drv.c:184) msm_drm_init from try_to_bring_up_aggregate_device (drivers/base/component.c:249 drivers/base/component.c:227) try_to_bring_up_aggregate_device from __component_add (drivers/base/component.c:269 drivers/base/component.c:748) __component_add from dsi_host_attach (drivers/gpu/drm/msm/dsi/dsi_host.c:1739) dsi_host_attach from mipi_dsi_attach (drivers/gpu/drm/drm_mipi_dsi.c:383) mipi_dsi_attach from sharp_nt_panel_probe (drivers/gpu/drm/panel/panel-sharp-ls043t1le01.c:247) Fixes: 506efcba3129 ("drm/msm: carve out KMS code from msm_drv.c") Signed-off-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Patchwork: https://patchwork.freedesktop.org/patch/742068/ Link: https://lore.kernel.org/r/20260723-msm-fix-crash-v1-1-78fb4721c2d9@oss.qualcomm.com --- drivers/gpu/drm/msm/disp/mdp4/mdp4_kms.c | 22 ++++++++-------------- drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c | 11 +++-------- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/drivers/gpu/drm/msm/disp/mdp4/mdp4_kms.c b/drivers/gpu/drm/msm/disp/mdp4/mdp4_kms.c index c289dff78cd5..9b1d1982e683 100644 --- a/drivers/gpu/drm/msm/disp/mdp4/mdp4_kms.c +++ b/drivers/gpu/drm/msm/disp/mdp4/mdp4_kms.c @@ -398,7 +398,7 @@ static int mdp4_kms_init(struct drm_device *dev) ret = mdp_kms_init(&mdp4_kms->base, &kms_funcs); if (ret) { DRM_DEV_ERROR(dev->dev, "failed to init kms\n"); - goto fail; + return ret; } kms = priv->kms; @@ -409,7 +409,7 @@ static int mdp4_kms_init(struct drm_device *dev) ret = regulator_enable(mdp4_kms->vdd); if (ret) { DRM_DEV_ERROR(dev->dev, "failed to enable regulator vdd: %d\n", ret); - goto fail; + return ret; } } @@ -421,7 +421,7 @@ static int mdp4_kms_init(struct drm_device *dev) DRM_DEV_ERROR(dev->dev, "unexpected MDP version: v%d.%d\n", major, minor); ret = -ENXIO; - goto fail; + return ret; } mdp4_kms->rev = minor; @@ -430,7 +430,7 @@ static int mdp4_kms_init(struct drm_device *dev) if (!mdp4_kms->lut_clk) { DRM_DEV_ERROR(dev->dev, "failed to get lut_clk\n"); ret = -ENODEV; - goto fail; + return ret; } clk_set_rate(mdp4_kms->lut_clk, max_clk); } @@ -452,7 +452,7 @@ static int mdp4_kms_init(struct drm_device *dev) vm = msm_kms_init_vm(mdp4_kms->dev, NULL); if (IS_ERR(vm)) { ret = PTR_ERR(vm); - goto fail; + return ret; } kms->vm = vm; @@ -460,7 +460,7 @@ static int mdp4_kms_init(struct drm_device *dev) ret = modeset_init(mdp4_kms); if (ret) { DRM_DEV_ERROR(dev->dev, "modeset_init failed: %d\n", ret); - goto fail; + return ret; } mdp4_kms->blank_cursor_bo = msm_gem_new(dev, SZ_16K, MSM_BO_WC | MSM_BO_SCANOUT, NULL); @@ -468,14 +468,14 @@ static int mdp4_kms_init(struct drm_device *dev) ret = PTR_ERR(mdp4_kms->blank_cursor_bo); DRM_DEV_ERROR(dev->dev, "could not allocate blank-cursor bo: %d\n", ret); mdp4_kms->blank_cursor_bo = NULL; - goto fail; + return ret; } ret = msm_gem_get_and_pin_iova(mdp4_kms->blank_cursor_bo, kms->vm, &mdp4_kms->blank_cursor_iova); if (ret) { DRM_DEV_ERROR(dev->dev, "could not pin blank-cursor bo: %d\n", ret); - goto fail; + return ret; } dev->mode_config.min_width = 0; @@ -484,12 +484,6 @@ static int mdp4_kms_init(struct drm_device *dev) dev->mode_config.max_height = 2048; return 0; - -fail: - if (kms) - mdp4_destroy(kms); - - return ret; } static const struct dev_pm_ops mdp4_pm_ops = { diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c index 0a004ab9fc85..3934cd060b27 100644 --- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c @@ -517,7 +517,7 @@ static int mdp5_kms_init(struct drm_device *dev) ret = mdp_kms_init(&mdp5_kms->base, &kms_funcs); if (ret) { DRM_DEV_ERROR(&pdev->dev, "failed to init kms\n"); - goto fail; + return ret; } config = mdp5_cfg_get_config(mdp5_kms->cfg); @@ -540,7 +540,7 @@ static int mdp5_kms_init(struct drm_device *dev) vm = msm_kms_init_vm(mdp5_kms->dev, pdev->dev.parent); if (IS_ERR(vm)) { ret = PTR_ERR(vm); - goto fail; + return ret; } kms->vm = vm; @@ -550,7 +550,7 @@ static int mdp5_kms_init(struct drm_device *dev) ret = modeset_init(mdp5_kms); if (ret) { DRM_DEV_ERROR(&pdev->dev, "modeset_init failed: %d\n", ret); - goto fail; + return ret; } dev->mode_config.min_width = 0; @@ -562,11 +562,6 @@ static int mdp5_kms_init(struct drm_device *dev) dev->vblank_disable_immediate = true; return 0; -fail: - if (kms) - mdp5_kms_destroy(kms); - - return ret; } static void mdp5_destroy(struct mdp5_kms *mdp5_kms) From 684f95fb4e9ad10aac39fbb1fa7592a59d7f54ea Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Wed, 22 Jul 2026 09:36:16 +0300 Subject: [PATCH 086/641] drm/msm/dp: reject YUV420-only modes without VSC SDP support DP conveys YUV 420 colorimetry through a VSC SDP. A sink that advertises a mode as YUV-420-only therefore cannot be driven at all unless the panel supports VSC SDP, yet msm_dp_bridge_mode_valid() only used the VSC SDP capability to decide whether to halve the pixel clock, otherwise letting such modes through to be validated (and possibly accepted) at the full RGB clock the sink cannot display. Reject 420-only modes with MODE_NO_420 when the panel does not support VSC SDP. With those modes filtered out, being a 420-only mode implies VSC SDP support, so the YUV-420 test reduces to drm_mode_is_420_only(): drop msm_dp_is_yuv_420_enabled() and call the DRM helper directly at its two callers (the DPU encoder already has the connector from the atomic state). Fixes: df9cf852ca30 ("drm/msm/dp: account for widebus and yuv420 during mode validation") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/741713/ Link: https://lore.kernel.org/r/20260722-drm-msm-display-interface-v1-1-368c10fe62fd@oss.qualcomm.com --- drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c | 3 +-- drivers/gpu/drm/msm/dp/dp_display.c | 28 +++++++++------------ drivers/gpu/drm/msm/msm_drv.h | 8 ------ 3 files changed, 13 insertions(+), 26 deletions(-) diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c index 778e231d4967..1f20695f81e3 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c @@ -710,8 +710,7 @@ void dpu_encoder_update_topology(struct drm_encoder *drm_enc, if (fb && MSM_FORMAT_IS_YUV(msm_framebuffer_format(fb))) topology->num_cdm++; } else if (disp_info->intf_type == INTF_DP) { - if (msm_dp_is_yuv_420_enabled(priv->kms->dp[disp_info->h_tile_instance[0]], - adj_mode)) + if (drm_mode_is_420_only(&connector->display_info, adj_mode)) topology->num_cdm++; } } diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index dc6f33809ca5..e0c44eef3aba 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -698,6 +698,7 @@ enum drm_mode_status msm_dp_bridge_mode_valid(struct drm_bridge *bridge, u32 mode_rate_khz = 0, supported_rate_khz = 0, mode_bpp = 0; struct msm_dp *dp; int mode_pclk_khz = mode->clock; + bool is_yuv_420; dp = to_dp_bridge(bridge)->msm_dp_display; @@ -709,9 +710,16 @@ enum drm_mode_status msm_dp_bridge_mode_valid(struct drm_bridge *bridge, msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); link_info = &msm_dp_display->panel->link_info; - if ((drm_mode_is_420_only(&dp->connector->display_info, mode) && - msm_dp_display->panel->vsc_sdp_supported) || - msm_dp_wide_bus_available(dp)) + is_yuv_420 = drm_mode_is_420_only(&dp->connector->display_info, mode); + + /* + * YUV 420 is carried over DP by signalling the colorimetry through a + * VSC SDP, so a 420-only mode cannot be driven without VSC SDP support. + */ + if (is_yuv_420 && !msm_dp_display->panel->vsc_sdp_supported) + return MODE_NO_420; + + if (is_yuv_420 || msm_dp_wide_bus_available(dp)) mode_pclk_khz /= 2; if (mode_pclk_khz > DP_MAX_PIXEL_CLK_KHZ) @@ -1277,22 +1285,10 @@ void __exit msm_dp_unregister(void) platform_driver_unregister(&msm_dp_display_driver); } -bool msm_dp_is_yuv_420_enabled(const struct msm_dp *msm_dp_display, - const struct drm_display_mode *mode) -{ - struct msm_dp_display_private *dp; - const struct drm_display_info *info; - - dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); - info = &msm_dp_display->connector->display_info; - - return dp->panel->vsc_sdp_supported && drm_mode_is_420_only(info, mode); -} - bool msm_dp_needs_periph_flush(const struct msm_dp *msm_dp_display, const struct drm_display_mode *mode) { - return msm_dp_is_yuv_420_enabled(msm_dp_display, mode); + return drm_mode_is_420_only(&msm_dp_display->connector->display_info, mode); } bool msm_dp_wide_bus_available(const struct msm_dp *msm_dp_display) diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h index 2a3ce6afca1c..eb4bbae8557b 100644 --- a/drivers/gpu/drm/msm/msm_drv.h +++ b/drivers/gpu/drm/msm/msm_drv.h @@ -354,8 +354,6 @@ void __exit msm_dp_unregister(void); int msm_dp_modeset_init(struct msm_dp *dp_display, struct drm_device *dev, struct drm_encoder *encoder, bool yuv_supported); void msm_dp_snapshot(struct msm_disp_state *disp_state, struct msm_dp *dp_display); -bool msm_dp_is_yuv_420_enabled(const struct msm_dp *dp_display, - const struct drm_display_mode *mode); bool msm_dp_needs_periph_flush(const struct msm_dp *dp_display, const struct drm_display_mode *mode); bool msm_dp_wide_bus_available(const struct msm_dp *dp_display); @@ -380,12 +378,6 @@ static inline void msm_dp_snapshot(struct msm_disp_state *disp_state, struct msm { } -static inline bool msm_dp_is_yuv_420_enabled(const struct msm_dp *dp_display, - const struct drm_display_mode *mode) -{ - return false; -} - static inline bool msm_dp_needs_periph_flush(const struct msm_dp *dp_display, const struct drm_display_mode *mode) { From bd926e62d355879133452bc3889447f8e89757f2 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Wed, 22 Jul 2026 09:36:30 +0300 Subject: [PATCH 087/641] drm/msm/dp: do not reject wide-bus modes while a YUV420 mode is active msm_dp_bridge_mode_valid() halves the candidate mode's pixel clock when the sink either uses YUV 420 output or drives the wide bus, so that modes relying on those to stay under DP_MAX_PIXEL_CLK_KHZ are accepted. The wide bus part is queried through msm_dp_wide_bus_available(), which returns false whenever the currently committed mode uses YUV 420 output: it inspects the stored msm_dp_mode.out_fmt_is_yuv_420 of the active mode, not the mode being validated. Consequently, while a YUV 420 mode is active, an RGB mode that needs the wide bus to fit under DP_MAX_PIXEL_CLK_KHZ has its pixel clock left un-halved and is wrongly rejected as MODE_CLOCK_HIGH. The candidate mode's YUV 420 status is already evaluated as is_yuv_420, and the wide bus is disabled precisely for YUV 420 output, so halving the pixel clock for either case is equivalent to halving it when the candidate is YUV 420 or the controller supports the wide bus. Test wide_bus_supported directly, so the decision no longer depends on the format of the active mode. Fixes: df9cf852ca30 ("drm/msm/dp: account for widebus and yuv420 during mode validation") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/741740/ Link: https://lore.kernel.org/r/20260722-drm-msm-display-interface-v1-15-368c10fe62fd@oss.qualcomm.com --- drivers/gpu/drm/msm/dp/dp_display.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index e0c44eef3aba..79e2b171e269 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -719,7 +719,7 @@ enum drm_mode_status msm_dp_bridge_mode_valid(struct drm_bridge *bridge, if (is_yuv_420 && !msm_dp_display->panel->vsc_sdp_supported) return MODE_NO_420; - if (is_yuv_420 || msm_dp_wide_bus_available(dp)) + if (is_yuv_420 || msm_dp_display->wide_bus_supported) mode_pclk_khz /= 2; if (mode_pclk_khz > DP_MAX_PIXEL_CLK_KHZ) From db6f98b9515f09cc345949b1002d6d71c0bef473 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 28 Jul 2026 18:21:30 +0800 Subject: [PATCH 088/641] drm/msm/dp: remove cached drm_edid from panel The cached drm_edid seems unnecessary here. Use the drm_edid pointer directly in the plug stage instead of caching it. Remove the cached drm_edid and the corresponding oneliner to simplify the code. Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/742727/ Link: https://lore.kernel.org/r/20260728-dp_mstclean-v9-1-f7779fce10f4@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_display.c | 28 +++++++++----- drivers/gpu/drm/msm/dp/dp_panel.c | 59 +++-------------------------- drivers/gpu/drm/msm/dp/dp_panel.h | 13 ++----- 3 files changed, 27 insertions(+), 73 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 79e2b171e269..ba3804a12a57 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -269,6 +269,7 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp) const struct drm_display_info *info = &connector->display_info; int rc = 0; u8 dpcd[DP_RECEIVER_CAP_SIZE]; + const struct drm_edid *drm_edid = NULL; rc = drm_dp_read_dpcd_caps(dp->aux, dpcd); if (rc) @@ -276,10 +277,20 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp) dp->link->lttpr_count = msm_dp_display_lttpr_init(dp, dpcd); - rc = msm_dp_panel_read_sink_caps(dp->panel, connector); + rc = msm_dp_panel_read_link_caps(dp->panel, connector); if (rc) goto end; + drm_edid = drm_edid_read_ddc(connector, &dp->aux->ddc); + drm_edid_connector_update(connector, drm_edid); + + if (!drm_edid) { + DRM_ERROR("panel edid read failed\n"); + /* check edid read fail is due to unplug */ + if (!msm_dp_aux_is_link_connected(dp->aux)) + return -ETIMEDOUT; + } + msm_dp_link_process_request(dp->link); if (!dp->msm_dp_display.is_edp) @@ -291,7 +302,7 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp) dp->msm_dp_display.psr_supported = dp->panel->psr_cap.version && psr_enabled; dp->audio_supported = info->has_audio; - msm_dp_panel_handle_sink_request(dp->panel); + msm_dp_panel_handle_sink_request(dp->panel, drm_edid); /* * set sink to normal operation mode -- D0 @@ -302,6 +313,7 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp) msm_dp_link_reset_phy_params_vx_px(dp->link); end: + drm_edid_free(drm_edid); return rc; } @@ -453,7 +465,7 @@ static int msm_dp_hpd_unplug_handle(struct msm_dp_display_private *dp) /* Don't forget modes for eDP */ if (!dp->msm_dp_display.is_edp) - msm_dp_panel_unplugged(dp->panel, dp->msm_dp_display.connector); + drm_edid_connector_update(dp->msm_dp_display.connector, NULL); /* triggered by irq_hdp with sink_count = 0 */ if (dp->link->sink_count == 0) @@ -515,7 +527,6 @@ static int msm_dp_irq_hpd_handle(struct msm_dp_display_private *dp) static void msm_dp_display_deinit_sub_modules(struct msm_dp_display_private *dp) { msm_dp_audio_put(dp->audio); - msm_dp_panel_put(dp->panel); msm_dp_aux_put(dp->aux); } @@ -566,7 +577,7 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp) rc = PTR_ERR(dp->ctrl); DRM_ERROR("failed to initialize ctrl, rc = %d\n", rc); dp->ctrl = NULL; - goto error_ctrl; + goto error_link; } dp->audio = msm_dp_audio_get(dp->msm_dp_display.pdev, dp->link_base); @@ -574,13 +585,11 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp) rc = PTR_ERR(dp->audio); pr_err("failed to initialize audio, rc = %d\n", rc); dp->audio = NULL; - goto error_ctrl; + goto error_link; } return rc; -error_ctrl: - msm_dp_panel_put(dp->panel); error_link: msm_dp_aux_put(dp->aux); error: @@ -752,8 +761,7 @@ int msm_dp_display_get_modes(struct msm_dp *dp) msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); - return msm_dp_panel_get_modes(msm_dp_display->panel, - dp->connector); + return drm_edid_connector_add_modes(msm_dp_display->panel->connector); } bool msm_dp_display_check_video_test(struct msm_dp *dp) diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c index 5b4954e7cb74..bde4a772d22c 100644 --- a/drivers/gpu/drm/msm/dp/dp_panel.c +++ b/drivers/gpu/drm/msm/dp/dp_panel.c @@ -232,8 +232,8 @@ static u32 msm_dp_panel_get_supported_bpp(struct msm_dp_panel *msm_dp_panel, return min_supported_bpp; } -int msm_dp_panel_read_sink_caps(struct msm_dp_panel *msm_dp_panel, - struct drm_connector *connector) +int msm_dp_panel_read_link_caps(struct msm_dp_panel *msm_dp_panel, + struct drm_connector *connector) { int rc, bw_code; int count; @@ -271,36 +271,9 @@ int msm_dp_panel_read_sink_caps(struct msm_dp_panel *msm_dp_panel, rc = drm_dp_read_downstream_info(panel->aux, msm_dp_panel->dpcd, msm_dp_panel->downstream_ports); - if (rc) - return rc; - - drm_edid_free(msm_dp_panel->drm_edid); - - msm_dp_panel->drm_edid = drm_edid_read_ddc(connector, &panel->aux->ddc); - - drm_edid_connector_update(connector, msm_dp_panel->drm_edid); - - if (!msm_dp_panel->drm_edid) { - DRM_ERROR("panel edid read failed\n"); - /* check edid read fail is due to unplug */ - if (!msm_dp_aux_is_link_connected(panel->aux)) { - rc = -ETIMEDOUT; - goto end; - } - } - -end: return rc; } -void msm_dp_panel_unplugged(struct msm_dp_panel *msm_dp_panel, - struct drm_connector *connector) -{ - drm_edid_connector_update(connector, NULL); - drm_edid_free(msm_dp_panel->drm_edid); - msm_dp_panel->drm_edid = NULL; -} - u32 msm_dp_panel_get_mode_bpp(struct msm_dp_panel *msm_dp_panel, u32 mode_edid_bpp, u32 mode_pclk_khz) { @@ -324,22 +297,6 @@ u32 msm_dp_panel_get_mode_bpp(struct msm_dp_panel *msm_dp_panel, return bpp; } -int msm_dp_panel_get_modes(struct msm_dp_panel *msm_dp_panel, - struct drm_connector *connector) -{ - if (!msm_dp_panel) { - DRM_ERROR("invalid input\n"); - return -EINVAL; - } - - if (msm_dp_panel->drm_edid) { - drm_edid_connector_update(connector, msm_dp_panel->drm_edid); - return drm_edid_connector_add_modes(connector); - } - - return 0; -} - static u8 msm_dp_panel_get_edid_checksum(const struct edid *edid) { edid += edid->extensions; @@ -347,7 +304,8 @@ static u8 msm_dp_panel_get_edid_checksum(const struct edid *edid) return edid->checksum; } -void msm_dp_panel_handle_sink_request(struct msm_dp_panel *msm_dp_panel) +void msm_dp_panel_handle_sink_request(struct msm_dp_panel *msm_dp_panel, + const struct drm_edid *drm_edid) { struct msm_dp_panel_private *panel; @@ -360,7 +318,7 @@ void msm_dp_panel_handle_sink_request(struct msm_dp_panel *msm_dp_panel) if (panel->link->sink_request & DP_TEST_LINK_EDID_READ) { /* FIXME: get rid of drm_edid_raw() */ - const struct edid *edid = drm_edid_raw(msm_dp_panel->drm_edid); + const struct edid *edid = drm_edid_raw(drm_edid); u8 checksum; if (edid) @@ -757,10 +715,3 @@ struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux return msm_dp_panel; } -void msm_dp_panel_put(struct msm_dp_panel *msm_dp_panel) -{ - if (!msm_dp_panel) - return; - - drm_edid_free(msm_dp_panel->drm_edid); -} diff --git a/drivers/gpu/drm/msm/dp/dp_panel.h b/drivers/gpu/drm/msm/dp/dp_panel.h index 9173e90a5053..53b7b4463551 100644 --- a/drivers/gpu/drm/msm/dp/dp_panel.h +++ b/drivers/gpu/drm/msm/dp/dp_panel.h @@ -33,7 +33,6 @@ struct msm_dp_panel { u8 downstream_ports[DP_MAX_DOWNSTREAM_PORTS]; struct msm_dp_link_info link_info; - const struct drm_edid *drm_edid; struct drm_connector *connector; struct msm_dp_display_mode msm_dp_mode; struct msm_dp_panel_psr psr_cap; @@ -47,15 +46,12 @@ struct msm_dp_panel { int msm_dp_panel_init_panel_info(struct msm_dp_panel *msm_dp_panel); int msm_dp_panel_deinit(struct msm_dp_panel *msm_dp_panel); int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en); -int msm_dp_panel_read_sink_caps(struct msm_dp_panel *msm_dp_panel, - struct drm_connector *connector); -void msm_dp_panel_unplugged(struct msm_dp_panel *msm_dp_panel, - struct drm_connector *connector); +int msm_dp_panel_read_link_caps(struct msm_dp_panel *msm_dp_panel, + struct drm_connector *connector); u32 msm_dp_panel_get_mode_bpp(struct msm_dp_panel *msm_dp_panel, u32 mode_max_bpp, u32 mode_pclk_khz); -int msm_dp_panel_get_modes(struct msm_dp_panel *msm_dp_panel, - struct drm_connector *connector); -void msm_dp_panel_handle_sink_request(struct msm_dp_panel *msm_dp_panel); +void msm_dp_panel_handle_sink_request(struct msm_dp_panel *msm_dp_panel, + const struct drm_edid *drm_edid); void msm_dp_panel_tpg_config(struct msm_dp_panel *msm_dp_panel, bool enable); void msm_dp_panel_clear_dsc_dto(struct msm_dp_panel *msm_dp_panel); @@ -94,5 +90,4 @@ struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux struct msm_dp_link *link, void __iomem *link_base, void __iomem *p0_base); -void msm_dp_panel_put(struct msm_dp_panel *msm_dp_panel); #endif /* _DP_PANEL_H_ */ From cd07d635884018b25573617fdd20924b0bf0af28 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 28 Jul 2026 18:21:31 +0800 Subject: [PATCH 089/641] drm/msm/dp: drop deprecated .mode_set() and use .atomic_pre_enable The bridge .mode_set() callback is deprecated. Remove it and move the mode setup logic to .atomic_pre_enable(), where the adjusted_mode is available from the atomic CRTC state. .atomic_pre_enable() is used rather than .atomic_enable() because the DPU encoder's .atomic_enable() reads the output mode's YUV420 / wide bus state through the msm_display callbacks, and it runs after all bridges' .atomic_pre_enable() but before their .atomic_enable(). Programming the mode from the DP bridge's .atomic_enable() would leave the encoder reading the previously committed mode's state. Drop msm_dp_mode from msm_dp_display_private and store the mode directly in the panel, as it was only used as a temporary cache. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Assisted-by: Claude:claude-opus-4-8 [DB: moved to atomic_pre_enable] Patchwork: https://patchwork.freedesktop.org/patch/742729/ Link: https://lore.kernel.org/r/20260728-dp_mstclean-v9-2-f7779fce10f4@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_display.c | 103 +++++++++++++--------------- drivers/gpu/drm/msm/dp/dp_drm.c | 4 +- drivers/gpu/drm/msm/dp/dp_drm.h | 5 +- 3 files changed, 51 insertions(+), 61 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index ba3804a12a57..3ff7360a6833 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -63,7 +63,6 @@ struct msm_dp_display_private { struct msm_dp_panel *panel; struct msm_dp_ctrl *ctrl; - struct msm_dp_display_mode msm_dp_mode; struct msm_dp msm_dp_display; /* wait for audio signaling */ @@ -597,16 +596,33 @@ error: } static int msm_dp_display_set_mode(struct msm_dp *msm_dp_display, - struct msm_dp_display_mode *mode) + const struct drm_display_mode *adjusted_mode, + struct msm_dp_panel *msm_dp_panel) { struct msm_dp_display_private *dp; + u32 bpp; dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); - drm_mode_copy(&dp->panel->msm_dp_mode.drm_mode, &mode->drm_mode); - dp->panel->msm_dp_mode.bpp = mode->bpp; - dp->panel->msm_dp_mode.out_fmt_is_yuv_420 = mode->out_fmt_is_yuv_420; - msm_dp_panel_init_panel_info(dp->panel); + drm_mode_copy(&msm_dp_panel->msm_dp_mode.drm_mode, adjusted_mode); + if (msm_dp_display_check_video_test(msm_dp_display)) + bpp = msm_dp_display_get_test_bpp(msm_dp_display); + else + bpp = msm_dp_panel->connector->display_info.bpc * 3; + + msm_dp_panel->msm_dp_mode.bpp = bpp ? bpp : 24; /* Default bpp */ + msm_dp_panel->msm_dp_mode.v_active_low = + !!(adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC); + msm_dp_panel->msm_dp_mode.h_active_low = + !!(adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC); + msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420 = + drm_mode_is_420_only(&msm_dp_panel->connector->display_info, adjusted_mode) && + msm_dp_panel->vsc_sdp_supported; + msm_dp_panel_init_panel_info(msm_dp_panel); + + /* populate wide_bus_support to different layers */ + dp->ctrl->wide_bus_en = + msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420 ? false : dp->wide_bus_supported; return 0; } @@ -1305,7 +1321,7 @@ bool msm_dp_wide_bus_available(const struct msm_dp *msm_dp_display) dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); - if (dp->msm_dp_mode.out_fmt_is_yuv_420) + if (dp->panel->msm_dp_mode.out_fmt_is_yuv_420) return false; return dp->wide_bus_supported; @@ -1356,6 +1372,30 @@ int msm_dp_modeset_init(struct msm_dp *msm_dp_display, struct drm_device *dev, return 0; } +void msm_dp_bridge_atomic_pre_enable(struct drm_bridge *drm_bridge, + struct drm_atomic_commit *state) +{ + struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(drm_bridge); + struct msm_dp *dp = msm_dp_bridge->msm_dp_display; + struct msm_dp_display_private *msm_dp_display; + struct drm_crtc *crtc; + struct drm_crtc_state *crtc_state; + + msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); + + crtc = drm_atomic_get_new_crtc_for_encoder(state, drm_bridge->encoder); + if (!crtc) + return; + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + + /* + * The DPU encoder's .atomic_enable() reads the mode's YUV420 / wide bus + * state and runs before the bridge's .atomic_enable(), so the mode must + * be programmed here, in .atomic_pre_enable(). + */ + msm_dp_display_set_mode(dp, &crtc_state->adjusted_mode, msm_dp_display->panel); +} + void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge, struct drm_atomic_commit *state) { @@ -1366,10 +1406,6 @@ void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge, bool force_link_train = false; msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); - if (!msm_dp_display->msm_dp_mode.drm_mode.clock) { - DRM_ERROR("invalid params\n"); - return; - } if (dp->is_edp) msm_dp_hpd_plug_handle(msm_dp_display); @@ -1382,12 +1418,6 @@ void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge, if (msm_dp_display->link->sink_count == 0) return; - rc = msm_dp_display_set_mode(dp, &msm_dp_display->msm_dp_mode); - if (rc) { - DRM_ERROR("Failed to perform a mode set, rc=%d\n", rc); - return; - } - if (!dp->power_on) { msm_dp_display_host_phy_init(msm_dp_display); force_link_train = true; @@ -1442,45 +1472,6 @@ void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge, pm_runtime_put_sync(&dp->pdev->dev); } -void msm_dp_bridge_mode_set(struct drm_bridge *drm_bridge, - const struct drm_display_mode *mode, - const struct drm_display_mode *adjusted_mode) -{ - struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(drm_bridge); - struct msm_dp *dp = msm_dp_bridge->msm_dp_display; - struct msm_dp_display_private *msm_dp_display; - struct msm_dp_panel *msm_dp_panel; - - msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); - msm_dp_panel = msm_dp_display->panel; - - memset(&msm_dp_display->msm_dp_mode, 0x0, sizeof(struct msm_dp_display_mode)); - - if (msm_dp_display_check_video_test(dp)) - msm_dp_display->msm_dp_mode.bpp = msm_dp_display_get_test_bpp(dp); - else /* Default num_components per pixel = 3 */ - msm_dp_display->msm_dp_mode.bpp = dp->connector->display_info.bpc * 3; - - if (!msm_dp_display->msm_dp_mode.bpp) - msm_dp_display->msm_dp_mode.bpp = 24; /* Default bpp */ - - drm_mode_copy(&msm_dp_display->msm_dp_mode.drm_mode, adjusted_mode); - - msm_dp_display->msm_dp_mode.v_active_low = - !!(msm_dp_display->msm_dp_mode.drm_mode.flags & DRM_MODE_FLAG_NVSYNC); - - msm_dp_display->msm_dp_mode.h_active_low = - !!(msm_dp_display->msm_dp_mode.drm_mode.flags & DRM_MODE_FLAG_NHSYNC); - - msm_dp_display->msm_dp_mode.out_fmt_is_yuv_420 = - drm_mode_is_420_only(&dp->connector->display_info, adjusted_mode) && - msm_dp_panel->vsc_sdp_supported; - - /* populate wide_bus_support to different layers */ - msm_dp_display->ctrl->wide_bus_en = - msm_dp_display->msm_dp_mode.out_fmt_is_yuv_420 ? false : msm_dp_display->wide_bus_supported; -} - void msm_dp_bridge_hpd_enable(struct drm_bridge *bridge) { struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(bridge); diff --git a/drivers/gpu/drm/msm/dp/dp_drm.c b/drivers/gpu/drm/msm/dp/dp_drm.c index 06881bfd6181..794a0df60414 100644 --- a/drivers/gpu/drm/msm/dp/dp_drm.c +++ b/drivers/gpu/drm/msm/dp/dp_drm.c @@ -53,10 +53,10 @@ static const struct drm_bridge_funcs msm_dp_bridge_ops = { .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_pre_enable = msm_dp_bridge_atomic_pre_enable, .atomic_enable = msm_dp_bridge_atomic_enable, .atomic_disable = msm_dp_bridge_atomic_disable, .atomic_post_disable = msm_dp_bridge_atomic_post_disable, - .mode_set = msm_dp_bridge_mode_set, .mode_valid = msm_dp_bridge_mode_valid, .get_modes = msm_dp_bridge_get_modes, .detect = msm_dp_bridge_detect, @@ -230,10 +230,10 @@ static void msm_edp_bridge_debugfs_init(struct drm_bridge *bridge, struct dentry } static const struct drm_bridge_funcs msm_edp_bridge_ops = { + .atomic_pre_enable = msm_dp_bridge_atomic_pre_enable, .atomic_enable = msm_edp_bridge_atomic_enable, .atomic_disable = msm_edp_bridge_atomic_disable, .atomic_post_disable = msm_edp_bridge_atomic_post_disable, - .mode_set = msm_dp_bridge_mode_set, .mode_valid = msm_edp_bridge_mode_valid, .atomic_create_state = drm_atomic_helper_bridge_create_state, .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, diff --git a/drivers/gpu/drm/msm/dp/dp_drm.h b/drivers/gpu/drm/msm/dp/dp_drm.h index 041aa026ae2e..4f733d8118b1 100644 --- a/drivers/gpu/drm/msm/dp/dp_drm.h +++ b/drivers/gpu/drm/msm/dp/dp_drm.h @@ -27,6 +27,8 @@ int msm_dp_bridge_init(struct msm_dp *msm_dp_display, struct drm_device *dev, enum drm_connector_status msm_dp_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connector); +void msm_dp_bridge_atomic_pre_enable(struct drm_bridge *drm_bridge, + struct drm_atomic_commit *state); void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge, struct drm_atomic_commit *state); void msm_dp_bridge_atomic_disable(struct drm_bridge *drm_bridge, @@ -36,9 +38,6 @@ void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge, enum drm_mode_status msm_dp_bridge_mode_valid(struct drm_bridge *bridge, const struct drm_display_info *info, const struct drm_display_mode *mode); -void msm_dp_bridge_mode_set(struct drm_bridge *drm_bridge, - const struct drm_display_mode *mode, - const struct drm_display_mode *adjusted_mode); void msm_dp_bridge_hpd_enable(struct drm_bridge *bridge); void msm_dp_bridge_hpd_disable(struct drm_bridge *bridge); void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge, From 1c96c88ec172d3d7e0b46c00f0b3dc0361436fdb Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 28 Jul 2026 18:21:32 +0800 Subject: [PATCH 090/641] drm/msm/dp: move mode setup into msm_dp_panel_init_panel_info() The display layer directly assigns msm_dp_panel mode fields (bpp, sync polarity, yuv420 flag) instead of letting the panel manage its own state. Pass adjusted_mode and bpp as parameters to msm_dp_panel_init_panel_info() and move the assignments inside it. Suggested-by: Dmitry Baryshkov Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/742731/ Link: https://lore.kernel.org/r/20260728-dp_mstclean-v9-3-f7779fce10f4@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_display.c | 11 +---------- drivers/gpu/drm/msm/dp/dp_panel.c | 18 +++++++++++++++--- drivers/gpu/drm/msm/dp/dp_panel.h | 4 +++- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 3ff7360a6833..cc94ce9635b7 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -604,21 +604,12 @@ static int msm_dp_display_set_mode(struct msm_dp *msm_dp_display, dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); - drm_mode_copy(&msm_dp_panel->msm_dp_mode.drm_mode, adjusted_mode); if (msm_dp_display_check_video_test(msm_dp_display)) bpp = msm_dp_display_get_test_bpp(msm_dp_display); else bpp = msm_dp_panel->connector->display_info.bpc * 3; - msm_dp_panel->msm_dp_mode.bpp = bpp ? bpp : 24; /* Default bpp */ - msm_dp_panel->msm_dp_mode.v_active_low = - !!(adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC); - msm_dp_panel->msm_dp_mode.h_active_low = - !!(adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC); - msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420 = - drm_mode_is_420_only(&msm_dp_panel->connector->display_info, adjusted_mode) && - msm_dp_panel->vsc_sdp_supported; - msm_dp_panel_init_panel_info(msm_dp_panel); + msm_dp_panel_init_panel_info(msm_dp_panel, adjusted_mode, bpp ? bpp : 24); /* populate wide_bus_support to different layers */ dp->ctrl->wide_bus_en = diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c index bde4a772d22c..e76dad0f6663 100644 --- a/drivers/gpu/drm/msm/dp/dp_panel.c +++ b/drivers/gpu/drm/msm/dp/dp_panel.c @@ -647,15 +647,27 @@ int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en) return 0; } -int msm_dp_panel_init_panel_info(struct msm_dp_panel *msm_dp_panel) +int msm_dp_panel_init_panel_info(struct msm_dp_panel *msm_dp_panel, + const struct drm_display_mode *adjusted_mode, + u32 bpp) { struct drm_display_mode *drm_mode; struct msm_dp_panel_private *panel; - drm_mode = &msm_dp_panel->msm_dp_mode.drm_mode; - panel = container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel); + drm_mode_copy(&msm_dp_panel->msm_dp_mode.drm_mode, adjusted_mode); + msm_dp_panel->msm_dp_mode.bpp = bpp; + msm_dp_panel->msm_dp_mode.v_active_low = + !!(adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC); + msm_dp_panel->msm_dp_mode.h_active_low = + !!(adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC); + msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420 = + drm_mode_is_420_only(&msm_dp_panel->connector->display_info, adjusted_mode) && + msm_dp_panel->vsc_sdp_supported; + + drm_mode = &msm_dp_panel->msm_dp_mode.drm_mode; + /* * print resolution info as this is a result * of user initiated action of cable connection diff --git a/drivers/gpu/drm/msm/dp/dp_panel.h b/drivers/gpu/drm/msm/dp/dp_panel.h index 53b7b4463551..4519ac374220 100644 --- a/drivers/gpu/drm/msm/dp/dp_panel.h +++ b/drivers/gpu/drm/msm/dp/dp_panel.h @@ -43,7 +43,9 @@ struct msm_dp_panel { u32 max_bw_code; }; -int msm_dp_panel_init_panel_info(struct msm_dp_panel *msm_dp_panel); +int msm_dp_panel_init_panel_info(struct msm_dp_panel *msm_dp_panel, + const struct drm_display_mode *adjusted_mode, + u32 bpp); int msm_dp_panel_deinit(struct msm_dp_panel *msm_dp_panel); int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en); int msm_dp_panel_read_link_caps(struct msm_dp_panel *msm_dp_panel, From 7de83c21291ff861a66c602ddcaf45ff8f43f547 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 28 Jul 2026 18:21:33 +0800 Subject: [PATCH 091/641] drm/msm/dp: split msm_dp_ctrl_config_ctrl() into link parts and stream parts The DP_CONFIGURATION_CTRL register contains both link-level and stream-specific fields. Currently, msm_dp_ctrl_config_ctrl() configures all of them together. Separate the configuration into link parts and stream parts to support MST. Clear the stream-specific fields before OR-ing new values in the stream path to avoid bit accumulation across repeated calls. Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/742733/ Link: https://lore.kernel.org/r/20260728-dp_mstclean-v9-4-f7779fce10f4@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 51 ++++++++++++++++++++++---------- drivers/gpu/drm/msm/dp/dp_reg.h | 2 ++ 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index 86ef8c89ad44..7c0649d5318f 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -388,26 +388,48 @@ void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl) drm_dbg_dp(ctrl->drm_dev, "mainlink off\n"); } -static void msm_dp_ctrl_config_ctrl(struct msm_dp_ctrl_private *ctrl) +static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *msm_dp_panel) { u32 config = 0, tbd; + + /* + * RMW: called from atomic_enable(), serialized by the DRM atomic framework. + * Clear stream-specific fields before OR-ing to avoid bit accumulation. + */ + config = msm_dp_read_link(ctrl, REG_DP_CONFIGURATION_CTRL); + config &= ~(DP_CONFIGURATION_CTRL_RGB_YUV_MASK | + DP_CONFIGURATION_CTRL_BPC_MASK | + DP_CONFIGURATION_CTRL_SEND_VSC); + + if (msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420) + config |= DP_CONFIGURATION_CTRL_RGB_YUV; /* YUV420 */ + + tbd = msm_dp_link_get_test_bits_depth(ctrl->link, + msm_dp_panel->msm_dp_mode.bpp); + + config |= tbd << DP_CONFIGURATION_CTRL_BPC_SHIFT; + + if (msm_dp_panel->psr_cap.version) + config |= DP_CONFIGURATION_CTRL_SEND_VSC; + + drm_dbg_dp(ctrl->drm_dev, "stream DP_CONFIGURATION_CTRL=0x%x\n", config); + + msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config); +} + +static void msm_dp_ctrl_config_ctrl_link(struct msm_dp_ctrl_private *ctrl) +{ + u32 config = 0; const u8 *dpcd = ctrl->panel->dpcd; /* Default-> LSCLK DIV: 1/4 LCLK */ config |= (2 << DP_CONFIGURATION_CTRL_LSCLK_DIV_SHIFT); - if (ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420) - config |= DP_CONFIGURATION_CTRL_RGB_YUV; /* YUV420 */ - /* Scrambler reset enable */ if (drm_dp_alternate_scrambler_reset_cap(dpcd)) config |= DP_CONFIGURATION_CTRL_ASSR; - tbd = msm_dp_link_get_test_bits_depth(ctrl->link, - ctrl->panel->msm_dp_mode.bpp); - - config |= tbd << DP_CONFIGURATION_CTRL_BPC_SHIFT; - /* Num of Lanes */ config |= ((ctrl->link->link_params.num_lanes - 1) << DP_CONFIGURATION_CTRL_NUM_OF_LANES_SHIFT); @@ -421,10 +443,7 @@ static void msm_dp_ctrl_config_ctrl(struct msm_dp_ctrl_private *ctrl) config |= DP_CONFIGURATION_CTRL_STATIC_DYNAMIC_CN; config |= DP_CONFIGURATION_CTRL_SYNC_ASYNC_CLK; - if (ctrl->panel->psr_cap.version) - config |= DP_CONFIGURATION_CTRL_SEND_VSC; - - drm_dbg_dp(ctrl->drm_dev, "DP_CONFIGURATION_CTRL=0x%x\n", config); + drm_dbg_dp(ctrl->drm_dev, "link DP_CONFIGURATION_CTRL=0x%x\n", config); msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config); } @@ -450,7 +469,8 @@ static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl msm_dp_ctrl_lane_mapping(ctrl); msm_dp_setup_peripheral_flush(ctrl); - msm_dp_ctrl_config_ctrl(ctrl); + msm_dp_ctrl_config_ctrl_link(ctrl); + msm_dp_ctrl_config_ctrl_streams(ctrl, ctrl->panel); test_bits_depth = msm_dp_link_get_test_bits_depth(ctrl->link, ctrl->panel->msm_dp_mode.bpp); colorimetry_cfg = msm_dp_link_get_colorimetry_config(ctrl->link); @@ -1628,7 +1648,8 @@ static int msm_dp_ctrl_link_train(struct msm_dp_ctrl_private *ctrl, u8 assr; struct msm_dp_link_info link_info = {0}; - msm_dp_ctrl_config_ctrl(ctrl); + msm_dp_ctrl_config_ctrl_link(ctrl); + msm_dp_ctrl_config_ctrl_streams(ctrl, ctrl->panel); link_info.num_lanes = ctrl->link->link_params.num_lanes; link_info.rate = ctrl->link->link_params.rate; diff --git a/drivers/gpu/drm/msm/dp/dp_reg.h b/drivers/gpu/drm/msm/dp/dp_reg.h index 3689642b7fc0..dda4b642c81b 100644 --- a/drivers/gpu/drm/msm/dp/dp_reg.h +++ b/drivers/gpu/drm/msm/dp/dp_reg.h @@ -149,8 +149,10 @@ #define DP_CONFIGURATION_CTRL_ENHANCED_FRAMING (0x00000040) #define DP_CONFIGURATION_CTRL_SEND_VSC (0x00000080) #define DP_CONFIGURATION_CTRL_BPC (0x00000100) +#define DP_CONFIGURATION_CTRL_BPC_MASK GENMASK(9, 8) #define DP_CONFIGURATION_CTRL_ASSR (0x00000400) #define DP_CONFIGURATION_CTRL_RGB_YUV (0x00000800) +#define DP_CONFIGURATION_CTRL_RGB_YUV_MASK GENMASK(12, 11) #define DP_CONFIGURATION_CTRL_LSCLK_DIV (0x00002000) #define DP_CONFIGURATION_CTRL_NUM_OF_LANES_SHIFT (0x04) #define DP_CONFIGURATION_CTRL_BPC_SHIFT (0x08) From 3a1edd5ac0226f06aef31b492c56b09f876acf5b Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 28 Jul 2026 18:21:34 +0800 Subject: [PATCH 092/641] drm/msm/dp: extract MISC1_MISC0 configuration into a separate function Refactor the MISC1_MISC0 register configuration into a standalone helper function to support MST. Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/742735/ Link: https://lore.kernel.org/r/20260728-dp_mstclean-v9-5-f7779fce10f4@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index 7c0649d5318f..c557e0f01dc6 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -462,17 +462,13 @@ static void msm_dp_ctrl_lane_mapping(struct msm_dp_ctrl_private *ctrl) ln_mapping); } -static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl) +static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *msm_dp_panel) { u32 colorimetry_cfg, test_bits_depth, misc_val; - msm_dp_ctrl_lane_mapping(ctrl); - msm_dp_setup_peripheral_flush(ctrl); - - msm_dp_ctrl_config_ctrl_link(ctrl); - msm_dp_ctrl_config_ctrl_streams(ctrl, ctrl->panel); - - test_bits_depth = msm_dp_link_get_test_bits_depth(ctrl->link, ctrl->panel->msm_dp_mode.bpp); + test_bits_depth = msm_dp_link_get_test_bits_depth(ctrl->link, + msm_dp_panel->msm_dp_mode.bpp); colorimetry_cfg = msm_dp_link_get_colorimetry_config(ctrl->link); misc_val = msm_dp_read_link(ctrl, REG_DP_MISC1_MISC0); @@ -486,6 +482,17 @@ static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl drm_dbg_dp(ctrl->drm_dev, "misc settings = 0x%x\n", misc_val); msm_dp_write_link(ctrl, REG_DP_MISC1_MISC0, misc_val); +} + +static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl) +{ + msm_dp_ctrl_lane_mapping(ctrl); + msm_dp_setup_peripheral_flush(ctrl); + + msm_dp_ctrl_config_ctrl_link(ctrl); + msm_dp_ctrl_config_ctrl_streams(ctrl, ctrl->panel); + + msm_dp_ctrl_config_misc1_misc0(ctrl, ctrl->panel); msm_dp_panel_timing_cfg(ctrl->panel, ctrl->msm_dp_ctrl.wide_bus_en); } From 0a19867dfb8dc4491737e64987566fc2c5962778 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 28 Jul 2026 18:21:35 +0800 Subject: [PATCH 093/641] drm/msm/dp: split link setup from source params msm_dp_ctrl_configure_source_params() should only handle stream-related configuration. Move the link setup out of it so MST can program link and stream settings separately. Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/742737/ Link: https://lore.kernel.org/r/20260728-dp_mstclean-v9-6-f7779fce10f4@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index c557e0f01dc6..f343bd6c2b81 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -486,10 +486,6 @@ static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl, static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl) { - msm_dp_ctrl_lane_mapping(ctrl); - msm_dp_setup_peripheral_flush(ctrl); - - msm_dp_ctrl_config_ctrl_link(ctrl); msm_dp_ctrl_config_ctrl_streams(ctrl, ctrl->panel); msm_dp_ctrl_config_misc1_misc0(ctrl, ctrl->panel); @@ -2556,6 +2552,10 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train */ reinit_completion(&ctrl->video_comp); + msm_dp_ctrl_lane_mapping(ctrl); + msm_dp_setup_peripheral_flush(ctrl); + msm_dp_ctrl_config_ctrl_link(ctrl); + msm_dp_ctrl_configure_source_params(ctrl); msm_dp_ctrl_config_msa(ctrl, From 38e4b121497923b0f1a9636bf947af730f38bb3b Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 28 Jul 2026 18:21:36 +0800 Subject: [PATCH 094/641] drm/msm/dp: move the pixel clock control to its own API Enable/Disable of DP pixel clock happens in multiple code paths leading to code duplication. Move it into individual helpers so that the helpers can be called wherever necessary. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/742738/ Link: https://lore.kernel.org/r/20260728-dp_mstclean-v9-7-f7779fce10f4@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 80 +++++++++++++++++--------------- 1 file changed, 42 insertions(+), 38 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index f343bd6c2b81..068d4f8b673f 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -2176,6 +2176,41 @@ static bool msm_dp_ctrl_send_phy_test_pattern(struct msm_dp_ctrl_private *ctrl) return success; } +static int msm_dp_ctrl_on_pixel_clk(struct msm_dp_ctrl_private *ctrl, unsigned long pixel_rate) +{ + int ret; + + ret = clk_set_rate(ctrl->pixel_clk, pixel_rate * 1000); + if (ret) { + DRM_ERROR("Failed to set pixel clock rate. ret=%d\n", ret); + return ret; + } + + if (WARN_ON_ONCE(ctrl->stream_clks_on)) + return 0; + + ret = clk_prepare_enable(ctrl->pixel_clk); + if (ret) { + DRM_ERROR("Failed to start pixel clocks. ret=%d\n", ret); + return ret; + } + ctrl->stream_clks_on = true; + + return ret; +} + +static void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl) +{ + struct msm_dp_ctrl_private *ctrl; + + ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); + + if (ctrl->stream_clks_on) { + clk_disable_unprepare(ctrl->pixel_clk); + ctrl->stream_clks_on = false; + } +} + static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl) { int ret; @@ -2201,22 +2236,9 @@ static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl } pixel_rate = ctrl->panel->msm_dp_mode.drm_mode.clock; - ret = clk_set_rate(ctrl->pixel_clk, pixel_rate * 1000); - if (ret) { - DRM_ERROR("Failed to set pixel clock rate. ret=%d\n", ret); + ret = msm_dp_ctrl_on_pixel_clk(ctrl, pixel_rate); + if (ret) return ret; - } - - if (ctrl->stream_clks_on) { - drm_dbg_dp(ctrl->drm_dev, "pixel clks already enabled\n"); - } else { - ret = clk_prepare_enable(ctrl->pixel_clk); - if (ret) { - DRM_ERROR("Failed to start pixel clocks. ret=%d\n", ret); - return ret; - } - ctrl->stream_clks_on = true; - } msm_dp_ctrl_send_phy_test_pattern(ctrl); @@ -2519,26 +2541,13 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train ret = msm_dp_ctrl_enable_mainlink_clocks(ctrl); if (ret) { DRM_ERROR("Failed to start link clocks. ret=%d\n", ret); - goto end; + return ret; } } - ret = clk_set_rate(ctrl->pixel_clk, pixel_rate * 1000); - if (ret) { - DRM_ERROR("Failed to set pixel clock rate. ret=%d\n", ret); - goto end; - } - - if (ctrl->stream_clks_on) { - drm_dbg_dp(ctrl->drm_dev, "pixel clks already enabled\n"); - } else { - ret = clk_prepare_enable(ctrl->pixel_clk); - if (ret) { - DRM_ERROR("Failed to start pixel clocks. ret=%d\n", ret); - goto end; - } - ctrl->stream_clks_on = true; - } + ret = msm_dp_ctrl_on_pixel_clk(ctrl, pixel_rate); + if (ret) + return ret; if (force_link_train || !msm_dp_ctrl_channel_eq_ok(ctrl)) msm_dp_ctrl_link_retrain(ctrl); @@ -2577,7 +2586,6 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train drm_dbg_dp(ctrl->drm_dev, "mainlink %s\n", mainlink_ready ? "READY" : "NOT READY"); -end: return ret; } @@ -2625,11 +2633,7 @@ void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl) msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl); - if (ctrl->stream_clks_on) { - clk_disable_unprepare(ctrl->pixel_clk); - ctrl->stream_clks_on = false; - } - + msm_dp_ctrl_off_pixel_clk(msm_dp_ctrl); dev_pm_opp_set_rate(ctrl->dev, 0); msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl); From ca16db3d18cfe2d3e45a2cb3f3f67f2b19685a02 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Tue, 28 Jul 2026 18:21:37 +0800 Subject: [PATCH 095/641] drm/msm/dp: break up dp_display_enable into two parts dp_display_enable() currently re-trains the link if needed and then enables the pixel clock, programs the controller to start sending the pixel stream. Split these two parts into prepare/enable APIs, to support MST bridges_enable insert the MST payloads funcs between enable stream_clks and program register. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/742740/ Link: https://lore.kernel.org/r/20260728-dp_mstclean-v9-8-f7779fce10f4@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 48 ++++++++----- drivers/gpu/drm/msm/dp/dp_ctrl.h | 3 +- drivers/gpu/drm/msm/dp/dp_display.c | 105 ++++++++++++++++++---------- 3 files changed, 103 insertions(+), 53 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index 068d4f8b673f..43094dda3434 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -2511,27 +2511,19 @@ static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl, msm_dp_write_link(ctrl, REG_DP_SOFTWARE_NVID, nvid); } -int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train) +int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train) { int ret = 0; - bool mainlink_ready = false; struct msm_dp_ctrl_private *ctrl; - unsigned long pixel_rate; - unsigned long pixel_rate_orig; if (!msm_dp_ctrl) return -EINVAL; ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); - pixel_rate = pixel_rate_orig = ctrl->panel->msm_dp_mode.drm_mode.clock; - - if (msm_dp_ctrl->wide_bus_en || ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420) - pixel_rate >>= 1; - - drm_dbg_dp(ctrl->drm_dev, "rate=%d, num_lanes=%d, pixel_rate=%lu\n", - ctrl->link->link_params.rate, - ctrl->link->link_params.num_lanes, pixel_rate); + drm_dbg_dp(ctrl->drm_dev, "rate=%d, num_lanes=%d\n", + ctrl->link->link_params.rate, + ctrl->link->link_params.num_lanes); drm_dbg_dp(ctrl->drm_dev, "core_clk_on=%d link_clk_on=%d stream_clk_on=%d\n", @@ -2545,16 +2537,40 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train } } - ret = msm_dp_ctrl_on_pixel_clk(ctrl, pixel_rate); - if (ret) - return ret; - if (force_link_train || !msm_dp_ctrl_channel_eq_ok(ctrl)) msm_dp_ctrl_link_retrain(ctrl); /* stop txing train pattern to end link training */ msm_dp_ctrl_clear_training_pattern(ctrl, DP_PHY_DPRX); + return ret; +} + +int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl) +{ + int ret = 0; + bool mainlink_ready = false; + struct msm_dp_ctrl_private *ctrl; + unsigned long pixel_rate; + unsigned long pixel_rate_orig; + + if (!msm_dp_ctrl) + return -EINVAL; + + ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); + + pixel_rate_orig = ctrl->panel->msm_dp_mode.drm_mode.clock; + pixel_rate = pixel_rate_orig; + + if (msm_dp_ctrl->wide_bus_en || ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420) + pixel_rate >>= 1; + + drm_dbg_dp(ctrl->drm_dev, "pixel_rate=%lu\n", pixel_rate); + + ret = msm_dp_ctrl_on_pixel_clk(ctrl, pixel_rate); + if (ret) + return ret; + /* * Set up transfer unit values and set controller state to send * video. diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h index f68bee62713f..1497f1a8fc2f 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.h +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h @@ -17,7 +17,8 @@ struct msm_dp_ctrl { struct phy; int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl); -int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train); +int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl); +int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train); void msm_dp_ctrl_off_link_stream(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl); diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index cc94ce9635b7..e2b4f0276737 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -617,7 +617,42 @@ static int msm_dp_display_set_mode(struct msm_dp *msm_dp_display, return 0; } -static int msm_dp_display_enable(struct msm_dp_display_private *dp, bool force_link_train) +static int msm_dp_display_prepare_link(struct msm_dp_display_private *dp) +{ + struct msm_dp *msm_dp_display = &dp->msm_dp_display; + int rc = 0; + bool force_link_train = false; + + drm_dbg_dp(dp->drm_dev, "sink_count=%d\n", dp->link->sink_count); + + if (msm_dp_display->is_edp) + msm_dp_hpd_plug_handle(dp); + + rc = pm_runtime_resume_and_get(&msm_dp_display->pdev->dev); + if (rc) { + DRM_ERROR("failed to pm_runtime_resume\n"); + return rc; + } + + if (dp->link->sink_count == 0) + return -ENOTCONN; + + if (!msm_dp_display->power_on) { + msm_dp_display_host_phy_init(dp); + force_link_train = true; + } + + rc = msm_dp_ctrl_on_link(dp->ctrl); + if (rc) { + DRM_ERROR("Failed link training (rc=%d)\n", rc); + // TODO: schedule drm_connector_set_link_status_property() + return rc; + } + + return msm_dp_ctrl_prepare_stream_on(dp->ctrl, force_link_train); +} + +static int msm_dp_display_enable(struct msm_dp_display_private *dp) { int rc = 0; struct msm_dp *msm_dp_display = &dp->msm_dp_display; @@ -628,7 +663,7 @@ static int msm_dp_display_enable(struct msm_dp_display_private *dp, bool force_l return 0; } - rc = msm_dp_ctrl_on_stream(dp->ctrl, force_link_train); + rc = msm_dp_ctrl_on_stream(dp->ctrl); if (!rc) msm_dp_display->power_on = true; @@ -658,13 +693,10 @@ static int msm_dp_display_post_enable(struct msm_dp *msm_dp_display) return 0; } -static int msm_dp_display_disable(struct msm_dp_display_private *dp) +static void msm_dp_display_audio_notify_disable(struct msm_dp_display_private *dp) { struct msm_dp *msm_dp_display = &dp->msm_dp_display; - if (!msm_dp_display->power_on) - return 0; - /* wait only if audio was enabled */ if (msm_dp_display->audio_enabled) { /* signal the disconnect event */ @@ -675,6 +707,14 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp) } msm_dp_display->audio_enabled = false; +} + +static int msm_dp_display_disable(struct msm_dp_display_private *dp) +{ + struct msm_dp *msm_dp_display = &dp->msm_dp_display; + + if (!msm_dp_display->power_on) + return 0; if (dp->link->sink_count == 0) { /* @@ -1391,45 +1431,29 @@ void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge, struct drm_atomic_commit *state) { struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(drm_bridge); - struct msm_dp *dp = msm_dp_bridge->msm_dp_display; + struct msm_dp *msm_dp_display = msm_dp_bridge->msm_dp_display; int rc = 0; - struct msm_dp_display_private *msm_dp_display; - bool force_link_train = false; + struct msm_dp_display_private *dp; - msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); + dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); - if (dp->is_edp) - msm_dp_hpd_plug_handle(msm_dp_display); - - if (pm_runtime_resume_and_get(&dp->pdev->dev)) { - DRM_ERROR("failed to pm_runtime_resume\n"); - return; - } - - if (msm_dp_display->link->sink_count == 0) - return; - - if (!dp->power_on) { - msm_dp_display_host_phy_init(msm_dp_display); - force_link_train = true; - } - - rc = msm_dp_ctrl_on_link(msm_dp_display->ctrl); + rc = msm_dp_display_prepare_link(dp); if (rc) { - DRM_ERROR("Failed link training (rc=%d)\n", rc); - // TODO: schedule drm_connector_set_link_status_property() + DRM_ERROR("DP display prepare failed, rc=%d\n", rc); return; } - msm_dp_display_enable(msm_dp_display, force_link_train); + rc = msm_dp_display_enable(dp); + if (rc) + DRM_ERROR("DP display enable failed, rc=%d\n", rc); - rc = msm_dp_display_post_enable(dp); + rc = msm_dp_display_post_enable(msm_dp_display); if (rc) { DRM_ERROR("DP display post enable failed, rc=%d\n", rc); - msm_dp_display_disable(msm_dp_display); + msm_dp_display_disable(dp); } - drm_dbg_dp(dp->drm_dev, "type=%d Done\n", dp->connector_type); + drm_dbg_dp(msm_dp_display->drm_dev, "type=%d Done\n", msm_dp_display->connector_type); } void msm_dp_bridge_atomic_disable(struct drm_bridge *drm_bridge, @@ -1444,6 +1468,15 @@ void msm_dp_bridge_atomic_disable(struct drm_bridge *drm_bridge, msm_dp_ctrl_push_idle(msm_dp_display->ctrl); } +static void msm_dp_display_unprepare(struct msm_dp_display_private *dp) +{ + struct msm_dp *msm_dp_display = &dp->msm_dp_display; + + pm_runtime_put_sync(&msm_dp_display->pdev->dev); + + drm_dbg_dp(dp->drm_dev, "type=%d Done\n", msm_dp_display->connector_type); +} + void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge, struct drm_atomic_commit *state) { @@ -1456,11 +1489,11 @@ void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge, if (dp->is_edp) msm_dp_hpd_unplug_handle(msm_dp_display); + msm_dp_display_audio_notify_disable(msm_dp_display); + msm_dp_display_disable(msm_dp_display); - drm_dbg_dp(dp->drm_dev, "type=%d Done\n", dp->connector_type); - - pm_runtime_put_sync(&dp->pdev->dev); + msm_dp_display_unprepare(msm_dp_display); } void msm_dp_bridge_hpd_enable(struct drm_bridge *bridge) From 477d23a69569720be64bd79cafae01b3b569db2e Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Tue, 28 Jul 2026 18:21:38 +0800 Subject: [PATCH 096/641] drm/msm/dp: re-arrange dp_display_disable() into functional parts dp_display_disable() handles special case of when monitor is disconnected from the dongle while the dongle stays connected thereby needing a separate function dp_ctrl_off_link_stream() for this. However with a slight rework this can still be handled by keeping common paths same for regular and special case. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/742742/ Link: https://lore.kernel.org/r/20260728-dp_mstclean-v9-9-f7779fce10f4@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 19 +------------------ drivers/gpu/drm/msm/dp/dp_ctrl.h | 2 +- drivers/gpu/drm/msm/dp/dp_display.c | 10 +++++++++- 3 files changed, 11 insertions(+), 20 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index 43094dda3434..272ac3b25b13 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -2605,7 +2605,7 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl) return ret; } -void msm_dp_ctrl_off_link_stream(struct msm_dp_ctrl *msm_dp_ctrl) +void msm_dp_ctrl_reinit_phy(struct msm_dp_ctrl *msm_dp_ctrl) { struct msm_dp_ctrl_private *ctrl; struct phy *phy; @@ -2613,23 +2613,6 @@ void msm_dp_ctrl_off_link_stream(struct msm_dp_ctrl *msm_dp_ctrl) ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); phy = ctrl->phy; - msm_dp_panel_disable_vsc_sdp(ctrl->panel); - - /* set dongle to D3 (power off) mode */ - msm_dp_link_psm_config(ctrl->link, &ctrl->panel->link_info, true); - - msm_dp_ctrl_mainlink_disable(ctrl); - - if (ctrl->stream_clks_on) { - clk_disable_unprepare(ctrl->pixel_clk); - ctrl->stream_clks_on = false; - } - - dev_pm_opp_set_rate(ctrl->dev, 0); - msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl); - - phy_power_off(phy); - /* aux channel down, reinit phy */ phy_exit(phy); phy_init(phy); diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h index 1497f1a8fc2f..5d615f50d13b 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.h +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h @@ -19,7 +19,6 @@ struct phy; int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl); int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl); int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train); -void msm_dp_ctrl_off_link_stream(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl); irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl); @@ -46,4 +45,5 @@ void msm_dp_ctrl_core_clk_disable(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_enable_irq(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_disable_irq(struct msm_dp_ctrl *msm_dp_ctrl); +void msm_dp_ctrl_reinit_phy(struct msm_dp_ctrl *msm_dp_ctrl); #endif /* _DP_CTRL_H_ */ diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index e2b4f0276737..f8f8f3889ea5 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -716,12 +716,20 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp) if (!msm_dp_display->power_on) return 0; + msm_dp_panel_disable_vsc_sdp(dp->panel); + + /* dongle is still connected but sinks are disconnected */ if (dp->link->sink_count == 0) { /* * irq_hpd with sink_count = 0 * hdmi unplugged out of dongle */ - msm_dp_ctrl_off_link_stream(dp->ctrl); + + /* set dongle to D3 (power off) mode */ + msm_dp_link_psm_config(dp->link, &dp->panel->link_info, true); + msm_dp_ctrl_off(dp->ctrl); + /* re-init the PHY so that we can listen to Dongle disconnect */ + msm_dp_ctrl_reinit_phy(dp->ctrl); } else { /* * unplugged interrupt From 2f0e61b6c592ded91a0a3544872c98b69bf33fba Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Tue, 28 Jul 2026 18:21:39 +0800 Subject: [PATCH 097/641] drm/msm/dp: allow dp_ctrl stream APIs to use any panel passed to it With MST, multiple sinks share a single DP controller, so a cached panel in msm_dp_ctrl_private can no longer represent the per-stream sink. Drop the cache and pass panel explicitly to all stream-related dp_ctrl APIs. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/742745/ Link: https://lore.kernel.org/r/20260728-dp_mstclean-v9-10-f7779fce10f4@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 201 ++++++++++++++++------------ drivers/gpu/drm/msm/dp/dp_ctrl.h | 28 ++-- drivers/gpu/drm/msm/dp/dp_display.c | 26 ++-- 3 files changed, 143 insertions(+), 112 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index 272ac3b25b13..9bed5a2fd2e7 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -114,7 +114,6 @@ struct msm_dp_ctrl_private { struct drm_device *drm_dev; struct device *dev; struct drm_dp_aux *aux; - struct msm_dp_panel *panel; struct msm_dp_link *link; void __iomem *ahb_base; void __iomem *link_base; @@ -202,7 +201,8 @@ static int msm_dp_aux_link_configure(struct drm_dp_aux *aux, /* * NOTE: resetting DP controller will also clear any pending HPD related interrupts */ -void msm_dp_ctrl_reset(struct msm_dp_ctrl *msm_dp_ctrl) +void msm_dp_ctrl_reset(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel) { struct msm_dp_ctrl_private *ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); @@ -217,10 +217,9 @@ void msm_dp_ctrl_reset(struct msm_dp_ctrl *msm_dp_ctrl) sw_reset &= ~DP_SW_RESET; msm_dp_write_ahb(ctrl, REG_DP_SW_RESET, sw_reset); - if (!ctrl->hw_revision) { + if (!ctrl->hw_revision) ctrl->hw_revision = msm_dp_read_ahb(ctrl, REG_DP_HW_VERSION); - ctrl->panel->hw_revision = ctrl->hw_revision; - } + panel->hw_revision = ctrl->hw_revision; } static u32 msm_dp_ctrl_get_aux_interrupt(struct msm_dp_ctrl_private *ctrl) @@ -418,10 +417,11 @@ static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl, msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config); } -static void msm_dp_ctrl_config_ctrl_link(struct msm_dp_ctrl_private *ctrl) +static void msm_dp_ctrl_config_ctrl_link(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel) { u32 config = 0; - const u8 *dpcd = ctrl->panel->dpcd; + const u8 *dpcd = panel->dpcd; /* Default-> LSCLK DIV: 1/4 LCLK */ config |= (2 << DP_CONFIGURATION_CTRL_LSCLK_DIV_SHIFT); @@ -484,13 +484,14 @@ static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl, msm_dp_write_link(ctrl, REG_DP_MISC1_MISC0, misc_val); } -static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl) +static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel) { - msm_dp_ctrl_config_ctrl_streams(ctrl, ctrl->panel); + msm_dp_ctrl_config_ctrl_streams(ctrl, panel); - msm_dp_ctrl_config_misc1_misc0(ctrl, ctrl->panel); + msm_dp_ctrl_config_misc1_misc0(ctrl, panel); - msm_dp_panel_timing_cfg(ctrl->panel, ctrl->msm_dp_ctrl.wide_bus_en); + msm_dp_panel_timing_cfg(panel, ctrl->msm_dp_ctrl.wide_bus_en); } /* @@ -1260,20 +1261,21 @@ tu_size_calc: } static void msm_dp_ctrl_calc_tu_parameters(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel, struct msm_dp_vc_tu_mapping_table *tu_table) { struct msm_dp_tu_calc_input in; - struct drm_display_mode *drm_mode; + const struct drm_display_mode *drm_mode; - drm_mode = &ctrl->panel->msm_dp_mode.drm_mode; + drm_mode = &panel->msm_dp_mode.drm_mode; in.lclk = ctrl->link->link_params.rate / 1000; in.pclk_khz = drm_mode->clock; in.hactive = drm_mode->hdisplay; in.hporch = drm_mode->htotal - drm_mode->hdisplay; in.nlanes = ctrl->link->link_params.num_lanes; - in.bpp = ctrl->panel->msm_dp_mode.bpp; - in.pixel_enc = ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420 ? 420 : 444; + in.bpp = panel->msm_dp_mode.bpp; + in.pixel_enc = panel->msm_dp_mode.out_fmt_is_yuv_420 ? 420 : 444; in.dsc_en = 0; in.async_en = 0; in.fec_en = 0; @@ -1283,14 +1285,15 @@ static void msm_dp_ctrl_calc_tu_parameters(struct msm_dp_ctrl_private *ctrl, _dp_ctrl_calc_tu(ctrl, &in, tu_table); } -static void msm_dp_ctrl_setup_tr_unit(struct msm_dp_ctrl_private *ctrl) +static void msm_dp_ctrl_setup_tr_unit(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel) { u32 msm_dp_tu = 0x0; u32 valid_boundary = 0x0; u32 valid_boundary2 = 0x0; struct msm_dp_vc_tu_mapping_table tu_calc_table; - msm_dp_ctrl_calc_tu_parameters(ctrl, &tu_calc_table); + msm_dp_ctrl_calc_tu_parameters(ctrl, panel, &tu_calc_table); msm_dp_tu |= tu_calc_table.tu_size_minus1; valid_boundary |= tu_calc_table.valid_boundary_link; @@ -1442,6 +1445,7 @@ static int msm_dp_ctrl_set_pattern_state_bit(struct msm_dp_ctrl_private *ctrl, } static int msm_dp_ctrl_link_train_1(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel, int *training_step, enum drm_dp_phy dp_phy) { int delay_us; @@ -1450,7 +1454,7 @@ static int msm_dp_ctrl_link_train_1(struct msm_dp_ctrl_private *ctrl, int const maximum_retries = 4; delay_us = drm_dp_read_clock_recovery_delay(ctrl->aux, - ctrl->panel->dpcd, dp_phy, false); + panel->dpcd, dp_phy, false); msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0); @@ -1536,14 +1540,15 @@ static int msm_dp_ctrl_link_rate_down_shift(struct msm_dp_ctrl_private *ctrl) return ret; } -static int msm_dp_ctrl_link_lane_down_shift(struct msm_dp_ctrl_private *ctrl) +static int msm_dp_ctrl_link_lane_down_shift(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel) { if (ctrl->link->link_params.num_lanes == 1) return -1; ctrl->link->link_params.num_lanes /= 2; - ctrl->link->link_params.rate = ctrl->panel->link_info.rate; + ctrl->link->link_params.rate = panel->link_info.rate; ctrl->link->phy_params.p_level = 0; ctrl->link->phy_params.v_level = 0; @@ -1552,6 +1557,7 @@ static int msm_dp_ctrl_link_lane_down_shift(struct msm_dp_ctrl_private *ctrl) } static void msm_dp_ctrl_clear_training_pattern(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel, enum drm_dp_phy dp_phy) { int delay_us; @@ -1559,11 +1565,12 @@ static void msm_dp_ctrl_clear_training_pattern(struct msm_dp_ctrl_private *ctrl, msm_dp_ctrl_train_pattern_set(ctrl, DP_TRAINING_PATTERN_DISABLE, dp_phy); delay_us = drm_dp_read_channel_eq_delay(ctrl->aux, - ctrl->panel->dpcd, dp_phy, false); + panel->dpcd, dp_phy, false); fsleep(delay_us); } static int msm_dp_ctrl_link_train_2(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel, int *training_step, enum drm_dp_phy dp_phy) { int delay_us; @@ -1574,16 +1581,16 @@ static int msm_dp_ctrl_link_train_2(struct msm_dp_ctrl_private *ctrl, u8 link_status[DP_LINK_STATUS_SIZE]; delay_us = drm_dp_read_channel_eq_delay(ctrl->aux, - ctrl->panel->dpcd, dp_phy, false); + panel->dpcd, dp_phy, false); msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0); *training_step = DP_TRAINING_2; - if (drm_dp_tps4_supported(ctrl->panel->dpcd)) { + if (drm_dp_tps4_supported(panel->dpcd)) { pattern = DP_TRAINING_PATTERN_4; state_ctrl_bit = 4; - } else if (drm_dp_tps3_supported(ctrl->panel->dpcd)) { + } else if (drm_dp_tps3_supported(panel->dpcd)) { pattern = DP_TRAINING_PATTERN_3; state_ctrl_bit = 3; } else { @@ -1620,18 +1627,19 @@ static int msm_dp_ctrl_link_train_2(struct msm_dp_ctrl_private *ctrl, } static int msm_dp_ctrl_link_train_1_2(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel, int *training_step, enum drm_dp_phy dp_phy) { int ret; - ret = msm_dp_ctrl_link_train_1(ctrl, training_step, dp_phy); + ret = msm_dp_ctrl_link_train_1(ctrl, panel, training_step, dp_phy); if (ret) { DRM_ERROR("link training #1 on phy %d failed. ret=%d\n", dp_phy, ret); return ret; } drm_dbg_dp(ctrl->drm_dev, "link training #1 on phy %d successful\n", dp_phy); - ret = msm_dp_ctrl_link_train_2(ctrl, training_step, dp_phy); + ret = msm_dp_ctrl_link_train_2(ctrl, panel, training_step, dp_phy); if (ret) { DRM_ERROR("link training #2 on phy %d failed. ret=%d\n", dp_phy, ret); return ret; @@ -1642,17 +1650,18 @@ static int msm_dp_ctrl_link_train_1_2(struct msm_dp_ctrl_private *ctrl, } static int msm_dp_ctrl_link_train(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel, int *training_step) { int i; int ret = 0; - const u8 *dpcd = ctrl->panel->dpcd; + const u8 *dpcd = panel->dpcd; u8 encoding[] = { 0, DP_SET_ANSI_8B10B }; u8 assr; struct msm_dp_link_info link_info = {0}; - msm_dp_ctrl_config_ctrl_link(ctrl); - msm_dp_ctrl_config_ctrl_streams(ctrl, ctrl->panel); + msm_dp_ctrl_config_ctrl_link(ctrl, panel); + msm_dp_ctrl_config_ctrl_streams(ctrl, panel); link_info.num_lanes = ctrl->link->link_params.num_lanes; link_info.rate = ctrl->link->link_params.rate; @@ -1675,8 +1684,8 @@ static int msm_dp_ctrl_link_train(struct msm_dp_ctrl_private *ctrl, for (i = ctrl->link->lttpr_count - 1; i >= 0; i--) { enum drm_dp_phy dp_phy = DP_PHY_LTTPR(i); - ret = msm_dp_ctrl_link_train_1_2(ctrl, training_step, dp_phy); - msm_dp_ctrl_clear_training_pattern(ctrl, dp_phy); + ret = msm_dp_ctrl_link_train_1_2(ctrl, panel, training_step, dp_phy); + msm_dp_ctrl_clear_training_pattern(ctrl, panel, dp_phy); if (ret) break; @@ -1687,7 +1696,7 @@ static int msm_dp_ctrl_link_train(struct msm_dp_ctrl_private *ctrl, goto end; } - ret = msm_dp_ctrl_link_train_1_2(ctrl, training_step, DP_PHY_DPRX); + ret = msm_dp_ctrl_link_train_1_2(ctrl, panel, training_step, DP_PHY_DPRX); if (ret) { DRM_ERROR("link training on sink failed. ret=%d\n", ret); goto end; @@ -1700,6 +1709,7 @@ end: } static int msm_dp_ctrl_setup_main_link(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel, int *training_step) { int ret = 0; @@ -1715,7 +1725,7 @@ static int msm_dp_ctrl_setup_main_link(struct msm_dp_ctrl_private *ctrl, * a link training pattern, we have to first do soft reset. */ - ret = msm_dp_ctrl_link_train(ctrl, training_step); + ret = msm_dp_ctrl_link_train(ctrl, panel, training_step); return ret; } @@ -1814,11 +1824,12 @@ static void msm_dp_ctrl_link_clk_disable(struct msm_dp_ctrl *msm_dp_ctrl) str_on_off(ctrl->core_clks_on)); } -static int msm_dp_ctrl_enable_mainlink_clocks(struct msm_dp_ctrl_private *ctrl) +static int msm_dp_ctrl_enable_mainlink_clocks(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel) { int ret = 0; struct phy *phy = ctrl->phy; - const u8 *dpcd = ctrl->panel->dpcd; + const u8 *dpcd = panel->dpcd; ctrl->phy_opts.dp.lanes = ctrl->link->link_params.num_lanes; ctrl->phy_opts.dp.link_rate = ctrl->link->link_params.rate / 100; @@ -1870,13 +1881,14 @@ static void msm_dp_ctrl_psr_exit(struct msm_dp_ctrl_private *ctrl) msm_dp_write_link(ctrl, REG_PSR_CMD, cmd); } -void msm_dp_ctrl_config_psr(struct msm_dp_ctrl *msm_dp_ctrl) +void msm_dp_ctrl_config_psr(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel) { struct msm_dp_ctrl_private *ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); u32 cfg; - if (!ctrl->panel->psr_cap.version) + if (!panel->psr_cap.version) return; /* enable PSR1 function */ @@ -1891,12 +1903,13 @@ void msm_dp_ctrl_config_psr(struct msm_dp_ctrl *msm_dp_ctrl) drm_dp_dpcd_write(ctrl->aux, DP_PSR_EN_CFG, &cfg, 1); } -void msm_dp_ctrl_set_psr(struct msm_dp_ctrl *msm_dp_ctrl, bool enter) +void msm_dp_ctrl_set_psr(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel, bool enter) { struct msm_dp_ctrl_private *ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); - if (!ctrl->panel->psr_cap.version) + if (!panel->psr_cap.version) return; /* @@ -1966,7 +1979,8 @@ void msm_dp_ctrl_phy_exit(struct msm_dp_ctrl *msm_dp_ctrl) phy_exit(phy); } -static int msm_dp_ctrl_reinitialize_mainlink(struct msm_dp_ctrl_private *ctrl) +static int msm_dp_ctrl_reinitialize_mainlink(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel) { struct phy *phy = ctrl->phy; int ret = 0; @@ -1987,7 +2001,7 @@ static int msm_dp_ctrl_reinitialize_mainlink(struct msm_dp_ctrl_private *ctrl) /* hw recommended delay before re-enabling clocks */ msleep(20); - ret = msm_dp_ctrl_enable_mainlink_clocks(ctrl); + ret = msm_dp_ctrl_enable_mainlink_clocks(ctrl, panel); if (ret) { DRM_ERROR("Failed to enable mainlink clks. ret=%d\n", ret); return ret; @@ -1996,7 +2010,8 @@ static int msm_dp_ctrl_reinitialize_mainlink(struct msm_dp_ctrl_private *ctrl) return ret; } -static int msm_dp_ctrl_deinitialize_mainlink(struct msm_dp_ctrl_private *ctrl) +static int msm_dp_ctrl_deinitialize_mainlink(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel) { struct phy *phy; @@ -2004,7 +2019,7 @@ static int msm_dp_ctrl_deinitialize_mainlink(struct msm_dp_ctrl_private *ctrl) msm_dp_ctrl_mainlink_disable(ctrl); - msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl); + msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl, panel); dev_pm_opp_set_rate(ctrl->dev, 0); msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl); @@ -2018,7 +2033,8 @@ static int msm_dp_ctrl_deinitialize_mainlink(struct msm_dp_ctrl_private *ctrl) return 0; } -static int msm_dp_ctrl_link_maintenance(struct msm_dp_ctrl_private *ctrl) +static int msm_dp_ctrl_link_maintenance(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel) { int ret = 0; int training_step = DP_TRAINING_NONE; @@ -2028,11 +2044,11 @@ static int msm_dp_ctrl_link_maintenance(struct msm_dp_ctrl_private *ctrl) ctrl->link->phy_params.p_level = 0; ctrl->link->phy_params.v_level = 0; - ret = msm_dp_ctrl_setup_main_link(ctrl, &training_step); + ret = msm_dp_ctrl_setup_main_link(ctrl, panel, &training_step); if (ret) goto end; - msm_dp_ctrl_clear_training_pattern(ctrl, DP_PHY_DPRX); + msm_dp_ctrl_clear_training_pattern(ctrl, panel, DP_PHY_DPRX); msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO); @@ -2211,7 +2227,8 @@ static void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl) } } -static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl) +static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel) { int ret; unsigned long pixel_rate; @@ -2227,15 +2244,15 @@ static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl * running. Add the global reset just before disabling the * link clocks and core clocks. */ - msm_dp_ctrl_off(&ctrl->msm_dp_ctrl); + msm_dp_ctrl_off(&ctrl->msm_dp_ctrl, panel); - ret = msm_dp_ctrl_on_link(&ctrl->msm_dp_ctrl); + ret = msm_dp_ctrl_on_link(&ctrl->msm_dp_ctrl, panel); if (ret) { DRM_ERROR("failed to enable DP link controller\n"); return ret; } - pixel_rate = ctrl->panel->msm_dp_mode.drm_mode.clock; + pixel_rate = panel->msm_dp_mode.drm_mode.clock; ret = msm_dp_ctrl_on_pixel_clk(ctrl, pixel_rate); if (ret) return ret; @@ -2245,7 +2262,8 @@ static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl return 0; } -void msm_dp_ctrl_handle_sink_request(struct msm_dp_ctrl *msm_dp_ctrl) +void msm_dp_ctrl_handle_sink_request(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel) { struct msm_dp_ctrl_private *ctrl; u32 sink_request = 0x0; @@ -2260,14 +2278,14 @@ void msm_dp_ctrl_handle_sink_request(struct msm_dp_ctrl *msm_dp_ctrl) if (sink_request & DP_TEST_LINK_PHY_TEST_PATTERN) { drm_dbg_dp(ctrl->drm_dev, "PHY_TEST_PATTERN request\n"); - if (msm_dp_ctrl_process_phy_test_request(ctrl)) { + if (msm_dp_ctrl_process_phy_test_request(ctrl, panel)) { DRM_ERROR("process phy_test_req failed\n"); return; } } if (sink_request & DP_LINK_STATUS_UPDATED) { - if (msm_dp_ctrl_link_maintenance(ctrl)) { + if (msm_dp_ctrl_link_maintenance(ctrl, panel)) { DRM_ERROR("LM failed: TEST_LINK_TRAINING\n"); return; } @@ -2275,7 +2293,7 @@ void msm_dp_ctrl_handle_sink_request(struct msm_dp_ctrl *msm_dp_ctrl) if (sink_request & DP_TEST_LINK_TRAINING) { msm_dp_link_send_test_response(ctrl->link); - if (msm_dp_ctrl_link_maintenance(ctrl)) { + if (msm_dp_ctrl_link_maintenance(ctrl, panel)) { DRM_ERROR("LM failed: TEST_LINK_TRAINING\n"); return; } @@ -2311,7 +2329,8 @@ static bool msm_dp_ctrl_channel_eq_ok(struct msm_dp_ctrl_private *ctrl) return drm_dp_channel_eq_ok(link_status, num_lanes); } -int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl) +int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel) { int rc = 0; struct msm_dp_ctrl_private *ctrl; @@ -2327,8 +2346,8 @@ int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl) ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); - rate = ctrl->panel->link_info.rate; - pixel_rate = ctrl->panel->msm_dp_mode.drm_mode.clock; + rate = panel->link_info.rate; + pixel_rate = panel->msm_dp_mode.drm_mode.clock; msm_dp_ctrl_core_clk_enable(&ctrl->msm_dp_ctrl); @@ -2340,8 +2359,8 @@ int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl) } else { ctrl->link->link_params.rate = rate; ctrl->link->link_params.num_lanes = - ctrl->panel->link_info.num_lanes; - if (ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420) + panel->link_info.num_lanes; + if (panel->msm_dp_mode.out_fmt_is_yuv_420) pixel_rate >>= 1; } @@ -2349,13 +2368,13 @@ int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl) ctrl->link->link_params.rate, ctrl->link->link_params.num_lanes, pixel_rate); - rc = msm_dp_ctrl_enable_mainlink_clocks(ctrl); + rc = msm_dp_ctrl_enable_mainlink_clocks(ctrl, panel); if (rc) return rc; while (--link_train_max_retries) { training_step = DP_TRAINING_NONE; - rc = msm_dp_ctrl_setup_main_link(ctrl, &training_step); + rc = msm_dp_ctrl_setup_main_link(ctrl, panel, &training_step); if (rc == 0) { /* training completed successfully */ break; @@ -2374,7 +2393,7 @@ int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl) * some lanes are ready, * reduce lane number */ - rc = msm_dp_ctrl_link_lane_down_shift(ctrl); + rc = msm_dp_ctrl_link_lane_down_shift(ctrl, panel); if (rc < 0) { /* lane == 1 already */ /* end with failure */ break; @@ -2395,7 +2414,7 @@ int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl) ctrl->link->link_params.num_lanes)) rc = msm_dp_ctrl_link_rate_down_shift(ctrl); else - rc = msm_dp_ctrl_link_lane_down_shift(ctrl); + rc = msm_dp_ctrl_link_lane_down_shift(ctrl, panel); if (rc < 0) { /* end with failure */ @@ -2403,10 +2422,10 @@ int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl) } /* stop link training before start re training */ - msm_dp_ctrl_clear_training_pattern(ctrl, DP_PHY_DPRX); + msm_dp_ctrl_clear_training_pattern(ctrl, panel, DP_PHY_DPRX); } - rc = msm_dp_ctrl_reinitialize_mainlink(ctrl); + rc = msm_dp_ctrl_reinitialize_mainlink(ctrl, panel); if (rc) { DRM_ERROR("Failed to reinitialize mainlink. rc=%d\n", rc); break; @@ -2427,20 +2446,21 @@ int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl) * link training failed * end txing train pattern here */ - msm_dp_ctrl_clear_training_pattern(ctrl, DP_PHY_DPRX); + msm_dp_ctrl_clear_training_pattern(ctrl, panel, DP_PHY_DPRX); - msm_dp_ctrl_deinitialize_mainlink(ctrl); + msm_dp_ctrl_deinitialize_mainlink(ctrl, panel); rc = -ECONNRESET; } return rc; } -static int msm_dp_ctrl_link_retrain(struct msm_dp_ctrl_private *ctrl) +static int msm_dp_ctrl_link_retrain(struct msm_dp_ctrl_private *ctrl, + struct msm_dp_panel *panel) { int training_step = DP_TRAINING_NONE; - return msm_dp_ctrl_setup_main_link(ctrl, &training_step); + return msm_dp_ctrl_setup_main_link(ctrl, panel, &training_step); } static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl, @@ -2511,7 +2531,9 @@ static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl, msm_dp_write_link(ctrl, REG_DP_SOFTWARE_NVID, nvid); } -int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train) +int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel, + bool force_link_train) { int ret = 0; struct msm_dp_ctrl_private *ctrl; @@ -2530,7 +2552,7 @@ int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_li ctrl->core_clks_on, ctrl->link_clks_on, ctrl->stream_clks_on); if (!ctrl->link_clks_on) { /* link clk is off */ - ret = msm_dp_ctrl_enable_mainlink_clocks(ctrl); + ret = msm_dp_ctrl_enable_mainlink_clocks(ctrl, panel); if (ret) { DRM_ERROR("Failed to start link clocks. ret=%d\n", ret); return ret; @@ -2538,15 +2560,15 @@ int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_li } if (force_link_train || !msm_dp_ctrl_channel_eq_ok(ctrl)) - msm_dp_ctrl_link_retrain(ctrl); + msm_dp_ctrl_link_retrain(ctrl, panel); /* stop txing train pattern to end link training */ - msm_dp_ctrl_clear_training_pattern(ctrl, DP_PHY_DPRX); + msm_dp_ctrl_clear_training_pattern(ctrl, panel, DP_PHY_DPRX); return ret; } -int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl) +int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel) { int ret = 0; bool mainlink_ready = false; @@ -2559,10 +2581,10 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl) ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); - pixel_rate_orig = ctrl->panel->msm_dp_mode.drm_mode.clock; + pixel_rate_orig = panel->msm_dp_mode.drm_mode.clock; pixel_rate = pixel_rate_orig; - if (msm_dp_ctrl->wide_bus_en || ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420) + if (msm_dp_ctrl->wide_bus_en || panel->msm_dp_mode.out_fmt_is_yuv_420) pixel_rate >>= 1; drm_dbg_dp(ctrl->drm_dev, "pixel_rate=%lu\n", pixel_rate); @@ -2579,18 +2601,18 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl) msm_dp_ctrl_lane_mapping(ctrl); msm_dp_setup_peripheral_flush(ctrl); - msm_dp_ctrl_config_ctrl_link(ctrl); + msm_dp_ctrl_config_ctrl_link(ctrl, panel); - msm_dp_ctrl_configure_source_params(ctrl); + msm_dp_ctrl_configure_source_params(ctrl, panel); msm_dp_ctrl_config_msa(ctrl, ctrl->link->link_params.rate, pixel_rate_orig, - ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420); + panel->msm_dp_mode.out_fmt_is_yuv_420); - msm_dp_panel_clear_dsc_dto(ctrl->panel); + msm_dp_panel_clear_dsc_dto(panel); - msm_dp_ctrl_setup_tr_unit(ctrl); + msm_dp_ctrl_setup_tr_unit(ctrl, panel); msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO); @@ -2618,7 +2640,8 @@ void msm_dp_ctrl_reinit_phy(struct msm_dp_ctrl *msm_dp_ctrl) phy_init(phy); } -void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl) +void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel) { struct msm_dp_ctrl_private *ctrl; struct phy *phy; @@ -2626,11 +2649,11 @@ void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl) ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); phy = ctrl->phy; - msm_dp_panel_disable_vsc_sdp(ctrl->panel); + msm_dp_panel_disable_vsc_sdp(panel); msm_dp_ctrl_mainlink_disable(ctrl); - msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl); + msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl, panel); msm_dp_ctrl_off_pixel_clk(msm_dp_ctrl); dev_pm_opp_set_rate(ctrl->dev, 0); @@ -2639,7 +2662,8 @@ void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl) phy_power_off(phy); } -irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl) +irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel) { struct msm_dp_ctrl_private *ctrl; u32 isr; @@ -2650,7 +2674,7 @@ irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl) ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); - if (ctrl->panel->psr_cap.version) { + if (panel->psr_cap.version) { isr = msm_dp_ctrl_get_psr_interrupt(ctrl); if (isr) @@ -2739,7 +2763,7 @@ static int msm_dp_ctrl_clk_init(struct msm_dp_ctrl *msm_dp_ctrl) } struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link, - struct msm_dp_panel *panel, struct drm_dp_aux *aux, + struct drm_dp_aux *aux, struct phy *phy, void __iomem *ahb_base, void __iomem *link_base) @@ -2747,7 +2771,7 @@ struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link struct msm_dp_ctrl_private *ctrl; int ret; - if (!dev || !panel || !aux || !link) { + if (!dev || !aux || !link) { DRM_ERROR("invalid input\n"); return ERR_PTR(-EINVAL); } @@ -2775,7 +2799,6 @@ struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link init_completion(&ctrl->video_comp); /* in parameters */ - ctrl->panel = panel; ctrl->aux = aux; ctrl->link = link; ctrl->dev = dev; diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h index 5d615f50d13b..00b430392a52 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.h +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h @@ -16,28 +16,36 @@ struct msm_dp_ctrl { struct phy; -int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl); -int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl); -int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train); -void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl); +int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel); +int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel); +int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel, + bool force_link_train); +void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel); void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl); -irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl); -void msm_dp_ctrl_handle_sink_request(struct msm_dp_ctrl *msm_dp_ctrl); +irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel); +void msm_dp_ctrl_handle_sink_request(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel); struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link, - struct msm_dp_panel *panel, struct drm_dp_aux *aux, struct phy *phy, void __iomem *ahb_base, void __iomem *link_base); -void msm_dp_ctrl_reset(struct msm_dp_ctrl *msm_dp_ctrl); +void msm_dp_ctrl_reset(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel); void msm_dp_ctrl_phy_init(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_phy_exit(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_irq_phy_exit(struct msm_dp_ctrl *msm_dp_ctrl); -void msm_dp_ctrl_set_psr(struct msm_dp_ctrl *msm_dp_ctrl, bool enable); -void msm_dp_ctrl_config_psr(struct msm_dp_ctrl *msm_dp_ctrl); +void msm_dp_ctrl_set_psr(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel, bool enable); +void msm_dp_ctrl_config_psr(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel); int msm_dp_ctrl_core_clk_enable(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_core_clk_disable(struct msm_dp_ctrl *msm_dp_ctrl); diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index f8f8f3889ea5..26810b8c3fff 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -359,7 +359,7 @@ static void msm_dp_display_host_init(struct msm_dp_display_private *dp) dp->phy_initialized); msm_dp_ctrl_core_clk_enable(dp->ctrl); - msm_dp_ctrl_reset(dp->ctrl); + msm_dp_ctrl_reset(dp->ctrl, dp->panel); msm_dp_ctrl_enable_irq(dp->ctrl); msm_dp_aux_init(dp->aux); dp->core_initialized = true; @@ -371,7 +371,7 @@ static void msm_dp_display_host_deinit(struct msm_dp_display_private *dp) dp->msm_dp_display.connector_type, dp->core_initialized, dp->phy_initialized); - msm_dp_ctrl_reset(dp->ctrl); + msm_dp_ctrl_reset(dp->ctrl, dp->panel); msm_dp_ctrl_disable_irq(dp->ctrl); msm_dp_aux_deinit(dp->aux); msm_dp_ctrl_core_clk_disable(dp->ctrl); @@ -392,7 +392,7 @@ static int msm_dp_display_handle_irq_hpd(struct msm_dp_display_private *dp) drm_dbg_dp(dp->drm_dev, "%d\n", sink_request); - msm_dp_ctrl_handle_sink_request(dp->ctrl); + msm_dp_ctrl_handle_sink_request(dp->ctrl, dp->panel); if (sink_request & DP_TEST_LINK_VIDEO_PATTERN) msm_dp_display_handle_video_request(dp); @@ -570,8 +570,8 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp) goto error_link; } - dp->ctrl = msm_dp_ctrl_get(dev, dp->link, dp->panel, dp->aux, - phy, dp->ahb_base, dp->link_base); + dp->ctrl = msm_dp_ctrl_get(dev, dp->link, dp->aux, + phy, dp->ahb_base, dp->link_base); if (IS_ERR(dp->ctrl)) { rc = PTR_ERR(dp->ctrl); DRM_ERROR("failed to initialize ctrl, rc = %d\n", rc); @@ -642,14 +642,14 @@ static int msm_dp_display_prepare_link(struct msm_dp_display_private *dp) force_link_train = true; } - rc = msm_dp_ctrl_on_link(dp->ctrl); + rc = msm_dp_ctrl_on_link(dp->ctrl, dp->panel); if (rc) { DRM_ERROR("Failed link training (rc=%d)\n", rc); // TODO: schedule drm_connector_set_link_status_property() return rc; } - return msm_dp_ctrl_prepare_stream_on(dp->ctrl, force_link_train); + return msm_dp_ctrl_prepare_stream_on(dp->ctrl, dp->panel, force_link_train); } static int msm_dp_display_enable(struct msm_dp_display_private *dp) @@ -663,7 +663,7 @@ static int msm_dp_display_enable(struct msm_dp_display_private *dp) return 0; } - rc = msm_dp_ctrl_on_stream(dp->ctrl); + rc = msm_dp_ctrl_on_stream(dp->ctrl, dp->panel); if (!rc) msm_dp_display->power_on = true; @@ -688,7 +688,7 @@ static int msm_dp_display_post_enable(struct msm_dp *msm_dp_display) msm_dp_display_handle_plugged_change(msm_dp_display, true); if (msm_dp_display->psr_supported) - msm_dp_ctrl_config_psr(dp->ctrl); + msm_dp_ctrl_config_psr(dp->ctrl, dp->panel); return 0; } @@ -727,7 +727,7 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp) /* set dongle to D3 (power off) mode */ msm_dp_link_psm_config(dp->link, &dp->panel->link_info, true); - msm_dp_ctrl_off(dp->ctrl); + msm_dp_ctrl_off(dp->ctrl, dp->panel); /* re-init the PHY so that we can listen to Dongle disconnect */ msm_dp_ctrl_reinit_phy(dp->ctrl); } else { @@ -735,7 +735,7 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp) * unplugged interrupt * dongle unplugged out of DUT */ - msm_dp_ctrl_off(dp->ctrl); + msm_dp_ctrl_off(dp->ctrl, dp->panel); msm_dp_display_host_phy_exit(dp); } @@ -879,7 +879,7 @@ void msm_dp_display_set_psr(struct msm_dp *msm_dp_display, bool enter) } dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); - msm_dp_ctrl_set_psr(dp->ctrl, enter); + msm_dp_ctrl_set_psr(dp->ctrl, dp->panel, enter); } /** @@ -989,7 +989,7 @@ static irqreturn_t msm_dp_display_irq_handler(int irq, void *dev_id) } /* DP controller isr */ - ret |= msm_dp_ctrl_isr(dp->ctrl); + ret |= msm_dp_ctrl_isr(dp->ctrl, dp->panel); return ret; } From a57a81926277e9d054b0a8af034bb6a7ccdcfbf7 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Tue, 28 Jul 2026 18:21:40 +0800 Subject: [PATCH 098/641] drm/msm/dp: split dp_ctrl_off() into stream and link parts Split dp_ctrl_off() into stream and link parts so that for MST cases we can control the link and pixel parts separately. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/742746/ Link: https://lore.kernel.org/r/20260728-dp_mstclean-v9-11-f7779fce10f4@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 10 +++++----- drivers/gpu/drm/msm/dp/dp_ctrl.h | 5 +++-- drivers/gpu/drm/msm/dp/dp_display.c | 7 ++++--- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index 9bed5a2fd2e7..5d896593771e 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -2215,7 +2215,7 @@ static int msm_dp_ctrl_on_pixel_clk(struct msm_dp_ctrl_private *ctrl, unsigned l return ret; } -static void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl) +void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl) { struct msm_dp_ctrl_private *ctrl; @@ -2244,7 +2244,8 @@ static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl * running. Add the global reset just before disabling the * link clocks and core clocks. */ - msm_dp_ctrl_off(&ctrl->msm_dp_ctrl, panel); + msm_dp_ctrl_off_pixel_clk(&ctrl->msm_dp_ctrl); + msm_dp_ctrl_off_link(&ctrl->msm_dp_ctrl, panel); ret = msm_dp_ctrl_on_link(&ctrl->msm_dp_ctrl, panel); if (ret) { @@ -2640,8 +2641,8 @@ void msm_dp_ctrl_reinit_phy(struct msm_dp_ctrl *msm_dp_ctrl) phy_init(phy); } -void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl, - struct msm_dp_panel *panel) +void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel) { struct msm_dp_ctrl_private *ctrl; struct phy *phy; @@ -2655,7 +2656,6 @@ void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl, msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl, panel); - msm_dp_ctrl_off_pixel_clk(msm_dp_ctrl); dev_pm_opp_set_rate(ctrl->dev, 0); msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl); diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h index 00b430392a52..5902cf7e746a 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.h +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h @@ -22,8 +22,9 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel * int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel, bool force_link_train); -void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl, - struct msm_dp_panel *panel); +void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl, + struct msm_dp_panel *panel); +void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl); irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel); diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 26810b8c3fff..44f4c2312fc3 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -727,15 +727,16 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp) /* set dongle to D3 (power off) mode */ msm_dp_link_psm_config(dp->link, &dp->panel->link_info, true); - msm_dp_ctrl_off(dp->ctrl, dp->panel); - /* re-init the PHY so that we can listen to Dongle disconnect */ + msm_dp_ctrl_off_pixel_clk(dp->ctrl); + msm_dp_ctrl_off_link(dp->ctrl, dp->panel); msm_dp_ctrl_reinit_phy(dp->ctrl); } else { /* * unplugged interrupt * dongle unplugged out of DUT */ - msm_dp_ctrl_off(dp->ctrl, dp->panel); + msm_dp_ctrl_off_pixel_clk(dp->ctrl); + msm_dp_ctrl_off_link(dp->ctrl, dp->panel); msm_dp_display_host_phy_exit(dp); } From 894a6e16abbec9a523078dbd5f4701fe3c11c283 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 28 Jul 2026 18:21:41 +0800 Subject: [PATCH 099/641] drm/msm/dp: simplify link and clock disable sequence Move the common disable steps out of the sink_count check to make the flow easier to follow. No functional change intended. Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/742749/ Link: https://lore.kernel.org/r/20260728-dp_mstclean-v9-12-f7779fce10f4@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_display.c | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 44f4c2312fc3..b4e1feead765 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -718,27 +718,19 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp) msm_dp_panel_disable_vsc_sdp(dp->panel); - /* dongle is still connected but sinks are disconnected */ - if (dp->link->sink_count == 0) { - /* - * irq_hpd with sink_count = 0 - * hdmi unplugged out of dongle - */ + msm_dp_ctrl_off_pixel_clk(dp->ctrl); - /* set dongle to D3 (power off) mode */ + /* dongle is still connected but sinks are disconnected */ + if (dp->link->sink_count == 0) msm_dp_link_psm_config(dp->link, &dp->panel->link_info, true); - msm_dp_ctrl_off_pixel_clk(dp->ctrl); - msm_dp_ctrl_off_link(dp->ctrl, dp->panel); + + msm_dp_ctrl_off_link(dp->ctrl, dp->panel); + + if (dp->link->sink_count == 0) + /* re-init the PHY so that we can listen to Dongle disconnect */ msm_dp_ctrl_reinit_phy(dp->ctrl); - } else { - /* - * unplugged interrupt - * dongle unplugged out of DUT - */ - msm_dp_ctrl_off_pixel_clk(dp->ctrl); - msm_dp_ctrl_off_link(dp->ctrl, dp->panel); + else msm_dp_display_host_phy_exit(dp); - } msm_dp_display->power_on = false; From e8d91d17f73bf3ba314bbc2953dc3f9551b48770 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Tue, 28 Jul 2026 18:21:42 +0800 Subject: [PATCH 100/641] drm/msm/dp: make bridge helpers use dp_display to allow re-use dp_bridge helpers take drm_bridge as an input and extract the dp_display object to be used in the dp_display module. Rather than doing it in a roundabout way, directly pass the dp_display object to these helpers so that the MST bridge can also re-use the same helpers. Signed-off-by: Abhinav Kumar Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/742750/ Link: https://lore.kernel.org/r/20260728-dp_mstclean-v9-13-f7779fce10f4@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_display.c | 41 +++++++++--------------- drivers/gpu/drm/msm/dp/dp_display.h | 9 ++++++ drivers/gpu/drm/msm/dp/dp_drm.c | 48 ++++++++++++++++++++++++++++- drivers/gpu/drm/msm/dp/dp_drm.h | 11 ------- 4 files changed, 70 insertions(+), 39 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index b4e1feead765..66990493917f 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -740,25 +740,22 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp) /** * msm_dp_bridge_mode_valid - callback to determine if specified mode is valid - * @bridge: Pointer to drm bridge structure + * @dp: Pointer to dp display structure * @info: display info * @mode: Pointer to drm mode structure * Returns: Validity status for specified mode */ -enum drm_mode_status msm_dp_bridge_mode_valid(struct drm_bridge *bridge, - const struct drm_display_info *info, - const struct drm_display_mode *mode) +enum drm_mode_status msm_dp_display_mode_valid(struct msm_dp *dp, + const struct drm_display_info *info, + const struct drm_display_mode *mode) { const u32 num_components = 3, default_bpp = 24; struct msm_dp_display_private *msm_dp_display; struct msm_dp_link_info *link_info; u32 mode_rate_khz = 0, supported_rate_khz = 0, mode_bpp = 0; - struct msm_dp *dp; int mode_pclk_khz = mode->clock; bool is_yuv_420; - dp = to_dp_bridge(bridge)->msm_dp_display; - if (!dp || !mode_pclk_khz || !dp->connector) { DRM_ERROR("invalid params\n"); return -EINVAL; @@ -1404,18 +1401,16 @@ int msm_dp_modeset_init(struct msm_dp *msm_dp_display, struct drm_device *dev, return 0; } -void msm_dp_bridge_atomic_pre_enable(struct drm_bridge *drm_bridge, - struct drm_atomic_commit *state) +void msm_dp_display_atomic_pre_enable(struct msm_dp *msm_dp_display, + struct drm_atomic_commit *state) { - struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(drm_bridge); - struct msm_dp *dp = msm_dp_bridge->msm_dp_display; - struct msm_dp_display_private *msm_dp_display; + struct msm_dp_display_private *dp; struct drm_crtc *crtc; struct drm_crtc_state *crtc_state; - msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); + dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); - crtc = drm_atomic_get_new_crtc_for_encoder(state, drm_bridge->encoder); + crtc = drm_atomic_get_new_crtc_for_encoder(state, msm_dp_display->bridge->encoder); if (!crtc) return; crtc_state = drm_atomic_get_new_crtc_state(state, crtc); @@ -1425,14 +1420,12 @@ void msm_dp_bridge_atomic_pre_enable(struct drm_bridge *drm_bridge, * state and runs before the bridge's .atomic_enable(), so the mode must * be programmed here, in .atomic_pre_enable(). */ - msm_dp_display_set_mode(dp, &crtc_state->adjusted_mode, msm_dp_display->panel); + msm_dp_display_set_mode(msm_dp_display, &crtc_state->adjusted_mode, dp->panel); } -void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge, - struct drm_atomic_commit *state) +void msm_dp_display_atomic_enable(struct msm_dp *msm_dp_display, + struct drm_atomic_commit *state) { - struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(drm_bridge); - struct msm_dp *msm_dp_display = msm_dp_bridge->msm_dp_display; int rc = 0; struct msm_dp_display_private *dp; @@ -1457,11 +1450,8 @@ void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge, drm_dbg_dp(msm_dp_display->drm_dev, "type=%d Done\n", msm_dp_display->connector_type); } -void msm_dp_bridge_atomic_disable(struct drm_bridge *drm_bridge, - struct drm_atomic_commit *state) +void msm_dp_display_atomic_disable(struct msm_dp *dp) { - struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(drm_bridge); - struct msm_dp *dp = msm_dp_bridge->msm_dp_display; struct msm_dp_display_private *msm_dp_display; msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); @@ -1478,11 +1468,8 @@ static void msm_dp_display_unprepare(struct msm_dp_display_private *dp) drm_dbg_dp(dp->drm_dev, "type=%d Done\n", msm_dp_display->connector_type); } -void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge, - struct drm_atomic_commit *state) +void msm_dp_display_atomic_post_disable(struct msm_dp *dp) { - struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(drm_bridge); - struct msm_dp *dp = msm_dp_bridge->msm_dp_display; struct msm_dp_display_private *msm_dp_display; msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h index 0b65e16c790d..83117e7619f3 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.h +++ b/drivers/gpu/drm/msm/dp/dp_display.h @@ -33,5 +33,14 @@ void msm_dp_display_signal_audio_start(struct msm_dp *msm_dp_display); void msm_dp_display_signal_audio_complete(struct msm_dp *msm_dp_display); void msm_dp_display_set_psr(struct msm_dp *dp, bool enter); void msm_dp_display_debugfs_init(struct msm_dp *msm_dp_display, struct dentry *dentry, bool is_edp); +void msm_dp_display_atomic_post_disable(struct msm_dp *dp_display); +void msm_dp_display_atomic_disable(struct msm_dp *dp_display); +void msm_dp_display_atomic_pre_enable(struct msm_dp *dp_display, + struct drm_atomic_commit *state); +void msm_dp_display_atomic_enable(struct msm_dp *dp_display, + struct drm_atomic_commit *state); +enum drm_mode_status msm_dp_display_mode_valid(struct msm_dp *dp, + const struct drm_display_info *info, + const struct drm_display_mode *mode); #endif /* _DP_DISPLAY_H_ */ diff --git a/drivers/gpu/drm/msm/dp/dp_drm.c b/drivers/gpu/drm/msm/dp/dp_drm.c index 794a0df60414..c1e4147bfef1 100644 --- a/drivers/gpu/drm/msm/dp/dp_drm.c +++ b/drivers/gpu/drm/msm/dp/dp_drm.c @@ -49,6 +49,52 @@ static void msm_dp_bridge_debugfs_init(struct drm_bridge *bridge, struct dentry msm_dp_display_debugfs_init(dp, root, false); } +static void msm_dp_bridge_atomic_pre_enable(struct drm_bridge *drm_bridge, + struct drm_atomic_commit *state) +{ + struct msm_dp_bridge *dp_bridge = to_dp_bridge(drm_bridge); + struct msm_dp *dp = dp_bridge->msm_dp_display; + + msm_dp_display_atomic_pre_enable(dp, state); +} + +static void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge, + struct drm_atomic_commit *state) +{ + struct msm_dp_bridge *dp_bridge = to_dp_bridge(drm_bridge); + struct msm_dp *dp = dp_bridge->msm_dp_display; + + msm_dp_display_atomic_enable(dp, state); +} + +static void msm_dp_bridge_atomic_disable(struct drm_bridge *drm_bridge, + struct drm_atomic_commit *state) +{ + struct msm_dp_bridge *dp_bridge = to_dp_bridge(drm_bridge); + struct msm_dp *dp = dp_bridge->msm_dp_display; + + msm_dp_display_atomic_disable(dp); +} + +static void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge, + struct drm_atomic_commit *state) +{ + struct msm_dp_bridge *dp_bridge = to_dp_bridge(drm_bridge); + struct msm_dp *dp = dp_bridge->msm_dp_display; + + msm_dp_display_atomic_post_disable(dp); +} + +static enum drm_mode_status msm_dp_bridge_mode_valid(struct drm_bridge *drm_bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + struct msm_dp_bridge *dp_bridge = to_dp_bridge(drm_bridge); + struct msm_dp *dp = dp_bridge->msm_dp_display; + + return msm_dp_display_mode_valid(dp, info, mode); +} + static const struct drm_bridge_funcs msm_dp_bridge_ops = { .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, @@ -116,7 +162,7 @@ static void msm_edp_bridge_atomic_enable(struct drm_bridge *drm_bridge, return; } - msm_dp_bridge_atomic_enable(drm_bridge, state); + msm_dp_display_atomic_enable(dp, state); } static void msm_edp_bridge_atomic_disable(struct drm_bridge *drm_bridge, diff --git a/drivers/gpu/drm/msm/dp/dp_drm.h b/drivers/gpu/drm/msm/dp/dp_drm.h index 4f733d8118b1..da412c788503 100644 --- a/drivers/gpu/drm/msm/dp/dp_drm.h +++ b/drivers/gpu/drm/msm/dp/dp_drm.h @@ -27,17 +27,6 @@ int msm_dp_bridge_init(struct msm_dp *msm_dp_display, struct drm_device *dev, enum drm_connector_status msm_dp_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connector); -void msm_dp_bridge_atomic_pre_enable(struct drm_bridge *drm_bridge, - struct drm_atomic_commit *state); -void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge, - struct drm_atomic_commit *state); -void msm_dp_bridge_atomic_disable(struct drm_bridge *drm_bridge, - struct drm_atomic_commit *state); -void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge, - struct drm_atomic_commit *state); -enum drm_mode_status msm_dp_bridge_mode_valid(struct drm_bridge *bridge, - const struct drm_display_info *info, - const struct drm_display_mode *mode); void msm_dp_bridge_hpd_enable(struct drm_bridge *bridge); void msm_dp_bridge_hpd_disable(struct drm_bridge *bridge); void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge, From c16ba6ba3da4672713dd809d5b9031f1583848fa Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Tue, 28 Jul 2026 18:21:43 +0800 Subject: [PATCH 101/641] drm/msm/dp: pass panel to display enable/disable helpers Pass struct msm_dp_panel to the display enable/disable helpers to make them easier to reuse for MST stream handling. Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/742752/ Link: https://lore.kernel.org/r/20260728-dp_mstclean-v9-14-f7779fce10f4@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_display.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 66990493917f..bc646d172abe 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -652,7 +652,8 @@ static int msm_dp_display_prepare_link(struct msm_dp_display_private *dp) return msm_dp_ctrl_prepare_stream_on(dp->ctrl, dp->panel, force_link_train); } -static int msm_dp_display_enable(struct msm_dp_display_private *dp) +static int msm_dp_display_enable(struct msm_dp_display_private *dp, + struct msm_dp_panel *msm_dp_panel) { int rc = 0; struct msm_dp *msm_dp_display = &dp->msm_dp_display; @@ -663,7 +664,7 @@ static int msm_dp_display_enable(struct msm_dp_display_private *dp) return 0; } - rc = msm_dp_ctrl_on_stream(dp->ctrl, dp->panel); + rc = msm_dp_ctrl_on_stream(dp->ctrl, msm_dp_panel); if (!rc) msm_dp_display->power_on = true; @@ -709,22 +710,23 @@ static void msm_dp_display_audio_notify_disable(struct msm_dp_display_private *d msm_dp_display->audio_enabled = false; } -static int msm_dp_display_disable(struct msm_dp_display_private *dp) +static int msm_dp_display_disable(struct msm_dp_display_private *dp, + struct msm_dp_panel *msm_dp_panel) { struct msm_dp *msm_dp_display = &dp->msm_dp_display; if (!msm_dp_display->power_on) return 0; - msm_dp_panel_disable_vsc_sdp(dp->panel); + msm_dp_panel_disable_vsc_sdp(msm_dp_panel); msm_dp_ctrl_off_pixel_clk(dp->ctrl); /* dongle is still connected but sinks are disconnected */ if (dp->link->sink_count == 0) - msm_dp_link_psm_config(dp->link, &dp->panel->link_info, true); + msm_dp_link_psm_config(dp->link, &msm_dp_panel->link_info, true); - msm_dp_ctrl_off_link(dp->ctrl, dp->panel); + msm_dp_ctrl_off_link(dp->ctrl, msm_dp_panel); if (dp->link->sink_count == 0) /* re-init the PHY so that we can listen to Dongle disconnect */ @@ -739,7 +741,7 @@ static int msm_dp_display_disable(struct msm_dp_display_private *dp) } /** - * msm_dp_bridge_mode_valid - callback to determine if specified mode is valid + * msm_dp_display_mode_valid - callback to determine if specified mode is valid * @dp: Pointer to dp display structure * @info: display info * @mode: Pointer to drm mode structure @@ -1437,14 +1439,14 @@ void msm_dp_display_atomic_enable(struct msm_dp *msm_dp_display, return; } - rc = msm_dp_display_enable(dp); + rc = msm_dp_display_enable(dp, dp->panel); if (rc) DRM_ERROR("DP display enable failed, rc=%d\n", rc); rc = msm_dp_display_post_enable(msm_dp_display); if (rc) { DRM_ERROR("DP display post enable failed, rc=%d\n", rc); - msm_dp_display_disable(dp); + msm_dp_display_disable(dp, dp->panel); } drm_dbg_dp(msm_dp_display->drm_dev, "type=%d Done\n", msm_dp_display->connector_type); @@ -1479,7 +1481,7 @@ void msm_dp_display_atomic_post_disable(struct msm_dp *dp) msm_dp_display_audio_notify_disable(msm_dp_display); - msm_dp_display_disable(msm_dp_display); + msm_dp_display_disable(msm_dp_display, msm_dp_display->panel); msm_dp_display_unprepare(msm_dp_display); } From 811c38907eab0f66c22c5e5708e6f8eab14d76fa Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Tue, 28 Jul 2026 15:20:45 +0200 Subject: [PATCH 102/641] drm/msm/dpu: Drop sneaky dev_pm_opp_set_rate(0) dev_pm_opp_set_rate(0) removes the vote specified in required-opps but does not actually park the clock, making it run without the necessary power backing. Prevent that from happening when _dpu_core_perf_get_core_clk_rate() returns 0. Fixes: 25fdd5933e4c ("drm/msm: Add SDM845 DPU support") Signed-off-by: Konrad Dybcio Patchwork: https://patchwork.freedesktop.org/patch/742779/ Link: https://lore.kernel.org/r/20260728-topic-dpu_power-v1-1-e7783b859a70@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.c index 2ff255d7795e..fea173e37464 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.c @@ -394,6 +394,10 @@ int dpu_core_perf_crtc_update(struct drm_crtc *crtc, trace_dpu_core_perf_update_clk(kms->dev, !crtc->enabled, clk_rate); + /* If we're going offline, PM callbacks will disable the clocks instead */ + if (!clk_rate) + return 0; + clk_rate = min(clk_rate, kms->perf.max_core_clk_rate); ret = dev_pm_opp_set_rate(&kms->pdev->dev, clk_rate); if (ret) { From cebfa9909e27ec7b7cbaec25ee5516cf886baa39 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Tue, 28 Jul 2026 15:20:46 +0200 Subject: [PATCH 103/641] drm/msm/dp: Drop dev_pm_opp_set_rate(0) dev_pm_opp_set_rate(0) removes the vote specified in required-opps but does not actually park the clock, making it run without the necessary power backing. Drop the explicit calls to it. Fixes: c943b4948b58 ("drm/msm/dp: add displayPort driver support") Signed-off-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/742781/ Link: https://lore.kernel.org/r/20260728-topic-dpu_power-v1-2-e7783b859a70@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index 5d896593771e..59070f399e2d 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -1988,13 +1988,12 @@ static int msm_dp_ctrl_reinitialize_mainlink(struct msm_dp_ctrl_private *ctrl, msm_dp_ctrl_mainlink_disable(ctrl); ctrl->phy_opts.dp.lanes = ctrl->link->link_params.num_lanes; phy_configure(phy, &ctrl->phy_opts); + /* * Disable and re-enable the mainlink clock since the * link clock might have been adjusted as part of the * link maintenance. */ - dev_pm_opp_set_rate(ctrl->dev, 0); - msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl); phy_power_off(phy); @@ -2021,7 +2020,6 @@ static int msm_dp_ctrl_deinitialize_mainlink(struct msm_dp_ctrl_private *ctrl, msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl, panel); - dev_pm_opp_set_rate(ctrl->dev, 0); msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl); phy_power_off(phy); @@ -2656,7 +2654,6 @@ void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl, msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl, panel); - dev_pm_opp_set_rate(ctrl->dev, 0); msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl); phy_power_off(phy); From 06b7ba206561619bb34116f49e0ef26b867ce3aa Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Tue, 28 Jul 2026 15:20:47 +0200 Subject: [PATCH 104/641] drm/msm/dsi: Drop dev_pm_opp_set_rate(0) dev_pm_opp_set_rate(0) removes the vote specified in required-opps but does not actually park the clock, making it run without the necessary power backing. Drop the explicit call to it. Every call site of ops->link_clk_disable() is followed by pm_runtime_put(), so the power vote will be rescinded if deemed safe. Fixes: 32d3e0feccfe ("drm/msm: dsi: Use OPP API to set clk/perf state") Signed-off-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/742783/ Link: https://lore.kernel.org/r/20260728-topic-dpu_power-v1-3-e7783b859a70@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dsi/dsi_host.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/gpu/drm/msm/dsi/dsi_host.c b/drivers/gpu/drm/msm/dsi/dsi_host.c index 59125d342fcb..7e4e3718b536 100644 --- a/drivers/gpu/drm/msm/dsi/dsi_host.c +++ b/drivers/gpu/drm/msm/dsi/dsi_host.c @@ -550,8 +550,6 @@ error: void dsi_link_clk_disable_6g(struct msm_dsi_host *msm_host) { - /* Drop the performance state vote */ - dev_pm_opp_set_rate(&msm_host->pdev->dev, 0); clk_disable_unprepare(msm_host->esc_clk); clk_disable_unprepare(msm_host->pixel_clk); clk_disable_unprepare(msm_host->byte_intf_clk); From 140b13475302601368c0cf4e193e66126a49feb3 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Thu, 30 Jul 2026 19:04:56 +0300 Subject: [PATCH 105/641] drm/msm: detach the ARM DMA mapping before attaching our own domain On ARM32 with CONFIG_ARM_DMA_USE_IOMMU, arch_setup_dma_ops() creates a dma_iommu_mapping for every IOMMU-backed device and attaches its domain to the device's IOMMU group. That domain is neither the group's default nor its blocking domain, so when msm_iommu_new() later attaches the domain the driver manages itself, __iommu_attach_group() refuses it: if (group->domain && group->domain != group->default_domain && group->domain != group->blocking_domain) return -EBUSY; Both the GPU and the display controller are hit by this on apq8064 (IFC6410), leaving the board with no GPU and no display: adreno 4300000.gpu: failed to load adreno gpu adreno 4300000.gpu: probe with driver adreno failed with error -16 mdp4 5100000.display-controller: [drm:msm_drm_kms_init] *ERROR* failed to load kms mdp4 5100000.display-controller: adev bind failed: -16 Other ARM32 DRM drivers that manage their own domains (tegra, rockchip, exynos) drop the arch mapping first. Do the same in msm_iommu_new(), which both the display and the GPU paths go through. With this the GPU and the KMS device both initialise: [drm] Initialized msm 1.13.0 for 4300000.gpu on minor 0 [drm] Initialized msm-kms 1.13.0 for 5100000.display-controller on minor 1 Assisted-by: Claude:claude-opus-5 Reviewed-by: Konrad Dybcio Signed-off-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/743333/ Link: https://lore.kernel.org/r/20260730-fix-qcom-smmu-v2-3-18e0daf2d836@oss.qualcomm.com --- drivers/gpu/drm/msm/msm_iommu.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/drivers/gpu/drm/msm/msm_iommu.c b/drivers/gpu/drm/msm/msm_iommu.c index 897e42c8d5c8..da6782fca6bd 100644 --- a/drivers/gpu/drm/msm/msm_iommu.c +++ b/drivers/gpu/drm/msm/msm_iommu.c @@ -7,6 +7,15 @@ #include #include #include + +#if defined(CONFIG_ARM_DMA_USE_IOMMU) +#include +#else +#define arm_iommu_detach_device(...) ({ }) +#define arm_iommu_release_mapping(...) ({ }) +#define to_dma_iommu_mapping(dev) NULL +#endif + #include "msm_drv.h" #include "msm_gpu_trace.h" #include "msm_mmu.h" @@ -749,6 +758,19 @@ struct msm_mmu *msm_iommu_new(struct device *dev, unsigned long quirks) mutex_init(&iommu->init_lock); + /* + * ARM32 attaches a DMA mapping domain to every IOMMU-backed device, + * which would make attaching our own domain fail with -EBUSY. + */ + if (IS_ENABLED(CONFIG_ARM_DMA_USE_IOMMU)) { + struct dma_iommu_mapping *mapping = to_dma_iommu_mapping(dev); + + if (mapping) { + arm_iommu_detach_device(dev); + arm_iommu_release_mapping(mapping); + } + } + ret = iommu_attach_device(iommu->domain, dev); if (ret) { iommu_domain_free(domain); From a54ffce4637acb0db8e695188a6c7f99f14c3576 Mon Sep 17 00:00:00 2001 From: Zhan Xusheng Date: Thu, 16 Jul 2026 10:59:08 +0800 Subject: [PATCH 106/641] f2fs: use the mount idmap for the owner check in f2fs_xattr_advise_set() f2fs_xattr_advise_set() calls inode_owner_or_capable() with &nop_mnt_idmap before allowing the "system.advise" xattr to be set, instead of the idmap that the VFS passes to the ->set() handler. f2fs supports idmapped mounts, so on such a mount this checks the caller's fsuid against the unmapped on-disk owner rather than the mapped owner: the actual owner can be wrongly denied with -EPERM and an unrelated caller wrongly allowed. Pass the handler's idmap instead. Fixes: 01beba7957a2 ("fs: port inode_owner_or_capable() to mnt_idmap") Cc: stable@vger.kernel.org Signed-off-by: Zhan Xusheng Reviewed-by: Chao Yu Acked-by: Christian Brauner (Amutable) Signed-off-by: Jaegeuk Kim --- fs/f2fs/xattr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/f2fs/xattr.c b/fs/f2fs/xattr.c index ed33e5110f2a..6728d1488cad 100644 --- a/fs/f2fs/xattr.c +++ b/fs/f2fs/xattr.c @@ -119,7 +119,7 @@ static int f2fs_xattr_advise_set(const struct xattr_handler *handler, unsigned char old_advise = F2FS_I(inode)->i_advise; unsigned char new_advise; - if (!inode_owner_or_capable(&nop_mnt_idmap, inode)) + if (!inode_owner_or_capable(idmap, inode)) return -EPERM; if (value == NULL) return -EINVAL; From e533889fc26aea0cd83c90327063f272061dd820 Mon Sep 17 00:00:00 2001 From: Wenjie Qi Date: Tue, 30 Jun 2026 11:17:00 +0800 Subject: [PATCH 107/641] f2fs: validate MOVE_RANGE destination size F2FS_IOC_MOVE_RANGE checks the source range, but not the destination end before updating i_size. A source hole can expose this: __clone_blkaddrs() skips NULL_ADDR entries and returns success, so the caller can still extend the destination inode with unchecked pos_out + len. Reject destination overflow and use inode_newsize_ok() before extending the destination inode. Fixes: 4dd6f977fc77 ("f2fs: support an ioctl to move a range of data blocks") Cc: stable@kernel.org Assisted-by: Codex:gpt-5.5 Signed-off-by: Wenjie Qi Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/file.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index c54897a25981..170c485eafaa 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -3127,8 +3127,9 @@ static int f2fs_move_file_range(struct file *file_in, loff_t pos_in, struct inode *dst = file_inode(file_out); struct f2fs_sb_info *sbi = F2FS_I_SB(src); struct f2fs_lock_context lc; - size_t olen = len, dst_max_i_size = 0; - size_t dst_osize; + size_t olen = len; + loff_t dst_max_i_size = 0; + loff_t dst_osize, dst_end; int ret; if (file_in->f_path.mnt != file_out->f_path.mnt || @@ -3185,8 +3186,15 @@ static int f2fs_move_file_range(struct file *file_in, loff_t pos_in, } dst_osize = dst->i_size; - if (pos_out + olen > dst->i_size) - dst_max_i_size = pos_out + olen; + if (olen > LLONG_MAX - pos_out) + goto out_unlock; + dst_end = pos_out + olen; + if (dst_end > dst->i_size) { + ret = inode_newsize_ok(dst, dst_end); + if (ret) + goto out_unlock; + dst_max_i_size = dst_end; + } /* verify the end result is block aligned */ if (!IS_ALIGNED(pos_in, F2FS_BLKSIZE) || From 01027b2fcb74dade59fb833b51023f6593b6a9a2 Mon Sep 17 00:00:00 2001 From: Wenjie Qi Date: Tue, 30 Jun 2026 16:23:30 +0800 Subject: [PATCH 108/641] f2fs: limit recovery filename logging to stored length F2FS stores recovery filenames as a length plus a fixed-size i_name buffer. The buffer is not NUL-terminated, but recover_inode() and recover_dentry() print it with %s. For a 255-byte filename, recovery logging can read past i_name into the following raw inode fields. Print the name with a precision bounded by i_namelen and F2FS_NAME_LEN. Fixes: f356fe0cba0e ("f2fs: add debug msgs in the recovery routine") Cc: stable@kernel.org Assisted-by: Codex:gpt-5.5 Signed-off-by: Wenjie Qi Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/recovery.c | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/fs/f2fs/recovery.c b/fs/f2fs/recovery.c index 89af8407b667..3fecfdbd5958 100644 --- a/fs/f2fs/recovery.c +++ b/fs/f2fs/recovery.c @@ -158,6 +158,22 @@ static int init_recovered_filename(const struct inode *dir, return 0; } +static const char *recover_printable_name(struct inode *inode, + struct f2fs_inode *raw, + int *name_len) +{ + static const char encrypted_name[] = ""; + + if (file_enc_name(inode)) { + *name_len = sizeof(encrypted_name) - 1; + return encrypted_name; + } + + *name_len = min_t(unsigned int, le32_to_cpu(raw->i_namelen), + F2FS_NAME_LEN); + return raw->i_name; +} + static int recover_dentry(struct inode *inode, struct folio *ifolio, struct list_head *dir_list) { @@ -170,7 +186,8 @@ static int recover_dentry(struct inode *inode, struct folio *ifolio, struct inode *dir, *einode; struct fsync_inode_entry *entry; int err = 0; - char *name; + const char *name; + int name_len; entry = get_fsync_inode(dir_list, pino); if (!entry) { @@ -229,12 +246,9 @@ retry: out_put: f2fs_folio_put(folio, false); out: - if (file_enc_name(inode)) - name = ""; - else - name = raw_inode->i_name; - f2fs_notice(F2FS_I_SB(inode), "%s: ino = %x, name = %s, dir = %llu, err = %d", - __func__, ino_of_node(ifolio), name, + name = recover_printable_name(inode, raw_inode, &name_len); + f2fs_notice(F2FS_I_SB(inode), "%s: ino = %x, name = %.*s, dir = %llu, err = %d", + __func__, ino_of_node(ifolio), name_len, name, IS_ERR(dir) ? 0 : dir->i_ino, err); return err; } @@ -282,7 +296,8 @@ static int recover_inode(struct inode *inode, struct folio *folio) { struct f2fs_inode *raw = F2FS_INODE(folio); struct f2fs_inode_info *fi = F2FS_I(inode); - char *name; + const char *name; + int name_len; int err; inode->i_mode = le16_to_cpu(raw->i_mode); @@ -331,13 +346,11 @@ static int recover_inode(struct inode *inode, struct folio *folio) f2fs_mark_inode_dirty_sync(inode, true); - if (file_enc_name(inode)) - name = ""; - else - name = F2FS_INODE(folio)->i_name; + name = recover_printable_name(inode, raw, &name_len); - f2fs_notice(F2FS_I_SB(inode), "recover_inode: ino = %x, name = %s, inline = %x", - ino_of_node(folio), name, raw->i_inline); + f2fs_notice(F2FS_I_SB(inode), "%s: ino = %x, name = %.*s, inline = %x", + __func__, ino_of_node(folio), name_len, name, + raw->i_inline); return 0; } From 85171332742e741ccd6f401c69b6e0d698119e72 Mon Sep 17 00:00:00 2001 From: Wenjie Qi Date: Mon, 27 Jul 2026 21:04:29 +0800 Subject: [PATCH 109/641] f2fs: only redirty pinned folios in redirty_blocks redirty_blocks() pins folios with read_cache_folio() and then walks the same range again with filemap_lock_folio() to redirty them and drop the references it took. Commit 5951fee46bef ("f2fs: Use a folio in redirty_blocks()") changed the second pass to a do/while loop. If read_cache_folio() fails before anything is pinned, page_idx does not advance but the cleanup loop still runs once. If readahead has already populated the failed folio in page cache, that extra iteration finds it and folio_put_refs(folio, 2) drops one reference too many. Later drop_caches or reclaim can then report "BUG: Bad page state". Only redirty the range that was pinned successfully. Fixes: 5951fee46bef ("f2fs: Use a folio in redirty_blocks()") Cc: stable@kernel.org Assisted-by: Codex:gpt-5.5 Signed-off-by: Wenjie Qi Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/file.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index 170c485eafaa..2773c6ce7ec1 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -4487,7 +4487,7 @@ static int redirty_blocks(struct inode *inode, pgoff_t page_idx, int len) page_idx = folio_next_index(folio); } while (page_len < len); - do { + while (redirty_idx < page_idx) { folio = filemap_lock_folio(mapping, redirty_idx); /* It will never fail, when folio has pinned above */ @@ -4500,7 +4500,7 @@ static int redirty_blocks(struct inode *inode, pgoff_t page_idx, int len) redirty_idx = folio_next_index(folio); folio_unlock(folio); folio_put_refs(folio, 2); - } while (redirty_idx < page_idx); + } return ret; } From f8a4108800254d6f7b2755515fbbd9d0caac561e Mon Sep 17 00:00:00 2001 From: Wenjie Qi Date: Mon, 27 Jul 2026 15:49:10 +0800 Subject: [PATCH 110/641] f2fs: return writeback error from collapse range f2fs_collapse_range() writes back pages moved by f2fs_do_collapse(), but ignores the return value. If writeback fails, the ioctl can still truncate page cache, shrink blocks, and report success. Return the error before truncating page cache or updating the file size. Fixes: b4ace3370324 ("f2fs: support FALLOC_FL_COLLAPSE_RANGE") Cc: stable@kernel.org Assisted-by: Codex:gpt-5.5 Signed-off-by: Wenjie Qi Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/file.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index 2773c6ce7ec1..c19f0de271a9 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -1623,11 +1623,14 @@ static int f2fs_collapse_range(struct inode *inode, loff_t offset, loff_t len) /* write out all moved pages, if possible */ filemap_invalidate_lock(inode->i_mapping); - filemap_write_and_wait_range(inode->i_mapping, offset, LLONG_MAX); + ret = filemap_write_and_wait_range(inode->i_mapping, offset, LLONG_MAX); + if (ret) + goto out_unlock; truncate_pagecache(inode, offset); new_size = i_size_read(inode) - len; ret = f2fs_truncate_blocks(inode, new_size, true); +out_unlock: filemap_invalidate_unlock(inode->i_mapping); if (!ret) f2fs_i_size_write(inode, new_size); From 28c1ef094e7c86977d9bf570dc0362fc54e36437 Mon Sep 17 00:00:00 2001 From: Hao-Qun Huang Date: Wed, 8 Jul 2026 14:54:39 +0800 Subject: [PATCH 111/641] f2fs: reject overlapping move range after len expansion F2FS_IOC_MOVE_RANGE treats a zero length as a request to move data from pos_in to EOF. However, the same-file overlap check runs before that expansion, so a request with len == 0 bypasses the overlap rejection added for same-file moves. For example, with a four-block file, moving from block 0 to block 1 with len == 0 is accepted by the old check because pos_in + len is still pos_in at that point. The code then expands len to cover the rest of the file and calls __exchange_data_block() on overlapping source and destination ranges in the same inode, which is the data-corruption case the overlap check was meant to reject. Move the overlap check after the source range has been validated and len == 0 has been expanded, so it sees the effective length. This is a no-op for non-zero len (the value is unchanged there) and keeps the existing early return for identical positions. Fixes: d95fd91c1ac1 ("f2fs: exclude special cases for f2fs_move_file_range") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-fable-5 Signed-off-by: Hao-Qun Huang Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/file.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index c19f0de271a9..f0b74984b658 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -3154,8 +3154,6 @@ static int f2fs_move_file_range(struct file *file_in, loff_t pos_in, if (src == dst) { if (pos_in == pos_out) return 0; - if (pos_out > pos_in && pos_out < pos_in + len) - return -EINVAL; } inode_lock(src); @@ -3181,6 +3179,8 @@ static int f2fs_move_file_range(struct file *file_in, loff_t pos_in, goto out_unlock; if (len == 0) olen = len = src->i_size - pos_in; + if (src == dst && pos_out > pos_in && pos_out < pos_in + len) + goto out_unlock; if (pos_in + len == src->i_size) len = ALIGN(src->i_size, F2FS_BLKSIZE) - pos_in; if (len == 0) { From 1b4db09b7f10895dc1a0bc32704c7de05188fb1d Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Tue, 23 Jun 2026 20:48:23 +0800 Subject: [PATCH 112/641] f2fs: fix to avoid move_range and defragment on device_alias file It's forbidden to migrate blocks of device alias file. Cc: stable@kernel.org Fixes: 128d333f0dff ("f2fs: introduce device aliasing file") Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/file.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index f0b74984b658..b99d9cdf9ba7 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -3092,6 +3092,9 @@ static int f2fs_ioc_defragment(struct file *filp, unsigned long arg) if (f2fs_readonly(sbi->sb)) return -EROFS; + if (IS_DEVICE_ALIASING(inode)) + return -EOPNOTSUPP; + if (copy_from_user(&range, (struct f2fs_defragment __user *)arg, sizeof(range))) return -EFAULT; @@ -3145,7 +3148,8 @@ static int f2fs_move_file_range(struct file *file_in, loff_t pos_in, if (!S_ISREG(src->i_mode) || !S_ISREG(dst->i_mode)) return -EINVAL; - if (IS_ENCRYPTED(src) || IS_ENCRYPTED(dst)) + if (IS_ENCRYPTED(src) || IS_ENCRYPTED(dst) || + IS_DEVICE_ALIASING(src) || IS_DEVICE_ALIASING(dst)) return -EOPNOTSUPP; if (pos_out < 0 || pos_in < 0) From 575f6f8ef2abe8552af96e42c65d70964db53563 Mon Sep 17 00:00:00 2001 From: Zhan Xusheng Date: Tue, 23 Jun 2026 15:26:41 +0800 Subject: [PATCH 113/641] f2fs: don't drop the top folio order in the f2fs_iostat tracepoint The f2fs_iostat tracepoint stores the per-order read folio counts in a fixed-size array and prints a fixed number of buckets, both hardcoded to 11. The sysfs iostat accounting array is instead sized by NR_PAGE_ORDERS (= MAX_PAGE_ORDER + 1), which is not always 11: arm64 16K pages -> MAX_PAGE_ORDER 11 -> NR_PAGE_ORDERS 12 arm64 64K pages -> MAX_PAGE_ORDER 13 -> NR_PAGE_ORDERS 14 f2fs enables large folios for immutable, non-compressed files, and the read folio order is bounded by MAX_PAGECACHE_ORDER, i.e. min(MAX_XAS_ORDER, PREFERRED_MAX_PAGECACHE_ORDER). With THP enabled this reaches order 11 on 16K/64K base-page kernels (MAX_XAS_ORDER caps it at 11). So an order-11 read folio is possible there and is accounted into index 11 of the array. On those configurations the sysfs file reports the order-11 count correctly, but the tracepoint silently drops it: the memcpy is capped at min(NR_PAGE_ORDERS, 11), so index 11 is never copied and the trace disagrees with sysfs. There is no memory-safety issue, only the order-11 bucket missing from the trace; 4K-page kernels (NR_PAGE_ORDERS == 11, max order <= 9) are unaffected. Size the array and the printed buckets by a ceiling that covers the largest possible NR_PAGE_ORDERS (14) with headroom, and add a BUILD_BUG_ON() so any future growth of NR_PAGE_ORDERS fails the build loudly instead of silently truncating again. The human-readable "order=count" output is preserved. Fixes: cb8ff3ead9a3 ("f2fs: add page-order information for large folio reads in iostat") Cc: stable@vger.kernel.org Signed-off-by: Zhan Xusheng Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/iostat.c | 6 ++++++ include/trace/events/f2fs.h | 20 ++++++++++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/fs/f2fs/iostat.c b/fs/f2fs/iostat.c index ae265e3e9b2c..12d4e18a6a50 100644 --- a/fs/f2fs/iostat.c +++ b/fs/f2fs/iostat.c @@ -332,6 +332,12 @@ void f2fs_destroy_iostat_processing(void) int f2fs_init_iostat(struct f2fs_sb_info *sbi) { + /* + * The f2fs_iostat tracepoint emits a fixed number of read folio order + * buckets; make sure every order fits so none is silently dropped. + */ + BUILD_BUG_ON(NR_PAGE_ORDERS > F2FS_IOSTAT_RD_FOLIO_ORDERS); + /* init iostat info */ spin_lock_init(&sbi->iostat_lock); spin_lock_init(&sbi->iostat_lat_lock); diff --git a/include/trace/events/f2fs.h b/include/trace/events/f2fs.h index 270c1a2c24c4..1dd9fc5afc46 100644 --- a/include/trace/events/f2fs.h +++ b/include/trace/events/f2fs.h @@ -2114,6 +2114,14 @@ DEFINE_EVENT(f2fs_zip_end, f2fs_decompress_pages_end, ); #ifdef CONFIG_F2FS_IOSTAT +/* + * Number of read folio order buckets emitted by the f2fs_iostat tracepoint. + * TP_printk() cannot loop, so the field count is fixed here and must be >= + * the largest possible NR_PAGE_ORDERS (14 on arm64 with 64K pages). The + * BUILD_BUG_ON() in f2fs_update_read_folio_count() enforces this. + */ +#define F2FS_IOSTAT_RD_FOLIO_ORDERS 16 + TRACE_EVENT(f2fs_iostat, TP_PROTO(struct f2fs_sb_info *sbi, unsigned long long *iostat, @@ -2151,7 +2159,7 @@ TRACE_EVENT(f2fs_iostat, __field(unsigned long long, fs_mrio) __field(unsigned long long, fs_discard) __field(unsigned long long, fs_reset_zone) - __array(unsigned long long, read_folio_count, 11) + __array(unsigned long long, read_folio_count, F2FS_IOSTAT_RD_FOLIO_ORDERS) ), TP_fast_assign( @@ -2186,7 +2194,8 @@ TRACE_EVENT(f2fs_iostat, __entry->fs_reset_zone = iostat[FS_ZONE_RESET_IO]; memset(__entry->read_folio_count, 0, sizeof(__entry->read_folio_count)); memcpy(__entry->read_folio_count, read_folio_count, - sizeof(unsigned long long) * min_t(int, NR_PAGE_ORDERS, 11)); + sizeof(unsigned long long) * + min_t(int, NR_PAGE_ORDERS, F2FS_IOSTAT_RD_FOLIO_ORDERS)); ), TP_printk("dev = (%d,%d), " @@ -2201,7 +2210,8 @@ TRACE_EVENT(f2fs_iostat, "fs [data=%llu, (gc_data=%llu, cdata=%llu), " "node=%llu, meta=%llu], " "read_folio_count [0=%llu, 1=%llu, 2=%llu, 3=%llu, 4=%llu, " - "5=%llu, 6=%llu, 7=%llu, 8=%llu, 9=%llu, 10=%llu]", + "5=%llu, 6=%llu, 7=%llu, 8=%llu, 9=%llu, 10=%llu, 11=%llu, " + "12=%llu, 13=%llu, 14=%llu, 15=%llu]", show_dev(__entry->dev), __entry->app_wio, __entry->app_dio, __entry->app_bio, __entry->app_mio, __entry->app_bcdio, __entry->app_mcdio, __entry->fs_dio, __entry->fs_cdio, @@ -2218,7 +2228,9 @@ TRACE_EVENT(f2fs_iostat, __entry->read_folio_count[4], __entry->read_folio_count[5], __entry->read_folio_count[6], __entry->read_folio_count[7], __entry->read_folio_count[8], __entry->read_folio_count[9], - __entry->read_folio_count[10]) + __entry->read_folio_count[10], __entry->read_folio_count[11], + __entry->read_folio_count[12], __entry->read_folio_count[13], + __entry->read_folio_count[14], __entry->read_folio_count[15]) ); #ifndef __F2FS_IOSTAT_LATENCY_TYPE From 9ec09d5f4b317a417c8655c14056f70cbe71eb6c Mon Sep 17 00:00:00 2001 From: Joanne Chang Date: Tue, 23 Jun 2026 06:34:28 +0000 Subject: [PATCH 114/641] f2fs: dirty directory inodes on mtime/ctime update Xfstests generic/547 sometimes fail with mismatched directory metadata before and after a power failure. This happens because when a directory entry is added, renamed, or deleted, its mtime and ctime are updated and the inode is marked dirty via f2fs_mark_inode_dirty_sync(dir, sync=false). The sync=false flag means the dirty inode is not added to the global DIRTY_META list. Therefore, subsequent checkpoints skip flushing these updated directory blocks, causing directory timestamps to revert to stale values after a sudden power failure. Address this by changing the dirtying parameter to sync=true during directory entry mutations and renames. This forces F2FS to immediately queue the updated directory blocks on the global DIRTY_META list, ensuring timestamps are committed to checkpoints. Fixes: 7c45729a4d6d ("f2fs: keep dirty inodes selectively for checkpoint") Cc: stable@vger.kernel.org Signed-off-by: Joanne Chang Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/dir.c | 6 +++--- fs/f2fs/inline.c | 2 +- fs/f2fs/namei.c | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/fs/f2fs/dir.c b/fs/f2fs/dir.c index 0562ad211351..fd0e2cd31a81 100644 --- a/fs/f2fs/dir.c +++ b/fs/f2fs/dir.c @@ -461,7 +461,7 @@ void f2fs_set_link(struct inode *dir, struct f2fs_dir_entry *de, folio_mark_dirty(folio); inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir)); - f2fs_mark_inode_dirty_sync(dir, false); + f2fs_mark_inode_dirty_sync(dir, true); f2fs_folio_put(folio, true); } @@ -616,7 +616,7 @@ void f2fs_update_parent_metadata(struct inode *dir, struct inode *inode, clear_inode_flag(inode, FI_NEW_INODE); } inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir)); - f2fs_mark_inode_dirty_sync(dir, false); + f2fs_mark_inode_dirty_sync(dir, true); if (F2FS_I(dir)->i_current_depth != current_depth) f2fs_i_depth_write(dir, current_depth); @@ -928,7 +928,7 @@ void f2fs_delete_entry(struct f2fs_dir_entry *dentry, struct folio *folio, f2fs_folio_put(folio, true); inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir)); - f2fs_mark_inode_dirty_sync(dir, false); + f2fs_mark_inode_dirty_sync(dir, true); if (inode) f2fs_drop_nlink(dir, inode); diff --git a/fs/f2fs/inline.c b/fs/f2fs/inline.c index e2f7bedf1552..aec06fb4fd76 100644 --- a/fs/f2fs/inline.c +++ b/fs/f2fs/inline.c @@ -732,7 +732,7 @@ void f2fs_delete_inline_entry(struct f2fs_dir_entry *dentry, f2fs_folio_put(folio, true); inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir)); - f2fs_mark_inode_dirty_sync(dir, false); + f2fs_mark_inode_dirty_sync(dir, true); if (inode) f2fs_drop_nlink(dir, inode); diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c index cac03b8e91a1..7ffdf23cea5e 100644 --- a/fs/f2fs/namei.c +++ b/fs/f2fs/namei.c @@ -1076,7 +1076,7 @@ static int f2fs_rename(struct mnt_idmap *idmap, struct inode *old_dir, f2fs_up_write(&F2FS_I(old_inode)->i_sem); inode_set_ctime_current(old_inode); - f2fs_mark_inode_dirty_sync(old_inode, false); + f2fs_mark_inode_dirty_sync(old_inode, true); f2fs_delete_entry(old_entry, old_folio, old_dir, NULL); old_folio = NULL; @@ -1246,7 +1246,7 @@ static int f2fs_cross_rename(struct inode *old_dir, struct dentry *old_dentry, f2fs_i_links_write(old_dir, old_nlink > 0); f2fs_up_write(&F2FS_I(old_dir)->i_sem); } - f2fs_mark_inode_dirty_sync(old_dir, false); + f2fs_mark_inode_dirty_sync(old_dir, true); /* update directory entry info of new dir inode */ f2fs_set_link(new_dir, new_entry, new_folio, old_inode); @@ -1265,7 +1265,7 @@ static int f2fs_cross_rename(struct inode *old_dir, struct dentry *old_dentry, f2fs_i_links_write(new_dir, new_nlink > 0); f2fs_up_write(&F2FS_I(new_dir)->i_sem); } - f2fs_mark_inode_dirty_sync(new_dir, false); + f2fs_mark_inode_dirty_sync(new_dir, true); if (F2FS_OPTION(sbi).fsync_mode == FSYNC_MODE_STRICT) { f2fs_add_ino_entry(sbi, old_dir->i_ino, TRANS_DIR_INO); From 0f046e45afb783bbf8c5a2eb9abe1f84db270203 Mon Sep 17 00:00:00 2001 From: Zhan Xusheng Date: Fri, 26 Jun 2026 11:07:28 +0800 Subject: [PATCH 115/641] f2fs: Remove unused curseg_segno() and curseg_alloc_type() The only callers of curseg_segno() and curseg_alloc_type() were removed by commit 5a4fed7cd97a ("f2fs: simplify do_checkpoint"); both helpers have been unused since then. Being static inline functions they do not trigger -Wunused-function, so the dead code has gone unnoticed. Remove them. No functional change. Signed-off-by: Zhan Xusheng Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/segment.h | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/fs/f2fs/segment.h b/fs/f2fs/segment.h index b0c06b3580b4..33a2257da1e6 100644 --- a/fs/f2fs/segment.h +++ b/fs/f2fs/segment.h @@ -796,20 +796,6 @@ F2FS_IPU_POLICY(F2FS_IPU_ASYNC); F2FS_IPU_POLICY(F2FS_IPU_NOCACHE); F2FS_IPU_POLICY(F2FS_IPU_HONOR_OPU_WRITE); -static inline unsigned int curseg_segno(struct f2fs_sb_info *sbi, - int type) -{ - struct curseg_info *curseg = CURSEG_I(sbi, type); - return curseg->segno; -} - -static inline unsigned char curseg_alloc_type(struct f2fs_sb_info *sbi, - int type) -{ - struct curseg_info *curseg = CURSEG_I(sbi, type); - return curseg->alloc_type; -} - static inline bool valid_main_segno(struct f2fs_sb_info *sbi, unsigned int segno) { From 6dc2b804a042d2ff0fdf4c8e3db78669c36b688d Mon Sep 17 00:00:00 2001 From: Wenjie Qi Date: Mon, 3 Aug 2026 19:41:53 +0800 Subject: [PATCH 116/641] f2fs: reject invalid recovered filename lengths Recovery uses raw_inode->i_namelen directly when rebuilding fsynced dentries. A zero-length name uses no dentry slots, so recovery can report success without recreating the dentry. Treat zero-length and oversized recovered names as corruption, mark NEED_FSCK, and stop recovery with -EFSCORRUPTED. Signed-off-by: Wenjie Qi Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/recovery.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/fs/f2fs/recovery.c b/fs/f2fs/recovery.c index 3fecfdbd5958..aaa5227739c8 100644 --- a/fs/f2fs/recovery.c +++ b/fs/f2fs/recovery.c @@ -116,18 +116,26 @@ static void del_fsync_inode(struct fsync_inode_entry *entry, int drop) } static int init_recovered_filename(const struct inode *dir, + struct inode *inode, struct f2fs_inode *raw_inode, struct f2fs_filename *fname, struct qstr *usr_fname) { + struct f2fs_sb_info *sbi = F2FS_I_SB(inode); int err; memset(fname, 0, sizeof(*fname)); fname->disk_name.len = le32_to_cpu(raw_inode->i_namelen); fname->disk_name.name = raw_inode->i_name; - if (WARN_ON(fname->disk_name.len > F2FS_NAME_LEN)) - return -ENAMETOOLONG; + if (unlikely(!fname->disk_name.len || + fname->disk_name.len > F2FS_NAME_LEN)) { + f2fs_err(sbi, "invalid recovered filename length %u for ino %llu", + fname->disk_name.len, inode->i_ino); + set_sbi_flag(sbi, SBI_NEED_FSCK); + f2fs_handle_error(sbi, ERROR_CORRUPTED_INODE); + return -EFSCORRUPTED; + } if (!IS_ENCRYPTED(dir)) { usr_fname->name = fname->disk_name.name; @@ -201,7 +209,7 @@ static int recover_dentry(struct inode *inode, struct folio *ifolio, } dir = entry->inode; - err = init_recovered_filename(dir, raw_inode, &fname, &usr_fname); + err = init_recovered_filename(dir, inode, raw_inode, &fname, &usr_fname); if (err) goto out; retry: From 3de6b80941152a384ee1b9cf88ac1c9dd4eec6dd Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Mon, 3 Aug 2026 09:52:22 -0700 Subject: [PATCH 117/641] f2fs: Run f2fs_write_end_io() asynchronously The bio_for_each_segment_all() loop can take more than 10 ms for a large bio on an ARM little core. This is too much for interrupt context. Hence perform the write bio completion work asynchronously if a bio is large and if f2fs_write_end_io() is called from atomic context. This patch reduces the time spent in f2fs_write_end_io() from about 10 ms to about 150 microseconds on an Arm Cortex-A520 core if the max_atc_write_bio_size parameter is changed to 16384. Signed-off-by: Bart Van Assche Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- Documentation/ABI/testing/sysfs-fs-f2fs | 10 ++++++++++ fs/f2fs/data.c | 23 +++++++++++++++++++++-- fs/f2fs/f2fs.h | 2 ++ fs/f2fs/super.c | 1 + fs/f2fs/sysfs.c | 2 ++ 5 files changed, 36 insertions(+), 2 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-fs-f2fs b/Documentation/ABI/testing/sysfs-fs-f2fs index 1b58c029abd0..f4e6a7415cde 100644 --- a/Documentation/ABI/testing/sysfs-fs-f2fs +++ b/Documentation/ABI/testing/sysfs-fs-f2fs @@ -1002,3 +1002,13 @@ Description: It can be used to tune priority of f2fs critical task, e.g. f2fs_ck threads, limitation as below: - it requires user has CAP_SYS_NICE capability. - the range is [100, 139], by default the value is 120. + +What: /sys/fs/f2fs//max_atc_write_bio_size +Date: June 2026 +Contact: Bart Van Assche +Description: Every time a write operation completes f2fs_write_end_io() is + called. This function may be called from an atomic context, + e.g. from inside an interrupt handler. This attribute controls + the maximum size of a write bio that is completed in atomic + (atc) context. The default value for this attribute is UINT_MAX + which means that this functionality is disabled by default. diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c index 042ed8ad9cc3..ef2a567acdac 100644 --- a/fs/f2fs/data.c +++ b/fs/f2fs/data.c @@ -411,13 +411,32 @@ static void f2fs_write_end_bio(struct bio *bio) bio_put(bio); } -static void f2fs_write_end_io(struct bio *bio) +static void f2fs_write_end_io_work(struct work_struct *work) { - iostat_update_and_unbind_ctx(bio); + struct bio *bio = &container_of(work, struct f2fs_bio, work)->bio; f2fs_write_end_bio(bio); } +static void f2fs_write_end_io(struct bio *bio) +{ + struct f2fs_sb_info *sbi; + + iostat_update_and_unbind_ctx(bio); + + sbi = bio->bi_private; + + if (in_atomic() && bio->bi_iter.bi_size > sbi->max_atc_write_bio_size) { + struct work_struct *w; + + w = &container_of(bio, struct f2fs_bio, bio)->work; + INIT_WORK(w, f2fs_write_end_io_work); + queue_work(sbi->wq, w); + } else { + f2fs_write_end_bio(bio); + } +} + #ifdef CONFIG_BLK_DEV_ZONED static void f2fs_zone_write_end_io(struct bio *bio) { diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index 8011bbdf2c68..8e2fb0bda467 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -1800,6 +1800,8 @@ struct f2fs_sb_info { struct f2fs_sm_info *sm_info; /* segment manager */ /* for bio operations */ + /* Largest write bio size completed in atomic context (atc). */ + u32 max_atc_write_bio_size; struct f2fs_bio_info *write_io[NR_PAGE_TYPE]; /* for write bios */ /* keep migration IO order for LFS mode */ struct f2fs_rwsem io_order_lock; diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index 90a47cf86378..25309e6d4156 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -5068,6 +5068,7 @@ try_onemore: sb->s_fs_info = sbi; sbi->raw_super = raw_super; + sbi->max_atc_write_bio_size = UINT_MAX; INIT_WORK(&sbi->s_error_work, f2fs_record_error_work); memcpy(sbi->errors, raw_super->s_errors, MAX_F2FS_ERRORS); diff --git a/fs/f2fs/sysfs.c b/fs/f2fs/sysfs.c index be92c05a5420..d9f81edca04a 100644 --- a/fs/f2fs/sysfs.c +++ b/fs/f2fs/sysfs.c @@ -1266,6 +1266,7 @@ F2FS_SBI_RW_ATTR(gc_idle_interval, interval_time[GC_TIME]); F2FS_SBI_RW_ATTR(umount_discard_timeout, interval_time[UMOUNT_DISCARD_TIMEOUT]); F2FS_SBI_RW_ATTR(gc_pin_file_thresh, gc_pin_file_threshold); F2FS_SBI_RW_ATTR(gc_reclaimed_segments, gc_reclaimed_segs); +F2FS_SBI_RW_ATTR(max_atc_write_bio_size, max_atc_write_bio_size); F2FS_SBI_GENERAL_RW_ATTR(max_victim_search); F2FS_SBI_GENERAL_RW_ATTR(migration_granularity); F2FS_SBI_GENERAL_RW_ATTR(migration_window_granularity); @@ -1509,6 +1510,7 @@ static struct attribute *f2fs_attrs[] = { ATTR_LIST(seq_file_ra_mul), ATTR_LIST(gc_segment_mode), ATTR_LIST(gc_reclaimed_segments), + ATTR_LIST(max_atc_write_bio_size), ATTR_LIST(max_fragment_chunk), ATTR_LIST(max_fragment_hole), ATTR_LIST(current_atomic_write), From 5cb33b00c8fbb6e8f1fa3d281c3036d5f7c7c41f Mon Sep 17 00:00:00 2001 From: Wenjie Qi Date: Tue, 4 Aug 2026 09:48:48 +0800 Subject: [PATCH 118/641] f2fs: avoid NULL checkpoint thread access in sysfs checkpoint_merge can be enabled even when no checkpoint merge thread is running. A read-only mount is one case: f2fs does not start f2fs_issue_ckpt there, but ckpt_thread_ioprio is still writable through sysfs. The ckpt_thread_ioprio store path updates the saved ioprio value and, when checkpoint_merge is enabled, calls set_task_ioprio() for the checkpoint thread. If cprc->f2fs_issue_ckpt is NULL, that dereferences a NULL task pointer. Protect ckpt_thread_ioprio sysfs writes with s_umount as well, so the checkpoint thread cannot disappear under the store path while updating its ioprio. Fixes: e65920661708 ("f2fs: add ckpt_thread_ioprio sysfs node") Cc: stable@kernel.org Signed-off-by: Wenjie Qi Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/sysfs.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/fs/f2fs/sysfs.c b/fs/f2fs/sysfs.c index d9f81edca04a..c976549ff1bc 100644 --- a/fs/f2fs/sysfs.c +++ b/fs/f2fs/sysfs.c @@ -557,7 +557,7 @@ out: return -EINVAL; cprc->ckpt_thread_ioprio = IOPRIO_PRIO_VALUE(class, level); - if (test_opt(sbi, MERGE_CHECKPOINT)) { + if (cprc->f2fs_issue_ckpt) { ret = set_task_ioprio(cprc->f2fs_issue_ckpt, cprc->ckpt_thread_ioprio); if (ret) @@ -1007,13 +1007,14 @@ static ssize_t f2fs_sbi_store(struct f2fs_attr *a, ssize_t ret; bool gc_entry = (!strcmp(a->attr.name, "gc_urgent") || a->struct_type == GC_THREAD); + bool thread_entry = !strcmp(a->attr.name, "ckpt_thread_ioprio"); - if (gc_entry) { + if (gc_entry || thread_entry) { if (!down_read_trylock(&sbi->sb->s_umount)) return -EAGAIN; } ret = __sbi_store(a, sbi, buf, count); - if (gc_entry) + if (gc_entry || thread_entry) up_read(&sbi->sb->s_umount); return ret; From 8e4692c6c165e81b2cbb847d8da4b45a53483b33 Mon Sep 17 00:00:00 2001 From: Wenjie Qi Date: Tue, 4 Aug 2026 09:48:49 +0800 Subject: [PATCH 119/641] f2fs: protect critical_task_priority updates with s_umount The sysfs store path already takes s_umount for GC thread control entries, and ckpt_thread_ioprio is covered as well. critical_task_priority also updates checkpoint or GC kthread scheduling state, but it is not covered by that serialization. It can race with remount or teardown paths that are stopping those threads. Protect critical_task_priority sysfs writes with s_umount too. Fixes: 52190933c37a ("f2fs: sysfs: introduce critical_task_priority") Cc: stable@kernel.org Signed-off-by: Wenjie Qi Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/sysfs.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/f2fs/sysfs.c b/fs/f2fs/sysfs.c index c976549ff1bc..3201e2185fea 100644 --- a/fs/f2fs/sysfs.c +++ b/fs/f2fs/sysfs.c @@ -1007,7 +1007,8 @@ static ssize_t f2fs_sbi_store(struct f2fs_attr *a, ssize_t ret; bool gc_entry = (!strcmp(a->attr.name, "gc_urgent") || a->struct_type == GC_THREAD); - bool thread_entry = !strcmp(a->attr.name, "ckpt_thread_ioprio"); + bool thread_entry = !strcmp(a->attr.name, "ckpt_thread_ioprio") || + !strcmp(a->attr.name, "critical_task_priority"); if (gc_entry || thread_entry) { if (!down_read_trylock(&sbi->sb->s_umount)) From 0f9af07ecc1ab486038373db6ae0436c5d674b19 Mon Sep 17 00:00:00 2001 From: Chen Changcheng Date: Tue, 4 Aug 2026 08:54:02 +0800 Subject: [PATCH 120/641] f2fs: fix valid block count leak on data block allocation failure In __allocate_data_block(), when allocating a new data block (dn->data_blkaddr == NULL_ADDR), inc_valid_block_count() is called first to increment total_valid_block_count and i_blocks. If the subsequent f2fs_allocate_data_block() fails, the function returns the error directly without rolling back the already-incremented block counts, causing a permanent leak. Fix this by calling dec_valid_block_count() to undo the increment before returning the error. The condition old_blkaddr == NULL_ADDR precisely identifies the case where inc_valid_block_count() was called. Fixes: 7d009e048d7c ("f2fs: fix to handle segment allocation failure correctly") Cc: Reviewed-by: Chao Yu Signed-off-by: Chen Changcheng Signed-off-by: Jaegeuk Kim --- fs/f2fs/data.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c index ef2a567acdac..30e8084da313 100644 --- a/fs/f2fs/data.c +++ b/fs/f2fs/data.c @@ -1552,8 +1552,11 @@ static int __allocate_data_block(struct dnode_of_data *dn, int seg_type) old_blkaddr = dn->data_blkaddr; err = f2fs_allocate_data_block(sbi, NULL, old_blkaddr, &dn->data_blkaddr, &sum, seg_type, NULL); - if (err) + if (err) { + if (old_blkaddr == NULL_ADDR) + dec_valid_block_count(sbi, dn->inode, count); return err; + } if (GET_SEGNO(sbi, old_blkaddr) != NULL_SEGNO) f2fs_invalidate_internal_cache(sbi, old_blkaddr, 1); From 7e8ee82e69fde9d589272ec5e6f702358903be1f Mon Sep 17 00:00:00 2001 From: Ethan Nelson-Moore Date: Sun, 14 Jun 2026 02:45:38 +0100 Subject: [PATCH 121/641] ARM: 9477/1: Disable broken eBPF JIT on the Risc PC The eBPF JIT unconditionally generates ldrh/strh instructions, which do not function correctly on the Risc PC because its bus is unable to signal half-word accesses. Work around this issue by disabling the eBPF JIT when building for ARMv3 (the Risc PC is the only currently supported machine whose kernel is built for ARMv3). Comments from Ethan Nelson-Moore: From LKML: https://lore.kernel.org/all/CAD++jL=0qYGoygUwGEXQL7C_ROnC7kfpRv8RA+H5tNWwYu+pQA@mail.gmail.com/ The commit message has been updated slightly relative to the version on LKML to clarify that the Risc PC is not actually ARMv3. Fixes: 39c13c204bb1 ("arm: eBPF JIT compiler") Cc: stable@vger.kernel.org Signed-off-by: Ethan Nelson-Moore Reviewed-by: Linus Walleij Signed-off-by: Russell King --- arch/arm/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 9187240a02db..b8aa3fcce107 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -98,7 +98,7 @@ config ARM select HAVE_ARCH_TRACEHOOK select HAVE_ARCH_TRANSPARENT_HUGEPAGE if ARM_LPAE select HAVE_ARM_SMCCC if CPU_V7 - select HAVE_EBPF_JIT if !CPU_ENDIAN_BE32 + select HAVE_EBPF_JIT if !CPU_ENDIAN_BE32 && !CPU_32v3 select HAVE_CONTEXT_TRACKING_USER select HAVE_C_RECORDMCOUNT select HAVE_BUILDTIME_MCOUNT_SORT From 8ed9bff906cf8036531d1559f10e82733a52b41f Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 4 Jul 2026 21:15:55 +0100 Subject: [PATCH 122/641] ARM: 9481/2: breakpoint: CFI breakpoints only on demand This removes the stub hw_breakpoint_cfi_handler() from ARM, making it not steal breakpoint type 0x03 (ARM_ENTRY_CFI_BREAKPOINT) unless CFI is actively used in the kernel. When not instrumenting with CFI, or when a breakpoint is issued in userspace, we fall through to return 1 from hw_breakpoint_pending() "unhandled fault" so userspace can make use of this breakpoint. Tested with LKDTM and this command line: echo CFI_FORWARD_PROTO > /sys/kernel/debug/provoke-crash/DIRECT still works as expected. Closes: https://lore.kernel.org/lkml/kJqktbpLphg_Pk5I5SPptgTLjl3E3eq5mN5UzCslyFj7Q1Irp-wDid4mj5eQVd2iZtRGXgeZd8goq195EkXdjyt864YMc8mVb2B9NGH91NQ=@protonmail.com/ Fixes: c3f89986fde7 ("ARM: 9391/2: hw_breakpoint: Handle CFI breakpoints") Reported-by: slipher Suggested-by: Mark Rutland Signed-off-by: Linus Walleij Signed-off-by: Russell King --- arch/arm/kernel/hw_breakpoint.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/arch/arm/kernel/hw_breakpoint.c b/arch/arm/kernel/hw_breakpoint.c index cd4b34c96e35..38feb30dfb5f 100644 --- a/arch/arm/kernel/hw_breakpoint.c +++ b/arch/arm/kernel/hw_breakpoint.c @@ -929,10 +929,6 @@ static void hw_breakpoint_cfi_handler(struct pt_regs *regs) break; } } -#else -static void hw_breakpoint_cfi_handler(struct pt_regs *regs) -{ -} #endif /* @@ -964,9 +960,14 @@ static int hw_breakpoint_pending(unsigned long addr, unsigned int fsr, case ARM_ENTRY_SYNC_WATCHPOINT: watchpoint_handler(addr, fsr, regs); break; +#ifdef CONFIG_CFI case ARM_ENTRY_CFI_BREAKPOINT: - hw_breakpoint_cfi_handler(regs); + if (user_mode(regs)) + ret = 1; /* Don't handle userspace BKPT */ + else + hw_breakpoint_cfi_handler(regs); break; +#endif default: ret = 1; /* Unhandled fault. */ } From 8a58a41100ea377e978d99600ec24a9bd0273662 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Fri, 24 Jul 2026 08:05:31 +0100 Subject: [PATCH 123/641] ARM: 9483/1: select HAVE_POSIX_CPU_TIMERS_TASK_WORK Commit c6e61c06d606 ("ARM: 9463/1: Allow to enable RT") enabled PREEMPT_RT on ARM but did not select HAVE_POSIX_CPU_TIMERS_TASK_WORK. This leaves CONFIG_POSIX_CPU_TIMERS_TASK_WORK disabled, so CPU timers expire in hard IRQ context. On PREEMPT_RT this makes run_posix_cpu_timers() take the sleeping sighand->siglock: BUG: sleeping function called from invalid context at spinlock_rt.c:48 rt_spin_lock from lock_task_sighand lock_task_sighand from run_posix_cpu_timers run_posix_cpu_timers from update_process_times ARM handles TIF_NOTIFY_RESUME on all return-to-user paths, including v7-M. ARM32 KVM host support was removed by commit 541ad0150ca4 ("arm: Remove 32bit KVM host support"), so the select need not be conditional on KVM. Select it to defer POSIX CPU timer expiry to task context. Reproduced with setrlimit(RLIMIT_CPU, ...) and a busy loop. The same path is used by setitimer(ITIMER_PROF or ITIMER_VIRTUAL) and POSIX CPU timers created with timer_create(). Assisted-by: Claude:claude-fable-5 Fixes: c6e61c06d606 ("ARM: 9463/1: Allow to enable RT") Signed-off-by: Karl Mehltretter Reviewed-by: Arnd Bergmann Reviewed-by: Sebastian Andrzej Siewior Signed-off-by: Russell King --- arch/arm/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index b8aa3fcce107..bcb53bc9b931 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -133,6 +133,7 @@ config ARM select HAVE_PERF_EVENTS select HAVE_PERF_REGS select HAVE_PERF_USER_STACK_DUMP + select HAVE_POSIX_CPU_TIMERS_TASK_WORK select MMU_GATHER_RCU_TABLE_FREE if SMP && ARM_LPAE select HAVE_REGS_AND_STACK_ACCESS_API select HAVE_RSEQ From e79ca91165d4fd18549c536abdb86101e889052f Mon Sep 17 00:00:00 2001 From: Xie Yuanbin Date: Tue, 28 Jul 2026 03:03:22 +0100 Subject: [PATCH 124/641] ARM: 9484/1: enable interrupts when unhandled user faults are triggered PREEMPT_RT requires interrupts to be enabled when sending signals. When do_DataAbort()/do_PrefetchAbort() triggers unhandled user faults, that is `inf->fn()` return a non-zero value, and the interrupts are not enabled within the hook function, force_sig_fault() will be called with interrupts disabled. This can be triggered by user programs executing the bkpt instruction, with kernel config CONFIG_PERF_EVENTS=n. Enable interrupts in do_DataAbort()/do_PrefetchAbort() when unhandled user faults are triggered to fix the issue. Fixes: c6e61c06d606 ("ARM: 9463/1: Allow to enable RT") Link: https://lore.kernel.org/20260629123349.134224-1-xieyuanbin1@huawei.com Suggested-by: Russell King Reviewed-by: Sebastian Andrzej Siewior Reviewed-by: Linus Walleij Signed-off-by: Xie Yuanbin Signed-off-by: Russell King --- arch/arm/mm/fault.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arch/arm/mm/fault.c b/arch/arm/mm/fault.c index e62cc4be5adf..c68677503532 100644 --- a/arch/arm/mm/fault.c +++ b/arch/arm/mm/fault.c @@ -633,6 +633,9 @@ do_DataAbort(unsigned long addr, unsigned int fsr, struct pt_regs *regs) if (!inf->fn(addr, fsr & ~FSR_LNX_PF, regs)) return; + if (likely(user_mode(regs))) + local_irq_enable(); + pr_alert("8<--- cut here ---\n"); pr_alert("Unhandled fault: %s (0x%03x) at 0x%08lx\n", inf->name, fsr, addr); @@ -663,6 +666,9 @@ do_PrefetchAbort(unsigned long addr, unsigned int ifsr, struct pt_regs *regs) if (!inf->fn(addr, ifsr | FSR_LNX_PF, regs)) return; + if (likely(user_mode(regs))) + local_irq_enable(); + pr_alert("8<--- cut here ---\n"); pr_alert("Unhandled prefetch abort: %s (0x%03x) at 0x%08lx\n", inf->name, ifsr, addr); From 1039bffd6ae9c75b42b7d148d6c1106134107b66 Mon Sep 17 00:00:00 2001 From: Xie Yuanbin Date: Tue, 28 Jul 2026 03:16:42 +0100 Subject: [PATCH 125/641] ARM: 9485/1: mm: acquire mmap write lock around show_pte() for user faults When CONFIG_DEBUG_USER=y, and cmdline "user_debug=31" is set, a user fault may trigger show_pte() without any lock. If another thread in the same process concurrently calls munmap(), the page table pages may be freed while show_pte() is still traversing them, causing a use-after-free in show_pte(). If CONFIG_ARM_LPAE=y, this may cause a kernel panic if the pages table of PMD are freed when show_pte() is running. Acquire mmap_write_lock() around show_pte() for user faults to fix the contention. For user faults, additionally restrict that show_pte() is called only when the addr is a user-space address (addr < TASK_SIZE). This is because the lock of tsk->mm only protects the virtual memory of user address space, furthermore, dumping the page tables of a kernel-space address for user faults is unnecessary and may have security implications. Keep everything unchanged for kernel faults, because the kernel is already in the "oops" state, acquiring a lock may risk a deadlock. Co-developed-by: Qi Xi Fixes: 6d021b724481 ("ARM: dump pgd, pmd and pte states on unhandled data abort faults") Link: https://lore.kernel.org/20260716014022.2823-1-xieyuanbin1@huawei.com Acked-by: Lorenzo Stoakes (ARM) Reviewed-by: Linus Walleij Signed-off-by: Qi Xi Signed-off-by: Xie Yuanbin Signed-off-by: Russell King --- arch/arm/mm/fault.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/arch/arm/mm/fault.c b/arch/arm/mm/fault.c index c68677503532..0a09d4ff7718 100644 --- a/arch/arm/mm/fault.c +++ b/arch/arm/mm/fault.c @@ -181,7 +181,11 @@ __do_user_fault(unsigned long addr, unsigned int fsr, unsigned int sig, pr_err("8<--- cut here ---\n"); pr_err("%s: unhandled page fault (%d) at 0x%08lx, code 0x%03x\n", tsk->comm, sig, addr, fsr); - show_pte(KERN_ERR, tsk->mm, addr); + if (likely(addr < TASK_SIZE)) { + mmap_write_lock(tsk->mm); + show_pte(KERN_ERR, tsk->mm, addr); + mmap_write_unlock(tsk->mm); + } show_regs(regs); } #endif @@ -639,7 +643,15 @@ do_DataAbort(unsigned long addr, unsigned int fsr, struct pt_regs *regs) pr_alert("8<--- cut here ---\n"); pr_alert("Unhandled fault: %s (0x%03x) at 0x%08lx\n", inf->name, fsr, addr); - show_pte(KERN_ALERT, current->mm, addr); + if (likely(user_mode(regs))) { + if (addr < TASK_SIZE) { + mmap_write_lock(current->mm); + show_pte(KERN_ALERT, current->mm, addr); + mmap_write_unlock(current->mm); + } + } else { + show_pte(KERN_ALERT, current->mm, addr); + } arm_notify_die("", regs, inf->sig, inf->code, (void __user *)addr, fsr, 0); From 055a410f7c9f7efa2b5524ad837d6cca78dd0b81 Mon Sep 17 00:00:00 2001 From: Ethan Nelson-Moore Date: Sun, 14 Jun 2026 02:56:01 +0100 Subject: [PATCH 126/641] ARM: 9478/1: Remove references to removed CONFIG_CPU_ARM92x_CPU_IDLE options Several assembly files in arch/arm/mm contain comments referring to CONFIG_CPU_ARM92x_CPU_IDLE options, which have not existed in the kernel since 2.4.21. Remove them. Discovered while searching for CONFIG_* symbols referenced in code but not defined in any Kconfig file. Reviewed-by: Linus Walleij Signed-off-by: Ethan Nelson-Moore Signed-off-by: Russell King --- arch/arm/mm/proc-arm920.S | 2 -- arch/arm/mm/proc-arm922.S | 2 -- arch/arm/mm/proc-arm925.S | 2 -- arch/arm/mm/proc-arm926.S | 2 -- 4 files changed, 8 deletions(-) diff --git a/arch/arm/mm/proc-arm920.S b/arch/arm/mm/proc-arm920.S index 4727f4b5b6e8..0326067c6c75 100644 --- a/arch/arm/mm/proc-arm920.S +++ b/arch/arm/mm/proc-arm920.S @@ -8,8 +8,6 @@ * * These are the low level assembler for performing cache and TLB * functions on the arm920. - * - * CONFIG_CPU_ARM920_CPU_IDLE -> nohlt */ #include #include diff --git a/arch/arm/mm/proc-arm922.S b/arch/arm/mm/proc-arm922.S index 5a4a3f4f2683..3fe6fdf0d325 100644 --- a/arch/arm/mm/proc-arm922.S +++ b/arch/arm/mm/proc-arm922.S @@ -9,8 +9,6 @@ * * These are the low level assembler for performing cache and TLB * functions on the arm922. - * - * CONFIG_CPU_ARM922_CPU_IDLE -> nohlt */ #include #include diff --git a/arch/arm/mm/proc-arm925.S b/arch/arm/mm/proc-arm925.S index 1c4830afe1d3..2d15467e4a08 100644 --- a/arch/arm/mm/proc-arm925.S +++ b/arch/arm/mm/proc-arm925.S @@ -15,8 +15,6 @@ * These are the low level assembler for performing cache and TLB * functions on the arm925. * - * CONFIG_CPU_ARM925_CPU_IDLE -> nohlt - * * Some additional notes based on deciphering the TI TRM on OMAP-5910: * * NOTE1: The TI925T Configuration Register bit "D-cache clean and flush diff --git a/arch/arm/mm/proc-arm926.S b/arch/arm/mm/proc-arm926.S index a09cc3e02efd..d94aa8199452 100644 --- a/arch/arm/mm/proc-arm926.S +++ b/arch/arm/mm/proc-arm926.S @@ -8,8 +8,6 @@ * * These are the low level assembler for performing cache and TLB * functions on the arm926. - * - * CONFIG_CPU_ARM926_CPU_IDLE -> nohlt */ #include #include From bb3b2cfeb206f5b10b859e134651b54120e3f530 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Tue, 30 Jun 2026 18:05:56 +0100 Subject: [PATCH 127/641] ARM: 9480/1: entry: expand comment in __switch_to As per discussion between the developers in the mail thread linked, expand the comment in __switch_to so that readers of the code understand what is going on. Suggested-by: Mark Rutland Acked-by: Mark Rutland Signed-off-by: Linus Walleij > Signed-off-by: Russell King --- arch/arm/kernel/entry-armv.S | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/arch/arm/kernel/entry-armv.S b/arch/arm/kernel/entry-armv.S index ef6a657c8d13..b7faefe5bce7 100644 --- a/arch/arm/kernel/entry-armv.S +++ b/arch/arm/kernel/entry-armv.S @@ -557,9 +557,16 @@ ENTRY(__switch_to) ldmia r4, {r4 - sl, fp, ip, lr} @ Load all regs saved previously #ifdef CONFIG_VMAP_STACK @ - @ Do a dummy read from the new stack while running from the old one so - @ that we can rely on do_translation_fault() to fix up any stale PMD - @ entries covering the vmalloc region. + @ For a non-lazy mm switch, check_vmalloc_seq() has ensured that + @ that the active mm's page tables have mappings for the prev + @ task's stack and the next task's stack. + @ + @ For a lazy mm switch the active mm's page tables have mappings + @ for the prev task's stack but might not have mappings for the + @ new task's stack. Do a dummy read from the new stack while + @ running from the old stack so that we can rely on + @ do_translation_fault() to populate missing PMD entries covering the + @ new task's stack in the old task's page tables. @ ldr r2, [ip] #ifdef CONFIG_KASAN_VMALLOC From 026d7aeadf27291f961893165edb6079a7078f6f Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Wed, 5 Aug 2026 12:20:08 +0000 Subject: [PATCH 128/641] f2fs: fix to return -EFSCORRUPTED in f2fs_get_node_info() correctly Otherwise, it will cache wrong nat info in cache. Cc: stable@kernel.org Fixes: 3cb396a2c790 ("f2fs: fix to do sanity check on nat entry of quota inode") Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/node.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/f2fs/node.c b/fs/f2fs/node.c index 38917e4a7319..9775fa90636a 100644 --- a/fs/f2fs/node.c +++ b/fs/f2fs/node.c @@ -660,6 +660,7 @@ sanity_check: __builtin_return_address(0), ni->ino, ni->nid, ni->blk_addr, ni->version, ni->flag); f2fs_handle_error(sbi, ERROR_INCONSISTENT_NAT); + return -EFSCORRUPTED; } /* cache nat entry */ From 7173538c41d6046f1a92d1f92bc6d0c7b7e314a2 Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Wed, 5 Aug 2026 12:20:50 +0000 Subject: [PATCH 129/641] f2fs: avoid unnecessary shrink in f2fs_shrink_scan() In f2fs_shrink_scan(), let's check if we have already shrinked enough number of memory before calling f2fs_shrink_read_extent_tree(). Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/shrinker.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/f2fs/shrinker.c b/fs/f2fs/shrinker.c index b88babcf6ab4..4f6bf5926de4 100644 --- a/fs/f2fs/shrinker.c +++ b/fs/f2fs/shrinker.c @@ -109,7 +109,8 @@ unsigned long f2fs_shrink_scan(struct shrinker *shrink, freed += f2fs_shrink_age_extent_tree(sbi, nr >> 2); /* shrink read extent cache entries */ - freed += f2fs_shrink_read_extent_tree(sbi, nr >> 2); + if (freed < nr) + freed += f2fs_shrink_read_extent_tree(sbi, nr >> 2); /* shrink clean nat cache entries */ if (freed < nr) From 5b86eab84ac8e9289b5afc52ef88ab18ba5bacab Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Wed, 5 Aug 2026 12:29:20 +0000 Subject: [PATCH 130/641] f2fs: fix to clear dirty flag on folio in error path If node block is corrupted due to chksum mismatch or inconsistent footer info, it needs to drop clear flag of node folio, in order to persist inconsistent node data to storage. Cc: stable@kernel.org Fixes: b42b179bda9f ("f2fs: fix to do checksum even if inode page is uptodate") Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/node.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/f2fs/node.c b/fs/f2fs/node.c index 9775fa90636a..a98855b8cc5e 100644 --- a/fs/f2fs/node.c +++ b/fs/f2fs/node.c @@ -1618,7 +1618,7 @@ page_hit: if (!err) return folio; out_err: - folio_clear_uptodate(folio); + clear_node_folio_dirty(folio); out_put_err: /* ENOENT comes from read_node_folio which is not an error. */ if (err != -ENOENT) From 5d59e6a3777c365dd63cf6b40e579460aed7afd6 Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Thu, 6 Aug 2026 10:52:24 +0800 Subject: [PATCH 131/641] f2fs: print error information in f2fs_put_super() So that we can know in which path we may missed to account the reference correclty: normal path or error handling path. Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/super.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index 25309e6d4156..5902b2da7ea4 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -2058,7 +2058,8 @@ static void f2fs_put_super(struct super_block *sb) if (!get_pages(sbi, i)) continue; f2fs_err(sbi, "detect filesystem reference count leak during " - "umount, type: %d, count: %lld", i, get_pages(sbi, i)); + "umount, type: %d, count: %lld, err: %d, cp_err: %d", + i, get_pages(sbi, i), err, f2fs_cp_error(sbi)); f2fs_bug_on(sbi, 1); } From e1116f8e98eaf76020561e8ab71a51bebfd23334 Mon Sep 17 00:00:00 2001 From: Yonggil Song Date: Thu, 6 Aug 2026 12:39:18 +0900 Subject: [PATCH 132/641] f2fs: issue multi-device flushes in parallel On a multi-device setup, submit_flush_wait() walked the dirty devices in order and aborted the whole loop on the first device whose flush failed, leaving the remaining dirty devices un-flushed. Each device still needs its own data made durable, so a failure on one device must not skip the others. It also waited for one device's flush to complete before issuing the next, even though the devices have independent flush queues and could be flushed concurrently. Flush every dirty device best-effort and in parallel instead: build one PREFLUSH bio per dirty device, submit them all, then wait for every completion, returning the first error seen (0 if all succeed). This bounds the flush window by the slowest device rather than the sum of all of them. No caller depends on the previous early-abort behaviour -- fsync only checks whether the return value is zero (fs/f2fs/file.c). The checkpoint path (f2fs_flush_device_cache) is unaffected; this only touches the fsync flush path. The per-device bio/completion array is small and bounded (at most MAX_DEVICES entries), so allocate it with __GFP_NOFAIL rather than keeping a separate serial fallback path for allocation failure. Signed-off-by: Yonggil Song Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/segment.c | 44 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c index 0b706568b034..00cc2af45ff9 100644 --- a/fs/f2fs/segment.c +++ b/fs/f2fs/segment.c @@ -565,21 +565,59 @@ static int __submit_flush_wait(struct f2fs_sb_info *sbi, return ret; } +static void f2fs_flush_end_io(struct bio *bio) +{ + complete(bio->bi_private); +} + +struct f2fs_flush_bio { + struct bio bio; + struct completion wait; +}; + static int submit_flush_wait(struct f2fs_sb_info *sbi, nid_t ino) { + struct f2fs_flush_bio *flush_bio; + unsigned long devices = 0; int ret = 0; int i; if (!f2fs_is_multi_device(sbi)) return __submit_flush_wait(sbi, sbi->sb->s_bdev); + flush_bio = kmalloc(array_size(sbi->s_ndevs, sizeof(*flush_bio)), + GFP_NOFS | __GFP_NOFAIL); + for (i = 0; i < sbi->s_ndevs; i++) { if (!f2fs_is_dirty_device(sbi, ino, i, FLUSH_INO)) continue; - ret = __submit_flush_wait(sbi, FDEV(i).bdev); - if (ret) - break; + + bio_init(&flush_bio[i].bio, FDEV(i).bdev, NULL, 0, + REQ_OP_WRITE | REQ_SYNC | REQ_PREFLUSH); + init_completion(&flush_bio[i].wait); + flush_bio[i].bio.bi_private = &flush_bio[i].wait; + flush_bio[i].bio.bi_end_io = f2fs_flush_end_io; + devices |= BIT(i); + submit_bio(&flush_bio[i].bio); } + + for (i = 0; i < sbi->s_ndevs; i++) { + int err; + + if (!(devices & BIT(i))) + continue; + + wait_for_completion(&flush_bio[i].wait); + err = blk_status_to_errno(flush_bio[i].bio.bi_status); + trace_f2fs_issue_flush(FDEV(i).bdev, test_opt(sbi, NOBARRIER), + test_opt(sbi, FLUSH_MERGE), err); + if (!err) + f2fs_update_iostat(sbi, NULL, FS_FLUSH_IO, 0); + else if (!ret) + ret = err; + bio_uninit(&flush_bio[i].bio); + } + kfree(flush_bio); return ret; } From 7e188e9f9437ab47c3237d609f1b26348d6fea1a Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Thu, 6 Aug 2026 20:35:37 +0800 Subject: [PATCH 133/641] f2fs: fix to pass folio->index to f2fs_sanity_check_node_footer() Otherwise in f2fs_sanity_check_node_footer(), it will check the same nid incorrectly. Cc: stable@kernel.org Fixes: 0a736109c9d2 ("f2fs: fix to do sanity check on node footer in __write_node_folio()") Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/node.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/f2fs/node.c b/fs/f2fs/node.c index a98855b8cc5e..46bea52e35c3 100644 --- a/fs/f2fs/node.c +++ b/fs/f2fs/node.c @@ -1790,7 +1790,7 @@ static bool __write_node_folio(struct folio *folio, bool atomic, bool do_fsync, /* get old block addr of this node page */ nid = nid_of_node(folio); - if (f2fs_sanity_check_node_footer(sbi, folio, nid, + if (f2fs_sanity_check_node_footer(sbi, folio, folio->index, NODE_TYPE_REGULAR, false)) { fserror_report_metadata(sbi->sb, -EFSCORRUPTED, GFP_NOFS); f2fs_stop_checkpoint(sbi, false, STOP_CP_REASON_CORRUPTED_NID); From 66cc9dec919dd63d8e4b3d386f7aed3ae684e645 Mon Sep 17 00:00:00 2001 From: Jad Keskes Date: Thu, 30 Jul 2026 15:55:48 +0100 Subject: [PATCH 134/641] EDAC/device_sysfs: Use kstrtouint() for poll_msec to prevent truncation The poll_msec sysfs store file uses simple_strtoul() which accepts an unsigned long, but the target field (poll_msec) is unsigned int. On 64-bit systems, a value > UINT_MAX is silently truncated when stored. Fix the mismatch by using kstrtouint() instead. This rejects values larger than UINT_MAX at parse time, making truncation impossible. Also add a check for value < 1 to reject the 0-delay case, which would cause the poll work to spin without delay and consume 100% CPU. Fixes: e27e3dac6517 ("drivers/edac: add edac_device class") Signed-off-by: Jad Keskes Signed-off-by: Borislav Petkov (AMD) Link: https://patch.msgid.link/20260730145549.148229-1-inasj268@gmail.com --- drivers/edac/edac_device_sysfs.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/edac/edac_device_sysfs.c b/drivers/edac/edac_device_sysfs.c index b1c2717cd023..6995ce039db9 100644 --- a/drivers/edac/edac_device_sysfs.c +++ b/drivers/edac/edac_device_sysfs.c @@ -88,14 +88,21 @@ static ssize_t edac_device_ctl_poll_msec_store(struct edac_device_ctl_info *ctl_info, const char *data, size_t count) { - unsigned long value; + unsigned int value; + int ret; /* get the value and enforce that it is non-zero, must be at least * one millisecond for the delay period, between scans * Then cancel last outstanding delay for the work request * and set a new one. */ - value = simple_strtoul(data, NULL, 0); + ret = kstrtouint(data, 0, &value); + if (ret < 0) + return ret; + + if (value < 1) + return -EINVAL; + edac_device_reset_delay_period(ctl_info, value); return count; From 9987979189133486632842d894603813b22937e1 Mon Sep 17 00:00:00 2001 From: "Borislav Petkov (AMD)" Date: Fri, 7 Aug 2026 14:16:17 -0700 Subject: [PATCH 135/641] EDAC/device_sysfs: Cleanup around edac_device_ctl_poll_msec_store() - Align function args - Fix comment style - Fixup formatting around edac_device_reset_delay_period() too The not-too-trivial change is converting the edac_device_reset_delay_period() msec argument to unsigned int as that is what the rest of the code expects. Signed-off-by: Borislav Petkov (AMD) --- drivers/edac/edac_device.c | 10 +++------- drivers/edac/edac_device_sysfs.c | 12 +++++------- drivers/edac/edac_module.h | 3 +-- 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/drivers/edac/edac_device.c b/drivers/edac/edac_device.c index cf0d3c2dfc04..638be1f47c59 100644 --- a/drivers/edac/edac_device.c +++ b/drivers/edac/edac_device.c @@ -342,14 +342,10 @@ static void edac_device_workq_teardown(struct edac_device_ctl_info *edac_dev) } /* - * edac_device_reset_delay_period - * - * need to stop any outstanding workq queued up at this time - * because we will be resetting the sleep time. - * Then restart the workq on the new delay + * Stop any outstanding workq queued up at this time because sleep time will + * be reset. Then restart the workq on the new delay. */ -void edac_device_reset_delay_period(struct edac_device_ctl_info *edac_dev, - unsigned long msec) +void edac_device_reset_delay_period(struct edac_device_ctl_info *edac_dev, unsigned int msec) { edac_dev->poll_msec = msec; edac_dev->delay = msecs_to_jiffies(msec); diff --git a/drivers/edac/edac_device_sysfs.c b/drivers/edac/edac_device_sysfs.c index 6995ce039db9..6359007701ba 100644 --- a/drivers/edac/edac_device_sysfs.c +++ b/drivers/edac/edac_device_sysfs.c @@ -84,17 +84,15 @@ static ssize_t edac_device_ctl_poll_msec_show(struct edac_device_ctl_info return sprintf(data, "%u\n", ctl_info->poll_msec); } -static ssize_t edac_device_ctl_poll_msec_store(struct edac_device_ctl_info - *ctl_info, const char *data, - size_t count) +static ssize_t edac_device_ctl_poll_msec_store(struct edac_device_ctl_info *ctl_info, + const char *data, size_t count) { unsigned int value; int ret; - /* get the value and enforce that it is non-zero, must be at least - * one millisecond for the delay period, between scans - * Then cancel last outstanding delay for the work request - * and set a new one. + /* + * Get the value, make sure it is non-zero, must be at least one millisecond + * for the delay period between scans. */ ret = kstrtouint(data, 0, &value); if (ret < 0) diff --git a/drivers/edac/edac_module.h b/drivers/edac/edac_module.h index 47593afdc234..eceef5539186 100644 --- a/drivers/edac/edac_module.h +++ b/drivers/edac/edac_module.h @@ -52,8 +52,7 @@ bool edac_queue_work(struct delayed_work *work, unsigned long delay); bool edac_stop_work(struct delayed_work *work); bool edac_mod_work(struct delayed_work *work, unsigned long delay); -extern void edac_device_reset_delay_period(struct edac_device_ctl_info - *edac_dev, unsigned long msec); +extern void edac_device_reset_delay_period(struct edac_device_ctl_info *edac_dev, unsigned int msec); extern void edac_mc_reset_delay_period(unsigned long value); /* From d6eac3868143568cc68ab3cc4817227f4af115d3 Mon Sep 17 00:00:00 2001 From: "Borislav Petkov (AMD)" Date: Mon, 10 Aug 2026 06:40:13 -0700 Subject: [PATCH 136/641] EDAC/thunderx: Orphan it Robert doesn't have hardware to test patches anymore and no one else has shown interest in maintaining this driver, so orphan it, for now at least. Signed-off-by: Borislav Petkov (AMD) Acked-by: Robert Richter Link: https://lore.kernel.org/r/annRsN6UBDPsFLr2@rric.localdomain --- MAINTAINERS | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index d413dac5c8b3..671515d21bfc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9308,9 +9308,8 @@ S: Maintained F: drivers/edac/octeon_edac* EDAC-CAVIUM THUNDERX -M: Robert Richter L: linux-edac@vger.kernel.org -S: Odd Fixes +S: Orphan F: drivers/edac/thunderx_edac* EDAC-CORE From eae3faf210bdc69181be717ce90437eadc6b3c80 Mon Sep 17 00:00:00 2001 From: Daeho Jeong Date: Wed, 5 Aug 2026 18:48:13 -0700 Subject: [PATCH 137/641] f2fs: support dynamic reserve/release for device aliasing This patch adds a dynamic management feature to the existing device aliasing functionality. It allows users to dynamically reserve or release specific devices from the filesystem's free pool at runtime through new ioctls. To support this, three new ioctls are introduced: - F2FS_IOC_RESERVE_DEV_ALIAS: This reclaims the space occupied by a device aliasing file. It first performs a capacity check, resets GC victim information for the target range, marks the segments as in-use to prevent new allocations, and then triggers GC to migrate existing valid data out of the range. Finally, it reserves these blocks in the SIT to effectively exclude the device from the usable capacity. - F2FS_IOC_RELEASE_DEV_ALIAS: This releases the reserved space of a previously reserved device aliasing file. It truncates the blocks associated with the file, which makes them available for general filesystem allocation again. - F2FS_IOC_GET_DEV_ALIAS_STATUS: This retrieves the current aliasing status of a device aliasing file, returning whether the file is released (inactive alias) or reserved (active alias, with blocks fully allocated on the device). Signed-off-by: Daeho Jeong Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- Documentation/filesystems/f2fs.rst | 35 ++++ fs/f2fs/data.c | 4 +- fs/f2fs/extent_cache.c | 9 + fs/f2fs/f2fs.h | 19 +- fs/f2fs/file.c | 273 ++++++++++++++++++++++++++++- fs/f2fs/gc.c | 30 ++-- fs/f2fs/namei.c | 14 ++ fs/f2fs/segment.c | 178 +++++++++++++------ fs/f2fs/segment.h | 22 +++ fs/f2fs/super.c | 36 ++++ include/uapi/linux/f2fs.h | 7 + 11 files changed, 558 insertions(+), 69 deletions(-) diff --git a/Documentation/filesystems/f2fs.rst b/Documentation/filesystems/f2fs.rst index 8c4a14ae444f..1a5fd4afe609 100644 --- a/Documentation/filesystems/f2fs.rst +++ b/Documentation/filesystems/f2fs.rst @@ -1045,6 +1045,41 @@ So, the key idea is, user can do any file operations on /dev/vdc, and reclaim the space after the use, while the space is counted as /data. That doesn't require modifying partition size and filesystem format. +Dynamic Device Aliasing Management +---------------------------------- + +In addition to static device aliasing by deleting the aliasing file, F2FS +supports dynamic management of device aliasing. This mechanism allows the system +to dynamically transition partition ownership between F2FS userdata and external +entities (e.g., zRAM, raw partition) based on system requirements without +deleting the master aliasing file or requiring unmount/remount. + +The master aliasing file is created during the initial format of the file system +and remains as a persistent control entity (ioctl gateway) in the root directory. + +- Partition Reservation (In-service to Aliased) + When a specific partition needs to be dedicated to external services (e.g., zRAM), + a user can reserve the device alias range via ioctl. The kernel resets GC victim + information for the target range, marks segments as in-use to prevent new + allocations, and triggers forced GC to migrate existing valid data out of the + range. Finally, it reserves these blocks in the SIT to effectively exclude the + device from the usable capacity. + +- Partition Release (Aliased to In-service) + When external usage concludes, the space is reclaimed not by deleting the file, + but through the release ioctl. The kernel truncates blocks associated with + the file, releasing them back to general filesystem allocation. + +.. code-block:: + + # f2fs_io dev_alias release /mnt/f2fs/vdc.file + # df -h + /dev/vdb 64G 753M 64G 2% /mnt/f2fs + + # f2fs_io dev_alias reserve /mnt/f2fs/vdc.file + # df -h + /dev/vdb 64G 33G 32G 52% /mnt/f2fs + Per-file Read-Only Large Folio Support -------------------------------------- diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c index 30e8084da313..c219ea76a3a7 100644 --- a/fs/f2fs/data.c +++ b/fs/f2fs/data.c @@ -1270,7 +1270,7 @@ int f2fs_reserve_new_blocks(struct dnode_of_data *dn, blkcnt_t count) if (unlikely(is_inode_flag_set(dn->inode, FI_NO_ALLOC))) return -EPERM; - err = inc_valid_block_count(sbi, dn->inode, &count, true); + err = inc_valid_block_count(sbi, dn->inode, &count, true, false); if (unlikely(err)) return err; @@ -1543,7 +1543,7 @@ static int __allocate_data_block(struct dnode_of_data *dn, int seg_type) dn->data_blkaddr = f2fs_data_blkaddr(dn); if (dn->data_blkaddr == NULL_ADDR) { - err = inc_valid_block_count(sbi, dn->inode, &count, true); + err = inc_valid_block_count(sbi, dn->inode, &count, true, false); if (unlikely(err)) return err; } diff --git a/fs/f2fs/extent_cache.c b/fs/f2fs/extent_cache.c index 61f6b9714366..37cf9fa8d537 100644 --- a/fs/f2fs/extent_cache.c +++ b/fs/f2fs/extent_cache.c @@ -17,6 +17,7 @@ #include "f2fs.h" #include "node.h" +#include "segment.h" #include bool sanity_check_extent_cache(struct inode *inode, struct folio *ifolio) @@ -62,6 +63,14 @@ bool sanity_check_extent_cache(struct inode *inode, struct folio *ifolio) __func__, inode->i_ino, ei.blk, ei.fofs, ei.len); return false; } + + if ((GET_SEGOFF_FROM_SEG0(sbi, ei.blk) % BLKS_PER_SEC(sbi)) || + (ei.len % BLKS_PER_SEC(sbi))) { + f2fs_warn(sbi, "%s: device alias inode (ino=%llx)'s extent info [%u, %u, %u] is not aligned to section size %u", + __func__, inode->i_ino, ei.blk, ei.fofs, ei.len, + BLKS_PER_SEC(sbi)); + return false; + } return true; } diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index 8e2fb0bda467..a380b8be8819 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -1404,6 +1404,8 @@ struct f2fs_dev_info { unsigned int total_segments; block_t start_blk; block_t end_blk; + bool has_alias; + bool is_reserving; #ifdef CONFIG_BLK_DEV_ZONED unsigned int nr_blkz; /* Total number of zones */ unsigned long *blkz_seq; /* Bitmap indicating sequential zones */ @@ -1884,6 +1886,7 @@ struct f2fs_sb_info { block_t last_valid_block_count; /* for recovery */ block_t reserved_blocks; /* configurable reserved blocks */ block_t current_reserved_blocks; /* current reserved blocks */ + block_t alias_reserved_blocks; /* reserved blocks for device alias */ /* Additional tracking for no checkpoint mode */ block_t unusable_block_count; /* # of blocks saved by last cp */ @@ -2586,7 +2589,8 @@ static inline unsigned int get_available_block_count(struct f2fs_sb_info *sbi, block_t avail_user_block_count; avail_user_block_count = sbi->user_block_count - - sbi->current_reserved_blocks; + sbi->current_reserved_blocks - + sbi->alias_reserved_blocks; if (test_opt(sbi, RESERVE_ROOT) && !__allow_reserved_root(sbi, inode, cap)) avail_user_block_count -= F2FS_OPTION(sbi).root_reserved_blocks; @@ -2603,7 +2607,8 @@ static inline unsigned int get_available_block_count(struct f2fs_sb_info *sbi, static inline void f2fs_i_blocks_write(struct inode *, block_t, bool, bool); static inline int inc_valid_block_count(struct f2fs_sb_info *sbi, - struct inode *inode, blkcnt_t *count, bool partial) + struct inode *inode, blkcnt_t *count, + bool partial, bool alias_reserved) { long long diff = 0, release = 0; block_t avail_user_block_count; @@ -2626,10 +2631,16 @@ static inline int inc_valid_block_count(struct f2fs_sb_info *sbi, spin_lock(&sbi->stat_lock); + if (alias_reserved) + sbi->alias_reserved_blocks -= *count; + avail_user_block_count = get_available_block_count(sbi, inode, true); diff = (long long)sbi->total_valid_block_count + *count - avail_user_block_count; if (unlikely(diff > 0)) { + if (alias_reserved) + sbi->alias_reserved_blocks += *count; + if (!partial) { spin_unlock(&sbi->stat_lock); release = *count; @@ -4037,6 +4048,8 @@ int f2fs_flush_device_cache(struct f2fs_sb_info *sbi); void f2fs_destroy_flush_cmd_control(struct f2fs_sb_info *sbi, bool free); void f2fs_invalidate_blocks(struct f2fs_sb_info *sbi, block_t addr, unsigned int len); +void f2fs_reserve_device_alias(struct f2fs_sb_info *sbi, block_t addr, + unsigned int len); bool f2fs_is_checkpointed_data(struct f2fs_sb_info *sbi, block_t blkaddr); int f2fs_start_discard_thread(struct f2fs_sb_info *sbi); void f2fs_drop_discard_cmd(struct f2fs_sb_info *sbi); @@ -4258,6 +4271,8 @@ void f2fs_build_gc_manager(struct f2fs_sb_info *sbi); int f2fs_gc_range(struct f2fs_sb_info *sbi, unsigned int start_seg, unsigned int end_seg, bool dry_run, unsigned int dry_run_sections); +void f2fs_reset_gc_victim_resource(struct f2fs_sb_info *sbi, + unsigned int start, unsigned int end); int f2fs_resize_fs(struct file *filp, __u64 block_count); int __init f2fs_create_garbage_collection_cache(void); void f2fs_destroy_garbage_collection_cache(void); diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index b99d9cdf9ba7..56529a82e027 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -813,13 +813,19 @@ int f2fs_do_truncate_blocks(struct inode *inode, u64 from, bool lock) if (IS_DEVICE_ALIASING(inode)) { struct extent_tree *et = F2FS_I(inode)->extent_tree[EX_READ]; - struct extent_info ei = et->largest; + struct extent_info ei; + + read_lock(&et->lock); + ei = et->largest; + read_unlock(&et->lock); f2fs_invalidate_blocks(sbi, ei.blk, ei.len); dec_valid_block_count(sbi, inode, ei.len); f2fs_update_time(sbi, REQ_TIME); + f2fs_drop_extent_tree(inode); + f2fs_folio_put(ifolio, true); goto out; } @@ -1100,8 +1106,9 @@ int f2fs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, if ((attr->ia_valid & ATTR_SIZE)) { if (mapping_large_folio_support(inode->i_mapping)) return -EOPNOTSUPP; - if (!f2fs_is_compress_backend_ready(inode) || - IS_DEVICE_ALIASING(inode)) + if (IS_DEVICE_ALIASING(inode)) + return -EPERM; + if (!f2fs_is_compress_backend_ready(inode)) return -EOPNOTSUPP; if (is_inode_flag_set(inode, FI_COMPRESS_RELEASED) && !IS_ALIGNED(attr->ia_size, @@ -2139,6 +2146,9 @@ static int f2fs_setflags_common(struct inode *inode, u32 iflags, u32 mask) if (IS_NOQUOTA(inode)) return -EPERM; + if (IS_DEVICE_ALIASING(inode)) + return -EPERM; + if ((iflags ^ masked_flags) & F2FS_CASEFOLD_FL) { if (!f2fs_sb_has_casefold(F2FS_I_SB(inode))) return -EOPNOTSUPP; @@ -2687,6 +2697,17 @@ static int f2fs_ioc_get_encryption_policy(struct file *filp, unsigned long arg) return fscrypt_ioctl_get_policy(filp, (void __user *)arg); } +static int f2fs_ioc_get_dev_alias_status(struct file *filp, unsigned long arg) +{ + struct inode *inode = file_inode(filp); + + if (!IS_DEVICE_ALIASING(inode)) + return -EINVAL; + + return put_user(F2FS_HAS_BLOCKS(inode) ? F2FS_DEV_ALIAS_STATUS_RESERVED : + F2FS_DEV_ALIAS_STATUS_RELEASED, (u32 __user *)arg); +} + static int f2fs_ioc_get_encryption_pwsalt(struct file *filp, unsigned long arg) { struct inode *inode = file_inode(filp); @@ -3637,6 +3658,241 @@ static int f2fs_ioc_get_dev_alias_file(struct file *filp, unsigned long arg) (u32 __user *)arg); } +static bool f2fs_get_dev_alias_extent(struct f2fs_sb_info *sbi, + struct dentry *dentry, + struct extent_info *ei) +{ + int i; + + for (i = 1; i < sbi->s_ndevs; i++) { + char *name = strrchr(FDEV(i).path, '/'); + + name = name ? name + 1 : FDEV(i).path; + if (strcmp(name, dentry->d_name.name)) + continue; + + ei->blk = FDEV(i).start_blk; + ei->len = FDEV(i).total_segments << sbi->log_blocks_per_seg; + ei->fofs = 0; + return true; + } + return false; +} + +static int f2fs_ioc_reserve_dev_alias(struct file *filp) +{ + struct inode *inode = file_inode(filp); + struct f2fs_sb_info *sbi = F2FS_I_SB(inode); + struct extent_tree *et = F2FS_I(inode)->extent_tree[EX_READ]; + struct extent_info ei; + struct cp_control cpc = { CP_SYNC, 0, 0, 0 }; + struct f2fs_lock_context lc, glc; + blkcnt_t count; + unsigned int start, end; + int type, err; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (unlikely(is_sbi_flag_set(sbi, SBI_CP_DISABLED))) + return -EINVAL; + + err = mnt_want_write_file(filp); + if (err) + return err; + + inode_lock(inode); + + if (!IS_DEVICE_ALIASING(inode)) { + err = -EINVAL; + goto out_inode_unlock; + } + + if (F2FS_HAS_BLOCKS(inode)) { + err = 0; + goto out_inode_unlock; + } + + if (!f2fs_get_dev_alias_extent(sbi, filp->f_path.dentry, &ei)) { + f2fs_warn(sbi, "device alias file (%s, ino=%llu) has no matching device", + filp->f_path.dentry->d_name.name, + (unsigned long long)inode->i_ino); + set_sbi_flag(sbi, SBI_NEED_FSCK); + f2fs_handle_error(sbi, ERROR_CORRUPTED_INODE); + err = -EFSCORRUPTED; + goto out_inode_unlock; + } + + spin_lock(&sbi->stat_lock); + if (sbi->total_valid_block_count + ei.len > + get_available_block_count(sbi, inode, true)) { + spin_unlock(&sbi->stat_lock); + err = -ENOSPC; + goto out_inode_unlock; + } + sbi->alias_reserved_blocks += ei.len; + spin_unlock(&sbi->stat_lock); + + spin_lock(&FREE_I(sbi)->segmap_lock); + FDEV(f2fs_target_device_index(sbi, ei.blk)).is_reserving = true; + spin_unlock(&FREE_I(sbi)->segmap_lock); + + start = GET_SEGNO(sbi, ei.blk); + end = GET_SEGNO(sbi, ei.blk + ei.len - 1); + + /* Acquire gc_lock for victim reset, curseg resize, and range GC */ + f2fs_down_write_trace(&sbi->gc_lock, &glc); + + /* Reset the victim information to prevent GC from targeting the range */ + f2fs_reset_gc_victim_resource(sbi, start, end); + + /* Move out cursegs from the target range */ + for (type = CURSEG_HOT_DATA; type < NR_CURSEG_PERSIST_TYPE; type++) { + err = f2fs_allocate_segment_for_resize(sbi, type, start, end); + if (err) + goto out_gc_unlock; + } + + f2fs_lock_op(sbi, &lc); + + if (unlikely(is_sbi_flag_set(sbi, SBI_CP_DISABLED))) { + err = -EINVAL; + f2fs_unlock_op(sbi, &lc); + goto out_gc_unlock; + } + + /* do GC to move out valid blocks in the range all at once! */ + err = f2fs_gc_range(sbi, start, end, false, 0); + if (err) { + f2fs_unlock_op(sbi, &lc); + goto out_gc_unlock; + } + + count = ei.len; + err = inc_valid_block_count(sbi, inode, &count, false, true); + if (err) { + f2fs_unlock_op(sbi, &lc); + goto out_gc_unlock; + } + + write_lock(&et->lock); + et->largest = ei; + write_unlock(&et->lock); + clear_inode_flag(inode, FI_NO_EXTENT); + + f2fs_reserve_device_alias(sbi, ei.blk, ei.len); + + i_size_write(inode, (loff_t)ei.len << sbi->log_blocksize); + f2fs_update_inode_page(inode); + + spin_lock(&FREE_I(sbi)->segmap_lock); + FDEV(f2fs_target_device_index(sbi, ei.blk)).is_reserving = false; + spin_unlock(&FREE_I(sbi)->segmap_lock); + + f2fs_unlock_op(sbi, &lc); + f2fs_up_write_trace(&sbi->gc_lock, &glc); + + inode_unlock(inode); + mnt_drop_write_file(filp); + + return f2fs_write_checkpoint(sbi, &cpc); + +out_gc_unlock: + spin_lock(&sbi->stat_lock); + sbi->alias_reserved_blocks -= ei.len; + spin_unlock(&sbi->stat_lock); + + spin_lock(&FREE_I(sbi)->segmap_lock); + FDEV(f2fs_target_device_index(sbi, ei.blk)).is_reserving = false; + spin_unlock(&FREE_I(sbi)->segmap_lock); + f2fs_up_write_trace(&sbi->gc_lock, &glc); + +out_inode_unlock: + inode_unlock(inode); + mnt_drop_write_file(filp); + return err; +} + +static int f2fs_ioc_release_dev_alias(struct file *filp) +{ + struct inode *inode = file_inode(filp); + struct f2fs_sb_info *sbi = F2FS_I_SB(inode); + struct extent_tree *et = F2FS_I(inode)->extent_tree[EX_READ]; + struct extent_info ei = {0, }; + struct cp_control cpc = { CP_SYNC, 0, 0, 0 }; + struct f2fs_lock_context lc, glc; + int err; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (unlikely(is_sbi_flag_set(sbi, SBI_CP_DISABLED))) + return -EINVAL; + + err = mnt_want_write_file(filp); + if (err) + return err; + + inode_lock(inode); + + if (!IS_DEVICE_ALIASING(inode)) { + err = -EINVAL; + goto out_inode_unlock; + } + + if (!F2FS_HAS_BLOCKS(inode)) { + err = 0; + goto out_inode_unlock; + } + + err = filemap_write_and_wait(inode->i_mapping); + if (err) + goto out_inode_unlock; + + read_lock(&et->lock); + ei = et->largest; + read_unlock(&et->lock); + + f2fs_down_write_trace(&sbi->gc_lock, &glc); + f2fs_lock_op(sbi, &lc); + + if (unlikely(is_sbi_flag_set(sbi, SBI_CP_DISABLED))) { + err = -EINVAL; + f2fs_unlock_op(sbi, &lc); + f2fs_up_write_trace(&sbi->gc_lock, &glc); + goto out_inode_unlock; + } + + filemap_invalidate_lock(inode->i_mapping); + truncate_setsize(inode, 0); + + err = f2fs_truncate_blocks(inode, 0, false); + if (err) + i_size_write(inode, (loff_t)ei.len << sbi->log_blocksize); + filemap_invalidate_unlock(inode->i_mapping); + + if (err) { + f2fs_unlock_op(sbi, &lc); + f2fs_up_write_trace(&sbi->gc_lock, &glc); + goto out_inode_unlock; + } + + f2fs_update_inode_page(inode); + + f2fs_unlock_op(sbi, &lc); + f2fs_up_write_trace(&sbi->gc_lock, &glc); + + inode_unlock(inode); + mnt_drop_write_file(filp); + + return f2fs_write_checkpoint(sbi, &cpc); + +out_inode_unlock: + inode_unlock(inode); + mnt_drop_write_file(filp); + return err; +} + static int f2fs_ioc_io_prio(struct file *filp, unsigned long arg) { struct inode *inode = file_inode(filp); @@ -4062,7 +4318,7 @@ static int reserve_compress_blocks(struct dnode_of_data *dn, pgoff_t count, } ret = inc_valid_block_count(sbi, dn->inode, - &to_reserved, false); + &to_reserved, false, false); if (unlikely(ret)) return ret; @@ -4763,8 +5019,14 @@ static long __f2fs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) return f2fs_ioc_compress_file(filp); case F2FS_IOC_GET_DEV_ALIAS_FILE: return f2fs_ioc_get_dev_alias_file(filp, arg); + case F2FS_IOC_GET_DEV_ALIAS_STATUS: + return f2fs_ioc_get_dev_alias_status(filp, arg); case F2FS_IOC_IO_PRIO: return f2fs_ioc_io_prio(filp, arg); + case F2FS_IOC_RESERVE_DEV_ALIAS: + return f2fs_ioc_reserve_dev_alias(filp); + case F2FS_IOC_RELEASE_DEV_ALIAS: + return f2fs_ioc_release_dev_alias(filp); default: return -ENOTTY; } @@ -5551,7 +5813,10 @@ long f2fs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) case F2FS_IOC_DECOMPRESS_FILE: case F2FS_IOC_COMPRESS_FILE: case F2FS_IOC_GET_DEV_ALIAS_FILE: + case F2FS_IOC_GET_DEV_ALIAS_STATUS: case F2FS_IOC_IO_PRIO: + case F2FS_IOC_RESERVE_DEV_ALIAS: + case F2FS_IOC_RELEASE_DEV_ALIAS: break; default: return -ENOIOCTLCMD; diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c index d04633f872ef..51fc8c69eb25 100644 --- a/fs/f2fs/gc.c +++ b/fs/f2fs/gc.c @@ -2191,29 +2191,37 @@ int f2fs_gc_range(struct f2fs_sb_info *sbi, return 0; } +void f2fs_reset_gc_victim_resource(struct f2fs_sb_info *sbi, + unsigned int start, unsigned int end) +{ + int i; + + mutex_lock(&DIRTY_I(sbi)->seglist_lock); + for (i = 0; i < MAX_GC_POLICY; i++) + if (SIT_I(sbi)->last_victim[i] >= start && + SIT_I(sbi)->last_victim[i] <= end) + SIT_I(sbi)->last_victim[i] = 0; + + for (i = BG_GC; i <= FG_GC; i++) + if (sbi->next_victim_seg[i] >= start && + sbi->next_victim_seg[i] <= end) + sbi->next_victim_seg[i] = NULL_SEGNO; + mutex_unlock(&DIRTY_I(sbi)->seglist_lock); +} + static int free_segment_range(struct f2fs_sb_info *sbi, unsigned int secs, bool dry_run) { unsigned int next_inuse, start, end; struct cp_control cpc = { CP_RESIZE, 0, 0, 0 }; - int gc_mode, gc_type; int err = 0; int type; - /* Force block allocation for GC */ MAIN_SECS(sbi) -= secs; start = MAIN_SECS(sbi) * SEGS_PER_SEC(sbi); end = MAIN_SEGS(sbi) - 1; - mutex_lock(&DIRTY_I(sbi)->seglist_lock); - for (gc_mode = 0; gc_mode < MAX_GC_POLICY; gc_mode++) - if (SIT_I(sbi)->last_victim[gc_mode] >= start) - SIT_I(sbi)->last_victim[gc_mode] = 0; - - for (gc_type = BG_GC; gc_type <= FG_GC; gc_type++) - if (sbi->next_victim_seg[gc_type] >= start) - sbi->next_victim_seg[gc_type] = NULL_SEGNO; - mutex_unlock(&DIRTY_I(sbi)->seglist_lock); + f2fs_reset_gc_victim_resource(sbi, start, end); /* Move out cursegs from the target range */ for (type = CURSEG_HOT_DATA; type < NR_CURSEG_PERSIST_TYPE; type++) { diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c index 7ffdf23cea5e..784f636244e1 100644 --- a/fs/f2fs/namei.c +++ b/fs/f2fs/namei.c @@ -425,6 +425,9 @@ static int f2fs_link(struct dentry *old_dentry, struct inode *dir, if (!f2fs_is_checkpoint_ready(sbi)) return -ENOSPC; + if (IS_DEVICE_ALIASING(inode)) + return -EPERM; + err = fscrypt_prepare_link(old_dentry, dir, dentry); if (err) return err; @@ -568,6 +571,9 @@ static int f2fs_unlink(struct inode *dir, struct dentry *dentry) trace_f2fs_unlink_enter(dir, dentry); + if (IS_DEVICE_ALIASING(inode)) + return -EPERM; + if (unlikely(f2fs_cp_error(sbi))) { err = -EIO; goto out; @@ -946,6 +952,9 @@ static int f2fs_rename(struct mnt_idmap *idmap, struct inode *old_dir, bool old_is_dir = S_ISDIR(old_inode->i_mode); int err; + if (IS_DEVICE_ALIASING(old_inode)) + return -EPERM; + if (unlikely(f2fs_cp_error(sbi))) return -EIO; if (!f2fs_is_checkpoint_ready(sbi)) @@ -1016,6 +1025,8 @@ static int f2fs_rename(struct mnt_idmap *idmap, struct inode *old_dir, } if (new_inode) { + if (IS_DEVICE_ALIASING(new_inode)) + return -EPERM; err = -ENOTEMPTY; if (old_is_dir && !f2fs_empty_dir(new_inode)) @@ -1143,6 +1154,9 @@ static int f2fs_cross_rename(struct inode *old_dir, struct dentry *old_dentry, int old_nlink = 0, new_nlink = 0; int err; + if (IS_DEVICE_ALIASING(old_inode) || IS_DEVICE_ALIASING(new_inode)) + return -EPERM; + if (unlikely(f2fs_cp_error(sbi))) return -EIO; if (!f2fs_is_checkpoint_ready(sbi)) diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c index 00cc2af45ff9..b81245ddd490 100644 --- a/fs/f2fs/segment.c +++ b/fs/f2fs/segment.c @@ -261,7 +261,7 @@ retry: } else { blkcnt_t count = 1; - err = inc_valid_block_count(sbi, inode, &count, true); + err = inc_valid_block_count(sbi, inode, &count, true, false); if (err) { f2fs_put_dnode(&dn); return err; @@ -2539,35 +2539,42 @@ static int update_sit_entry_for_alloc(struct f2fs_sb_info *sbi, struct seg_entry unsigned int segno, block_t blkaddr, unsigned int offset, int del) { bool exist; + int del_count = del; + int i; - exist = f2fs_test_and_set_bit(offset, se->cur_valid_map); - if (unlikely(exist)) { - f2fs_err(sbi, "Bitmap was wrongly set, blk:%u", blkaddr); - f2fs_bug_on(sbi, 1); - se->valid_blocks--; - del = 0; - } + f2fs_bug_on(sbi, GET_SEGNO(sbi, blkaddr) != GET_SEGNO(sbi, blkaddr + del_count - 1)); - if (f2fs_block_unit_discard(sbi) && - !f2fs_test_and_set_bit(offset, se->discard_map)) - sbi->discard_blks--; - - /* - * SSR should never reuse block which is checkpointed - * or newly invalidated. - */ - if (!is_sbi_flag_set(sbi, SBI_CP_DISABLED)) { - if (!f2fs_test_and_set_bit(offset, se->ckpt_valid_map)) { - se->ckpt_valid_blocks++; - if (__is_large_section(sbi)) - get_sec_entry(sbi, segno)->ckpt_valid_blocks++; + for (i = 0; i < del_count; i++) { + exist = f2fs_test_and_set_bit(offset + i, se->cur_valid_map); + if (unlikely(exist)) { + f2fs_err(sbi, "Bitmap was wrongly set, blk:%u", blkaddr + i); + f2fs_bug_on(sbi, 1); + se->valid_blocks--; + del -= 1; + continue; } - } - if (!f2fs_test_bit(offset, se->ckpt_valid_map)) { - se->ckpt_valid_blocks += del; - if (__is_large_section(sbi)) - get_sec_entry(sbi, segno)->ckpt_valid_blocks += del; + if (f2fs_block_unit_discard(sbi) && + !f2fs_test_and_set_bit(offset + i, se->discard_map)) + sbi->discard_blks--; + + /* + * SSR should never reuse block which is checkpointed + * or newly invalidated. + */ + if (!is_sbi_flag_set(sbi, SBI_CP_DISABLED)) { + if (!f2fs_test_and_set_bit(offset + i, se->ckpt_valid_map)) { + se->ckpt_valid_blocks++; + if (__is_large_section(sbi)) + get_sec_entry(sbi, segno)->ckpt_valid_blocks++; + } + } + + if (!f2fs_test_bit(offset + i, se->ckpt_valid_map)) { + se->ckpt_valid_blocks += 1; + if (__is_large_section(sbi)) + get_sec_entry(sbi, segno)->ckpt_valid_blocks += 1; + } } if (__is_large_section(sbi)) @@ -2622,9 +2629,14 @@ void f2fs_invalidate_blocks(struct f2fs_sb_info *sbi, block_t addr, unsigned int segno = GET_SEGNO(sbi, addr); struct sit_info *sit_i = SIT_I(sbi); block_t addr_start = addr, addr_end = addr + len - 1; - unsigned int seg_num = GET_SEGNO(sbi, addr_end) - segno + 1; + unsigned int seg_num; unsigned int i = 1, max_blocks = sbi->blocks_per_seg, cnt; + if (len == 0) + return; + + seg_num = GET_SEGNO(sbi, addr_end) - segno + 1; + f2fs_bug_on(sbi, addr == NULL_ADDR); if (addr == NEW_ADDR || addr == COMPRESS_ADDR) return; @@ -2657,6 +2669,52 @@ void f2fs_invalidate_blocks(struct f2fs_sb_info *sbi, block_t addr, up_write(&sit_i->sentry_lock); } +void f2fs_reserve_device_alias(struct f2fs_sb_info *sbi, block_t addr, + unsigned int len) +{ + unsigned int segno = GET_SEGNO(sbi, addr); + struct sit_info *sit_i = SIT_I(sbi); + block_t addr_start = addr, addr_end = addr + len - 1; + unsigned int seg_num; + unsigned int i = 1, max_blocks = sbi->blocks_per_seg, cnt; + + if (len == 0) + return; + + seg_num = GET_SEGNO(sbi, addr_end) - segno + 1; + + down_write(&sit_i->sentry_lock); + + if (seg_num == 1) + cnt = len; + else + cnt = max_blocks - GET_BLKOFF_FROM_SEG0(sbi, addr); + + do { + update_segment_mtime(sbi, addr_start, 0); + update_sit_entry(sbi, addr_start, cnt); + __set_test_and_inuse(sbi, segno); + + /* Remove the segment from PRE (prefree) to prevent checkpoint from freeing it! */ + mutex_lock(&DIRTY_I(sbi)->seglist_lock); + if (test_and_clear_bit(segno, DIRTY_I(sbi)->dirty_segmap[PRE])) + DIRTY_I(sbi)->nr_dirty[PRE]--; + mutex_unlock(&DIRTY_I(sbi)->seglist_lock); + + /* add it into dirty seglist */ + locate_dirty_segment(sbi, segno); + + /* update @addr_start and @cnt and @segno */ + addr_start = START_BLOCK(sbi, ++segno); + if (++i == seg_num) + cnt = GET_BLKOFF_FROM_SEG0(sbi, addr_end) + 1; + else + cnt = max_blocks; + } while (i <= seg_num); + + up_write(&sit_i->sentry_lock); +} + bool f2fs_is_checkpointed_data(struct f2fs_sb_info *sbi, block_t blkaddr) { struct sit_info *sit_i = SIT_I(sbi); @@ -2795,8 +2853,13 @@ static int is_next_segment_free(struct f2fs_sb_info *sbi, unsigned int segno = curseg->segno + 1; struct free_segmap_info *free_i = FREE_I(sbi); - if (segno < MAIN_SEGS(sbi) && segno % SEGS_PER_SEC(sbi)) + if (segno < MAIN_SEGS(sbi) && segno % SEGS_PER_SEC(sbi)) { + int devi = f2fs_target_device_index(sbi, START_BLOCK(sbi, segno)); + + if (f2fs_dev_is_reserving(sbi, devi)) + return 0; return !test_bit(segno, free_i->free_segmap); + } return 0; } @@ -2815,7 +2878,8 @@ static int get_new_segment(struct f2fs_sb_info *sbi, unsigned int alloc_policy = sbi->allocate_section_policy; unsigned int alloc_hint = sbi->allocate_section_hint; bool init = true; - int i; + bool looped = false; + int i, devi; int ret = 0; spin_lock(&free_i->segmap_lock); @@ -2828,8 +2892,13 @@ static int get_new_segment(struct f2fs_sb_info *sbi, if (!new_sec && ((*newseg + 1) % SEGS_PER_SEC(sbi))) { segno = find_next_zero_bit(free_i->free_segmap, GET_SEG_FROM_SEC(sbi, hint + 1), *newseg + 1); - if (segno < GET_SEG_FROM_SEC(sbi, hint + 1)) + if (segno < GET_SEG_FROM_SEC(sbi, hint + 1)) { + devi = f2fs_target_device_index(sbi, START_BLOCK(sbi, segno)); + + if (f2fs_dev_is_alloc_blocked(sbi, devi, pinning)) + goto find_other_zone; goto got_it; + } } #ifdef CONFIG_BLK_DEV_ZONED @@ -2865,33 +2934,42 @@ static int get_new_segment(struct f2fs_sb_info *sbi, find_other_zone: secno = find_next_zero_bit(free_i->free_secmap, MAIN_SECS(sbi), hint); -#ifdef CONFIG_BLK_DEV_ZONED - if (secno >= MAIN_SECS(sbi) && f2fs_sb_has_blkzoned(sbi)) { - /* Write only to sequential zones */ - if (sbi->blkzone_alloc_policy == BLKZONE_ALLOC_ONLY_SEQ) { - hint = GET_SEC_FROM_SEG(sbi, sbi->first_seq_zone_segno); - secno = find_next_zero_bit(free_i->free_secmap, MAIN_SECS(sbi), hint); - } else - secno = find_first_zero_bit(free_i->free_secmap, - MAIN_SECS(sbi)); - if (secno >= MAIN_SECS(sbi)) { - ret = -ENOSPC; - f2fs_bug_on(sbi, 1); - goto out_unlock; - } - } -#endif - if (secno >= MAIN_SECS(sbi)) { - secno = find_first_zero_bit(free_i->free_secmap, - MAIN_SECS(sbi)); - if (secno >= MAIN_SECS(sbi)) { + if (looped) { ret = -ENOSPC; f2fs_bug_on(sbi, !pinning); goto out_unlock; } + hint = 0; +#ifdef CONFIG_BLK_DEV_ZONED + /* Write only to sequential zones */ + if (f2fs_sb_has_blkzoned(sbi) && + sbi->blkzone_alloc_policy == BLKZONE_ALLOC_ONLY_SEQ) + hint = GET_SEC_FROM_SEG(sbi, sbi->first_seq_zone_segno); +#endif + looped = true; + goto find_other_zone; } + segno = GET_SEG_FROM_SEC(sbi, secno); + + devi = f2fs_target_device_index(sbi, START_BLOCK(sbi, segno)); + + if (f2fs_dev_is_alloc_blocked(sbi, devi, pinning)) { + while (devi < sbi->s_ndevs && + f2fs_dev_is_alloc_blocked(sbi, devi, pinning)) { + unsigned int end_segno = GET_SEGNO(sbi, FDEV(devi).end_blk); + + hint = GET_SEC_FROM_SEG(sbi, end_segno) + 1; + devi++; + } + goto find_other_zone; + } + + if (sec_usage_check(sbi, secno)) { + hint = secno + 1; + goto find_other_zone; + } zoneno = GET_ZONE_FROM_SEC(sbi, secno); /* give up on finding another zone */ diff --git a/fs/f2fs/segment.h b/fs/f2fs/segment.h index 33a2257da1e6..db1079169a23 100644 --- a/fs/f2fs/segment.h +++ b/fs/f2fs/segment.h @@ -940,10 +940,32 @@ static inline block_t sum_blk_addr(struct f2fs_sb_info *sbi, int base, int type) - (base + 1) + type; } +static inline bool f2fs_dev_is_reserving(struct f2fs_sb_info *sbi, int devi) +{ + if (!f2fs_sb_has_device_alias(sbi)) + return false; + return FDEV(devi).is_reserving; +} + +static inline bool f2fs_dev_is_alloc_blocked(struct f2fs_sb_info *sbi, + int devi, bool pinning) +{ + if (!f2fs_sb_has_device_alias(sbi)) + return false; + return (pinning && FDEV(devi).has_alias) || FDEV(devi).is_reserving; +} + static inline bool sec_usage_check(struct f2fs_sb_info *sbi, unsigned int secno) { if (is_cursec(sbi, secno) || (sbi->cur_victim_sec == secno)) return true; + if (f2fs_sb_has_device_alias(sbi)) { + block_t start_blk = START_BLOCK(sbi, GET_SEG_FROM_SEC(sbi, secno)); + int devi = f2fs_target_device_index(sbi, start_blk); + + if (f2fs_dev_is_reserving(sbi, devi)) + return true; + } return false; } diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index 5902b2da7ea4..ed9e0ba9cf0b 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -5005,6 +5005,39 @@ static void f2fs_tuning_parameters(struct f2fs_sb_info *sbi) sbi->readdir_ra = true; } +static void f2fs_restore_device_alias(struct f2fs_sb_info *sbi) +{ + struct inode *root = d_inode(sbi->sb->s_root); + struct f2fs_dir_entry *de; + struct folio *folio; + int i; + + if (!f2fs_sb_has_device_alias(sbi)) + return; + + for (i = 1; i < sbi->s_ndevs; i++) { + char *name = strrchr(FDEV(i).path, '/'); + struct inode *inode; + struct qstr qstr; + + name = name ? name + 1 : FDEV(i).path; + qstr.name = name; + qstr.len = strlen(name); + + de = f2fs_find_entry(root, &qstr, &folio); + if (!de) + continue; + + inode = f2fs_iget(sbi->sb, le32_to_cpu(de->ino)); + if (!IS_ERR(inode)) { + if (IS_DEVICE_ALIASING(inode)) + FDEV(i).has_alias = true; + iput(inode); + } + f2fs_folio_put(folio, 0); + } +} + static int f2fs_fill_super(struct super_block *sb, struct fs_context *fc) { struct f2fs_fs_context *ctx = fc->fs_private; @@ -5209,6 +5242,7 @@ try_onemore: sbi->last_valid_block_count = sbi->total_valid_block_count; sbi->reserved_blocks = 0; sbi->current_reserved_blocks = 0; + sbi->alias_reserved_blocks = 0; limit_reserve_root(sbi); adjust_unusable_cap_perc(sbi); @@ -5436,6 +5470,8 @@ reset_checkpoint: f2fs_update_time(sbi, REQ_TIME); clear_sbi_flag(sbi, SBI_CP_DISABLED_QUICK); + f2fs_restore_device_alias(sbi); + sbi->umount_lock_holder = NULL; return 0; diff --git a/include/uapi/linux/f2fs.h b/include/uapi/linux/f2fs.h index 795e26258355..4409ada2fecb 100644 --- a/include/uapi/linux/f2fs.h +++ b/include/uapi/linux/f2fs.h @@ -45,6 +45,9 @@ #define F2FS_IOC_START_ATOMIC_REPLACE _IO(F2FS_IOCTL_MAGIC, 25) #define F2FS_IOC_GET_DEV_ALIAS_FILE _IOR(F2FS_IOCTL_MAGIC, 26, __u32) #define F2FS_IOC_IO_PRIO _IOW(F2FS_IOCTL_MAGIC, 27, __u32) +#define F2FS_IOC_RESERVE_DEV_ALIAS _IO(F2FS_IOCTL_MAGIC, 28) +#define F2FS_IOC_RELEASE_DEV_ALIAS _IO(F2FS_IOCTL_MAGIC, 29) +#define F2FS_IOC_GET_DEV_ALIAS_STATUS _IOR(F2FS_IOCTL_MAGIC, 30, __u32) /* * should be same as XFS_IOC_GOINGDOWN. @@ -70,6 +73,10 @@ enum { F2FS_IOPRIO_MAX, }; +/* for F2FS_IOC_GET_DEV_ALIAS_STATUS */ +#define F2FS_DEV_ALIAS_STATUS_RELEASED 0 +#define F2FS_DEV_ALIAS_STATUS_RESERVED 1 + struct f2fs_gc_range { __u32 sync; __u64 start; From 0d7477640f44851a0bcb82d220e2f451fa67864e Mon Sep 17 00:00:00 2001 From: Jaegeuk Kim Date: Fri, 7 Aug 2026 21:31:11 +0000 Subject: [PATCH 138/641] f2fs: refactor f2fs_evict_inode having three major parts 1. f2fs_pre_evict_inode() : drop all in-memory structures 2. f2fs_delete_inode() : truncate inode blocks, if it was unlinked. 3. f2fs_post_evict_inode() : update inode records for future access Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/inode.c | 132 +++++++++++++++++++++++++++++------------------- 1 file changed, 79 insertions(+), 53 deletions(-) diff --git a/fs/f2fs/inode.c b/fs/f2fs/inode.c index c95e0b126da4..553b1e338aa1 100644 --- a/fs/f2fs/inode.c +++ b/fs/f2fs/inode.c @@ -855,15 +855,12 @@ void f2fs_remove_donate_inode(struct inode *inode) } /* - * Called at the last iput() if i_nlink is zero + * Return true, if we shouldn't go through post_evict_inode. */ -void f2fs_evict_inode(struct inode *inode) +static bool f2fs_pre_evict_inode(struct inode *inode) { struct f2fs_sb_info *sbi = F2FS_I_SB(inode); struct f2fs_inode_info *fi = F2FS_I(inode); - nid_t xnid = fi->i_xattr_nid; - int err = 0; - bool freeze_protected = false; f2fs_abort_atomic_write(inode, true); @@ -883,13 +880,13 @@ void f2fs_evict_inode(struct inode *inode) truncate_inode_pages_final(&inode->i_data); if ((inode->i_nlink || is_bad_inode(inode)) && - test_opt(sbi, COMPRESS_CACHE) && f2fs_compressed_file(inode)) + test_opt(sbi, COMPRESS_CACHE) && f2fs_compressed_file(inode)) f2fs_invalidate_compress_pages(sbi, inode->i_ino); if (inode->i_ino == F2FS_NODE_INO(sbi) || - inode->i_ino == F2FS_META_INO(sbi) || - inode->i_ino == F2FS_COMPRESS_INO(sbi)) - goto out_clear; + inode->i_ino == F2FS_META_INO(sbi) || + inode->i_ino == F2FS_COMPRESS_INO(sbi)) + return true; f2fs_bug_on(sbi, get_dirty_pages(inode)); f2fs_remove_dirty_inode(inode); @@ -898,14 +895,18 @@ void f2fs_evict_inode(struct inode *inode) if (!IS_DEVICE_ALIASING(inode)) f2fs_destroy_extent_tree(inode); - if (inode->i_nlink || is_bad_inode(inode)) - goto no_delete; + return false; +} - err = f2fs_dquot_initialize(inode); - if (err) { - err = 0; +static void f2fs_delete_inode(struct inode *inode) +{ + struct f2fs_sb_info *sbi = F2FS_I_SB(inode); + bool freeze_protected = false; + struct f2fs_lock_context lc; + int err = 0; + + if (f2fs_dquot_initialize(inode)) set_sbi_flag(sbi, SBI_QUOTA_NEED_REPAIR); - } f2fs_remove_ino_entry(sbi, inode->i_ino, APPEND_INO); f2fs_remove_ino_entry(sbi, inode->i_ino, UPDATE_INO); @@ -924,30 +925,30 @@ retry: if (time_to_inject(sbi, FAULT_EVICT_INODE)) err = -EIO; - if (!err) { - struct f2fs_lock_context lc; + if (err) + goto error_check; - f2fs_lock_op(sbi, &lc); - err = f2fs_remove_inode_page(inode); - f2fs_unlock_op(sbi, &lc); - if (err == -ENOENT) { - err = 0; + f2fs_lock_op(sbi, &lc); + err = f2fs_remove_inode_page(inode); + f2fs_unlock_op(sbi, &lc); - /* - * in fuzzed image, another node may has the same - * block address as inode's, if it was truncated - * previously, truncation of inode node will fail. - */ - if (is_inode_flag_set(inode, FI_DIRTY_INODE)) { - f2fs_warn(F2FS_I_SB(inode), - "f2fs_evict_inode: inconsistent node id, ino:%llu", - inode->i_ino); - f2fs_inode_synced(inode); - set_sbi_flag(sbi, SBI_NEED_FSCK); - } + if (err == -ENOENT) { + err = 0; + + /* + * in fuzzed image, another node may has the same + * block address as inode's, if it was truncated + * previously, truncation of inode node will fail. + */ + if (is_inode_flag_set(inode, FI_DIRTY_INODE)) { + f2fs_warn(F2FS_I_SB(inode), + "f2fs_evict_inode: inconsistent node id, ino:%llu", + inode->i_ino); + f2fs_inode_synced(inode); + set_sbi_flag(sbi, SBI_NEED_FSCK); } } - +error_check: /* give more chances, if ENOMEM case */ if (err == -ENOMEM) { err = 0; @@ -957,27 +958,37 @@ retry: if (IS_DEVICE_ALIASING(inode)) f2fs_destroy_extent_tree(inode); - if (err) { - f2fs_update_inode_page(inode); - if (dquot_initialize_needed(inode)) - set_sbi_flag(sbi, SBI_QUOTA_NEED_REPAIR); + if (!err) + goto unfreeze_out; - /* - * If both f2fs_truncate() and f2fs_update_inode_page() failed - * due to fuzzed corrupted inode, call f2fs_inode_synced() to - * avoid triggering later f2fs_bug_on(). - */ - if (is_inode_flag_set(inode, FI_DIRTY_INODE)) { - f2fs_warn(sbi, - "f2fs_evict_inode: inode is dirty, ino:%llu", - inode->i_ino); - f2fs_inode_synced(inode); - set_sbi_flag(sbi, SBI_NEED_FSCK); - } + f2fs_update_inode_page(inode); + + if (dquot_initialize_needed(inode)) + set_sbi_flag(sbi, SBI_QUOTA_NEED_REPAIR); + + /* + * If both f2fs_truncate() and f2fs_update_inode_page() failed + * due to fuzzed corrupted inode, call f2fs_inode_synced() to + * avoid triggering later f2fs_bug_on(). + */ + if (is_inode_flag_set(inode, FI_DIRTY_INODE)) { + f2fs_warn(sbi, + "f2fs_evict_inode: inode is dirty, ino:%llu", + inode->i_ino); + f2fs_inode_synced(inode); + set_sbi_flag(sbi, SBI_NEED_FSCK); } +unfreeze_out: if (freeze_protected) sb_end_intwrite(inode->i_sb); -no_delete: +} + +static void f2fs_post_evict_inode(struct inode *inode) +{ + struct f2fs_sb_info *sbi = F2FS_I_SB(inode); + struct f2fs_inode_info *fi = F2FS_I(inode); + nid_t xnid = fi->i_xattr_nid; + dquot_drop(inode); stat_dec_inline_xattr(inode); @@ -1019,7 +1030,22 @@ no_delete: * In that case, f2fs_check_nid_range() is enough to give a clue. */ } -out_clear: +} + +/* + * Called at the last iput() if i_nlink is zero + */ +void f2fs_evict_inode(struct inode *inode) +{ + if (f2fs_pre_evict_inode(inode)) + goto clear_out; + + if (!inode->i_nlink && !is_bad_inode(inode)) + f2fs_delete_inode(inode); + + f2fs_post_evict_inode(inode); + +clear_out: fscrypt_put_encryption_info(inode); clear_inode(inode); } From 43e214ede664d5d59859e5173cb93659d2559b17 Mon Sep 17 00:00:00 2001 From: Greg Ungerer Date: Tue, 7 Jul 2026 23:01:02 +1000 Subject: [PATCH 139/641] m68k: defconfig: add config for M52358EVB board Add a default configuration for a basic M5235 based EVB board. The SoC has been supported for a long time but there is no default configuration. Create one to improve build and test coverage. Signed-off-by: Greg Ungerer --- arch/m68k/configs/m5235evb_defconfig | 52 ++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 arch/m68k/configs/m5235evb_defconfig diff --git a/arch/m68k/configs/m5235evb_defconfig b/arch/m68k/configs/m5235evb_defconfig new file mode 100644 index 000000000000..fcac4ccd332d --- /dev/null +++ b/arch/m68k/configs/m5235evb_defconfig @@ -0,0 +1,52 @@ +CONFIG_LOG_BUF_SHIFT=14 +CONFIG_EXPERT=y +# CONFIG_FUTEX is not set +# CONFIG_EPOLL is not set +# CONFIG_SIGNALFD is not set +# CONFIG_TIMERFD is not set +# CONFIG_EVENTFD is not set +# CONFIG_AIO is not set +# CONFIG_KALLSYMS is not set +# CONFIG_MMU is not set +CONFIG_M523x=y +CONFIG_M5235EVB=y +CONFIG_RAMBASE=0x00000000 +CONFIG_RAMSIZE=0x01000000 +CONFIG_VECTORBASE=0x00000000 +CONFIG_IPSBAR=0x40000000 +CONFIG_KERNELBASE=0x00020000 +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_BINFMT_FLAT=y +# CONFIG_VM_EVENT_COUNTERS is not set +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_INET=y +# CONFIG_INET_DIAG is not set +# CONFIG_IPV6 is not set +# CONFIG_FW_LOADER is not set +CONFIG_MTD=y +CONFIG_MTD_BLOCK=y +CONFIG_MTD_RAM=y +CONFIG_MTD_UCLINUX=y +CONFIG_BLK_DEV_RAM=y +CONFIG_NETDEVICES=y +CONFIG_FEC=y +# CONFIG_INPUT is not set +# CONFIG_SERIO is not set +# CONFIG_VT is not set +# CONFIG_UNIX98_PTYS is not set +CONFIG_SERIAL_MCF=y +CONFIG_SERIAL_MCF_CONSOLE=y +# CONFIG_HWMON is not set +# CONFIG_USB_SUPPORT is not set +CONFIG_EXT2_FS=y +# CONFIG_FILE_LOCKING is not set +# CONFIG_DNOTIFY is not set +CONFIG_ROMFS_FS=y +CONFIG_ROMFS_BACKED_BY_MTD=y +# CONFIG_NETWORK_FILESYSTEMS is not set +# CONFIG_SLUB_DEBUG is not set +CONFIG_BOOTPARAM=y +CONFIG_BOOTPARAM_STRING="root=/dev/mtdblock0" From ab3e29ac591d69a51120b1113c65b60707ba7bd0 Mon Sep 17 00:00:00 2001 From: Greg Ungerer Date: Tue, 7 Jul 2026 23:02:08 +1000 Subject: [PATCH 140/641] m68k: defconfig: add config for M5282EVB board Add a default configuration for a basic M5282 based EVB board. The SoC has been supported for a long time but there is no default configuration. Create one to improve build and test coverage. Signed-off-by: Greg Ungerer --- arch/m68k/configs/m5282evb_defconfig | 51 ++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 arch/m68k/configs/m5282evb_defconfig diff --git a/arch/m68k/configs/m5282evb_defconfig b/arch/m68k/configs/m5282evb_defconfig new file mode 100644 index 000000000000..85040dc3e45e --- /dev/null +++ b/arch/m68k/configs/m5282evb_defconfig @@ -0,0 +1,51 @@ +CONFIG_LOG_BUF_SHIFT=14 +CONFIG_EXPERT=y +# CONFIG_FUTEX is not set +# CONFIG_EPOLL is not set +# CONFIG_SIGNALFD is not set +# CONFIG_TIMERFD is not set +# CONFIG_EVENTFD is not set +# CONFIG_AIO is not set +# CONFIG_KALLSYMS is not set +# CONFIG_MMU is not set +CONFIG_M528x=y +CONFIG_RAMBASE=0x00000000 +CONFIG_RAMSIZE=0x00800000 +CONFIG_VECTORBASE=0x00000000 +CONFIG_IPSBAR=0x40000000 +CONFIG_KERNELBASE=0x00020000 +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_BINFMT_FLAT=y +# CONFIG_VM_EVENT_COUNTERS is not set +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_INET=y +# CONFIG_INET_DIAG is not set +# CONFIG_IPV6 is not set +# CONFIG_FW_LOADER is not set +CONFIG_MTD=y +CONFIG_MTD_BLOCK=y +CONFIG_MTD_RAM=y +CONFIG_MTD_UCLINUX=y +CONFIG_BLK_DEV_RAM=y +CONFIG_NETDEVICES=y +CONFIG_FEC=y +# CONFIG_INPUT is not set +# CONFIG_SERIO is not set +# CONFIG_VT is not set +# CONFIG_UNIX98_PTYS is not set +CONFIG_SERIAL_MCF=y +CONFIG_SERIAL_MCF_CONSOLE=y +# CONFIG_HWMON is not set +# CONFIG_USB_SUPPORT is not set +CONFIG_EXT2_FS=y +# CONFIG_FILE_LOCKING is not set +# CONFIG_DNOTIFY is not set +CONFIG_ROMFS_FS=y +CONFIG_ROMFS_BACKED_BY_MTD=y +# CONFIG_NETWORK_FILESYSTEMS is not set +# CONFIG_SLUB_DEBUG is not set +CONFIG_BOOTPARAM=y +CONFIG_BOOTPARAM_STRING="root=/dev/mtdblock0" From b47eeaa48ee5f28213056a749cb02ebdcb1d42bd Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 10 Jul 2026 23:19:47 +0200 Subject: [PATCH 141/641] m68k/coldfire: replace linux/gpio.h inclusions linux/gpio.h should no longer be used, convert these instead to linux/gpio/legacy.h for coldfire. Acked-by: Bartosz Golaszewski Signed-off-by: Arnd Bergmann Signed-off-by: Greg Ungerer --- arch/m68k/coldfire/device.c | 2 +- arch/m68k/include/asm/mcfgpio.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/m68k/coldfire/device.c b/arch/m68k/coldfire/device.c index 1420bae0964f..9a0258acd998 100644 --- a/arch/m68k/coldfire/device.c +++ b/arch/m68k/coldfire/device.c @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/arch/m68k/include/asm/mcfgpio.h b/arch/m68k/include/asm/mcfgpio.h index 7103cfa4edb6..29726aa40eb6 100644 --- a/arch/m68k/include/asm/mcfgpio.h +++ b/arch/m68k/include/asm/mcfgpio.h @@ -16,7 +16,7 @@ int __mcfgpio_request(unsigned gpio); void __mcfgpio_free(unsigned gpio); #ifdef CONFIG_GPIOLIB -#include +#include #else /* our alternate 'gpiolib' functions */ From de0dab22cbf6943d0f12f3b1e2eb1bbdd807f039 Mon Sep 17 00:00:00 2001 From: Jean-Michel Hautbois Date: Wed, 17 Jun 2026 21:17:16 +0200 Subject: [PATCH 142/641] m68k: coldfire/5441x: register mcf-rcm-reset platform device Add SoC-level registration of the mcf-rcm-reset platform device so the power_on_reason sysfs attribute is created on every MCF5441x board without per-board boilerplate. Signed-off-by: Jean-Michel Hautbois Signed-off-by: Greg Ungerer --- arch/m68k/coldfire/m5441x.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/arch/m68k/coldfire/m5441x.c b/arch/m68k/coldfire/m5441x.c index 5b5e09ecf487..ad3d0021578f 100644 --- a/arch/m68k/coldfire/m5441x.c +++ b/arch/m68k/coldfire/m5441x.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -242,6 +243,30 @@ static void __init m5441x_fec_init(void) mcf_write8(0x03, MCFGPIO_PAR_FEC); } +/* + * Reset Controller Module status register. Exposed to userspace as + * /sys/devices/platform/mcf-rcm-reset/power_on_reason by the mcf-rcm-reset + * driver (drivers/power/reset/mcf-rcm-reset.c). + */ +static struct resource m5441x_rcm_resource[] = { + { + .start = MCF_RSR, + .end = MCF_RSR, + .flags = IORESOURCE_MEM, + }, +}; + +static int __init m5441x_rcm_init(void) +{ + struct platform_device *pdev; + + pdev = platform_device_register_simple("mcf-rcm-reset", -1, + m5441x_rcm_resource, + ARRAY_SIZE(m5441x_rcm_resource)); + return PTR_ERR_OR_ZERO(pdev); +} +arch_initcall(m5441x_rcm_init); + void __init config_BSP(char *commandp, int size) { m5441x_clk_init(); From 314c9e476ffcc77289c25d354c611aa6d1209784 Mon Sep 17 00:00:00 2001 From: Jaegeuk Kim Date: Mon, 3 Aug 2026 20:32:30 +0000 Subject: [PATCH 143/641] f2fs: call __add_ino_entry out of the eviction path The f2fs_evict_inode() can be called during the direct reclaim path, but __add_ino_entry requires allocating some memory. Since we don't need to do that in that context, let's migrate it in other workqueue context. Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/checkpoint.c | 11 ++++++ fs/f2fs/data.c | 13 ++++++- fs/f2fs/f2fs.h | 4 ++ fs/f2fs/inode.c | 92 +++++++++++++++++++++++++++++++++++++++++--- fs/f2fs/super.c | 9 ++++- 5 files changed, 122 insertions(+), 7 deletions(-) diff --git a/fs/f2fs/checkpoint.c b/fs/f2fs/checkpoint.c index 064f5b537423..4413eccb5ecb 100644 --- a/fs/f2fs/checkpoint.c +++ b/fs/f2fs/checkpoint.c @@ -766,6 +766,15 @@ static void __remove_ino_entry(struct f2fs_sb_info *sbi, nid_t ino, int type) spin_unlock(&im->ino_lock); } +static void f2fs_wait_for_inode_record(struct f2fs_sb_info *sbi, int mode) +{ + if (mode != APPEND_INO && mode != UPDATE_INO) + return; + + /* Let's wait for some pending updates for APPEND_INO and UPDATE_INO. */ + flush_workqueue(sbi->evict_wq); +} + void f2fs_add_ino_entry(struct f2fs_sb_info *sbi, nid_t ino, int type) { /* add new dirty ino entry into list */ @@ -798,6 +807,8 @@ void f2fs_release_ino_entry(struct f2fs_sb_info *sbi, bool all) for (i = all ? ORPHAN_INO : APPEND_INO; i < MAX_INO_ENTRY; i++) { struct inode_management *im = &sbi->im[i]; + f2fs_wait_for_inode_record(sbi, i); + spin_lock(&im->ino_lock); list_for_each_entry_safe(e, tmp, &im->ino_list, list) { list_del(&e->list); diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c index c219ea76a3a7..6ae0eb37d20f 100644 --- a/fs/f2fs/data.c +++ b/fs/f2fs/data.c @@ -4558,13 +4558,24 @@ int f2fs_init_wq(struct f2fs_sb_info *sbi) { sbi->wq = alloc_workqueue("f2fs_wq", WQ_UNBOUND | WQ_HIGHPRI, num_online_cpus()); - return sbi->wq ? 0 : -ENOMEM; + if (!sbi->wq) + return -ENOMEM; + + sbi->evict_wq = alloc_workqueue("f2fs_evict_wq", + WQ_UNBOUND | WQ_HIGHPRI, num_online_cpus()); + if (!sbi->evict_wq) { + destroy_workqueue(sbi->wq); + return -ENOMEM; + } + return 0; } void f2fs_destroy_wq(struct f2fs_sb_info *sbi) { if (sbi->wq) destroy_workqueue(sbi->wq); + if (sbi->evict_wq) + destroy_workqueue(sbi->evict_wq); } int __init f2fs_init_bio_entry_cache(void) diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index a380b8be8819..7fd3a181af61 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -2013,6 +2013,8 @@ struct f2fs_sb_info { struct workqueue_struct *wq; /* bio completion workqueue */ + struct workqueue_struct *evict_wq; /* inode eviction workqueue */ + /* * If we are in irq context, let's update error information into * on-disk superblock in the work. @@ -3872,6 +3874,8 @@ int f2fs_write_inode(struct inode *inode, struct writeback_control *wbc); void f2fs_remove_donate_inode(struct inode *inode); void f2fs_evict_inode(struct inode *inode); void f2fs_handle_failed_inode(struct inode *inode, struct f2fs_lock_context *lc); +int f2fs_init_evict_inode_work(void); +void f2fs_destroy_evict_inode_work(void); /* * namei.c diff --git a/fs/f2fs/inode.c b/fs/f2fs/inode.c index 553b1e338aa1..bac1e360d966 100644 --- a/fs/f2fs/inode.c +++ b/fs/f2fs/inode.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -24,6 +25,18 @@ extern const struct address_space_operations f2fs_compress_aops; #endif +#define NUM_PREALLOC_EVICT_INODE_WORK 8 + +static struct kmem_cache *evict_inode_work_cache; +static mempool_t *evict_inode_work_pool; + +struct evict_inode_work { + struct work_struct work; + struct f2fs_sb_info *sbi; + nid_t ino; + unsigned int add_ino_entry_bits; +}; + void f2fs_mark_inode_dirty_sync(struct inode *inode, bool sync) { if (is_inode_flag_set(inode, FI_NEW_INODE)) @@ -637,6 +650,9 @@ make_now: inode->i_fop = &f2fs_dir_operations; inode->i_mapping->a_ops = &f2fs_dblock_aops; mapping_set_gfp_mask(inode->i_mapping, GFP_NOFS); + + /* Let's prepare APPEND/UPDATE_INO before future access. */ + flush_workqueue(sbi->evict_wq); } else if (S_ISLNK(inode->i_mode)) { if (file_is_encrypt(inode)) inode->i_op = &f2fs_encrypted_symlink_inode_operations; @@ -854,6 +870,25 @@ void f2fs_remove_donate_inode(struct inode *inode) spin_unlock(&sbi->inode_lock[DONATE_INODE]); } +static void f2fs_record_inode_state(struct f2fs_sb_info *sbi, nid_t ino, + unsigned int bits) +{ + if (bits & BIT(APPEND_INO)) + f2fs_add_ino_entry(sbi, ino, APPEND_INO); + if (bits & BIT(UPDATE_INO)) + f2fs_add_ino_entry(sbi, ino, UPDATE_INO); +} + +static void f2fs_evict_inode_work(struct work_struct *work) +{ + struct evict_inode_work *ew = + container_of(work, struct evict_inode_work, work); + + f2fs_record_inode_state(ew->sbi, ew->ino, ew->add_ino_entry_bits); + + mempool_free(ew, evict_inode_work_pool); +} + /* * Return true, if we shouldn't go through post_evict_inode. */ @@ -988,6 +1023,7 @@ static void f2fs_post_evict_inode(struct inode *inode) struct f2fs_sb_info *sbi = F2FS_I_SB(inode); struct f2fs_inode_info *fi = F2FS_I(inode); nid_t xnid = fi->i_xattr_nid; + unsigned int record_bits = 0; dquot_drop(inode); @@ -1014,12 +1050,32 @@ static void f2fs_post_evict_inode(struct inode *inode) inode->i_ino); if (xnid) invalidate_mapping_pages(NODE_MAPPING(sbi), xnid, xnid); - if (inode->i_nlink) { - if (is_inode_flag_set(inode, FI_APPEND_WRITE)) - f2fs_add_ino_entry(sbi, inode->i_ino, APPEND_INO); - if (is_inode_flag_set(inode, FI_UPDATE_WRITE)) - f2fs_add_ino_entry(sbi, inode->i_ino, UPDATE_INO); + + if (!inode->i_nlink) + goto skip_record; + + if (is_inode_flag_set(inode, FI_APPEND_WRITE)) + record_bits = BIT(APPEND_INO); + if (is_inode_flag_set(inode, FI_UPDATE_WRITE)) + record_bits = BIT(UPDATE_INO); + + if (!record_bits) + goto skip_record; + + /* Let's do this in workqueue out of the direct reclaim path. */ + if (current_is_kswapd()) { + f2fs_record_inode_state(sbi, inode->i_ino, record_bits); + } else { + struct evict_inode_work *ew = + mempool_alloc(evict_inode_work_pool, GFP_NOFS); + + ew->sbi = sbi; + ew->ino = inode->i_ino; + ew->add_ino_entry_bits = record_bits; + INIT_WORK(&ew->work, f2fs_evict_inode_work); + queue_work(sbi->evict_wq, &ew->work); } +skip_record: if (is_inode_flag_set(inode, FI_FREE_NID)) { f2fs_alloc_nid_failed(sbi, inode->i_ino); clear_inode_flag(inode, FI_FREE_NID); @@ -1105,3 +1161,29 @@ out: /* iput will drop the inode object */ iput(inode); } + +int __init f2fs_init_evict_inode_work(void) +{ + evict_inode_work_cache = + kmem_cache_create("f2fs_evict_inode_work", + sizeof(struct evict_inode_work), 0, 0, NULL); + if (!evict_inode_work_cache) + goto fail; + evict_inode_work_pool = + mempool_create_slab_pool(NUM_PREALLOC_EVICT_INODE_WORK, + evict_inode_work_cache); + if (!evict_inode_work_pool) + goto fail_free_cache; + return 0; + +fail_free_cache: + kmem_cache_destroy(evict_inode_work_cache); +fail: + return -ENOMEM; +} + +void f2fs_destroy_evict_inode_work(void) +{ + mempool_destroy(evict_inode_work_pool); + kmem_cache_destroy(evict_inode_work_cache); +} diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index ed9e0ba9cf0b..fa4a7a7288d5 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -5739,10 +5739,16 @@ static int __init init_f2fs_fs(void) err = f2fs_init_xattr_cache(); if (err) goto free_casefold_cache; - err = register_filesystem(&f2fs_fs_type); + err = f2fs_init_evict_inode_work(); if (err) goto free_xattr_cache; + err = register_filesystem(&f2fs_fs_type); + if (err) + goto free_evict_inode_cache; return 0; + +free_evict_inode_cache: + f2fs_destroy_evict_inode_work(); free_xattr_cache: f2fs_destroy_xattr_cache(); free_casefold_cache: @@ -5785,6 +5791,7 @@ fail: static void __exit exit_f2fs_fs(void) { unregister_filesystem(&f2fs_fs_type); + f2fs_destroy_evict_inode_work(); f2fs_destroy_xattr_cache(); f2fs_destroy_casefold_cache(); f2fs_destroy_compress_cache(); From f06711486c12c1fbb3ea9c0c595ca244b111891f Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Wed, 29 Jul 2026 10:20:42 -0700 Subject: [PATCH 144/641] ubifs: debug.h: fix kernel-doc struct prototypes Use the "struct" keyword to describe structs in kernel-doc format. This avoids kernel-doc warnings: Warning: fs/ubifs/debug.h:75 cannot understand function prototype: 'struct ubifs_debug_info' Warning: fs/ubifs/debug.h:130 cannot understand function prototype: 'struct ubifs_global_debug_info' Signed-off-by: Randy Dunlap Signed-off-by: Richard Weinberger --- fs/ubifs/debug.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/ubifs/debug.h b/fs/ubifs/debug.h index d425861e6b82..48cd64eb2d01 100644 --- a/fs/ubifs/debug.h +++ b/fs/ubifs/debug.h @@ -26,7 +26,7 @@ typedef int (*dbg_znode_callback)(struct ubifs_info *c, #define UBIFS_DFS_DIR_LEN (3 + 1 + 2 + 3 + 1) /** - * ubifs_debug_info - per-FS debugging information. + * struct ubifs_debug_info - per-FS debugging information. * @old_zroot: old index root - used by 'dbg_check_old_index()' * @old_zroot_level: old index root level - used by 'dbg_check_old_index()' * @old_zroot_sqnum: old index root sqnum - used by 'dbg_check_old_index()' @@ -119,7 +119,7 @@ struct ubifs_debug_info { }; /** - * ubifs_global_debug_info - global (not per-FS) UBIFS debugging information. + * struct ubifs_global_debug_info - global (not per-FS) UBIFS debugging information. * * @chk_gen: if general extra checks are enabled * @chk_index: if index xtra checks are enabled From abd6591fb5cc1490ab2cf5d9ff513a6677fa57b2 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Wed, 29 Jul 2026 10:20:43 -0700 Subject: [PATCH 145/641] ubifs: key.h: use correct function parameter name Use @hash as the function parameter name to prevent kernel-doc warnings: Warning: fs/ubifs/key.h:42 function parameter 'hash' not described in 'key_mask_hash' Warning: fs/ubifs/key.h:42 Excess function parameter 'val' description in 'key_mask_hash' Signed-off-by: Randy Dunlap Reviewed-by: Zhihao Cheng Signed-off-by: Richard Weinberger --- fs/ubifs/key.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ubifs/key.h b/fs/ubifs/key.h index 8142d9d6fe5d..71497c1b4753 100644 --- a/fs/ubifs/key.h +++ b/fs/ubifs/key.h @@ -33,7 +33,7 @@ /** * key_mask_hash - mask a valid hash value. - * @val: value to be masked + * @hash: value to be masked * * We use hash values as offset in directories, so values %0 and %1 are * reserved for "." and "..". %2 is reserved for "end of readdir" marker. This From 4e5e02340a18183b0f8ad633da1516cad92ee43e Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Wed, 29 Jul 2026 10:20:44 -0700 Subject: [PATCH 146/641] ubifs: ubifs.h: clean up kernel-doc comments - use the struct keyword when describing a struct in kernel-doc format. - add or correct missing struct members @leaf, @eof, and @superblock_need_write. - add Returns: clauses for 4 function descriptions. to prevent kernel-doc warnings: Warning: fs/ubifs/ubifs.h:755 struct member 'leaf' not described in 'ubifs_zbranch' Warning: fs/ubifs/ubifs.h:814 struct member 'eof' not described in 'bu_info' Warning: fs/ubifs/ubifs.h:814 Excess struct member 'oef' description in 'bu_info' Warning: fs/ubifs/ubifs.h:992 cannot understand function prototype: 'struct ubifs_stats_info' Warning: fs/ubifs/ubifs.h:1513 struct member 'superblock_need_write' not described in 'ubifs_info' Warning: fs/ubifs/ubifs.h:1597 No description found for return value of 'ubifs_check_hash' Warning: fs/ubifs/ubifs.h:1612 No description found for return value of 'ubifs_check_hmac' Warning: fs/ubifs/ubifs.h:1653 No description found for return value of 'ubifs_branch_hash' Warning: fs/ubifs/ubifs.h:1703 No description found for return value of 'ubifs_auth_node_sz' Signed-off-by: Randy Dunlap Reviewed-by: Zhihao Cheng Signed-off-by: Richard Weinberger --- fs/ubifs/ubifs.h | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/fs/ubifs/ubifs.h b/fs/ubifs/ubifs.h index b62a154c7bd4..00db0d19a85e 100644 --- a/fs/ubifs/ubifs.h +++ b/fs/ubifs/ubifs.h @@ -738,6 +738,7 @@ struct ubifs_jhead { * struct ubifs_zbranch - key/coordinate/length branch stored in znodes. * @key: key * @znode: znode address in memory + * @leaf: leaf node * @lnum: LEB number of the target node (indexing node or data node) * @offs: target node offset within @lnum * @len: target node length @@ -801,7 +802,7 @@ struct ubifs_znode { * @gc_seq: GC sequence number to detect races with GC * @cnt: number of data nodes for bulk read * @blk_cnt: number of data blocks including holes - * @oef: end of file reached + * @eof: end of file reached */ struct bu_info { union ubifs_key key; @@ -985,7 +986,7 @@ struct ubifs_budg_info { }; /** - * ubifs_stats_info - per-FS statistics information. + * struct ubifs_stats_info - per-FS statistics information. * @magic_errors: number of bad magic numbers (will be reset with a new mount). * @node_errors: number of bad nodes (will be reset with a new mount). * @crc_errors: number of bad crcs (will be reset with a new mount). @@ -1051,6 +1052,7 @@ struct ubifs_debug_info; * @rw_incompat: the media is not R/W compatible * @assert_action: action to take when a ubifs_assert() fails * @authenticated: flag indigating the FS is mounted in authenticated mode + * @superblock_need_write: superblock node needs to be written * * @tnc_mutex: protects the Tree Node Cache (TNC), @zroot, @cnext, @enext, and * @calc_idx_sz @@ -1588,8 +1590,9 @@ int ubifs_prepare_auth_node(struct ubifs_info *c, void *node, * @expected: first hash * @got: second hash * - * Compare two hashes @expected and @got. Returns 0 when they are equal, a - * negative error code otherwise. + * Compare two hashes @expected and @got. + * + * Returns: 0 when they are equal, a negative error code otherwise. */ static inline int ubifs_check_hash(const struct ubifs_info *c, const u8 *expected, const u8 *got) @@ -1603,8 +1606,9 @@ static inline int ubifs_check_hash(const struct ubifs_info *c, * @expected: first HMAC * @got: second HMAC * - * Compare two hashes @expected and @got. Returns 0 when they are equal, a - * negative error code otherwise. + * Compare two hashes @expected and @got. + * + * Returns: 0 when they are equal, a negative error code otherwise. */ static inline int ubifs_check_hmac(const struct ubifs_info *c, const u8 *expected, const u8 *got) @@ -1644,7 +1648,7 @@ static inline void ubifs_exit_authentication(struct ubifs_info *c) * @c: UBIFS file-system description object * @br: branch to get the hash from * - * This returns a pointer to the hash of a branch. Since the key already is a + * Returns: a pointer to the hash of a branch. Since the key already is a * dynamically sized object we cannot use a struct member here. */ static inline u8 *ubifs_branch_hash(struct ubifs_info *c, @@ -1694,7 +1698,7 @@ static inline int ubifs_node_verify_hmac(const struct ubifs_info *c, * ubifs_auth_node_sz - returns the size of an authentication node * @c: UBIFS file-system description object * - * This function returns the size of an authentication node which can + * Returns: the size of an authentication node which can * be 0 for unauthenticated filesystems or the real size of an auth node * authentication is enabled. */ From 420477a84f1e52babfe60a43dbdd0988ed956845 Mon Sep 17 00:00:00 2001 From: Zhihao Cheng Date: Sat, 4 Jul 2026 16:30:40 +0800 Subject: [PATCH 147/641] UBI: Preserve torture flag when rescheduling failed erasures In __erase_worker(), when ubi_sync_erase() returns -EINTR, -ENOMEM, -EAGAIN, or -EBUSY, the physical eraseblock is rescheduled for erasure via schedule_erase() with the torture flag hardcoded to 0. This unconditionally drops the torture request. If the error occurred before torture_peb() completed (e.g., -ENOMEM from the ec_hdr allocation in ubi_sync_erase(), errors from self_check_not_bad()/nor_erase_prepare() in ubi_io_sync_erase(), or torture_peb() failing mid-test because its internal do_sync_erase()/ ubi_io_read()/ubi_io_write() returned one of the above error codes), the torture test was never finished. Such a PEB may reach the free pool without being tortured, defeating the purpose of the torture test for detecting marginally-bad eraseblocks. If the error occurred after torture_peb() succeeded (the final do_sync_erase() in ubi_io_sync_erase() or ubi_io_write_ec_hdr() in ubi_sync_erase() failed), the torture has already been done and need not be repeated. Pass the torture flag by pointer to ubi_sync_erase() and ubi_io_sync_erase(), and clear it to zero once torture_peb() has completed successfully. __erase_worker() then forwards the updated wl_wrk->torture to schedule_erase(): torture already done is dropped, torture not completed is preserved across the reschedule. Fixes: 784c145444e7 ("UBI: fix error handling in erase worker") Signed-off-by: Zhihao Cheng Signed-off-by: Richard Weinberger --- drivers/mtd/ubi/attach.c | 4 ++-- drivers/mtd/ubi/fastmap.c | 6 +++--- drivers/mtd/ubi/io.c | 11 +++++++---- drivers/mtd/ubi/ubi.h | 4 ++-- drivers/mtd/ubi/wl.c | 15 +++++++++------ 5 files changed, 23 insertions(+), 17 deletions(-) diff --git a/drivers/mtd/ubi/attach.c b/drivers/mtd/ubi/attach.c index 0fa115cbf3ad..0ce7ff7400d3 100644 --- a/drivers/mtd/ubi/attach.c +++ b/drivers/mtd/ubi/attach.c @@ -771,7 +771,7 @@ void ubi_remove_av(struct ubi_attach_info *ai, struct ubi_ainf_volume *av) static int early_erase_peb(struct ubi_device *ubi, const struct ubi_attach_info *ai, int pnum, int ec) { - int err; + int err, torture = 0; struct ubi_ec_hdr *ec_hdr; if ((long long)ec >= UBI_MAX_ERASECOUNTER) { @@ -790,7 +790,7 @@ static int early_erase_peb(struct ubi_device *ubi, ec_hdr->ec = cpu_to_be64(ec); - err = ubi_io_sync_erase(ubi, pnum, 0); + err = ubi_io_sync_erase(ubi, pnum, &torture); if (err < 0) goto out_free; diff --git a/drivers/mtd/ubi/fastmap.c b/drivers/mtd/ubi/fastmap.c index 3bce1b4d8464..98cc698d9c34 100644 --- a/drivers/mtd/ubi/fastmap.c +++ b/drivers/mtd/ubi/fastmap.c @@ -1488,7 +1488,7 @@ static void return_fm_pebs(struct ubi_device *ubi, */ int ubi_update_fastmap(struct ubi_device *ubi) { - int ret, i, j; + int ret, i, j, torture = 0; struct ubi_fastmap_layout *new_fm, *old_fm; struct ubi_wl_entry *tmp_e; @@ -1526,7 +1526,7 @@ int ubi_update_fastmap(struct ubi_device *ubi) if (!tmp_e) { if (old_fm && old_fm->e[i]) { - ret = ubi_sync_erase(ubi, old_fm->e[i], 0); + ret = ubi_sync_erase(ubi, old_fm->e[i], &torture); if (ret < 0) { ubi_err(ubi, "could not erase old fastmap PEB"); @@ -1578,7 +1578,7 @@ int ubi_update_fastmap(struct ubi_device *ubi) if (old_fm) { /* no fresh anchor PEB was found, reuse the old one */ if (!tmp_e) { - ret = ubi_sync_erase(ubi, old_fm->e[0], 0); + ret = ubi_sync_erase(ubi, old_fm->e[0], &torture); if (ret < 0) { ubi_err(ubi, "could not erase old anchor PEB"); diff --git a/drivers/mtd/ubi/io.c b/drivers/mtd/ubi/io.c index 915eb64cb001..d70d1313ad4e 100644 --- a/drivers/mtd/ubi/io.c +++ b/drivers/mtd/ubi/io.c @@ -519,8 +519,10 @@ error: /** * ubi_io_sync_erase - synchronously erase a physical eraseblock. * @ubi: UBI device description object - * @pnum: physical eraseblock number to erase - * @torture: if this physical eraseblock has to be tortured + * @pnum: the physical eraseblock number to erase + * @torture: if this physical eraseblock has to be tortured; cleared to zero + * once the torture test has completed successfully so that a retry + * of the erase does not torture the physical eraseblock again * * This function synchronously erases physical eraseblock @pnum. If @torture * flag is not zero, the physical eraseblock is checked by means of writing @@ -532,7 +534,7 @@ error: * codes in case of other errors. Note, %-EIO means that the physical * eraseblock is bad. */ -int ubi_io_sync_erase(struct ubi_device *ubi, int pnum, int torture) +int ubi_io_sync_erase(struct ubi_device *ubi, int pnum, int *torture) { int err, ret = 0; @@ -560,10 +562,11 @@ int ubi_io_sync_erase(struct ubi_device *ubi, int pnum, int torture) return err; } - if (torture) { + if (*torture) { ret = torture_peb(ubi, pnum); if (ret < 0) return ret; + *torture = 0; } err = do_sync_erase(ubi, pnum); diff --git a/drivers/mtd/ubi/ubi.h b/drivers/mtd/ubi/ubi.h index af466cd83ae0..80b762892922 100644 --- a/drivers/mtd/ubi/ubi.h +++ b/drivers/mtd/ubi/ubi.h @@ -901,7 +901,7 @@ int self_check_eba(struct ubi_device *ubi, struct ubi_attach_info *ai_fastmap, struct ubi_attach_info *ai_scan); /* wl.c */ -int ubi_sync_erase(struct ubi_device *ubi, struct ubi_wl_entry *e, int torture); +int ubi_sync_erase(struct ubi_device *ubi, struct ubi_wl_entry *e, int *torture); int ubi_wl_get_peb(struct ubi_device *ubi); int ubi_wl_put_peb(struct ubi_device *ubi, int vol_id, int lnum, int pnum, int torture); @@ -923,7 +923,7 @@ int ubi_io_read(const struct ubi_device *ubi, void *buf, int pnum, int offset, int len); int ubi_io_write(struct ubi_device *ubi, const void *buf, int pnum, int offset, int len); -int ubi_io_sync_erase(struct ubi_device *ubi, int pnum, int torture); +int ubi_io_sync_erase(struct ubi_device *ubi, int pnum, int *torture); int ubi_io_is_bad(const struct ubi_device *ubi, int pnum); int ubi_io_mark_bad(const struct ubi_device *ubi, int pnum); int ubi_io_read_ec_hdr(struct ubi_device *ubi, int pnum, diff --git a/drivers/mtd/ubi/wl.c b/drivers/mtd/ubi/wl.c index e3705db8e570..edfab98f7064 100644 --- a/drivers/mtd/ubi/wl.c +++ b/drivers/mtd/ubi/wl.c @@ -443,12 +443,14 @@ static int prot_queue_del(struct ubi_device *ubi, int pnum) * ubi_sync_erase - synchronously erase a physical eraseblock. * @ubi: UBI device description object * @e: the physical eraseblock to erase - * @torture: if the physical eraseblock has to be tortured + * @torture: if the physical eraseblock has to be tortured; cleared to zero + * once the torture test has completed successfully so that a retry + * of the erase does not torture the physical eraseblock again * * This function returns zero in case of success and a negative error code in * case of failure. */ -int ubi_sync_erase(struct ubi_device *ubi, struct ubi_wl_entry *e, int torture) +int ubi_sync_erase(struct ubi_device *ubi, struct ubi_wl_entry *e, int *torture) { int err; struct ubi_ec_hdr *ec_hdr; @@ -1113,7 +1115,7 @@ static int __erase_worker(struct ubi_device *ubi, struct ubi_work *wl_wrk) dbg_wl("erase PEB %d EC %d LEB %d:%d", pnum, e->ec, wl_wrk->vol_id, wl_wrk->lnum); - err = ubi_sync_erase(ubi, e, wl_wrk->torture); + err = ubi_sync_erase(ubi, e, &wl_wrk->torture); if (!err) { spin_lock(&ubi->wl_lock); @@ -1150,7 +1152,8 @@ static int __erase_worker(struct ubi_device *ubi, struct ubi_work *wl_wrk) int err1; /* Re-schedule the LEB for erasure */ - err1 = schedule_erase(ubi, e, vol_id, lnum, 0, true); + err1 = schedule_erase(ubi, e, vol_id, lnum, wl_wrk->torture, + true); if (err1) { spin_lock(&ubi->wl_lock); wl_entry_destroy(ubi, e); @@ -1757,7 +1760,7 @@ static void shutdown_work(struct ubi_device *ubi) static int erase_aeb(struct ubi_device *ubi, struct ubi_ainf_peb *aeb, bool sync) { struct ubi_wl_entry *e; - int err; + int err, torture = 0; e = kmem_cache_alloc(ubi_wl_entry_slab, GFP_KERNEL); if (!e) @@ -1768,7 +1771,7 @@ static int erase_aeb(struct ubi_device *ubi, struct ubi_ainf_peb *aeb, bool sync ubi->lookuptbl[e->pnum] = e; if (sync) { - err = ubi_sync_erase(ubi, e, false); + err = ubi_sync_erase(ubi, e, &torture); if (err) goto out_free; From be04fce9945aac2a843f5d16725391eb80d948ef Mon Sep 17 00:00:00 2001 From: Zhihao Cheng Date: Sat, 4 Jul 2026 16:30:41 +0800 Subject: [PATCH 148/641] UBI: fastmap: Pass to_be_tortured when reusing old fastmap PEBs In ubi_update_fastmap(), when no fresh PEB is available to replace an old fastmap PEB, the old PEB is reused by erasing it synchronously via ubi_sync_erase() with the torture flag hardcoded to zero. However, old_fm->to_be_tortured[] may be non-zero: it is set during fastmap scanning when ubi_io_read_data()/ubi_io_read_ec_hdr() reports UBI_IO_BITFLIPS, meaning the PEB showed signs of bit decay and should be tortured to verify it is still reliable. When the old PEB is instead returned to the WL sub-system via ubi_wl_put_fm_peb(), the to_be_tortured flag is honored. The synchronous reuse path is inconsistent: it silently skips the torture test for PEBs that exhibited bit-flips. Fix it by passing &old_fm->to_be_tortured[i] to ubi_sync_erase(). Fixes: dbb7d2a88d2a ("UBI: Add fastmap core") Signed-off-by: Zhihao Cheng Signed-off-by: Richard Weinberger --- drivers/mtd/ubi/fastmap.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/mtd/ubi/fastmap.c b/drivers/mtd/ubi/fastmap.c index 98cc698d9c34..3f9fb7c181ff 100644 --- a/drivers/mtd/ubi/fastmap.c +++ b/drivers/mtd/ubi/fastmap.c @@ -1488,7 +1488,7 @@ static void return_fm_pebs(struct ubi_device *ubi, */ int ubi_update_fastmap(struct ubi_device *ubi) { - int ret, i, j, torture = 0; + int ret, i, j; struct ubi_fastmap_layout *new_fm, *old_fm; struct ubi_wl_entry *tmp_e; @@ -1526,7 +1526,8 @@ int ubi_update_fastmap(struct ubi_device *ubi) if (!tmp_e) { if (old_fm && old_fm->e[i]) { - ret = ubi_sync_erase(ubi, old_fm->e[i], &torture); + ret = ubi_sync_erase(ubi, old_fm->e[i], + &old_fm->to_be_tortured[i]); if (ret < 0) { ubi_err(ubi, "could not erase old fastmap PEB"); @@ -1578,7 +1579,8 @@ int ubi_update_fastmap(struct ubi_device *ubi) if (old_fm) { /* no fresh anchor PEB was found, reuse the old one */ if (!tmp_e) { - ret = ubi_sync_erase(ubi, old_fm->e[0], &torture); + ret = ubi_sync_erase(ubi, old_fm->e[0], + &old_fm->to_be_tortured[0]); if (ret < 0) { ubi_err(ubi, "could not erase old anchor PEB"); From 95d27c1708bb6e8823c8e7c623f9abc2a91bf4bf Mon Sep 17 00:00:00 2001 From: Ibrahim Hashimov Date: Fri, 24 Jul 2026 09:43:27 +0200 Subject: [PATCH 149/641] ubifs: fix out-of-bounds read in signature length check ubifs_sb_verify_signature() bounds the on-disk ubifs_sig_node->len field before handing the signature payload to verify_pkcs7_signature(), but the check has the wrong sign: if (le32_to_cpu(signode->len) > snod->len + sizeof(struct ubifs_sig_node)) The signature bytes start sizeof(struct ubifs_sig_node) (UBIFS_SIG_NODE_SZ, 64 bytes) into the node, so the payload is at most snod->len - sizeof(struct ubifs_sig_node) bytes long. Adding the header size instead of subtracting it accepts a declared length up to 2 * UBIFS_SIG_NODE_SZ larger than the node actually holds -- past the end of c->sbuf, which is vmalloc(c->leb_size). verify_pkcs7_signature() -> pkcs7_parse_message() -> asn1_ber_decoder() is then handed that inflated length and reads beyond the allocation while walking the DER headers. The node length comes straight from the mounted image, so a crafted signed UBIFS image reaches this via ubifs_read_superblock() before the signature is cryptographically checked. snod->len is guaranteed to be >= UBIFS_SIG_NODE_SZ by the node scanner (c->ranges[UBIFS_SIG_NODE].min_len == UBIFS_SIG_NODE_SZ), so the corrected subtraction cannot underflow. Legitimately signed images are unaffected: a correct superblock never declares a signature longer than the node it is embedded in. Fixes: 817aa094842d ("ubifs: support offline signed images") Cc: stable@vger.kernel.org Signed-off-by: Ibrahim Hashimov Assisted-by: AuditCode-AI:2026.07 Reviewed-by: Richard Weinberger Reviewed-by: Zhihao Cheng Signed-off-by: Richard Weinberger --- fs/ubifs/auth.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ubifs/auth.c b/fs/ubifs/auth.c index a4a0158f712d..1f770795ee70 100644 --- a/fs/ubifs/auth.c +++ b/fs/ubifs/auth.c @@ -217,7 +217,7 @@ int ubifs_sb_verify_signature(struct ubifs_info *c, signode = snod->node; - if (le32_to_cpu(signode->len) > snod->len + sizeof(struct ubifs_sig_node)) { + if (le32_to_cpu(signode->len) > snod->len - sizeof(struct ubifs_sig_node)) { ubifs_err(c, "invalid signature len %d", le32_to_cpu(signode->len)); err = -EINVAL; goto out_destroy; From 5b0a6b554e12a97f9771a9a9f4ea1f5457373c73 Mon Sep 17 00:00:00 2001 From: Yuho Choi Date: Mon, 8 Jun 2026 00:06:39 -0400 Subject: [PATCH 150/641] ubi: Fix rollback for explicit UBI device numbers ubi_init_attach() rolls back module initialization failures by scanning ubi_devices[0..i-1], where i is the mtd= parameter index. That assumes the parameter index matches the UBI device number. That assumption is not true when mtd= specifies an explicit ubi_num. A successfully attached device can be stored at a higher ubi_devices[] slot, and a later failure can miss it during rollback. Scan the full ubi_devices[] array and detach by the actual array index, matching the way UBI devices are stored. Fixes: 83ff59a06663 ("UBI: support ubi_num on mtd.ubi command line") Signed-off-by: Yuho Choi Reviewed-by: Zhihao Cheng Signed-off-by: Richard Weinberger --- drivers/mtd/ubi/build.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mtd/ubi/build.c b/drivers/mtd/ubi/build.c index 674ad87809df..90c5c83d90e3 100644 --- a/drivers/mtd/ubi/build.c +++ b/drivers/mtd/ubi/build.c @@ -1317,10 +1317,10 @@ static int __init ubi_init_attach(void) return 0; out_detach: - for (k = 0; k < i; k++) + for (k = 0; k < UBI_MAX_DEVICES; k++) if (ubi_devices[k]) { mutex_lock(&ubi_devices_mutex); - ubi_detach_mtd_dev(ubi_devices[k]->ubi_num, 1); + ubi_detach_mtd_dev(k, 1); mutex_unlock(&ubi_devices_mutex); } return err; From 31dd710cd84d5dd63c49f640d3a9f36c9699ca95 Mon Sep 17 00:00:00 2001 From: Yuho Choi Date: Fri, 7 Aug 2026 16:45:50 -0400 Subject: [PATCH 151/641] mtd: ubi: Release device reference on busy detach ubi_detach_mtd_dev() obtains a device reference through ubi_get_device() before checking whether the UBI device is busy. The busy return path drops ubi->ref_count but leaves the device reference held, so the device object cannot be released after a later detach. Drop the device reference before returning -EBUSY. Fixes: 7e84c961b2eb ("mtd: ubi: introduce pre-removal notification for UBI volumes") Signed-off-by: Yuho Choi Signed-off-by: Richard Weinberger --- drivers/mtd/ubi/build.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mtd/ubi/build.c b/drivers/mtd/ubi/build.c index 90c5c83d90e3..1997bb70b702 100644 --- a/drivers/mtd/ubi/build.c +++ b/drivers/mtd/ubi/build.c @@ -1105,6 +1105,7 @@ int ubi_detach_mtd_dev(int ubi_num, int anyway) ubi->ref_count -= 1; if (ubi->ref_count) { if (!anyway) { + put_device(&ubi->dev); spin_unlock(&ubi_devices_lock); return -EBUSY; } From ff7937d14f5333ec7e6fb50d00a152d7974f4105 Mon Sep 17 00:00:00 2001 From: Ran Hongyun Date: Thu, 23 Jul 2026 10:02:32 +0800 Subject: [PATCH 152/641] UBI: fix two issues in the ubi.mtd MODULE_PARM_DESC Fix two issues introduced before: - The parameter format string was missing the enable_fm and need_resv_pool tokens introduced in an earlier commit. - The bad-block reservation note was misplaced after Example 5 instead of after Example 3. It was misplaced due to an earlier patch. Fixes: 83ff59a06663 ("UBI: support ubi_num on mtd.ubi command line") Signed-off-by: Ran Hongyun Reviewed-by: Zhihao Cheng Signed-off-by: Richard Weinberger --- drivers/mtd/ubi/build.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/mtd/ubi/build.c b/drivers/mtd/ubi/build.c index 1997bb70b702..456090a12f52 100644 --- a/drivers/mtd/ubi/build.c +++ b/drivers/mtd/ubi/build.c @@ -1575,7 +1575,7 @@ static int ubi_mtd_param_parse(const char *val, const struct kernel_param *kp) } module_param_call(mtd, ubi_mtd_param_parse, NULL, NULL, 0400); -MODULE_PARM_DESC(mtd, "MTD devices to attach. Parameter format: mtd=[,[,max_beb_per1024[,ubi_num]]].\n" +MODULE_PARM_DESC(mtd, "MTD devices to attach. Parameter format: mtd=[,[,max_beb_per1024[,ubi_num[,enable_fm[,need_resv_pool]]]]].\n" "Multiple \"mtd\" parameters may be specified.\n" "MTD devices may be specified by their number, name, or path to the MTD character device node.\n" "Optional \"vid_hdr_offs\" parameter specifies UBI VID header position to be used by UBI. (default value if 0)\n" @@ -1588,9 +1588,9 @@ MODULE_PARM_DESC(mtd, "MTD devices to attach. Parameter format: mtd= Date: Thu, 23 Jul 2026 10:02:33 +0800 Subject: [PATCH 153/641] UBI: support per-device wear-leveling threshold The UBI wear-leveling threshold (CONFIG_MTD_UBI_WL_THRESHOLD) is currently a compile-time constant shared by all UBI devices. When a single kernel image must support multiple NAND flashes with different erase lifetimes, one global threshold cannot suit all devices. Add a per-device configurable wl_threshold parameter: - UAPI: add __s32 wl_threshold to struct ubi_attach_req, carved from the existing padding. - Module parameter: ubi.mtd gains a new optional token "wl_threshold": ubi.mtd=0,0,0,0,0,0,256 ubi.mtd=1,0,0,0,0,0,4096 0 means "use the kernel default", the accepted range is 2-65536. Signed-off-by: Ran Hongyun Reviewed-by: Zhihao Cheng Signed-off-by: Richard Weinberger --- drivers/mtd/ubi/build.c | 43 ++++++++++++++++++++++++++++++------ drivers/mtd/ubi/cdev.c | 2 +- drivers/mtd/ubi/fastmap-wl.c | 8 +++---- drivers/mtd/ubi/ubi.h | 21 +++++++++++++++++- drivers/mtd/ubi/wl.c | 39 ++++++++------------------------ include/uapi/mtd/ubi-user.h | 10 ++++++++- 6 files changed, 79 insertions(+), 44 deletions(-) diff --git a/drivers/mtd/ubi/build.c b/drivers/mtd/ubi/build.c index 456090a12f52..910e6b0ff5e7 100644 --- a/drivers/mtd/ubi/build.c +++ b/drivers/mtd/ubi/build.c @@ -36,7 +36,7 @@ #define MTD_PARAM_LEN_MAX 64 /* Maximum number of comma-separated items in the 'mtd=' parameter */ -#define MTD_PARAM_MAX_COUNT 6 +#define MTD_PARAM_MAX_COUNT 7 /* Maximum value for the number of bad PEBs per 1024 PEBs */ #define MAX_MTD_UBI_BEB_LIMIT 768 @@ -56,6 +56,7 @@ * @max_beb_per1024: maximum expected number of bad PEBs per 1024 PEBs * @enable_fm: enable fastmap when value is non-zero * @need_resv_pool: reserve pool->max_size pebs when value is none-zero + * @wl_threshold: wear-leveling threshold, 0 means use CONFIG_MTD_UBI_WL_THRESHOLD */ struct mtd_dev_param { char name[MTD_PARAM_LEN_MAX]; @@ -64,6 +65,7 @@ struct mtd_dev_param { int max_beb_per1024; int enable_fm; int need_resv_pool; + int wl_threshold; }; /* Numbers of elements set in the @mtd_dev_param array */ @@ -832,6 +834,8 @@ static int autoresize(struct ubi_device *ubi, int vol_id) * @max_beb_per1024: maximum expected number of bad PEB per 1024 PEBs * @disable_fm: whether disable fastmap * @need_resv_pool: whether reserve pebs to fill fm_pool + * @wl_threshold: wear-leveling threshold for this UBI device; 0 means use + * %CONFIG_MTD_UBI_WL_THRESHOLD; accepted range is 2-65536 * * This function attaches MTD device @mtd_dev to UBI and assign @ubi_num number * to the newly created UBI device, unless @ubi_num is %UBI_DEV_NUM_AUTO, in @@ -848,7 +852,7 @@ static int autoresize(struct ubi_device *ubi, int vol_id) */ int ubi_attach_mtd_dev(struct mtd_info *mtd, int ubi_num, int vid_hdr_offset, int max_beb_per1024, bool disable_fm, - bool need_resv_pool) + bool need_resv_pool, int wl_threshold) { struct ubi_device *ubi; int i, err; @@ -859,6 +863,15 @@ int ubi_attach_mtd_dev(struct mtd_info *mtd, int ubi_num, if (!max_beb_per1024) max_beb_per1024 = CONFIG_MTD_UBI_BEB_LIMIT; + if (!wl_threshold) + wl_threshold = CONFIG_MTD_UBI_WL_THRESHOLD; + + if (wl_threshold < 2 || wl_threshold > 65536) { + pr_err("ubi: bad wear-leveling threshold %d\n", + wl_threshold); + return -EINVAL; + } + /* * Check if we already have the same MTD device attached. * @@ -944,6 +957,8 @@ int ubi_attach_mtd_dev(struct mtd_info *mtd, int ubi_num, ubi->ubi_num = ubi_num; ubi->vid_hdr_offset = vid_hdr_offset; ubi->autoresize_vol_id = -1; + ubi->wl_threshold = wl_threshold; + ubi->wl_free_max_diff = wl_threshold * 2; #ifdef CONFIG_MTD_UBI_FASTMAP ubi->fm_pool.used = ubi->fm_pool.size = 0; @@ -1044,7 +1059,7 @@ int ubi_attach_mtd_dev(struct mtd_info *mtd, int ubi_num, ubi->vol_count - UBI_INT_VOL_COUNT, UBI_INT_VOL_COUNT, ubi->vtbl_slots); ubi_msg(ubi, "max/mean erase counter: %d/%d, WL threshold: %d, image sequence number: %u", - ubi->max_ec, ubi->mean_ec, CONFIG_MTD_UBI_WL_THRESHOLD, + ubi->max_ec, ubi->mean_ec, ubi->wl_threshold, ubi->image_seq); ubi_msg(ubi, "available PEBs: %d, total reserved PEBs: %d, PEBs reserved for bad PEB handling: %d", ubi->avail_pebs, ubi->rsvd_pebs, ubi->beb_rsvd_pebs); @@ -1248,7 +1263,7 @@ static void ubi_notify_add(struct mtd_info *mtd) /* called while holding mtd_table_mutex */ mutex_lock_nested(&ubi_devices_mutex, SINGLE_DEPTH_NESTING); - err = ubi_attach_mtd_dev(mtd, UBI_DEV_NUM_AUTO, 0, 0, false, false); + err = ubi_attach_mtd_dev(mtd, UBI_DEV_NUM_AUTO, 0, 0, false, false, 0); mutex_unlock(&ubi_devices_mutex); if (err < 0) __put_mtd_device(mtd); @@ -1290,7 +1305,8 @@ static int __init ubi_init_attach(void) err = ubi_attach_mtd_dev(mtd, p->ubi_num, p->vid_hdr_offs, p->max_beb_per1024, p->enable_fm == 0, - p->need_resv_pool != 0); + p->need_resv_pool != 0, + p->wl_threshold); mutex_unlock(&ubi_devices_mutex); if (err < 0) { pr_err("UBI error: cannot attach mtd%d\n", @@ -1570,12 +1586,24 @@ static int ubi_mtd_param_parse(const char *val, const struct kernel_param *kp) } else p->need_resv_pool = 0; + token = tokens[6]; + if (token) { + int err = kstrtoint(token, 10, &p->wl_threshold); + + if (err) { + pr_err("UBI error: bad value for wl_threshold parameter: %s\n", + token); + return -EINVAL; + } + } else + p->wl_threshold = 0; + mtd_devs += 1; return 0; } module_param_call(mtd, ubi_mtd_param_parse, NULL, NULL, 0400); -MODULE_PARM_DESC(mtd, "MTD devices to attach. Parameter format: mtd=[,[,max_beb_per1024[,ubi_num[,enable_fm[,need_resv_pool]]]]].\n" +MODULE_PARM_DESC(mtd, "MTD devices to attach. Parameter format: mtd=[,[,max_beb_per1024[,ubi_num[,enable_fm[,need_resv_pool[,wl_threshold]]]]]].\n" "Multiple \"mtd\" parameters may be specified.\n" "MTD devices may be specified by their number, name, or path to the MTD character device node.\n" "Optional \"vid_hdr_offs\" parameter specifies UBI VID header position to be used by UBI. (default value if 0)\n" @@ -1590,7 +1618,8 @@ MODULE_PARM_DESC(mtd, "MTD devices to attach. Parameter format: mtd=free, WL_FREE_MAX_DIFF, + e = find_wl_entry(ubi, &ubi->free, ubi->wl_free_max_diff, !can_fill_pools(ubi, left_free)); self_check_in_wl_tree(ubi, e, &ubi->free); rb_erase(&e->u.rb, &ubi->free); @@ -392,18 +392,18 @@ static bool need_wear_leveling(struct ubi_device *ubi) if (!e) { if (!ubi->free.rb_node) return false; - e = find_wl_entry(ubi, &ubi->free, WL_FREE_MAX_DIFF, 0); + e = find_wl_entry(ubi, &ubi->free, ubi->wl_free_max_diff, 0); ec = e->ec; } else { ec = e->ec; if (ubi->free.rb_node) { - e = find_wl_entry(ubi, &ubi->free, WL_FREE_MAX_DIFF, 0); + e = find_wl_entry(ubi, &ubi->free, ubi->wl_free_max_diff, 0); ec = max(ec, e->ec); } } e = rb_entry(rb_first(&ubi->used), struct ubi_wl_entry, u.rb); - return ec - e->ec >= UBI_WL_THRESHOLD; + return ec - e->ec >= ubi->wl_threshold; } /* get_peb_for_wl - returns a PEB to be used internally by the WL sub-system. diff --git a/drivers/mtd/ubi/ubi.h b/drivers/mtd/ubi/ubi.h index 80b762892922..8a9ac60ff2f4 100644 --- a/drivers/mtd/ubi/ubi.h +++ b/drivers/mtd/ubi/ubi.h @@ -516,6 +516,22 @@ struct ubi_debug_info { * @bgt_thread: background thread description object * @thread_enabled: if the background thread is enabled * @bgt_name: background thread name + * @wl_threshold: Maximum difference between two erase counters. If this + * threshold is exceeded, the WL sub-system starts moving + * data from used physical eraseblocks with low erase + * counter to free physical eraseblocks with high erase counter. + * @wl_free_max_diff: When a physical eraseblock is moved, the WL sub-system + * has to pick the target physical eraseblock to move to. + * The simplest way would be just to pick the one with the + * highest erase counter. But in certain workloads this + * could lead to an unlimited wear of one or few physical + * eraseblock. Indeed, imagine a situation when the picked + * physical eraseblock is constantly erased after the + * data is written to it. So, we have a constant which + * limits the highest erase counter of the free physical + * eraseblock to pick. Namely, the WL sub-system does not + * pick eraseblocks with erase counter greater than the + * lowest erase counter plus @wl_free_max_diff. * * @flash_size: underlying MTD device size (in bytes) * @peb_count: count of physical eraseblocks on the MTD device @@ -623,6 +639,8 @@ struct ubi_device { struct task_struct *bgt_thread; int thread_enabled; char bgt_name[sizeof(UBI_BGT_NAME_PATTERN)+2]; + int wl_threshold; + int wl_free_max_diff; /* I/O sub-system's stuff */ long long flash_size; @@ -938,7 +956,8 @@ int ubi_io_write_vid_hdr(struct ubi_device *ubi, int pnum, /* build.c */ int ubi_attach_mtd_dev(struct mtd_info *mtd, int ubi_num, int vid_hdr_offset, int max_beb_per1024, - bool disable_fm, bool need_resv_pool); + bool disable_fm, bool need_resv_pool, + int wl_threshold); int ubi_detach_mtd_dev(int ubi_num, int anyway); struct ubi_device *ubi_get_device(int ubi_num); void ubi_put_device(struct ubi_device *ubi); diff --git a/drivers/mtd/ubi/wl.c b/drivers/mtd/ubi/wl.c index edfab98f7064..5a0db928a90e 100644 --- a/drivers/mtd/ubi/wl.c +++ b/drivers/mtd/ubi/wl.c @@ -95,27 +95,6 @@ /* Number of physical eraseblocks reserved for wear-leveling purposes */ #define WL_RESERVED_PEBS 1 -/* - * Maximum difference between two erase counters. If this threshold is - * exceeded, the WL sub-system starts moving data from used physical - * eraseblocks with low erase counter to free physical eraseblocks with high - * erase counter. - */ -#define UBI_WL_THRESHOLD CONFIG_MTD_UBI_WL_THRESHOLD - -/* - * When a physical eraseblock is moved, the WL sub-system has to pick the target - * physical eraseblock to move to. The simplest way would be just to pick the - * one with the highest erase counter. But in certain workloads this could lead - * to an unlimited wear of one or few physical eraseblock. Indeed, imagine a - * situation when the picked physical eraseblock is constantly erased after the - * data is written to it. So, we have a constant which limits the highest erase - * counter of the free physical eraseblock to pick. Namely, the WL sub-system - * does not pick eraseblocks with erase counter greater than the lowest erase - * counter plus %WL_FREE_MAX_DIFF. - */ -#define WL_FREE_MAX_DIFF (2*UBI_WL_THRESHOLD) - /* * Maximum number of consecutive background thread failures which is enough to * switch to read-only mode. @@ -358,7 +337,7 @@ static struct ubi_wl_entry *find_wl_entry(struct ubi_device *ubi, * * This function looks for a wear leveling entry with medium erase counter, * but not greater or equivalent than the lowest erase counter plus - * %WL_FREE_MAX_DIFF/2. + * @ubi->wl_free_max_diff/2. */ static struct ubi_wl_entry *find_mean_wl_entry(struct ubi_device *ubi, struct rb_root *root) @@ -368,7 +347,7 @@ static struct ubi_wl_entry *find_mean_wl_entry(struct ubi_device *ubi, first = rb_entry(rb_first(root), struct ubi_wl_entry, u.rb); last = rb_entry(rb_last(root), struct ubi_wl_entry, u.rb); - if (last->ec - first->ec < WL_FREE_MAX_DIFF) { + if (last->ec - first->ec < ubi->wl_free_max_diff) { e = rb_entry(root->rb_node, struct ubi_wl_entry, u.rb); /* @@ -379,7 +358,7 @@ static struct ubi_wl_entry *find_mean_wl_entry(struct ubi_device *ubi, */ e = may_reserve_for_fm(ubi, e, root); } else - e = find_wl_entry(ubi, root, WL_FREE_MAX_DIFF/2, 0); + e = find_wl_entry(ubi, root, ubi->wl_free_max_diff/2, 0); return e; } @@ -708,7 +687,7 @@ static int wear_leveling_worker(struct ubi_device *ubi, struct ubi_work *wrk, #ifdef CONFIG_MTD_UBI_FASTMAP e1 = find_anchor_wl_entry(&ubi->used); if (e1 && ubi->fm_anchor && - (ubi->fm_anchor->ec - e1->ec >= UBI_WL_THRESHOLD)) { + (ubi->fm_anchor->ec - e1->ec >= ubi->wl_threshold)) { ubi->fm_do_produce_anchor = 1; /* * fm_anchor is no longer considered a good anchor. @@ -745,7 +724,7 @@ static int wear_leveling_worker(struct ubi_device *ubi, struct ubi_work *wrk, if (!e2) goto out_cancel; - if (!(e2->ec - e1->ec >= UBI_WL_THRESHOLD)) { + if (!(e2->ec - e1->ec >= ubi->wl_threshold)) { dbg_wl("no WL needed: min used EC %d, max free EC %d", e1->ec, e2->ec); @@ -1058,12 +1037,12 @@ static int ensure_wear_leveling(struct ubi_device *ubi, int nested) * We schedule wear-leveling only if the difference between the * lowest erase counter of used physical eraseblocks and a high * erase counter of free physical eraseblocks is greater than - * %UBI_WL_THRESHOLD. + * @ubi->wl_threshold. */ e1 = rb_entry(rb_first(&ubi->used), struct ubi_wl_entry, u.rb); - e2 = find_wl_entry(ubi, &ubi->free, WL_FREE_MAX_DIFF, 0); + e2 = find_wl_entry(ubi, &ubi->free, ubi->wl_free_max_diff, 0); - if (!(e2->ec - e1->ec >= UBI_WL_THRESHOLD)) + if (!(e2->ec - e1->ec >= ubi->wl_threshold)) goto out_unlock; #endif dbg_wl("schedule wear-leveling"); @@ -2093,7 +2072,7 @@ static struct ubi_wl_entry *get_peb_for_wl(struct ubi_device *ubi) { struct ubi_wl_entry *e; - e = find_wl_entry(ubi, &ubi->free, WL_FREE_MAX_DIFF, 0); + e = find_wl_entry(ubi, &ubi->free, ubi->wl_free_max_diff, 0); self_check_in_wl_tree(ubi, e, &ubi->free); ubi->free_count--; ubi_assert(ubi->free_count >= 0); diff --git a/include/uapi/mtd/ubi-user.h b/include/uapi/mtd/ubi-user.h index aa872a41ffb9..3538e11b5175 100644 --- a/include/uapi/mtd/ubi-user.h +++ b/include/uapi/mtd/ubi-user.h @@ -289,6 +289,13 @@ enum { * If @disable_fm is not zero, ubi doesn't create new fastmap even the module * param 'fm_autoconvert' is set, and existed old fastmap will be destroyed * after doing full scanning. + * + * The @wl_threshold defines the maximum difference between the highest and the + * lowest erase counter value of eraseblocks of this UBI device. When this + * threshold is exceeded, UBI starts performing wear leveling by means of + * moving data from eraseblock with low erase counter to eraseblocks with high + * erase counter. If @wl_threshold is zero, the default kernel value of + * %CONFIG_MTD_UBI_WL_THRESHOLD is used. The accepted range is 2-65536. */ struct ubi_attach_req { __s32 ubi_num; @@ -297,7 +304,8 @@ struct ubi_attach_req { __s16 max_beb_per1024; __s8 disable_fm; __s8 need_resv_pool; - __s8 padding[8]; + __s32 wl_threshold; + __s8 padding[4]; }; /* From b2205d3cfd6c76fd5c5443ee9fdb498cfb0e1c66 Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Tue, 11 Aug 2026 15:16:36 +0800 Subject: [PATCH 154/641] f2fs: fix to off-by-one issue in f2fs_zero_post_eof_page() Otherwise, it will drop one more page after new_size which is not necessary. Cc: stable@kernel.org Fixes: ba8dac350faf ("f2fs: fix to zero post-eof page") Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/file.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index 56529a82e027..9dbe509926b2 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -50,7 +50,7 @@ static void f2fs_zero_post_eof_page(struct inode *inode, if (lock) filemap_invalidate_lock(inode->i_mapping); /* zero or drop pages only in range of [old_size, new_size] */ - truncate_inode_pages_range(inode->i_mapping, old_size, new_size); + truncate_inode_pages_range(inode->i_mapping, old_size, new_size - 1); if (lock) filemap_invalidate_unlock(inode->i_mapping); } From 5eced87b7d19dbc76ebdddaf322046f9ac582fcb Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Tue, 11 Aug 2026 15:16:37 +0800 Subject: [PATCH 155/641] f2fs: fix to zero post-EOF data when extending file size generic/794 4s ... - output mismatch (see /share/git/fstests/results//generic/794.out.bad) --- tests/generic/794.out 2026-06-12 08:46:32.766426241 +0800 +++ /share/git/fstests/results//generic/794.out.bad 2026-07-05 18:32:55.000000000 +0800 @@ -1,4 +1,16 @@ QA output created by 794 append_write +FAIL: non-zero data in gap [4080,4096) after shutdown+remount +000000 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a >ZZZZZZZZZZZZZZZZ< +* +001000 truncate_up ... (Run 'diff -u /share/git/fstests/tests/generic/794.out /share/git/fstests/results//generic/794.out.bad' to see the entire diff) Ran: generic/794 Failures: generic/794 Failed 1 of 1 tests Steps of generic/794: 1. write 4096 bytes to file w/ 0x5a 2. use fiemap to get PBA of first block in file 3. truncate file to 4080 4. umount; write 4096 bytes to file w/ 0x5a directly via PBA; mount 5. extend filesize via a) append 4096 from offset 4096, or b) truncate 8192, or c) fallocate 4096 from offset 4096 6. verify the gap is zeroed in memory [4080,4096) 7. sync range 4096 from offset 4096; shutdown -f (flush meta before shutdown) 8. umount; mount; verify [4080,4096) is zeroed or not. When extending file size (e.g. via truncate, fallocate, or write) across an unaligned EOF boundary, we need to ensure that post-EOF data in the partial page is zeroed out in pagecache and marked dirty, then writeback the cache to persist zeroed data before committing inode w/ updated i_size. This help to prevent stale disk data beyond the previous EOF from being exposed after remounting or crash recovery. Since f2fs is a LFS filesystem, we only support direct write via PBA in pinfile, and pinfile has section-aligned filesize, so in Android, there should no problem, but for other usage in different environment, let's fix this w/ fsync_mode=strict mount option. Cc: stable@kernel.org Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/file.c | 100 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 84 insertions(+), 16 deletions(-) diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index 9dbe509926b2..76316e537f65 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -36,16 +36,52 @@ #include #include -static void f2fs_zero_post_eof_page(struct inode *inode, - loff_t new_size, bool lock) +static int fill_zero(struct inode *inode, pgoff_t index, + loff_t start, loff_t len); + +static int do_zero_post_eof_page(struct inode *inode, loff_t new_size) { loff_t old_size = i_size_read(inode); + unsigned int offset, len; + pgoff_t index; + int err; + + offset = old_size & (PAGE_SIZE - 1); + + if (!offset) + return 0; + + len = min_t(loff_t, PAGE_SIZE - offset, new_size - old_size); + index = old_size >> PAGE_SHIFT; + + if (f2fs_has_inline_data(inode)) { + /* data post eof should be always zero */ + if (new_size <= MAX_INLINE_DATA(inode)) + return 0; + err = f2fs_convert_inline_inode(inode); + if (err) + return err; + } + + err = fill_zero(inode, index, offset, len); + if (err) + return err; + return filemap_write_and_wait_range(inode->i_mapping, + old_size, old_size + len - 1); +} + +static int f2fs_zero_post_eof_page(struct inode *inode, + loff_t new_size, bool lock, bool writeback) +{ + loff_t old_size = i_size_read(inode); + bool strict = + F2FS_OPTION(F2FS_I_SB(inode)).fsync_mode == FSYNC_MODE_STRICT; if (old_size >= new_size) - return; + return 0; - if (mapping_empty(inode->i_mapping)) - return; + if (!strict && mapping_empty(inode->i_mapping)) + return 0; if (lock) filemap_invalidate_lock(inode->i_mapping); @@ -53,6 +89,16 @@ static void f2fs_zero_post_eof_page(struct inode *inode, truncate_inode_pages_range(inode->i_mapping, old_size, new_size - 1); if (lock) filemap_invalidate_unlock(inode->i_mapping); + + if (!writeback || !strict) + return 0; + /* + * In fsync_mode=strict, when we expand an unaligned EOF size, we + * should zero post EOF data and writeback the data immediately, + * so that it can avoid exposing stale data after metadata flush + * and POR. + */ + return do_zero_post_eof_page(inode, new_size); } static vm_fault_t f2fs_filemap_fault(struct vm_fault *vmf) @@ -132,7 +178,10 @@ static vm_fault_t f2fs_vm_page_mkwrite(struct vm_fault *vmf) f2fs_bug_on(sbi, f2fs_has_inline_data(inode)); - f2fs_zero_post_eof_page(inode, (folio->index + 1) << PAGE_SHIFT, true); + err = f2fs_zero_post_eof_page(inode, + (folio->index + 1) << PAGE_SHIFT, true, false); + if (err) + goto out_pagefault; file_update_time(vmf->vma->vm_file); filemap_invalidate_lock_shared(inode->i_mapping); @@ -189,7 +238,7 @@ static vm_fault_t f2fs_vm_page_mkwrite(struct vm_fault *vmf) out_sem: filemap_invalidate_unlock_shared(inode->i_mapping); - +out_pagefault: sb_end_pagefault(inode->i_sb); out: ret = vmf_fs_error(err); @@ -1182,8 +1231,12 @@ int f2fs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, f2fs_down_write(&fi->i_gc_rwsem[WRITE]); filemap_invalidate_lock(inode->i_mapping); - if (attr->ia_size > old_size) - f2fs_zero_post_eof_page(inode, attr->ia_size, false); + if (attr->ia_size > old_size) { + err = f2fs_zero_post_eof_page(inode, + attr->ia_size, false, true); + if (err) + goto err_out; + } truncate_setsize(inode, attr->ia_size); if (attr->ia_size <= old_size) @@ -1192,6 +1245,7 @@ int f2fs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, * do not trim all blocks after i_size if target size is * larger than i_size. */ +err_out: filemap_invalidate_unlock(inode->i_mapping); f2fs_up_write(&fi->i_gc_rwsem[WRITE]); if (err) @@ -1303,7 +1357,9 @@ static int f2fs_punch_hole(struct inode *inode, loff_t offset, loff_t len) if (ret) return ret; - f2fs_zero_post_eof_page(inode, offset + len, true); + ret = f2fs_zero_post_eof_page(inode, offset + len, true, false); + if (ret) + return ret; pg_start = ((unsigned long long) offset) >> PAGE_SHIFT; pg_end = ((unsigned long long) offset + len) >> PAGE_SHIFT; @@ -1590,7 +1646,9 @@ static int f2fs_do_collapse(struct inode *inode, loff_t offset, loff_t len) f2fs_down_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]); filemap_invalidate_lock(inode->i_mapping); - f2fs_zero_post_eof_page(inode, offset + len, false); + ret = f2fs_zero_post_eof_page(inode, offset + len, false, false); + if (ret) + goto out_unlock; f2fs_lock_op(sbi, &lc); f2fs_drop_extent_tree(inode); @@ -1598,6 +1656,7 @@ static int f2fs_do_collapse(struct inode *inode, loff_t offset, loff_t len) ret = __exchange_data_block(inode, inode, end, start, nrpages - end, true); f2fs_unlock_op(sbi, &lc); +out_unlock: filemap_invalidate_unlock(inode->i_mapping); f2fs_up_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]); return ret; @@ -1719,7 +1778,9 @@ static int f2fs_zero_range(struct inode *inode, loff_t offset, loff_t len, if (ret) return ret; - f2fs_zero_post_eof_page(inode, offset + len, true); + ret = f2fs_zero_post_eof_page(inode, offset + len, true, false); + if (ret) + return ret; pg_start = ((unsigned long long) offset) >> PAGE_SHIFT; pg_end = ((unsigned long long) offset + len) >> PAGE_SHIFT; @@ -1854,7 +1915,9 @@ static int f2fs_insert_range(struct inode *inode, loff_t offset, loff_t len) f2fs_down_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]); filemap_invalidate_lock(mapping); - f2fs_zero_post_eof_page(inode, offset + len, false); + ret = f2fs_zero_post_eof_page(inode, offset + len, false, false); + if (ret) + goto out_unlock; truncate_pagecache(inode, offset); while (!ret && idx > pg_start) { @@ -1872,6 +1935,7 @@ static int f2fs_insert_range(struct inode *inode, loff_t offset, loff_t len) idx + delta, nr, false); f2fs_unlock_op(sbi, &lc); } +out_unlock: filemap_invalidate_unlock(mapping); f2fs_up_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]); if (ret) @@ -1914,7 +1978,9 @@ static int f2fs_expand_inode_data(struct inode *inode, loff_t offset, if (err) return err; - f2fs_zero_post_eof_page(inode, offset + len, true); + err = f2fs_zero_post_eof_page(inode, offset + len, true, true); + if (err) + return err; f2fs_balance_fs(sbi, true); @@ -5285,8 +5351,10 @@ static ssize_t f2fs_write_checks(struct kiocb *iocb, struct iov_iter *from) if (err) return err; - f2fs_zero_post_eof_page(inode, - iocb->ki_pos + iov_iter_count(from), true); + err = f2fs_zero_post_eof_page(inode, + iocb->ki_pos + iov_iter_count(from), true, true); + if (err) + return err; return count; } From d3915a0a356e58b52ed05f4f22a90b02b663688a Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Sun, 9 Aug 2026 09:26:38 +0800 Subject: [PATCH 156/641] f2fs: unify add/remove ino entry API for all ino types - Call f2fs_add_ino_entry() and f2fs_remove_ino_entry() for ORPHAN_INO - introduce __f2fs_add_ino_entry() to wrap __add_ino_entry(), so that both f2fs_add_ino_entry() and f2fs_set_dirty_device() will call __f2fs_add_ino_entry(). So, after this change: add delete lookup ORPHAN_INO f2fs_add_ino_entry f2fs_remove_ino_entry N/A FLUSH_INO f2fs_set_dirty_device f2fs_remove_ino_entry f2fs_is_dirty_device APPEND_INO f2fs_add_ino_entry f2fs_remove_ino_entry f2fs_exist_written_data UPDATA_INO f2fs_add_ino_entry f2fs_remove_ino_entry f2fs_exist_written_data TRANS_DIR_INO f2fs_add_ino_entry N/A f2fs_exist_written_data XATTR_DIR_INO f2fs_add_ino_entry N/A f2fs_exist_written_data Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/checkpoint.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/fs/f2fs/checkpoint.c b/fs/f2fs/checkpoint.c index 4413eccb5ecb..eb2f955b1e2a 100644 --- a/fs/f2fs/checkpoint.c +++ b/fs/f2fs/checkpoint.c @@ -775,10 +775,16 @@ static void f2fs_wait_for_inode_record(struct f2fs_sb_info *sbi, int mode) flush_workqueue(sbi->evict_wq); } -void f2fs_add_ino_entry(struct f2fs_sb_info *sbi, nid_t ino, int type) +static void __f2fs_add_ino_entry(struct f2fs_sb_info *sbi, nid_t ino, + unsigned int devidx, int type) { /* add new dirty ino entry into list */ - __add_ino_entry(sbi, ino, 0, type); + __add_ino_entry(sbi, ino, devidx, type); +} + +void f2fs_add_ino_entry(struct f2fs_sb_info *sbi, nid_t ino, int type) +{ + __f2fs_add_ino_entry(sbi, ino, 0, type); } void f2fs_remove_ino_entry(struct f2fs_sb_info *sbi, nid_t ino, int type) @@ -823,7 +829,7 @@ void f2fs_release_ino_entry(struct f2fs_sb_info *sbi, bool all) void f2fs_set_dirty_device(struct f2fs_sb_info *sbi, nid_t ino, unsigned int devidx, int type) { - __add_ino_entry(sbi, ino, devidx, type); + __f2fs_add_ino_entry(sbi, ino, devidx, type); } bool f2fs_is_dirty_device(struct f2fs_sb_info *sbi, nid_t ino, @@ -875,14 +881,14 @@ void f2fs_release_orphan_inode(struct f2fs_sb_info *sbi) void f2fs_add_orphan_inode(struct inode *inode) { /* add new orphan ino entry into list */ - __add_ino_entry(F2FS_I_SB(inode), inode->i_ino, 0, ORPHAN_INO); + f2fs_add_ino_entry(F2FS_I_SB(inode), inode->i_ino, ORPHAN_INO); f2fs_update_inode_page(inode); } void f2fs_remove_orphan_inode(struct f2fs_sb_info *sbi, nid_t ino) { /* remove orphan entry from orphan list */ - __remove_ino_entry(sbi, ino, ORPHAN_INO); + f2fs_remove_ino_entry(sbi, ino, ORPHAN_INO); } static int recover_orphan_inode(struct f2fs_sb_info *sbi, nid_t ino) From b6c46ab0bdee90c238e96ea4a74972118c97900d Mon Sep 17 00:00:00 2001 From: Yuho Choi Date: Sun, 2 Aug 2026 21:55:50 -0400 Subject: [PATCH 157/641] ipmi: Remove all sysfs files on registration failure ipmi_add_smi() creates the nr_users and nr_msgs files before trying to create the maintenance_mode file. If that last creation fails, the error path removes only nr_users before dropping the final reference to the interface. Remove nr_msgs as well so no sysfs attribute embedded in the freed interface remains registered. Fixes: 627118470fcc ("ipmi: Add a maintenance mode sysfs file") Cc: stable@vger.kernel.org # 6.18 Signed-off-by: Yuho Choi Message-ID: <20260803015550.618808-1-dbgh9129@gmail.com> Signed-off-by: Corey Minyard --- drivers/char/ipmi/ipmi_msghandler.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/char/ipmi/ipmi_msghandler.c b/drivers/char/ipmi/ipmi_msghandler.c index 8d9f2e647d9b..6ff9a15cced8 100644 --- a/drivers/char/ipmi/ipmi_msghandler.c +++ b/drivers/char/ipmi/ipmi_msghandler.c @@ -3740,6 +3740,7 @@ int ipmi_add_smi(struct module *owner, sysfs_attr_init(&intf->maintenance_mode_devattr.attr); rv = device_create_file(intf->si_dev, &intf->maintenance_mode_devattr); if (rv) { + device_remove_file(intf->si_dev, &intf->nr_msgs_devattr); device_remove_file(intf->si_dev, &intf->nr_users_devattr); goto out_err_bmc_reg; } From 18e633dd9674c8bf118ea8583bb5a531edc2728a Mon Sep 17 00:00:00 2001 From: Michail Tatas Date: Fri, 7 Aug 2026 01:30:20 +0300 Subject: [PATCH 158/641] ipmi: Fix leak in __ipmi_bmc_register In case that ida_alloc(&ipmi_bmc_ida,...) succeeds and then platform_device_register() fails, ipmi_bmc_ida is leaked. Fix by freeing the error path Signed-off-by: Michail Tatas Message-ID: Signed-off-by: Corey Minyard --- drivers/char/ipmi/ipmi_msghandler.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/char/ipmi/ipmi_msghandler.c b/drivers/char/ipmi/ipmi_msghandler.c index 6ff9a15cced8..7634dff99f41 100644 --- a/drivers/char/ipmi/ipmi_msghandler.c +++ b/drivers/char/ipmi/ipmi_msghandler.c @@ -3301,6 +3301,7 @@ out_list_del: list_del(&intf->bmc_link); mutex_unlock(&bmc->dyn_mutex); intf->bmc = &intf->tmp_bmc; + ida_free(&ipmi_bmc_ida, bmc->pdev.id); put_device(&bmc->pdev.dev); goto out; } From eee1ea58c81ecb4d05c7267c05da73544386f3d9 Mon Sep 17 00:00:00 2001 From: "Pawel Zalewski (The Capable Hub)" Date: Fri, 7 Aug 2026 12:26:10 +0100 Subject: [PATCH 159/641] char: ipmi: use named initializers for acpi_device_id Use a named initializer for the acpi_device_id fields which makes the code more readable and consistent with how lists are initialized in the rest of the kernel code base. Also drop explicitly setting fields to 0 where it is redundant. While we are at it - unify the list terminator to have a single space between the brackets and no trailing comma. Signed-off-by: Pawel Zalewski (The Capable Hub) Message-ID: <20260807-acpi-char-v1-3-742c450254dd@thegoodpenguin.co.uk> Signed-off-by: Corey Minyard --- drivers/char/ipmi/ipmb_dev_int.c | 4 ++-- drivers/char/ipmi/ipmi_si_platform.c | 4 ++-- drivers/char/ipmi/ipmi_ssif.c | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/char/ipmi/ipmb_dev_int.c b/drivers/char/ipmi/ipmb_dev_int.c index e4c50d9ae3e1..eae31ec92382 100644 --- a/drivers/char/ipmi/ipmb_dev_int.c +++ b/drivers/char/ipmi/ipmb_dev_int.c @@ -361,8 +361,8 @@ MODULE_DEVICE_TABLE(i2c, ipmb_id); #ifdef CONFIG_ACPI static const struct acpi_device_id acpi_ipmb_id[] = { - { "IPMB0001", 0 }, - {}, + { .id = "IPMB0001" }, + { } }; MODULE_DEVICE_TABLE(acpi, acpi_ipmb_id); #endif diff --git a/drivers/char/ipmi/ipmi_si_platform.c b/drivers/char/ipmi/ipmi_si_platform.c index bdc481ce1302..fa221cbb4b3b 100644 --- a/drivers/char/ipmi/ipmi_si_platform.c +++ b/drivers/char/ipmi/ipmi_si_platform.c @@ -387,8 +387,8 @@ static int acpi_ipmi_probe(struct platform_device *pdev) } static const struct acpi_device_id acpi_ipmi_match[] = { - { "IPI0001", 0 }, - { }, + { .id = "IPI0001" }, + { } }; MODULE_DEVICE_TABLE(acpi, acpi_ipmi_match); #else diff --git a/drivers/char/ipmi/ipmi_ssif.c b/drivers/char/ipmi/ipmi_ssif.c index 07f1d2327bb7..2361103c5edc 100644 --- a/drivers/char/ipmi/ipmi_ssif.c +++ b/drivers/char/ipmi/ipmi_ssif.c @@ -2057,8 +2057,8 @@ static unsigned short *ssif_address_list(void) #ifdef CONFIG_ACPI static const struct acpi_device_id ssif_acpi_match[] = { - { "IPI0001", 0 }, - { }, + { .id = "IPI0001" }, + { } }; MODULE_DEVICE_TABLE(acpi, ssif_acpi_match); #endif From 431e735e9bb6b7fd8e2ea9d83f0627bb5c5687ee Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Tue, 11 Aug 2026 14:39:51 -0700 Subject: [PATCH 160/641] scsi: qla2xxx: Fix size_t format specifier in qla29xx_process_rd_image() After commit c3930ec119cb ("scsi: qla2xxx: Add FC operational firmware load for 29xx"), there is a warning due to an incorrect format specifier for a 'size_t' variable when building for 32-bit platforms, for which 'size_t' is 'unsigned int': drivers/scsi/qla2xxx/qla_init.c: In function 'qla29xx_process_rd_image': drivers/scsi/qla2xxx/qla_init.c:9272:74: error: format '%lx' expects argument of type 'long unsigned int', but argument 6 has type 'size_t' {aka 'unsigned int'} [-Werror=format=] 9272 | "TIM section too large (0x%x bytes, ring 0x%lx bytes).\n", | ~~^ | | | long unsigned int | %x 9273 | section_size, 9274 | req->length * qla_req_entry_size(ha)); | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | | | size_t {aka unsigned int} cc1: all warnings being treated as errors Use '%zx', the proper 'size_t' format specifier, to clear up the warning. Fixes: c3930ec119cb ("scsi: qla2xxx: Add FC operational firmware load for 29xx") Signed-off-by: Nathan Chancellor Reviewed-by: Bart Van Assche Link: https://patch.msgid.link/20260811-scsi-qla2xxxx-qla_init-wformat-v1-1-50760021914f@kernel.org Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/qla2xxx/qla_init.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/scsi/qla2xxx/qla_init.c b/drivers/scsi/qla2xxx/qla_init.c index 2b9a9c672ec6..900cd141928e 100644 --- a/drivers/scsi/qla2xxx/qla_init.c +++ b/drivers/scsi/qla2xxx/qla_init.c @@ -9269,7 +9269,7 @@ static int qla29xx_process_rd_image(struct scsi_qla_host *vha, if (section == TIM) { if (section_size > req->length * qla_req_entry_size(ha)) { ql_log(ql_log_fatal, vha, 0x0098, - "TIM section too large (0x%x bytes, ring 0x%lx bytes).\n", + "TIM section too large (0x%x bytes, ring 0x%zx bytes).\n", section_size, req->length * qla_req_entry_size(ha)); return QLA_FUNCTION_FAILED; From b9f679dfe629004b593f018df33b330d799bcee4 Mon Sep 17 00:00:00 2001 From: Chandrakanth Patil Date: Sat, 8 Aug 2026 20:40:10 +0530 Subject: [PATCH 161/641] scsi: mpt3sas: Avoid freeing unallocated PCIe SGL buffers _base_release_memory_pools() unconditionally frees every ioc->pcie_sg_lookup[] entry, including ones the setup loop never allocated after a partial failure, causing a "bad dma" warning on debug kernels or a NULL pointer dereference otherwise. Fixes: dbec4c9040ed ("scsi: mpt3sas: lockless command submission") Reported-by: Laurence Oberman Signed-off-by: Chandrakanth Patil Link: https://patch.msgid.link/20260808151010.185603-1-chandrakanth.patil@broadcom.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/mpt3sas/mpt3sas_base.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/scsi/mpt3sas/mpt3sas_base.c b/drivers/scsi/mpt3sas/mpt3sas_base.c index 791a3c5fbf44..fed7aeffec58 100644 --- a/drivers/scsi/mpt3sas/mpt3sas_base.c +++ b/drivers/scsi/mpt3sas/mpt3sas_base.c @@ -5870,6 +5870,8 @@ _base_release_memory_pools(struct MPT3SAS_ADAPTER *ioc) if (ioc->pcie_sgl_dma_pool) { for (i = 0; i < ioc->scsiio_depth; i++) { + if (!ioc->pcie_sg_lookup[i].pcie_sgl) + continue; dma_pool_free(ioc->pcie_sgl_dma_pool, ioc->pcie_sg_lookup[i].pcie_sgl, ioc->pcie_sg_lookup[i].pcie_sgl_dma); From 4c2128c1a399318859d9eea0b74613d90f4bcb66 Mon Sep 17 00:00:00 2001 From: Xingui Yang Date: Tue, 11 Aug 2026 12:03:33 +0800 Subject: [PATCH 162/641] scsi: libsas: Add sas_ex_phy_to_dev() helper Add sas_ex_phy_to_dev() to return any device type attached to an expander phy, and refactor sas_ex_to_ata() to use it. No functional changes intended. Signed-off-by: Xingui Yang Reviewed-by: John Garry Link: https://patch.msgid.link/20260811040334.4184911-2-yangxingui@huawei.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/libsas/sas_expander.c | 13 +++++++++---- drivers/scsi/libsas/sas_internal.h | 1 + 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/scsi/libsas/sas_expander.c b/drivers/scsi/libsas/sas_expander.c index f471ab464a78..a5c5327cd0dd 100644 --- a/drivers/scsi/libsas/sas_expander.c +++ b/drivers/scsi/libsas/sas_expander.c @@ -345,11 +345,10 @@ static void sas_set_ex_phy(struct domain_device *dev, int phy_id, SAS_ADDR(phy->attached_sas_addr), type); } -/* check if we have an existing attached ata device on this expander phy */ -struct domain_device *sas_ex_to_ata(struct domain_device *ex_dev, int phy_id) +/* Return the domain device attached to an expander phy */ +struct domain_device *sas_ex_phy_to_dev(struct domain_device *ex_dev, int phy_id) { struct ex_phy *ex_phy = &ex_dev->ex_dev.ex_phy[phy_id]; - struct domain_device *dev; struct sas_rphy *rphy; if (!ex_phy->port) @@ -359,7 +358,13 @@ struct domain_device *sas_ex_to_ata(struct domain_device *ex_dev, int phy_id) if (!rphy) return NULL; - dev = sas_find_dev_by_rphy(rphy); + return sas_find_dev_by_rphy(rphy); +} + +/* Check if we have an existing attached ata device on this expander phy */ +struct domain_device *sas_ex_to_ata(struct domain_device *ex_dev, int phy_id) +{ + struct domain_device *dev = sas_ex_phy_to_dev(ex_dev, phy_id); if (dev && dev_is_sata(dev)) return dev; diff --git a/drivers/scsi/libsas/sas_internal.h b/drivers/scsi/libsas/sas_internal.h index 7dce0f587149..f5c75ab10dc4 100644 --- a/drivers/scsi/libsas/sas_internal.h +++ b/drivers/scsi/libsas/sas_internal.h @@ -91,6 +91,7 @@ int sas_smp_get_phy_events(struct sas_phy *phy); void sas_device_set_phy(struct domain_device *dev, struct sas_port *port); struct domain_device *sas_find_dev_by_rphy(struct sas_rphy *rphy); +struct domain_device *sas_ex_phy_to_dev(struct domain_device *ex_dev, int phy_id); struct domain_device *sas_ex_to_ata(struct domain_device *ex_dev, int phy_id); int sas_ex_phy_discover(struct domain_device *dev, int single); int sas_get_report_phy_sata(struct domain_device *dev, int phy_id, From db441dcb8c1452279956b7ea4e394b5ce8ce77f1 Mon Sep 17 00:00:00 2001 From: Xingui Yang Date: Tue, 11 Aug 2026 12:03:34 +0800 Subject: [PATCH 163/641] scsi: libsas: Add linkrate and sas_addr change detection in rediscover Introduce sas_dev_is_flutter() and sas_rediscover_ex_phy() to improve flutter and device replace detection during rediscovery. sas_dev_is_flutter() calls sas_ex_phy_discover() before looking up the child device via sas_ex_phy_to_dev(), ensuring the PHY state is always updated and avoiding use-after-free since the child device pointer is obtained after the sleeping SMP request completes. Add validation for linkrate and sas_addr changes. When the SAS address changes, phy->attached_sas_addr is restored to the original address before returning false, so sas_unregister_devs_sas_addr() can properly match and unregister the old device. The sas_addr check is ordered before the linkrate check to avoid skipping the restoration when both change simultaneously. sas_rediscover_ex_phy() uses the async discovery pattern (sas_discover_event) instead of the synchronous sas_discover_new() to ensure proper ordering between device unregistration and rediscovery, avoiding sysfs_warn_dup() errors. Signed-off-by: Xingui Yang Suggested-by: John Garry Link: https://patch.msgid.link/20260811040334.4184911-3-yangxingui@huawei.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/libsas/sas_expander.c | 85 ++++++++++++++++++++++++------ 1 file changed, 70 insertions(+), 15 deletions(-) diff --git a/drivers/scsi/libsas/sas_expander.c b/drivers/scsi/libsas/sas_expander.c index a5c5327cd0dd..811c9eb4fef1 100644 --- a/drivers/scsi/libsas/sas_expander.c +++ b/drivers/scsi/libsas/sas_expander.c @@ -1963,6 +1963,72 @@ static bool dev_type_flutter(enum sas_device_type new, enum sas_device_type old) return false; } +static void sas_rediscover_ex_phy(struct domain_device *dev, int phy_id, + bool last) +{ + struct expander_device *ex = &dev->ex_dev; + struct ex_phy *phy = &ex->ex_phy[phy_id]; + + phy->phy_change_count = -1; + ex->ex_change_count = -1; + sas_unregister_devs_sas_addr(dev, phy_id, last); + sas_discover_event(dev->port, DISCE_REVALIDATE_DOMAIN); +} + +static bool sas_dev_is_flutter(struct domain_device *dev, int phy_id, + u8 *sas_addr, enum sas_device_type type) +{ + struct expander_device *ex = &dev->ex_dev; + struct ex_phy *phy = &ex->ex_phy[phy_id]; + struct domain_device *child_dev; + char *action = ""; + int res; + + if (SAS_ADDR(sas_addr) != SAS_ADDR(phy->attached_sas_addr) || + !dev_type_flutter(type, phy->attached_dev_type)) + return false; + + res = sas_ex_phy_discover(dev, phy_id); + if (res) + return false; + + child_dev = sas_ex_phy_to_dev(dev, phy_id); + if (!child_dev) + goto out; + + if (dev_is_sata(child_dev) && + phy->attached_dev_type == SAS_SATA_PENDING) { + action = ", needs recovery"; + goto out; + } + + if (SAS_ADDR(child_dev->sas_addr) != SAS_ADDR(phy->attached_sas_addr)) { + pr_info("ex %016llx phy%02d sas_addr changed from %016llx to %016llx\n", + SAS_ADDR(dev->sas_addr), phy_id, + SAS_ADDR(child_dev->sas_addr), + SAS_ADDR(phy->attached_sas_addr)); + /* + * Device unregistering relies on address matching. Restore + * attached_sas_addr back to the original address so that the old + * device can be unregistered later + */ + memcpy(phy->attached_sas_addr, child_dev->sas_addr, SAS_ADDR_SIZE); + return false; + } + + if (child_dev->linkrate != phy->linkrate) { + pr_info("ex %016llx phy%02d linkrate changed from %d to %d\n", + SAS_ADDR(dev->sas_addr), phy_id, + child_dev->linkrate, phy->linkrate); + return false; + } + +out: + pr_debug("ex %016llx phy%02d broadcast flutter%s\n", + SAS_ADDR(dev->sas_addr), phy_id, action); + return true; +} + static int sas_rediscover_dev(struct domain_device *dev, int phy_id, bool last, int sibling) { @@ -2016,27 +2082,16 @@ static int sas_rediscover_dev(struct domain_device *dev, int phy_id, if (res == 0) sas_set_ex_phy(dev, phy_id, disc_resp); goto out_free_resp; - } else if (SAS_ADDR(sas_addr) == SAS_ADDR(phy->attached_sas_addr) && - dev_type_flutter(type, phy->attached_dev_type)) { - struct domain_device *ata_dev = sas_ex_to_ata(dev, phy_id); - char *action = ""; - - sas_ex_phy_discover(dev, phy_id); - - if (ata_dev && phy->attached_dev_type == SAS_SATA_PENDING) - action = ", needs recovery"; - pr_debug("ex %016llx phy%02d broadcast flutter%s\n", - SAS_ADDR(dev->sas_addr), phy_id, action); - goto out_free_resp; } + if (sas_dev_is_flutter(dev, phy_id, sas_addr, type)) + goto out_free_resp; + /* we always have to delete the old device when we went here */ pr_info("ex %016llx phy%02d replace %016llx\n", SAS_ADDR(dev->sas_addr), phy_id, SAS_ADDR(phy->attached_sas_addr)); - sas_unregister_devs_sas_addr(dev, phy_id, last); - - res = sas_discover_new(dev, phy_id); + sas_rediscover_ex_phy(dev, phy_id, last); out_free_resp: kfree(disc_resp); return res; From 720d8b2f2457f4962ca353dd90395e2660d9a10d Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 7 Aug 2026 15:49:46 -0700 Subject: [PATCH 164/641] scsi: ufs: core: Set task state before io_schedule_timeout() Set the task state to TASK_UNINTERRUPTIBLE before calling io_schedule_timeout() in ufshcd_wait_for_pending_cmds(). Without setting the task state, io_schedule_timeout() returns immediately because the task state remains TASK_RUNNING. This results in a busy loop that wastes CPU cycles. Fixes: 2000bc309703 ("scsi: ufs: core: Reduce the clock scaling latency") Reviewed-by: Peter Wang Reported-by: Sashiko Signed-off-by: Bart Van Assche Link: https://patch.msgid.link/8fe4526ce272811b28e99048b42358dd8f7c48af.1786142946.git.bvanassche@acm.org Signed-off-by: Martin K. Petersen (Oracle) --- drivers/ufs/core/ufshcd.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c index a51e071916cf..2c5d8e0e696e 100644 --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c @@ -1317,6 +1317,7 @@ static int ufshcd_wait_for_pending_cmds(struct ufs_hba *hba, break; } + __set_current_state(TASK_UNINTERRUPTIBLE); io_schedule_timeout(msecs_to_jiffies(20)); if (ktime_to_us(ktime_sub(ktime_get(), start)) > wait_timeout_us) { From ff5d552022b35f7475bdc538173b3ceb95d55d85 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 7 Aug 2026 15:49:47 -0700 Subject: [PATCH 165/641] scsi: ufs: core: Enable context analysis Annotate functions that modify the state of a synchronization object. Remove the struct semaphore annotations because lock context annotations are not supported for semaphores. Reviewed-by: Peter Wang Signed-off-by: Bart Van Assche Link: https://patch.msgid.link/3c975386a5bcb939f8a2a0d47fd621f234321a9e.1786142946.git.bvanassche@acm.org Signed-off-by: Martin K. Petersen (Oracle) --- drivers/ufs/core/Makefile | 2 ++ drivers/ufs/core/ufs-debugfs.c | 8 ++++++-- drivers/ufs/core/ufshcd.c | 14 ++++++++++++++ drivers/ufs/host/Makefile | 2 ++ 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/drivers/ufs/core/Makefile b/drivers/ufs/core/Makefile index ce7d16d2cf35..67ab9ffbdf5d 100644 --- a/drivers/ufs/core/Makefile +++ b/drivers/ufs/core/Makefile @@ -1,5 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 +CONTEXT_ANALYSIS := y + obj-$(CONFIG_SCSI_UFSHCD) += ufshcd-core.o ufshcd-core-y += ufshcd.o ufs-sysfs.o ufs-mcq.o ufs-txeq.o ufshcd-core-$(CONFIG_RPMB) += ufs-rpmb.o diff --git a/drivers/ufs/core/ufs-debugfs.c b/drivers/ufs/core/ufs-debugfs.c index be527209540d..ed1de70e2ec1 100644 --- a/drivers/ufs/core/ufs-debugfs.c +++ b/drivers/ufs/core/ufs-debugfs.c @@ -65,8 +65,10 @@ static int ee_usr_mask_get(void *data, u64 *val) return 0; } +token_context_lock(ufs_debugfs); + static int ufs_debugfs_get_user_access(struct ufs_hba *hba) -__acquires(&hba->host_sem) + __cond_acquires(0, ufs_debugfs) { down(&hba->host_sem); if (!ufshcd_is_user_access_allowed(hba)) { @@ -74,14 +76,16 @@ __acquires(&hba->host_sem) return -EBUSY; } ufshcd_rpm_get_sync(hba); + __acquire(ufs_debugfs); return 0; } static void ufs_debugfs_put_user_access(struct ufs_hba *hba) -__releases(&hba->host_sem) + __releases(ufs_debugfs) { ufshcd_rpm_put_sync(hba); up(&hba->host_sem); + __release(ufs_debugfs); } static int ee_usr_mask_set(void *data, u64 val) diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c index 2c5d8e0e696e..67745d2796d0 100644 --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c @@ -1354,6 +1354,8 @@ out: * On failure, all acquired locks are released and the tagset is unquiesced. */ int ufshcd_pause_command_processing(struct ufs_hba *hba, u64 timeout_us) + __cond_acquires(0, &hba->host->scan_mutex) + __cond_acquires(0, &hba->clk_scaling_lock) { int ret = 0; @@ -1378,6 +1380,8 @@ int ufshcd_pause_command_processing(struct ufs_hba *hba, u64 timeout_us) * This function resumes command submissions. */ void ufshcd_resume_command_processing(struct ufs_hba *hba) + __releases(&hba->clk_scaling_lock) + __releases(&hba->host->scan_mutex) { up_write(&hba->clk_scaling_lock); blk_mq_unquiesce_tagset(&hba->host->tag_set); @@ -1447,6 +1451,9 @@ config_pwr_mode: * Return: 0 upon success; -EBUSY upon timeout. */ static int ufshcd_clock_scaling_prepare(struct ufs_hba *hba, u64 timeout_us) + __cond_acquires(0, &hba->host->scan_mutex) + __cond_acquires(0, &hba->wb_mutex) + __cond_acquires(0, &hba->clk_scaling_lock) { int ret = 0; /* @@ -1476,6 +1483,9 @@ out: } static void ufshcd_clock_scaling_unprepare(struct ufs_hba *hba, int err) + __releases(&hba->clk_scaling_lock) + __releases(&hba->wb_mutex) + __releases(&hba->host->scan_mutex) { up_write(&hba->clk_scaling_lock); mutex_unlock(&hba->wb_mutex); @@ -3305,6 +3315,8 @@ ufshcd_dev_cmd_completion(struct ufs_hba *hba, struct ufshcd_lrb *lrbp) } static void ufshcd_dev_man_lock(struct ufs_hba *hba) + __acquires(&hba->dev_cmd.lock) + __acquires_shared(&hba->clk_scaling_lock) { ufshcd_hold(hba); mutex_lock(&hba->dev_cmd.lock); @@ -3312,6 +3324,8 @@ static void ufshcd_dev_man_lock(struct ufs_hba *hba) } static void ufshcd_dev_man_unlock(struct ufs_hba *hba) + __releases_shared(&hba->clk_scaling_lock) + __releases(&hba->dev_cmd.lock) { up_read(&hba->clk_scaling_lock); mutex_unlock(&hba->dev_cmd.lock); diff --git a/drivers/ufs/host/Makefile b/drivers/ufs/host/Makefile index 65d8bb23ab7b..7d8db67eb23c 100644 --- a/drivers/ufs/host/Makefile +++ b/drivers/ufs/host/Makefile @@ -1,5 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 +CONTEXT_ANALYSIS := y + obj-$(CONFIG_SCSI_UFS_DWC_TC_PCI) += tc-dwc-g210-pci.o ufshcd-dwc.o tc-dwc-g210.o obj-$(CONFIG_SCSI_UFS_DWC_TC_PLATFORM) += tc-dwc-g210-pltfrm.o ufshcd-dwc.o tc-dwc-g210.o obj-$(CONFIG_SCSI_UFS_CDNS_PLATFORM) += cdns-pltfrm.o From e70647b25a641b9f5602558839182d036b512de5 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 7 Aug 2026 15:49:48 -0700 Subject: [PATCH 166/641] scsi: core: Pass the SCSI host pointer directly to scanning functions In the functions scsi_probe_and_add_lun(), scsi_sequential_lun_scan(), scsi_report_lun_scan() and __scsi_scan_target() the SCSI host pointer is derived from the SCSI target pointer. Pass the SCSI host pointer directly. This patch prepares for enabling context analysis. With this patch applied, context annotations can refer to the SCSI host pointer directly, e.g. __must_hold(&shost->scan_mutex). Without this patch, the following annotation would have to be used: __must_hold(&dev_to_shost(starget->dev.parent)->scan_mutex) Additionally, in code that locks shost->scan_mutex, the following would have to be added to help the compiler understand that shost == dev_to_shost(starget->dev.parent): __assume_ctx_lock(&dev_to_shost(starget->dev.parent)->scan_mutex); __assume_ctx_lock() statements should be avoided if there is a good alternative. Hence this patch. No functionality has been changed. Reviewed-by: John Garry Signed-off-by: Bart Van Assche Link: https://patch.msgid.link/49d2fc5fae5cb5dca2536818155581c73f39c883.1786142946.git.bvanassche@acm.org Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/scsi_scan.c | 47 ++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/drivers/scsi/scsi_scan.c b/drivers/scsi/scsi_scan.c index e27da038603a..d7e0e9393194 100644 --- a/drivers/scsi/scsi_scan.c +++ b/drivers/scsi/scsi_scan.c @@ -1169,6 +1169,7 @@ static unsigned char *scsi_inq_str(unsigned char *buf, unsigned char *inq, /** * scsi_probe_and_add_lun - probe a LUN, if a LUN is found add it + * @shost: SCSI host pointer * @starget: pointer to target device structure * @lun: LUN of target device * @bflagsp: store bflags here if not NULL @@ -1188,7 +1189,8 @@ static unsigned char *scsi_inq_str(unsigned char *buf, unsigned char *inq, * attached at the LUN * - SCSI_SCAN_LUN_PRESENT: a new scsi_device was allocated and initialized **/ -static int scsi_probe_and_add_lun(struct scsi_target *starget, +static int scsi_probe_and_add_lun(struct Scsi_Host *shost, + struct scsi_target *starget, u64 lun, blist_flags_t *bflagsp, struct scsi_device **sdevp, enum scsi_scan_mode rescan, @@ -1198,7 +1200,6 @@ static int scsi_probe_and_add_lun(struct scsi_target *starget, unsigned char *result; blist_flags_t bflags; int res = SCSI_SCAN_NO_RESPONSE, result_len = 256; - struct Scsi_Host *shost = dev_to_shost(starget->dev.parent); /* * The rescan flag is used as an optimization, the first scan of a @@ -1334,6 +1335,7 @@ static int scsi_probe_and_add_lun(struct scsi_target *starget, /** * scsi_sequential_lun_scan - sequentially scan a SCSI target + * @shost: SCSI host pointer * @starget: pointer to target structure to scan * @bflags: black/white list flag for LUN 0 * @scsi_level: Which version of the standard does this device adhere to @@ -1346,13 +1348,13 @@ static int scsi_probe_and_add_lun(struct scsi_target *starget, * * Modifies sdevscan->lun. **/ -static void scsi_sequential_lun_scan(struct scsi_target *starget, +static void scsi_sequential_lun_scan(struct Scsi_Host *shost, + struct scsi_target *starget, blist_flags_t bflags, int scsi_level, enum scsi_scan_mode rescan) { uint max_dev_lun; u64 sparse_lun, lun; - struct Scsi_Host *shost = dev_to_shost(starget->dev.parent); SCSI_LOG_SCAN_BUS(3, starget_printk(KERN_INFO, starget, "scsi scan: Sequential scan\n")); @@ -1412,14 +1414,15 @@ static void scsi_sequential_lun_scan(struct scsi_target *starget, * sparse_lun. */ for (lun = 1; lun < max_dev_lun; ++lun) - if ((scsi_probe_and_add_lun(starget, lun, NULL, NULL, rescan, - NULL) != SCSI_SCAN_LUN_PRESENT) && + if (scsi_probe_and_add_lun(shost, starget, lun, NULL, NULL, + rescan, NULL) != SCSI_SCAN_LUN_PRESENT && !sparse_lun) return; } /** * scsi_report_lun_scan - Scan using SCSI REPORT LUN results + * @shost: SCSI host pointer * @starget: which target * @bflags: Zero or a mix of BLIST_NOLUN, BLIST_REPORTLUN2, or BLIST_NOREPORTLUN * @rescan: nonzero if we can skip code only needed on first scan @@ -1438,8 +1441,9 @@ static void scsi_sequential_lun_scan(struct scsi_target *starget, * 0: scan completed (or no memory, so further scanning is futile) * 1: could not scan with REPORT LUN **/ -static int scsi_report_lun_scan(struct scsi_target *starget, blist_flags_t bflags, - enum scsi_scan_mode rescan) +static int scsi_report_lun_scan(struct Scsi_Host *shost, + struct scsi_target *starget, blist_flags_t bflags, + enum scsi_scan_mode rescan) { unsigned char scsi_cmd[MAX_COMMAND_SIZE]; unsigned int length; @@ -1448,7 +1452,6 @@ static int scsi_report_lun_scan(struct scsi_target *starget, blist_flags_t bflag int result; struct scsi_lun *lunp, *lun_data; struct scsi_device *sdev; - struct Scsi_Host *shost = dev_to_shost(&starget->dev); struct scsi_failure failure_defs[] = { { .sense = UNIT_ATTENTION, @@ -1594,7 +1597,7 @@ retry: } else { int res; - res = scsi_probe_and_add_lun(starget, + res = scsi_probe_and_add_lun(shost, starget, lun, NULL, NULL, rescan, NULL); if (res == SCSI_SCAN_NO_RESPONSE) { /* @@ -1641,7 +1644,7 @@ struct scsi_device *__scsi_add_device(struct Scsi_Host *shost, uint channel, scsi_complete_async_scans(); if (scsi_host_scan_allowed(shost) && scsi_autopm_get_host(shost) == 0) { - scsi_probe_and_add_lun(starget, lun, NULL, &sdev, + scsi_probe_and_add_lun(shost, starget, lun, NULL, &sdev, SCSI_SCAN_RESCAN, hostdata); scsi_autopm_put_host(shost); } @@ -1763,10 +1766,10 @@ unlock: } EXPORT_SYMBOL(scsi_rescan_device); -static void __scsi_scan_target(struct device *parent, unsigned int channel, - unsigned int id, u64 lun, enum scsi_scan_mode rescan) +static void __scsi_scan_target(struct Scsi_Host *shost, struct device *parent, + unsigned int channel, unsigned int id, u64 lun, + enum scsi_scan_mode rescan) { - struct Scsi_Host *shost = dev_to_shost(parent); blist_flags_t bflags = 0; int res; struct scsi_target *starget; @@ -1786,7 +1789,8 @@ static void __scsi_scan_target(struct device *parent, unsigned int channel, /* * Scan for a specific host/chan/id/lun. */ - scsi_probe_and_add_lun(starget, lun, NULL, NULL, rescan, NULL); + scsi_probe_and_add_lun(shost, starget, lun, NULL, NULL, rescan, + NULL); goto out_reap; } @@ -1794,14 +1798,15 @@ static void __scsi_scan_target(struct device *parent, unsigned int channel, * Scan LUN 0, if there is some response, scan further. Ideally, we * would not configure LUN 0 until all LUNs are scanned. */ - res = scsi_probe_and_add_lun(starget, 0, &bflags, NULL, rescan, NULL); + res = scsi_probe_and_add_lun(shost, starget, 0, &bflags, NULL, rescan, + NULL); if (res == SCSI_SCAN_LUN_PRESENT || res == SCSI_SCAN_TARGET_PRESENT) { - if (scsi_report_lun_scan(starget, bflags, rescan) != 0) + if (scsi_report_lun_scan(shost, starget, bflags, rescan) != 0) /* * The REPORT LUN did not scan the target, * do a sequential scan. */ - scsi_sequential_lun_scan(starget, bflags, + scsi_sequential_lun_scan(shost, starget, bflags, starget->scsi_level, rescan); } @@ -1851,7 +1856,7 @@ void scsi_scan_target(struct device *parent, unsigned int channel, scsi_complete_async_scans(); if (scsi_host_scan_allowed(shost) && scsi_autopm_get_host(shost) == 0) { - __scsi_scan_target(parent, channel, id, lun, rescan); + __scsi_scan_target(shost, parent, channel, id, lun, rescan); scsi_autopm_put_host(shost); } mutex_unlock(&shost->scan_mutex); @@ -1882,11 +1887,11 @@ static void scsi_scan_channel(struct Scsi_Host *shost, unsigned int channel, order_id = shost->max_id - id - 1; else order_id = id; - __scsi_scan_target(&shost->shost_gendev, channel, + __scsi_scan_target(shost, &shost->shost_gendev, channel, order_id, lun, rescan); } else - __scsi_scan_target(&shost->shost_gendev, channel, + __scsi_scan_target(shost, &shost->shost_gendev, channel, id, lun, rescan); } From 09982efcc07e739c7e4ac6089e3e521132388b53 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 7 Aug 2026 15:49:49 -0700 Subject: [PATCH 167/641] scsi: core: Add lock context annotations Document which functions expect that shost->scan_mutex is held. Reviewed-by: John Garry Signed-off-by: Bart Van Assche Link: https://patch.msgid.link/ad5ca37acf8c933a12830c0811c293af54c87573.1786142946.git.bvanassche@acm.org Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/scsi_scan.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/scsi/scsi_scan.c b/drivers/scsi/scsi_scan.c index d7e0e9393194..3b82e80e807a 100644 --- a/drivers/scsi/scsi_scan.c +++ b/drivers/scsi/scsi_scan.c @@ -1195,6 +1195,7 @@ static int scsi_probe_and_add_lun(struct Scsi_Host *shost, struct scsi_device **sdevp, enum scsi_scan_mode rescan, void *hostdata) + __must_hold(&shost->scan_mutex) { struct scsi_device *sdev; unsigned char *result; @@ -1352,6 +1353,7 @@ static void scsi_sequential_lun_scan(struct Scsi_Host *shost, struct scsi_target *starget, blist_flags_t bflags, int scsi_level, enum scsi_scan_mode rescan) + __must_hold(&shost->scan_mutex) { uint max_dev_lun; u64 sparse_lun, lun; @@ -1444,6 +1446,7 @@ static void scsi_sequential_lun_scan(struct Scsi_Host *shost, static int scsi_report_lun_scan(struct Scsi_Host *shost, struct scsi_target *starget, blist_flags_t bflags, enum scsi_scan_mode rescan) + __must_hold(&shost->scan_mutex) { unsigned char scsi_cmd[MAX_COMMAND_SIZE]; unsigned int length; @@ -1769,6 +1772,7 @@ EXPORT_SYMBOL(scsi_rescan_device); static void __scsi_scan_target(struct Scsi_Host *shost, struct device *parent, unsigned int channel, unsigned int id, u64 lun, enum scsi_scan_mode rescan) + __must_hold(&shost->scan_mutex) { blist_flags_t bflags = 0; int res; @@ -1866,6 +1870,7 @@ EXPORT_SYMBOL(scsi_scan_target); static void scsi_scan_channel(struct Scsi_Host *shost, unsigned int channel, unsigned int id, u64 lun, enum scsi_scan_mode rescan) + __must_hold(&shost->scan_mutex) { uint order_id; From 4c461ee2b2a5a7c327fe092b41de1fcc002adc01 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 7 Aug 2026 15:49:50 -0700 Subject: [PATCH 168/641] scsi: core: Protect host state changes with the host lock Some but not all SCSI host state changes are protected with the SCSI host lock. Annotate the SCSI host state with __guarded_by(host_lock) and protect all SCSI host state changes with the SCSI host lock. This patch prevents that KCSAN complains about data races when accessing the SCSI host state. Reported-by: Jianzhou Zhao Closes: https://lore.kernel.org/all/36d59d0e.6db0.19cdbeee01b.Coremail.luckd0g@163.com/ Signed-off-by: Bart Van Assche Reviewed-by: John Garry Link: https://patch.msgid.link/681e4a5260c182feb5fc1d96f0d43c62c21dc6c9.1786142946.git.bvanassche@acm.org Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/hosts.c | 19 ++++++++++-------- drivers/scsi/leapraid/leapraid_func.c | 2 +- drivers/scsi/leapraid/leapraid_os.c | 2 +- drivers/scsi/megaraid/megaraid_sas_base.c | 2 +- drivers/scsi/mpi3mr/mpi3mr_os.c | 2 +- drivers/scsi/mpt3sas/mpt3sas_scsih.c | 2 +- drivers/scsi/qla4xxx/ql4_os.c | 6 ++---- drivers/scsi/scsi_lib.c | 3 +-- drivers/scsi/scsi_sysfs.c | 7 ++++--- include/scsi/scsi_host.h | 24 ++++++++++++++++------- 10 files changed, 40 insertions(+), 29 deletions(-) diff --git a/drivers/scsi/hosts.c b/drivers/scsi/hosts.c index d512080268af..9610dd7aa85f 100644 --- a/drivers/scsi/hosts.c +++ b/drivers/scsi/hosts.c @@ -73,8 +73,9 @@ static struct class shost_class = { * transition is illegal. **/ int scsi_host_set_state(struct Scsi_Host *shost, enum scsi_host_state state) + __must_hold(shost->host_lock) { - enum scsi_host_state oldstate = shost->shost_state; + enum scsi_host_state oldstate = READ_ONCE(shost->shost_state); if (state == oldstate) return 0; @@ -145,7 +146,7 @@ int scsi_host_set_state(struct Scsi_Host *shost, enum scsi_host_state state) } break; } - shost->shost_state = state; + WRITE_ONCE(shost->shost_state, state); return 0; illegal: @@ -276,7 +277,8 @@ int scsi_add_host_with_dma(struct Scsi_Host *shost, struct device *dev, if (error) goto out_disable_runtime_pm; - scsi_host_set_state(shost, SHOST_RUNNING); + scoped_guard(spinlock_irq, shost->host_lock) + scsi_host_set_state(shost, SHOST_RUNNING); get_device(shost->shost_gendev.parent); device_enable_async_suspend(&shost->shost_dev); @@ -350,6 +352,7 @@ EXPORT_SYMBOL(scsi_add_host_with_dma); static void scsi_host_dev_release(struct device *dev) { struct Scsi_Host *shost = dev_to_shost(dev); + enum scsi_host_state state = scsi_get_host_state(shost); struct device *parent = dev->parent; /* Wait for functions invoked through call_rcu(&scmd->rcu, ...) */ @@ -362,7 +365,7 @@ static void scsi_host_dev_release(struct device *dev) if (shost->work_q) destroy_workqueue(shost->work_q); - if (shost->shost_state == SHOST_CREATED) { + if (state == SHOST_CREATED) { /* * Free the shost_dev device name and remove the proc host dir * here if scsi_host_{alloc,put}() have been called but neither @@ -378,7 +381,7 @@ static void scsi_host_dev_release(struct device *dev) ida_free(&host_index_ida, shost->host_no); - if (shost->shost_state != SHOST_CREATED) + if (state != SHOST_CREATED) put_device(parent); kfree(shost); } @@ -411,8 +414,8 @@ struct Scsi_Host *scsi_host_alloc(const struct scsi_host_template *sht, int priv return NULL; shost->host_lock = &shost->default_lock; - spin_lock_init(shost->host_lock); - shost->shost_state = SHOST_CREATED; + scoped_guard(spinlock_init, shost->host_lock) + shost->shost_state = SHOST_CREATED; INIT_LIST_HEAD(&shost->__devices); INIT_LIST_HEAD(&shost->__targets); INIT_LIST_HEAD(&shost->eh_abort_list); @@ -598,7 +601,7 @@ EXPORT_SYMBOL(scsi_host_lookup); **/ struct Scsi_Host *scsi_host_get(struct Scsi_Host *shost) { - if ((shost->shost_state == SHOST_DEL) || + if (scsi_get_host_state(shost) == SHOST_DEL || !get_device(&shost->shost_gendev)) return NULL; return shost; diff --git a/drivers/scsi/leapraid/leapraid_func.c b/drivers/scsi/leapraid/leapraid_func.c index 3e1aeceab994..089d0810bd13 100644 --- a/drivers/scsi/leapraid/leapraid_func.c +++ b/drivers/scsi/leapraid/leapraid_func.c @@ -37,7 +37,7 @@ static noinline bool leapraid_shost_in_recovery(struct Scsi_Host *shost) { enum scsi_host_state state; - state = READ_ONCE(shost->shost_state); + state = scsi_get_host_state(shost); return state == SHOST_RECOVERY || state == SHOST_CANCEL_RECOVERY || state == SHOST_DEL_RECOVERY || diff --git a/drivers/scsi/leapraid/leapraid_os.c b/drivers/scsi/leapraid/leapraid_os.c index 2f3d4ac7490c..a8e1c9f33896 100644 --- a/drivers/scsi/leapraid/leapraid_os.c +++ b/drivers/scsi/leapraid/leapraid_os.c @@ -808,7 +808,7 @@ static bool leapraid_should_queuecommand(struct leapraid_adapter *adapter, goto no_connect; if (sdev_priv->block && - scmd->device->host->shost_state == SHOST_RECOVERY && + scsi_get_host_state(scmd->device->host) == SHOST_RECOVERY && scmd->cmnd[0] == TEST_UNIT_READY) { scsi_build_sense(scmd, 0, UNIT_ATTENTION, LEAPRAID_SCSI_ASC_POWER_ON_RESET, diff --git a/drivers/scsi/megaraid/megaraid_sas_base.c b/drivers/scsi/megaraid/megaraid_sas_base.c index ecd365d78ae3..f0152b043e18 100644 --- a/drivers/scsi/megaraid/megaraid_sas_base.c +++ b/drivers/scsi/megaraid/megaraid_sas_base.c @@ -3072,7 +3072,7 @@ static int megasas_reset_bus_host(struct scsi_cmnd *scmd) scmd_printk(KERN_INFO, scmd, "SCSI host state: %d SCSI host busy: %d FW outstanding: %d\n", - scmd->device->host->shost_state, + scsi_get_host_state(scmd->device->host), scsi_host_busy(scmd->device->host), atomic_read(&instance->fw_outstanding)); /* diff --git a/drivers/scsi/mpi3mr/mpi3mr_os.c b/drivers/scsi/mpi3mr/mpi3mr_os.c index 402d1f35d214..f80a21ec161b 100644 --- a/drivers/scsi/mpi3mr/mpi3mr_os.c +++ b/drivers/scsi/mpi3mr/mpi3mr_os.c @@ -5172,7 +5172,7 @@ static enum scsi_qc_status mpi3mr_qcmd(struct Scsi_Host *shost, /* Avoid error handling escalation when device is removed or blocked */ - if (scmd->device->host->shost_state == SHOST_RECOVERY && + if (scsi_get_host_state(scmd->device->host) == SHOST_RECOVERY && scmd->cmnd[0] == TEST_UNIT_READY && (stgt_priv_data->dev_removed || (dev_handle == MPI3MR_INVALID_DEV_HANDLE))) { scsi_build_sense(scmd, 0, UNIT_ATTENTION, 0x29, 0x07); diff --git a/drivers/scsi/mpt3sas/mpt3sas_scsih.c b/drivers/scsi/mpt3sas/mpt3sas_scsih.c index dea78688cc9b..0e12009a87f6 100644 --- a/drivers/scsi/mpt3sas/mpt3sas_scsih.c +++ b/drivers/scsi/mpt3sas/mpt3sas_scsih.c @@ -5472,7 +5472,7 @@ static enum scsi_qc_status scsih_qcmd(struct Scsi_Host *shost, * Avoid error handling escallation when device is disconnected */ if (handle == MPT3SAS_INVALID_DEVICE_HANDLE || sas_device_priv_data->block) { - if (scmd->device->host->shost_state == SHOST_RECOVERY && + if (scsi_get_host_state(scmd->device->host) == SHOST_RECOVERY && scmd->cmnd[0] == TEST_UNIT_READY) { scsi_build_sense(scmd, 0, UNIT_ATTENTION, 0x29, 0x07); scsi_done(scmd); diff --git a/drivers/scsi/qla4xxx/ql4_os.c b/drivers/scsi/qla4xxx/ql4_os.c index d598ab4126f8..c9d9fc7c81fb 100644 --- a/drivers/scsi/qla4xxx/ql4_os.c +++ b/drivers/scsi/qla4xxx/ql4_os.c @@ -9411,11 +9411,9 @@ static int qla4xxx_eh_target_reset(struct scsi_cmnd *cmd) * This routine finds that if reset host is called in EH * scenario or from some application like sg_reset **/ -static int qla4xxx_is_eh_active(struct Scsi_Host *shost) +static bool qla4xxx_is_eh_active(struct Scsi_Host *shost) { - if (shost->shost_state == SHOST_RECOVERY) - return 1; - return 0; + return scsi_get_host_state(shost) == SHOST_RECOVERY; } /** diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c index 686cef2406b3..2e07b686090f 100644 --- a/drivers/scsi/scsi_lib.c +++ b/drivers/scsi/scsi_lib.c @@ -1663,10 +1663,9 @@ static enum scsi_qc_status scsi_dispatch_cmd(struct scsi_cmnd *cmd) goto done; } - if (unlikely(host->shost_state == SHOST_DEL)) { + if (unlikely(scsi_get_host_state(host) == SHOST_DEL)) { cmd->result = (DID_NO_CONNECT << 16); goto done; - } trace_scsi_dispatch_cmd_start(cmd); diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c index dfc3559e7e04..9480432f650b 100644 --- a/drivers/scsi/scsi_sysfs.c +++ b/drivers/scsi/scsi_sysfs.c @@ -214,8 +214,9 @@ store_shost_state(struct device *dev, struct device_attribute *attr, if (!state) return -EINVAL; - if (scsi_host_set_state(shost, state)) - return -EINVAL; + scoped_guard(spinlock_irq, shost->host_lock) + if (scsi_host_set_state(shost, state)) + return -EINVAL; return count; } @@ -223,7 +224,7 @@ static ssize_t show_shost_state(struct device *dev, struct device_attribute *attr, char *buf) { struct Scsi_Host *shost = class_to_shost(dev); - const char *name = scsi_host_state_name(shost->shost_state); + const char *name = scsi_host_state_name(scsi_get_host_state(shost)); if (!name) return -EINVAL; diff --git a/include/scsi/scsi_host.h b/include/scsi/scsi_host.h index 7e2011830ba4..c9754771bf29 100644 --- a/include/scsi/scsi_host.h +++ b/include/scsi/scsi_host.h @@ -2,6 +2,7 @@ #ifndef _SCSI_SCSI_HOST_H #define _SCSI_SCSI_HOST_H +#include #include #include #include @@ -727,7 +728,7 @@ struct Scsi_Host { unsigned int irq; - enum scsi_host_state shost_state; + enum scsi_host_state shost_state __guarded_by(host_lock); /* ldm bits */ struct device shost_gendev, shost_dev; @@ -785,11 +786,18 @@ static inline struct Scsi_Host *dev_to_shost(struct device *dev) return container_of(dev, struct Scsi_Host, shost_gendev); } +static inline enum scsi_host_state scsi_get_host_state(struct Scsi_Host *shost) +{ + return context_unsafe(READ_ONCE(shost->shost_state)); +} + static inline int scsi_host_in_recovery(struct Scsi_Host *shost) { - return shost->shost_state == SHOST_RECOVERY || - shost->shost_state == SHOST_CANCEL_RECOVERY || - shost->shost_state == SHOST_DEL_RECOVERY || + enum scsi_host_state state = scsi_get_host_state(shost); + + return state == SHOST_RECOVERY || + state == SHOST_CANCEL_RECOVERY || + state == SHOST_DEL_RECOVERY || shost->tmf_in_progress; } @@ -835,8 +843,9 @@ static inline struct device *scsi_get_device(struct Scsi_Host *shost) **/ static inline int scsi_host_scan_allowed(struct Scsi_Host *shost) { - return shost->shost_state == SHOST_RUNNING || - shost->shost_state == SHOST_RECOVERY; + enum scsi_host_state state = scsi_get_host_state(shost); + + return state == SHOST_RUNNING || state == SHOST_RECOVERY; } extern void scsi_unblock_requests(struct Scsi_Host *); @@ -940,6 +949,7 @@ static inline unsigned char scsi_host_get_guard(struct Scsi_Host *shost) return shost->prot_guard_type; } -extern int scsi_host_set_state(struct Scsi_Host *, enum scsi_host_state); +int scsi_host_set_state(struct Scsi_Host *shost, enum scsi_host_state state) + __must_hold(shost->host_lock); #endif /* _SCSI_SCSI_HOST_H */ From fb0fc67db96292a65539e63f536c6f586490c78b Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 7 Aug 2026 15:49:51 -0700 Subject: [PATCH 169/641] scsi: core: Enable context analysis Enable context analysis for those SCSI core files that build without triggering any context analysis warnings. Signed-off-by: Bart Van Assche Reviewed-by: John Garry Link: https://patch.msgid.link/2576d2f7e3530b721b5050ac6d25c413037d7e7e.1786142946.git.bvanassche@acm.org Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/Makefile | 22 ++++++++++++++++++++++ drivers/scsi/device_handler/Makefile | 3 +++ 2 files changed, 25 insertions(+) diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile index 098f35219e7d..72eb395ccf1d 100644 --- a/drivers/scsi/Makefile +++ b/drivers/scsi/Makefile @@ -14,6 +14,28 @@ # satisfy certain initialization assumptions in the SCSI layer. # *!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*! +CONTEXT_ANALYSIS_constants.o := y +CONTEXT_ANALYSIS_scsi.o := y +CONTEXT_ANALYSIS_scsi_common.o := y +CONTEXT_ANALYSIS_scsi_devinfo.o := y +CONTEXT_ANALYSIS_scsi_dh.o := y +CONTEXT_ANALYSIS_scsi_error.o := y +CONTEXT_ANALYSIS_scsi_ioctl.o := y +CONTEXT_ANALYSIS_scsi_lib.o := y +CONTEXT_ANALYSIS_scsi_lib_dma.o := y +CONTEXT_ANALYSIS_scsi_logging.o := y +CONTEXT_ANALYSIS_scsi_netlink.o := y +CONTEXT_ANALYSIS_scsi_pm.o := y +CONTEXT_ANALYSIS_scsi_proc.o := y +CONTEXT_ANALYSIS_scsi_scan.o := y +CONTEXT_ANALYSIS_scsi_sysfs.o := y +CONTEXT_ANALYSIS_scsi_trace.o := y +CONTEXT_ANALYSIS_scsicam.o := y +CONTEXT_ANALYSIS_sd.o := y +CONTEXT_ANALYSIS_sd_dif.o := y +CONTEXT_ANALYSIS_sd_zbc.o := y +CONTEXT_ANALYSIS_sr.o := y +CONTEXT_ANALYSIS_sr_ioctl.o := y CFLAGS_aha152x.o = -DAHA152X_STAT -DAUTOCONF diff --git a/drivers/scsi/device_handler/Makefile b/drivers/scsi/device_handler/Makefile index 0a603aefd2bb..5aa282a63e24 100644 --- a/drivers/scsi/device_handler/Makefile +++ b/drivers/scsi/device_handler/Makefile @@ -2,6 +2,9 @@ # # SCSI Device Handler # + +CONTEXT_ANALYSIS := y + obj-$(CONFIG_SCSI_DH_RDAC) += scsi_dh_rdac.o obj-$(CONFIG_SCSI_DH_HP_SW) += scsi_dh_hp_sw.o obj-$(CONFIG_SCSI_DH_EMC) += scsi_dh_emc.o From 5a03dbfd670092a6960de433005aae1734c18f1f Mon Sep 17 00:00:00 2001 From: Ian Bridges Date: Wed, 29 Jul 2026 09:46:13 -0500 Subject: [PATCH 170/641] scsi: lpfc: Replace strlcat() with seq_buf in lpfc_info() In preparation for removing the strlcat() API[1], replace its uses in lpfc_info(). The function accumulates a variable number of optional fragments, which is what seq_buf is for. The intermediate tmp buffer and the per fragment overflow checks become unnecessary. seq_buf is memory safe by construction and silently truncates in the same way as the replaced pattern. The old code passed phba->ModelDesc as the format string of the first scnprintf() call. The model description comes from adapter VPD data. seq_buf_printf() takes a format string, so the replacement prints it through "%s". A model description containing conversion specifiers is no longer interpreted. Link: https://github.com/KSPP/linux/issues/370 [1] Signed-off-by: Ian Bridges Link: https://patch.msgid.link/20260729144617.1388646-2-icb@fastmail.org Reviewed-by: Nigel Kirkland Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/lpfc/lpfc_scsi.c | 49 +++++++++++------------------------ 1 file changed, 15 insertions(+), 34 deletions(-) diff --git a/drivers/scsi/lpfc/lpfc_scsi.c b/drivers/scsi/lpfc/lpfc_scsi.c index f2cab134af7f..8a795c65e3c3 100644 --- a/drivers/scsi/lpfc/lpfc_scsi.c +++ b/drivers/scsi/lpfc/lpfc_scsi.c @@ -21,6 +21,7 @@ * included with this package. * *******************************************************************/ #include +#include #include #include #include @@ -5103,57 +5104,37 @@ lpfc_info(struct Scsi_Host *host) struct lpfc_hba *phba = vport->phba; int link_speed = 0; static char lpfcinfobuf[384]; - char tmp[384] = {0}; + struct seq_buf s; memset(lpfcinfobuf, 0, sizeof(lpfcinfobuf)); + seq_buf_init(&s, lpfcinfobuf, sizeof(lpfcinfobuf)); if (phba && phba->pcidev){ /* Model Description */ - scnprintf(tmp, sizeof(tmp), phba->ModelDesc); - if (strlcat(lpfcinfobuf, tmp, sizeof(lpfcinfobuf)) >= - sizeof(lpfcinfobuf)) - goto buffer_done; + seq_buf_printf(&s, "%s", phba->ModelDesc); /* PCI Info */ - scnprintf(tmp, sizeof(tmp), - " on PCI bus %02x device %02x irq %d", - phba->pcidev->bus->number, phba->pcidev->devfn, - phba->pcidev->irq); - if (strlcat(lpfcinfobuf, tmp, sizeof(lpfcinfobuf)) >= - sizeof(lpfcinfobuf)) - goto buffer_done; + seq_buf_printf(&s, " on PCI bus %02x device %02x irq %d", + phba->pcidev->bus->number, phba->pcidev->devfn, + phba->pcidev->irq); /* Port Number */ - if (phba->Port[0]) { - scnprintf(tmp, sizeof(tmp), " port %s", phba->Port); - if (strlcat(lpfcinfobuf, tmp, sizeof(lpfcinfobuf)) >= - sizeof(lpfcinfobuf)) - goto buffer_done; - } + if (phba->Port[0]) + seq_buf_printf(&s, " port %s", phba->Port); /* Link Speed */ link_speed = lpfc_sli_port_speed_get(phba); - if (link_speed != 0) { - scnprintf(tmp, sizeof(tmp), - " Logical Link Speed: %d Mbps", link_speed); - if (strlcat(lpfcinfobuf, tmp, sizeof(lpfcinfobuf)) >= - sizeof(lpfcinfobuf)) - goto buffer_done; - } + if (link_speed != 0) + seq_buf_printf(&s, " Logical Link Speed: %d Mbps", + link_speed); /* Support for BSG ioctls */ - scnprintf(tmp, sizeof(tmp), " BSG"); - if (strlcat(lpfcinfobuf, tmp, sizeof(lpfcinfobuf)) >= - sizeof(lpfcinfobuf)) - goto buffer_done; + seq_buf_printf(&s, " BSG"); /* PCI resettable */ - if (!lpfc_check_pci_resettable(phba)) { - scnprintf(tmp, sizeof(tmp), " PCI resettable"); - strlcat(lpfcinfobuf, tmp, sizeof(lpfcinfobuf)); - } + if (!lpfc_check_pci_resettable(phba)) + seq_buf_printf(&s, " PCI resettable"); } -buffer_done: return lpfcinfobuf; } From 07f46a9f8964b91d56951ef7f74c551af0eddeac Mon Sep 17 00:00:00 2001 From: Ian Bridges Date: Wed, 29 Jul 2026 09:46:14 -0500 Subject: [PATCH 171/641] scsi: lpfc: Replace strlcat() with scnprintf() in lpfc_vport_symbolic_node_name() In preparation for removing the strlcat() API[1], replace its uses in lpfc_vport_symbolic_node_name(). The function builds five unconditional fragments, so one scnprintf() call composes the whole string. The intermediate tmp buffer and the per fragment overflow checks become unnecessary. scnprintf() truncates at the buffer size and returns the number of bytes it wrote, which equals the length that the removed strnlen() call computed. The old code capped every fragment at MAXHOSTNAMELEN bytes before appending it, independently of the room left in the destination. The replacement formats each fragment directly into the destination, so a fragment longer than MAXHOSTNAMELEN is no longer truncated when the destination has room for it. Link: https://github.com/KSPP/linux/issues/370 [1] Signed-off-by: Ian Bridges Link: https://patch.msgid.link/20260729144617.1388646-3-icb@fastmail.org Reviewed-by: Nigel Kirkland Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/lpfc/lpfc_ct.c | 29 +++++------------------------ 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/drivers/scsi/lpfc/lpfc_ct.c b/drivers/scsi/lpfc/lpfc_ct.c index c7853e7fe071..0734ab3be3e3 100644 --- a/drivers/scsi/lpfc/lpfc_ct.c +++ b/drivers/scsi/lpfc/lpfc_ct.c @@ -1823,34 +1823,15 @@ lpfc_vport_symbolic_node_name(struct lpfc_vport *vport, char *symbol, size_t size) { char fwrev[FW_REV_STR_SIZE] = {0}; - char tmp[MAXHOSTNAMELEN] = {0}; - - memset(symbol, 0, size); - - scnprintf(tmp, sizeof(tmp), "Emulex %s", vport->phba->ModelName); - if (strlcat(symbol, tmp, size) >= size) - goto buffer_done; lpfc_decode_firmware_rev(vport->phba, fwrev, 0); - scnprintf(tmp, sizeof(tmp), " FV%s", fwrev); - if (strlcat(symbol, tmp, size) >= size) - goto buffer_done; - - scnprintf(tmp, sizeof(tmp), " DV%s", lpfc_release_version); - if (strlcat(symbol, tmp, size) >= size) - goto buffer_done; - - scnprintf(tmp, sizeof(tmp), " HN:%s", vport->phba->os_host_name); - if (strlcat(symbol, tmp, size) >= size) - goto buffer_done; + memset(symbol, 0, size); /* Note :- OS name is "Linux" */ - scnprintf(tmp, sizeof(tmp), " OS:%s", init_utsname()->sysname); - strlcat(symbol, tmp, size); - -buffer_done: - return strnlen(symbol, size); - + return scnprintf(symbol, size, "Emulex %s FV%s DV%s HN:%s OS:%s", + vport->phba->ModelName, fwrev, + lpfc_release_version, vport->phba->os_host_name, + init_utsname()->sysname); } static uint32_t From 22d4cbf6f7509f0aab58d60d488a0082916a2fb0 Mon Sep 17 00:00:00 2001 From: Ian Bridges Date: Wed, 29 Jul 2026 09:46:15 -0500 Subject: [PATCH 172/641] scsi: lpfc: Replace strlcat() with seq_buf in lpfc_rx_monitor_report() In preparation for removing the strlcat() API[1], replace its use in lpfc_rx_monitor_report(). The function accumulates one line per ring entry, which is what seq_buf is for. seq_buf tracks the write position, so the per entry strlen() rescans of the destination are gone. Each record is still formatted into the tmp buffer. seq_buf_puts() appends it only when it fits whole, so the output keeps ending at the last complete record. The loop still stops on overflow without consuming the current entry, and the returned count and the ring head keep their old meaning. The produced bytes are unchanged. Link: https://github.com/KSPP/linux/issues/370 [1] Signed-off-by: Ian Bridges Link: https://patch.msgid.link/20260729144617.1388646-4-icb@fastmail.org Reviewed-by: Nigel Kirkland Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/lpfc/lpfc_sli.c | 43 ++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/drivers/scsi/lpfc/lpfc_sli.c b/drivers/scsi/lpfc/lpfc_sli.c index 62a30a92b792..cfa4371169f1 100644 --- a/drivers/scsi/lpfc/lpfc_sli.c +++ b/drivers/scsi/lpfc/lpfc_sli.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -8109,17 +8110,18 @@ u32 lpfc_rx_monitor_report(struct lpfc_hba *phba, u32 cnt = 0; char tmp[DBG_LOG_STR_SZ] = {0}; bool log_to_kmsg = (!buf || !buf_len) ? true : false; + struct seq_buf s; if (!log_to_kmsg) { /* clear the buffer to be sure */ memset(buf, 0, buf_len); - scnprintf(buf, buf_len, "\t%-16s%-16s%-16s%-16s%-8s%-8s%-8s" - "%-8s%-8s%-8s%-16s\n", - "MaxBPI", "Tot_Data_CMF", - "Tot_Data_Cmd", "Tot_Data_Cmpl", - "Lat(us)", "Avg_IO", "Max_IO", "Bsy", - "IO_cnt", "Info", "BWutil(ms)"); + seq_buf_init(&s, buf, buf_len); + seq_buf_printf(&s, "\t%-16s%-16s%-16s%-16s%-8s%-8s%-8s%-8s%-8s%-8s%-16s\n", + "MaxBPI", "Tot_Data_CMF", + "Tot_Data_Cmd", "Tot_Data_Cmpl", + "Lat(us)", "Avg_IO", "Max_IO", "Bsy", + "IO_cnt", "Info", "BWutil(ms)"); } /* Needs to be _irq because record is called from timer interrupt @@ -8131,24 +8133,27 @@ u32 lpfc_rx_monitor_report(struct lpfc_hba *phba, /* Read out this entry's data. */ if (!log_to_kmsg) { - /* If !log_to_kmsg, then store to buf. */ + /* + * Drop a record whole if it does not fit, without + * consuming its ring entry. + */ scnprintf(tmp, sizeof(tmp), - "%03d:\t%-16llu%-16llu%-16llu%-16llu%-8llu" - "%-8llu%-8llu%-8u%-8u%-8u%u(%u)\n", - *head_idx, entry->max_bytes_per_interval, - entry->cmf_bytes, entry->total_bytes, - entry->rcv_bytes, entry->avg_io_latency, - entry->avg_io_size, entry->max_read_cnt, + "%03d:\t%-16llu%-16llu%-16llu%-16llu%-8llu%-8llu%-8llu%-8u%-8u%-8u%u(%u)\n", + *head_idx, + entry->max_bytes_per_interval, + entry->cmf_bytes, + entry->total_bytes, + entry->rcv_bytes, + entry->avg_io_latency, + entry->avg_io_size, + entry->max_read_cnt, entry->cmf_busy, entry->io_cnt, - entry->cmf_info, entry->timer_utilization, + entry->cmf_info, + entry->timer_utilization, entry->timer_interval); - /* Check for buffer overflow */ - if ((strlen(buf) + strlen(tmp)) >= buf_len) + if (seq_buf_puts(&s, tmp) < 0) break; - - /* Append entry's data to buffer */ - strlcat(buf, tmp, buf_len); } else { lpfc_printf_log(phba, KERN_INFO, LOG_CGN_MGMT, "4410 %02u: MBPI %llu Xmit %llu " From 4832a60e0a7818400afffa392b74776d084ddf4a Mon Sep 17 00:00:00 2001 From: Ian Bridges Date: Wed, 29 Jul 2026 09:46:16 -0500 Subject: [PATCH 173/641] scsi: lpfc: Replace strlcat() with seq_buf in the debugfs dump helpers In preparation for removing the strlcat() API[1], replace its uses in lpfc_debugfs_multixripools_data(), lpfc_debugfs_scsistat_data() and lpfc_debugfs_hdwqstat_data(). Each helper accumulates a variable number of lines into the debugfs buffer, which is what seq_buf is for. The intermediate tmp buffers and the per fragment overflow checks become unnecessary. Once a seq_buf overflows, later writes to it do nothing, so dropping the early exits does not change the produced bytes. Each loop that appends keeps one seq_buf_has_overflowed() exit, so a full buffer stops the iteration. lpfc_debugfs_multixripools_data() and lpfc_debugfs_hdwqstat_data() append to whatever the buffer already holds, so their seq_buf is anchored at the current end of the string. All three helpers keep returning strnlen() because seq_buf_used() reports the full buffer size after an overflow. Link: https://github.com/KSPP/linux/issues/370 [1] Signed-off-by: Ian Bridges Link: https://patch.msgid.link/20260729144617.1388646-5-icb@fastmail.org Reviewed-by: Nigel Kirkland Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/lpfc/lpfc_debugfs.c | 198 +++++++++++++------------------ 1 file changed, 85 insertions(+), 113 deletions(-) diff --git a/drivers/scsi/lpfc/lpfc_debugfs.c b/drivers/scsi/lpfc/lpfc_debugfs.c index 052023fc1733..30e3cc050aaa 100644 --- a/drivers/scsi/lpfc/lpfc_debugfs.c +++ b/drivers/scsi/lpfc/lpfc_debugfs.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -462,7 +463,8 @@ lpfc_debugfs_multixripools_data(struct lpfc_hba *phba, char *buf, int size) struct lpfc_pvt_pool *pvt_pool; struct lpfc_pbl_pool *pbl_pool; u32 txcmplq_cnt; - char tmp[LPFC_DEBUG_OUT_LINE_SZ] = {0}; + size_t used; + struct seq_buf s; if (phba->sli_rev != LPFC_SLI_REV4) return 0; @@ -475,6 +477,9 @@ lpfc_debugfs_multixripools_data(struct lpfc_hba *phba, char *buf, int size) return i; } + used = strnlen(buf, size); + seq_buf_init(&s, buf + used, size - used); + /* * Pbl: Current number of free XRIs in public pool * Pvt: Current number of free XRIs in private pool @@ -484,10 +489,8 @@ lpfc_debugfs_multixripools_data(struct lpfc_hba *phba, char *buf, int size) * pbl_empty: Incremented by 1 when all pbl_pool are empty during * IO submission */ - scnprintf(tmp, sizeof(tmp), - "HWQ: Pbl Pvt Busy HWM | pvt_empty pbl_empty "); - if (strlcat(buf, tmp, size) >= size) - return strnlen(buf, size); + seq_buf_printf(&s, + "HWQ: Pbl Pvt Busy HWM | pvt_empty pbl_empty "); #ifdef LPFC_MXP_STAT /* @@ -501,25 +504,17 @@ lpfc_debugfs_multixripools_data(struct lpfc_hba *phba, char *buf, int size) * othPbl_hit: Incremented by 1 if successfully get a batch of XRI from * other pbl_pool */ - scnprintf(tmp, sizeof(tmp), - "MAXH above_lmt below_lmt locPbl_hit othPbl_hit"); - if (strlcat(buf, tmp, size) >= size) - return strnlen(buf, size); + seq_buf_printf(&s, "MAXH above_lmt below_lmt locPbl_hit othPbl_hit"); /* * sPbl: snapshot of Pbl 15 sec after stat gets cleared * sPvt: snapshot of Pvt 15 sec after stat gets cleared * sBusy: snapshot of Busy 15 sec after stat gets cleared */ - scnprintf(tmp, sizeof(tmp), - " | sPbl sPvt sBusy"); - if (strlcat(buf, tmp, size) >= size) - return strnlen(buf, size); + seq_buf_printf(&s, " | sPbl sPvt sBusy"); #endif - scnprintf(tmp, sizeof(tmp), "\n"); - if (strlcat(buf, tmp, size) >= size) - return strnlen(buf, size); + seq_buf_printf(&s, "\n"); hwq_count = phba->cfg_hdw_queue; for (i = 0; i < hwq_count; i++) { @@ -531,36 +526,32 @@ lpfc_debugfs_multixripools_data(struct lpfc_hba *phba, char *buf, int size) pvt_pool = &multixri_pool->pvt_pool; txcmplq_cnt = qp->io_wq->pring->txcmplq_cnt; - scnprintf(tmp, sizeof(tmp), - "%03d: %4d %4d %4d %4d | %10d %10d ", - i, pbl_pool->count, pvt_pool->count, - txcmplq_cnt, pvt_pool->high_watermark, - qp->empty_io_bufs, multixri_pool->pbl_empty_count); - if (strlcat(buf, tmp, size) >= size) - break; + seq_buf_printf(&s, + "%03d: %4d %4d %4d %4d | %10d %10d ", + i, pbl_pool->count, pvt_pool->count, + txcmplq_cnt, pvt_pool->high_watermark, + qp->empty_io_bufs, + multixri_pool->pbl_empty_count); #ifdef LPFC_MXP_STAT - scnprintf(tmp, sizeof(tmp), - "%4d %10d %10d %10d %10d", - multixri_pool->stat_max_hwm, - multixri_pool->above_limit_count, - multixri_pool->below_limit_count, - multixri_pool->local_pbl_hit_count, - multixri_pool->other_pbl_hit_count); - if (strlcat(buf, tmp, size) >= size) - break; + seq_buf_printf(&s, + "%4d %10d %10d %10d %10d", + multixri_pool->stat_max_hwm, + multixri_pool->above_limit_count, + multixri_pool->below_limit_count, + multixri_pool->local_pbl_hit_count, + multixri_pool->other_pbl_hit_count); - scnprintf(tmp, sizeof(tmp), - " | %4d %4d %5d", - multixri_pool->stat_pbl_count, - multixri_pool->stat_pvt_count, - multixri_pool->stat_busy_count); - if (strlcat(buf, tmp, size) >= size) - break; + seq_buf_printf(&s, + " | %4d %4d %5d", + multixri_pool->stat_pbl_count, + multixri_pool->stat_pvt_count, + multixri_pool->stat_busy_count); #endif - scnprintf(tmp, sizeof(tmp), "\n"); - if (strlcat(buf, tmp, size) >= size) + seq_buf_printf(&s, "\n"); + + if (seq_buf_has_overflowed(&s)) break; } return strnlen(buf, size); @@ -1261,13 +1252,14 @@ lpfc_debugfs_scsistat_data(struct lpfc_vport *vport, char *buf, int size) u64 data1, data2, data3; u64 tot, totin, totout; int i; - char tmp[LPFC_MAX_SCSI_INFO_TMP_LEN] = {0}; + struct seq_buf s; if (!(vport->cfg_enable_fc4_type & LPFC_ENABLE_FCP) || (phba->sli_rev != LPFC_SLI_REV4)) return 0; - scnprintf(buf, size, "SCSI HDWQ Statistics\n"); + seq_buf_init(&s, buf, size); + seq_buf_printf(&s, "SCSI HDWQ Statistics\n"); totin = 0; totout = 0; @@ -1280,21 +1272,18 @@ lpfc_debugfs_scsistat_data(struct lpfc_vport *vport, char *buf, int size) data3 = cstat->control_requests; totout += (data1 + data2 + data3); - scnprintf(tmp, sizeof(tmp), "HDWQ (%d): Rd %016llx Wr %016llx " - "IO %016llx ", i, data1, data2, data3); - if (strlcat(buf, tmp, size) >= size) - goto buffer_done; + seq_buf_printf(&s, "HDWQ (%d): Rd %016llx Wr %016llx IO %016llx ", + i, data1, data2, data3); - scnprintf(tmp, sizeof(tmp), "Cmpl %016llx OutIO %016llx\n", - tot, ((data1 + data2 + data3) - tot)); - if (strlcat(buf, tmp, size) >= size) - goto buffer_done; + seq_buf_printf(&s, "Cmpl %016llx OutIO %016llx\n", + tot, ((data1 + data2 + data3) - tot)); + + if (seq_buf_has_overflowed(&s)) + break; } - scnprintf(tmp, sizeof(tmp), "Total FCP Cmpl %016llx Issue %016llx " - "OutIO %016llx\n", totin, totout, totout - totin); - strlcat(buf, tmp, size); + seq_buf_printf(&s, "Total FCP Cmpl %016llx Issue %016llx OutIO %016llx\n", + totin, totout, totout - totin); -buffer_done: len = strnlen(buf, size); return len; @@ -1704,28 +1693,23 @@ lpfc_debugfs_hdwqstat_data(struct lpfc_vport *vport, char *buf, int size) uint32_t tot_xmt; uint32_t tot_rcv; uint32_t tot_cmpl; - char tmp[LPFC_MAX_SCSI_INFO_TMP_LEN] = {0}; + size_t used = strnlen(buf, size); + struct seq_buf s; - scnprintf(tmp, sizeof(tmp), "HDWQ Stats:\n\n"); - if (strlcat(buf, tmp, size) >= size) - goto buffer_done; + seq_buf_init(&s, buf + used, size - used); - scnprintf(tmp, sizeof(tmp), "(NVME Accounting: %s) ", - (phba->hdwqstat_on & - (LPFC_CHECK_NVME_IO | LPFC_CHECK_NVMET_IO) ? - "Enabled" : "Disabled")); - if (strlcat(buf, tmp, size) >= size) - goto buffer_done; + seq_buf_printf(&s, "HDWQ Stats:\n\n"); - scnprintf(tmp, sizeof(tmp), "(SCSI Accounting: %s) ", - (phba->hdwqstat_on & LPFC_CHECK_SCSI_IO ? - "Enabled" : "Disabled")); - if (strlcat(buf, tmp, size) >= size) - goto buffer_done; + seq_buf_printf(&s, "(NVME Accounting: %s) ", + (phba->hdwqstat_on & + (LPFC_CHECK_NVME_IO | LPFC_CHECK_NVMET_IO) ? + "Enabled" : "Disabled")); - scnprintf(tmp, sizeof(tmp), "\n\n"); - if (strlcat(buf, tmp, size) >= size) - goto buffer_done; + seq_buf_printf(&s, "(SCSI Accounting: %s) ", + (phba->hdwqstat_on & LPFC_CHECK_SCSI_IO ? + "Enabled" : "Disabled")); + + seq_buf_printf(&s, "\n\n"); for (i = 0; i < phba->cfg_hdw_queue; i++) { tot_rcv = 0; @@ -1744,62 +1728,50 @@ lpfc_debugfs_hdwqstat_data(struct lpfc_vport *vport, char *buf, int size) !c_stat->rcv_io) continue; - if (!tot_xmt && !tot_cmpl && !tot_rcv) { - /* Print HDWQ string only the first time */ - scnprintf(tmp, sizeof(tmp), "[HDWQ %d]:\t", i); - if (strlcat(buf, tmp, size) >= size) - goto buffer_done; - } + /* Print HDWQ string only the first time */ + if (!tot_xmt && !tot_cmpl && !tot_rcv) + seq_buf_printf(&s, "[HDWQ %d]:\t", i); tot_xmt += c_stat->xmt_io; tot_cmpl += c_stat->cmpl_io; if (phba->nvmet_support) tot_rcv += c_stat->rcv_io; - scnprintf(tmp, sizeof(tmp), "| [CPU %d]: ", j); - if (strlcat(buf, tmp, size) >= size) - goto buffer_done; + seq_buf_printf(&s, "| [CPU %d]: ", j); - if (phba->nvmet_support) { - scnprintf(tmp, sizeof(tmp), - "XMT 0x%x CMPL 0x%x RCV 0x%x |", - c_stat->xmt_io, c_stat->cmpl_io, - c_stat->rcv_io); - if (strlcat(buf, tmp, size) >= size) - goto buffer_done; - } else { - scnprintf(tmp, sizeof(tmp), - "XMT 0x%x CMPL 0x%x |", - c_stat->xmt_io, c_stat->cmpl_io); - if (strlcat(buf, tmp, size) >= size) - goto buffer_done; - } + if (phba->nvmet_support) + seq_buf_printf(&s, + "XMT 0x%x CMPL 0x%x RCV 0x%x |", + c_stat->xmt_io, c_stat->cmpl_io, + c_stat->rcv_io); + else + seq_buf_printf(&s, + "XMT 0x%x CMPL 0x%x |", + c_stat->xmt_io, c_stat->cmpl_io); + + if (seq_buf_has_overflowed(&s)) + break; } + if (seq_buf_has_overflowed(&s)) + break; + /* Check if nothing to display */ if (!tot_xmt && !tot_cmpl && !tot_rcv) continue; - scnprintf(tmp, sizeof(tmp), "\t->\t[HDWQ Total: "); - if (strlcat(buf, tmp, size) >= size) - goto buffer_done; + seq_buf_printf(&s, "\t->\t[HDWQ Total: "); - if (phba->nvmet_support) { - scnprintf(tmp, sizeof(tmp), - "XMT 0x%x CMPL 0x%x RCV 0x%x]\n\n", - tot_xmt, tot_cmpl, tot_rcv); - if (strlcat(buf, tmp, size) >= size) - goto buffer_done; - } else { - scnprintf(tmp, sizeof(tmp), - "XMT 0x%x CMPL 0x%x]\n\n", - tot_xmt, tot_cmpl); - if (strlcat(buf, tmp, size) >= size) - goto buffer_done; - } + if (phba->nvmet_support) + seq_buf_printf(&s, + "XMT 0x%x CMPL 0x%x RCV 0x%x]\n\n", + tot_xmt, tot_cmpl, tot_rcv); + else + seq_buf_printf(&s, + "XMT 0x%x CMPL 0x%x]\n\n", + tot_xmt, tot_cmpl); } -buffer_done: len = strnlen(buf, size); return len; } From 36b6dcb2b7463b290d63c83d82bec8065a1a93de Mon Sep 17 00:00:00 2001 From: Ian Bridges Date: Wed, 29 Jul 2026 09:46:17 -0500 Subject: [PATCH 174/641] scsi: lpfc: Replace strlcat() with sysfs_emit_at() in the sysfs show functions In preparation for removing the strlcat() API[1], replace its uses in lpfc_cmf_info_show(), lpfc_nvme_info_show() and lpfc_scsi_stat_show(). The three functions build sysfs attribute output, and sysfs_emit_at() is the designated helper for that. The single write paths become sysfs_emit(), the offset zero form of the same helper. Each intermediate tmp buffer and its per fragment overflow check become unnecessary. Once the page is full, sysfs_emit_at() writes nothing more, so dropping the early exits does not change the produced bytes. Each loop that appends keeps one exit, so a full page stops the iteration. In lpfc_nvme_info_show() the exit also releases the fc_nodes_list_lock as it did before. The unlock_buf_done label loses its last user and is removed. The old code capped every fragment at LPFC_MAX_INFO_TMP_LEN or LPFC_MAX_SCSI_INFO_TMP_LEN bytes before appending it. The replacement formats each fragment directly into the page, so a fragment longer than its old tmp buffer is no longer truncated when the page has room for it. Both macros lose their last user and are removed. The running length that sysfs_emit_at() maintains equals the length that the removed strnlen() calls computed, so the "Could be more info" overflow markers keep their trigger condition. Link: https://github.com/KSPP/linux/issues/370 [1] Signed-off-by: Ian Bridges Link: https://patch.msgid.link/20260729144617.1388646-6-icb@fastmail.org Reviewed-by: Nigel Kirkland Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/lpfc/lpfc_attr.c | 533 ++++++++++++++-------------------- drivers/scsi/lpfc/lpfc_scsi.h | 3 - 2 files changed, 219 insertions(+), 317 deletions(-) diff --git a/drivers/scsi/lpfc/lpfc_attr.c b/drivers/scsi/lpfc/lpfc_attr.c index f4e8164b94ab..bb5c84f0b787 100644 --- a/drivers/scsi/lpfc/lpfc_attr.c +++ b/drivers/scsi/lpfc/lpfc_attr.c @@ -57,7 +57,6 @@ #define LPFC_MIN_DEVLOSS_TMO 1 #define LPFC_MAX_DEVLOSS_TMO 255 -#define LPFC_MAX_INFO_TMP_LEN 100 #define LPFC_INFO_MORE_STR "\nCould be more info...\n" /* * Write key size should be multiple of 4. If write key is changed @@ -126,133 +125,121 @@ lpfc_cmf_info_show(struct device *dev, struct device_attribute *attr, int len = 0; int cpu; u64 rcv, total; - char tmp[LPFC_MAX_INFO_TMP_LEN] = {0}; if (phba->cgn_i) cp = (struct lpfc_cgn_info *)phba->cgn_i->virt; - scnprintf(tmp, sizeof(tmp), - "Congestion Mgmt Info: E2Eattr %d Ver %d " - "CMF %d cnt %d\n", - phba->sli4_hba.pc_sli4_params.mi_cap, - cp ? cp->cgn_info_version : 0, - phba->sli4_hba.pc_sli4_params.cmf, phba->cmf_timer_cnt); - - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len = strnlen(buf, PAGE_SIZE); + len += sysfs_emit_at(buf, len, + "Congestion Mgmt Info: E2Eattr %d Ver %d CMF %d cnt %d\n", + phba->sli4_hba.pc_sli4_params.mi_cap, + cp ? cp->cgn_info_version : 0, + phba->sli4_hba.pc_sli4_params.cmf, + phba->cmf_timer_cnt); if (!phba->sli4_hba.pc_sli4_params.cmf) goto buffer_done; switch (phba->cgn_init_reg_signal) { case EDC_CG_SIG_WARN_ONLY: - scnprintf(tmp, sizeof(tmp), - "Register: Init: Signal:WARN "); + len += sysfs_emit_at(buf, len, + "Register: Init: Signal:WARN "); break; case EDC_CG_SIG_WARN_ALARM: - scnprintf(tmp, sizeof(tmp), - "Register: Init: Signal:WARN|ALARM "); + len += sysfs_emit_at(buf, len, + "Register: Init: Signal:WARN|ALARM "); break; default: - scnprintf(tmp, sizeof(tmp), - "Register: Init: Signal:NONE "); + len += sysfs_emit_at(buf, len, + "Register: Init: Signal:NONE "); break; } - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; switch (phba->cgn_init_reg_fpin) { case LPFC_CGN_FPIN_WARN: - scnprintf(tmp, sizeof(tmp), - "FPIN:WARN\n"); + len += sysfs_emit_at(buf, len, "FPIN:WARN\n"); break; case LPFC_CGN_FPIN_ALARM: - scnprintf(tmp, sizeof(tmp), - "FPIN:ALARM\n"); + len += sysfs_emit_at(buf, len, "FPIN:ALARM\n"); break; case LPFC_CGN_FPIN_BOTH: - scnprintf(tmp, sizeof(tmp), - "FPIN:WARN|ALARM\n"); + len += sysfs_emit_at(buf, len, "FPIN:WARN|ALARM\n"); break; default: - scnprintf(tmp, sizeof(tmp), - "FPIN:NONE\n"); + len += sysfs_emit_at(buf, len, "FPIN:NONE\n"); break; } - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; switch (phba->cgn_reg_signal) { case EDC_CG_SIG_WARN_ONLY: - scnprintf(tmp, sizeof(tmp), - " Current: Signal:WARN "); + len += sysfs_emit_at(buf, len, + " Current: Signal:WARN "); break; case EDC_CG_SIG_WARN_ALARM: - scnprintf(tmp, sizeof(tmp), - " Current: Signal:WARN|ALARM "); + len += sysfs_emit_at(buf, len, + " Current: Signal:WARN|ALARM "); break; default: - scnprintf(tmp, sizeof(tmp), - " Current: Signal:NONE "); + len += sysfs_emit_at(buf, len, + " Current: Signal:NONE "); break; } - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; switch (phba->cgn_reg_fpin) { case LPFC_CGN_FPIN_WARN: - scnprintf(tmp, sizeof(tmp), - "FPIN:WARN ACQEcnt:%d\n", phba->cgn_acqe_cnt); + len += sysfs_emit_at(buf, len, + "FPIN:WARN ACQEcnt:%d\n", + phba->cgn_acqe_cnt); break; case LPFC_CGN_FPIN_ALARM: - scnprintf(tmp, sizeof(tmp), - "FPIN:ALARM ACQEcnt:%d\n", phba->cgn_acqe_cnt); + len += sysfs_emit_at(buf, len, + "FPIN:ALARM ACQEcnt:%d\n", + phba->cgn_acqe_cnt); break; case LPFC_CGN_FPIN_BOTH: - scnprintf(tmp, sizeof(tmp), - "FPIN:WARN|ALARM ACQEcnt:%d\n", phba->cgn_acqe_cnt); + len += sysfs_emit_at(buf, len, + "FPIN:WARN|ALARM ACQEcnt:%d\n", + phba->cgn_acqe_cnt); break; default: - scnprintf(tmp, sizeof(tmp), - "FPIN:NONE ACQEcnt:%d\n", phba->cgn_acqe_cnt); + len += sysfs_emit_at(buf, len, + "FPIN:NONE ACQEcnt:%d\n", + phba->cgn_acqe_cnt); break; } - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; if (phba->cmf_active_mode != phba->cgn_p.cgn_param_mode) { switch (phba->cmf_active_mode) { case LPFC_CFG_OFF: - scnprintf(tmp, sizeof(tmp), "Active: Mode:Off\n"); + len += sysfs_emit_at(buf, len, "Active: Mode:Off\n"); break; case LPFC_CFG_MANAGED: - scnprintf(tmp, sizeof(tmp), "Active: Mode:Managed\n"); + len += sysfs_emit_at(buf, len, + "Active: Mode:Managed\n"); break; case LPFC_CFG_MONITOR: - scnprintf(tmp, sizeof(tmp), "Active: Mode:Monitor\n"); + len += sysfs_emit_at(buf, len, + "Active: Mode:Monitor\n"); break; default: - scnprintf(tmp, sizeof(tmp), "Active: Mode:Unknown\n"); + len += sysfs_emit_at(buf, len, + "Active: Mode:Unknown\n"); } - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; } switch (phba->cgn_p.cgn_param_mode) { case LPFC_CFG_OFF: - scnprintf(tmp, sizeof(tmp), "Config: Mode:Off "); + len += sysfs_emit_at(buf, len, "Config: Mode:Off "); break; case LPFC_CFG_MANAGED: - scnprintf(tmp, sizeof(tmp), "Config: Mode:Managed "); + len += sysfs_emit_at(buf, len, "Config: Mode:Managed "); break; case LPFC_CFG_MONITOR: - scnprintf(tmp, sizeof(tmp), "Config: Mode:Monitor "); + len += sysfs_emit_at(buf, len, "Config: Mode:Monitor "); break; default: - scnprintf(tmp, sizeof(tmp), "Config: Mode:Unknown "); + len += sysfs_emit_at(buf, len, "Config: Mode:Unknown "); } - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; total = 0; rcv = 0; @@ -262,24 +249,18 @@ lpfc_cmf_info_show(struct device *dev, struct device_attribute *attr, rcv += atomic64_read(&cgs->rcv_bytes); } - scnprintf(tmp, sizeof(tmp), - "IObusy:%d Info:%d Bytes: Rcv:x%llx Total:x%llx\n", - atomic_read(&phba->cmf_busy), - phba->cmf_active_info, rcv, total); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "IObusy:%d Info:%d Bytes: Rcv:x%llx Total:x%llx\n", + atomic_read(&phba->cmf_busy), + phba->cmf_active_info, rcv, total); - scnprintf(tmp, sizeof(tmp), - "Port_speed:%d Link_byte_cnt:%ld " - "Max_byte_per_interval:%ld\n", - lpfc_sli_port_speed_get(phba), - (unsigned long)phba->cmf_link_byte_count, - (unsigned long)phba->cmf_max_bytes_per_interval); - strlcat(buf, tmp, PAGE_SIZE); + len += sysfs_emit_at(buf, len, + "Port_speed:%d Link_byte_cnt:%ld Max_byte_per_interval:%ld\n", + lpfc_sli_port_speed_get(phba), + (unsigned long)phba->cmf_link_byte_count, + (unsigned long)phba->cmf_max_bytes_per_interval); buffer_done: - len = strnlen(buf, PAGE_SIZE); - if (unlikely(len >= (PAGE_SIZE - 1))) { lpfc_printf_log(phba, KERN_INFO, LOG_CGN_MGMT, "6312 Catching potential buffer " @@ -480,17 +461,19 @@ lpfc_nvme_info_show(struct device *dev, struct device_attribute *attr, char *statep; int i; int len = 0; - char tmp[LPFC_MAX_INFO_TMP_LEN] = {0}; if (!(vport->cfg_enable_fc4_type & LPFC_ENABLE_NVME)) { - len = scnprintf(buf, PAGE_SIZE, "NVME Disabled\n"); + len = sysfs_emit(buf, "NVME Disabled\n"); return len; } + + len = strnlen(buf, PAGE_SIZE); + if (phba->nvmet_support) { if (!phba->targetport) { - len = scnprintf(buf, PAGE_SIZE, - "NVME Target: x%llx is not allocated\n", - wwn_to_u64(vport->fc_portname.u.wwn)); + len = sysfs_emit(buf, + "NVME Target: x%llx is not allocated\n", + wwn_to_u64(vport->fc_portname.u.wwn)); return len; } /* Port state is only one of two values for now. */ @@ -498,167 +481,131 @@ lpfc_nvme_info_show(struct device *dev, struct device_attribute *attr, statep = "REGISTERED"; else statep = "INIT"; - scnprintf(tmp, sizeof(tmp), - "NVME Target Enabled State %s\n", - statep); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "NVME Target Enabled State %s\n", + statep); - scnprintf(tmp, sizeof(tmp), - "%s%d WWPN x%llx WWNN x%llx DID x%06x\n", - "NVME Target: lpfc", - phba->brd_no, - wwn_to_u64(vport->fc_portname.u.wwn), - wwn_to_u64(vport->fc_nodename.u.wwn), - phba->targetport->port_id); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "%s%d WWPN x%llx WWNN x%llx DID x%06x\n", + "NVME Target: lpfc", + phba->brd_no, + wwn_to_u64(vport->fc_portname.u.wwn), + wwn_to_u64(vport->fc_nodename.u.wwn), + phba->targetport->port_id); - if (strlcat(buf, "\nNVME Target: Statistics\n", PAGE_SIZE) - >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, "\nNVME Target: Statistics\n"); tgtp = (struct lpfc_nvmet_tgtport *)phba->targetport->private; - scnprintf(tmp, sizeof(tmp), - "LS: Rcv %08x Drop %08x Abort %08x\n", - atomic_read(&tgtp->rcv_ls_req_in), - atomic_read(&tgtp->rcv_ls_req_drop), - atomic_read(&tgtp->xmt_ls_abort)); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "LS: Rcv %08x Drop %08x Abort %08x\n", + atomic_read(&tgtp->rcv_ls_req_in), + atomic_read(&tgtp->rcv_ls_req_drop), + atomic_read(&tgtp->xmt_ls_abort)); if (atomic_read(&tgtp->rcv_ls_req_in) != atomic_read(&tgtp->rcv_ls_req_out)) { - scnprintf(tmp, sizeof(tmp), - "Rcv LS: in %08x != out %08x\n", - atomic_read(&tgtp->rcv_ls_req_in), - atomic_read(&tgtp->rcv_ls_req_out)); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "Rcv LS: in %08x != out %08x\n", + atomic_read(&tgtp->rcv_ls_req_in), + atomic_read(&tgtp->rcv_ls_req_out)); } - scnprintf(tmp, sizeof(tmp), - "LS: Xmt %08x Drop %08x Cmpl %08x\n", - atomic_read(&tgtp->xmt_ls_rsp), - atomic_read(&tgtp->xmt_ls_drop), - atomic_read(&tgtp->xmt_ls_rsp_cmpl)); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "LS: Xmt %08x Drop %08x Cmpl %08x\n", + atomic_read(&tgtp->xmt_ls_rsp), + atomic_read(&tgtp->xmt_ls_drop), + atomic_read(&tgtp->xmt_ls_rsp_cmpl)); - scnprintf(tmp, sizeof(tmp), - "LS: RSP Abort %08x xb %08x Err %08x\n", - atomic_read(&tgtp->xmt_ls_rsp_aborted), - atomic_read(&tgtp->xmt_ls_rsp_xb_set), - atomic_read(&tgtp->xmt_ls_rsp_error)); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "LS: RSP Abort %08x xb %08x Err %08x\n", + atomic_read(&tgtp->xmt_ls_rsp_aborted), + atomic_read(&tgtp->xmt_ls_rsp_xb_set), + atomic_read(&tgtp->xmt_ls_rsp_error)); - scnprintf(tmp, sizeof(tmp), - "FCP: Rcv %08x Defer %08x Release %08x " - "Drop %08x\n", - atomic_read(&tgtp->rcv_fcp_cmd_in), - atomic_read(&tgtp->rcv_fcp_cmd_defer), - atomic_read(&tgtp->xmt_fcp_release), - atomic_read(&tgtp->rcv_fcp_cmd_drop)); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "FCP: Rcv %08x Defer %08x Release %08x Drop %08x\n", + atomic_read(&tgtp->rcv_fcp_cmd_in), + atomic_read(&tgtp->rcv_fcp_cmd_defer), + atomic_read(&tgtp->xmt_fcp_release), + atomic_read(&tgtp->rcv_fcp_cmd_drop)); if (atomic_read(&tgtp->rcv_fcp_cmd_in) != atomic_read(&tgtp->rcv_fcp_cmd_out)) { - scnprintf(tmp, sizeof(tmp), - "Rcv FCP: in %08x != out %08x\n", - atomic_read(&tgtp->rcv_fcp_cmd_in), - atomic_read(&tgtp->rcv_fcp_cmd_out)); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "Rcv FCP: in %08x != out %08x\n", + atomic_read(&tgtp->rcv_fcp_cmd_in), + atomic_read(&tgtp->rcv_fcp_cmd_out)); } - scnprintf(tmp, sizeof(tmp), - "FCP Rsp: RD %08x rsp %08x WR %08x rsp %08x " - "drop %08x\n", - atomic_read(&tgtp->xmt_fcp_read), - atomic_read(&tgtp->xmt_fcp_read_rsp), - atomic_read(&tgtp->xmt_fcp_write), - atomic_read(&tgtp->xmt_fcp_rsp), - atomic_read(&tgtp->xmt_fcp_drop)); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "FCP Rsp: RD %08x rsp %08x WR %08x rsp %08x drop %08x\n", + atomic_read(&tgtp->xmt_fcp_read), + atomic_read(&tgtp->xmt_fcp_read_rsp), + atomic_read(&tgtp->xmt_fcp_write), + atomic_read(&tgtp->xmt_fcp_rsp), + atomic_read(&tgtp->xmt_fcp_drop)); - scnprintf(tmp, sizeof(tmp), - "FCP Rsp Cmpl: %08x err %08x drop %08x\n", - atomic_read(&tgtp->xmt_fcp_rsp_cmpl), - atomic_read(&tgtp->xmt_fcp_rsp_error), - atomic_read(&tgtp->xmt_fcp_rsp_drop)); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "FCP Rsp Cmpl: %08x err %08x drop %08x\n", + atomic_read(&tgtp->xmt_fcp_rsp_cmpl), + atomic_read(&tgtp->xmt_fcp_rsp_error), + atomic_read(&tgtp->xmt_fcp_rsp_drop)); - scnprintf(tmp, sizeof(tmp), - "FCP Rsp Abort: %08x xb %08x xricqe %08x\n", - atomic_read(&tgtp->xmt_fcp_rsp_aborted), - atomic_read(&tgtp->xmt_fcp_rsp_xb_set), - atomic_read(&tgtp->xmt_fcp_xri_abort_cqe)); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "FCP Rsp Abort: %08x xb %08x xricqe %08x\n", + atomic_read(&tgtp->xmt_fcp_rsp_aborted), + atomic_read(&tgtp->xmt_fcp_rsp_xb_set), + atomic_read(&tgtp->xmt_fcp_xri_abort_cqe)); - scnprintf(tmp, sizeof(tmp), - "ABORT: Xmt %08x Cmpl %08x\n", - atomic_read(&tgtp->xmt_fcp_abort), - atomic_read(&tgtp->xmt_fcp_abort_cmpl)); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "ABORT: Xmt %08x Cmpl %08x\n", + atomic_read(&tgtp->xmt_fcp_abort), + atomic_read(&tgtp->xmt_fcp_abort_cmpl)); - scnprintf(tmp, sizeof(tmp), - "ABORT: Sol %08x Usol %08x Err %08x Cmpl %08x\n", - atomic_read(&tgtp->xmt_abort_sol), - atomic_read(&tgtp->xmt_abort_unsol), - atomic_read(&tgtp->xmt_abort_rsp), - atomic_read(&tgtp->xmt_abort_rsp_error)); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "ABORT: Sol %08x Usol %08x Err %08x Cmpl %08x\n", + atomic_read(&tgtp->xmt_abort_sol), + atomic_read(&tgtp->xmt_abort_unsol), + atomic_read(&tgtp->xmt_abort_rsp), + atomic_read(&tgtp->xmt_abort_rsp_error)); - scnprintf(tmp, sizeof(tmp), - "DELAY: ctx %08x fod %08x wqfull %08x\n", - atomic_read(&tgtp->defer_ctx), - atomic_read(&tgtp->defer_fod), - atomic_read(&tgtp->defer_wqfull)); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "DELAY: ctx %08x fod %08x wqfull %08x\n", + atomic_read(&tgtp->defer_ctx), + atomic_read(&tgtp->defer_fod), + atomic_read(&tgtp->defer_wqfull)); /* Calculate outstanding IOs */ tot = atomic_read(&tgtp->rcv_fcp_cmd_drop); tot += atomic_read(&tgtp->xmt_fcp_release); tot = atomic_read(&tgtp->rcv_fcp_cmd_in) - tot; - scnprintf(tmp, sizeof(tmp), - "IO_CTX: %08x WAIT: cur %08x tot %08x\n" - "CTX Outstanding %08llx\n\n", - phba->sli4_hba.nvmet_xri_cnt, - phba->sli4_hba.nvmet_io_wait_cnt, - phba->sli4_hba.nvmet_io_wait_total, - tot); - strlcat(buf, tmp, PAGE_SIZE); + len += sysfs_emit_at(buf, len, + "IO_CTX: %08x WAIT: cur %08x tot %08x\n" + "CTX Outstanding %08llx\n\n", + phba->sli4_hba.nvmet_xri_cnt, + phba->sli4_hba.nvmet_io_wait_cnt, + phba->sli4_hba.nvmet_io_wait_total, + tot); goto buffer_done; } localport = vport->localport; if (!localport) { - len = scnprintf(buf, PAGE_SIZE, - "NVME Initiator x%llx is not allocated\n", - wwn_to_u64(vport->fc_portname.u.wwn)); + len = sysfs_emit(buf, + "NVME Initiator x%llx is not allocated\n", + wwn_to_u64(vport->fc_portname.u.wwn)); return len; } lport = (struct lpfc_nvme_lport *)localport->private; - if (strlcat(buf, "\nNVME Initiator Enabled\n", PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, "\nNVME Initiator Enabled\n"); - scnprintf(tmp, sizeof(tmp), - "XRI Dist lpfc%d Total %d IO %d ELS %d\n", - phba->brd_no, - phba->sli4_hba.max_cfg_param.max_xri, - phba->sli4_hba.io_xri_max, - lpfc_sli4_get_els_iocb_cnt(phba)); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "XRI Dist lpfc%d Total %d IO %d ELS %d\n", + phba->brd_no, + phba->sli4_hba.max_cfg_param.max_xri, + phba->sli4_hba.io_xri_max, + lpfc_sli4_get_els_iocb_cnt(phba)); /* Port state is only one of two values for now. */ if (localport->port_id) @@ -666,15 +613,13 @@ lpfc_nvme_info_show(struct device *dev, struct device_attribute *attr, else statep = "UNKNOWN "; - scnprintf(tmp, sizeof(tmp), - "%s%d WWPN x%llx WWNN x%llx DID x%06x %s\n", - "NVME LPORT lpfc", - phba->brd_no, - wwn_to_u64(vport->fc_portname.u.wwn), - wwn_to_u64(vport->fc_nodename.u.wwn), - localport->port_id, statep); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "%s%d WWPN x%llx WWNN x%llx DID x%06x %s\n", + "NVME LPORT lpfc", + phba->brd_no, + wwn_to_u64(vport->fc_portname.u.wwn), + wwn_to_u64(vport->fc_nodename.u.wwn), + localport->port_id, statep); spin_lock_irqsave(&vport->fc_nodes_list_lock, iflags); @@ -702,77 +647,55 @@ lpfc_nvme_info_show(struct device *dev, struct device_attribute *attr, } /* Tab in to show lport ownership. */ - if (strlcat(buf, "NVME RPORT ", PAGE_SIZE) >= PAGE_SIZE) - goto unlock_buf_done; - if (phba->brd_no >= 10) { - if (strlcat(buf, " ", PAGE_SIZE) >= PAGE_SIZE) - goto unlock_buf_done; - } + len += sysfs_emit_at(buf, len, "NVME RPORT "); + if (phba->brd_no >= 10) + len += sysfs_emit_at(buf, len, " "); - scnprintf(tmp, sizeof(tmp), "WWPN x%llx ", - nrport->port_name); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto unlock_buf_done; + len += sysfs_emit_at(buf, len, "WWPN x%llx ", + nrport->port_name); - scnprintf(tmp, sizeof(tmp), "WWNN x%llx ", - nrport->node_name); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto unlock_buf_done; + len += sysfs_emit_at(buf, len, "WWNN x%llx ", + nrport->node_name); - scnprintf(tmp, sizeof(tmp), "DID x%06x ", - nrport->port_id); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto unlock_buf_done; + len += sysfs_emit_at(buf, len, "DID x%06x ", + nrport->port_id); /* An NVME rport can have multiple roles. */ - if (nrport->port_role & FC_PORT_ROLE_NVME_INITIATOR) { - if (strlcat(buf, "INITIATOR ", PAGE_SIZE) >= PAGE_SIZE) - goto unlock_buf_done; - } - if (nrport->port_role & FC_PORT_ROLE_NVME_TARGET) { - if (strlcat(buf, "TARGET ", PAGE_SIZE) >= PAGE_SIZE) - goto unlock_buf_done; - } - if (nrport->port_role & FC_PORT_ROLE_NVME_DISCOVERY) { - if (strlcat(buf, "DISCSRVC ", PAGE_SIZE) >= PAGE_SIZE) - goto unlock_buf_done; - } + if (nrport->port_role & FC_PORT_ROLE_NVME_INITIATOR) + len += sysfs_emit_at(buf, len, "INITIATOR "); + if (nrport->port_role & FC_PORT_ROLE_NVME_TARGET) + len += sysfs_emit_at(buf, len, "TARGET "); + if (nrport->port_role & FC_PORT_ROLE_NVME_DISCOVERY) + len += sysfs_emit_at(buf, len, "DISCSRVC "); if (nrport->port_role & ~(FC_PORT_ROLE_NVME_INITIATOR | FC_PORT_ROLE_NVME_TARGET | - FC_PORT_ROLE_NVME_DISCOVERY)) { - scnprintf(tmp, sizeof(tmp), "UNKNOWN ROLE x%x", - nrport->port_role); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto unlock_buf_done; - } + FC_PORT_ROLE_NVME_DISCOVERY)) + len += sysfs_emit_at(buf, len, "UNKNOWN ROLE x%x", + nrport->port_role); - scnprintf(tmp, sizeof(tmp), "%s\n", statep); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto unlock_buf_done; + len += sysfs_emit_at(buf, len, "%s\n", statep); + + if (len >= PAGE_SIZE - 1) + break; } spin_unlock_irqrestore(&vport->fc_nodes_list_lock, iflags); if (!lport) goto buffer_done; - if (strlcat(buf, "\nNVME Statistics\n", PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, "\nNVME Statistics\n"); - scnprintf(tmp, sizeof(tmp), - "LS: Xmt %010x Cmpl %010x Abort %08x\n", - atomic_read(&lport->fc4NvmeLsRequests), - atomic_read(&lport->fc4NvmeLsCmpls), - atomic_read(&lport->xmt_ls_abort)); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "LS: Xmt %010x Cmpl %010x Abort %08x\n", + atomic_read(&lport->fc4NvmeLsRequests), + atomic_read(&lport->fc4NvmeLsCmpls), + atomic_read(&lport->xmt_ls_abort)); - scnprintf(tmp, sizeof(tmp), - "LS XMIT: Err %08x CMPL: xb %08x Err %08x\n", - atomic_read(&lport->xmt_ls_err), - atomic_read(&lport->cmpl_ls_xb), - atomic_read(&lport->cmpl_ls_err)); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "LS XMIT: Err %08x CMPL: xb %08x Err %08x\n", + atomic_read(&lport->xmt_ls_err), + atomic_read(&lport->cmpl_ls_xb), + atomic_read(&lport->cmpl_ls_err)); totin = 0; totout = 0; @@ -785,40 +708,25 @@ lpfc_nvme_info_show(struct device *dev, struct device_attribute *attr, data3 = cstat->control_requests; totout += (data1 + data2 + data3); } - scnprintf(tmp, sizeof(tmp), - "Total FCP Cmpl %016llx Issue %016llx " - "OutIO %016llx\n", - totin, totout, totout - totin); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "Total FCP Cmpl %016llx Issue %016llx OutIO %016llx\n", + totin, totout, totout - totin); - scnprintf(tmp, sizeof(tmp), - "\tabort %08x noxri %08x nondlp %08x qdepth %08x " - "wqerr %08x err %08x\n", - atomic_read(&lport->xmt_fcp_abort), - atomic_read(&lport->xmt_fcp_noxri), - atomic_read(&lport->xmt_fcp_bad_ndlp), - atomic_read(&lport->xmt_fcp_qdepth), - atomic_read(&lport->xmt_fcp_wqerr), - atomic_read(&lport->xmt_fcp_err)); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "\tabort %08x noxri %08x nondlp %08x qdepth %08x wqerr %08x err %08x\n", + atomic_read(&lport->xmt_fcp_abort), + atomic_read(&lport->xmt_fcp_noxri), + atomic_read(&lport->xmt_fcp_bad_ndlp), + atomic_read(&lport->xmt_fcp_qdepth), + atomic_read(&lport->xmt_fcp_wqerr), + atomic_read(&lport->xmt_fcp_err)); - scnprintf(tmp, sizeof(tmp), - "FCP CMPL: xb %08x Err %08x\n", - atomic_read(&lport->cmpl_fcp_xb), - atomic_read(&lport->cmpl_fcp_err)); - strlcat(buf, tmp, PAGE_SIZE); - - /* host_lock is already unlocked. */ - goto buffer_done; - - unlock_buf_done: - spin_unlock_irqrestore(&vport->fc_nodes_list_lock, iflags); + len += sysfs_emit_at(buf, len, + "FCP CMPL: xb %08x Err %08x\n", + atomic_read(&lport->cmpl_fcp_xb), + atomic_read(&lport->cmpl_fcp_err)); buffer_done: - len = strnlen(buf, PAGE_SIZE); - if (unlikely(len >= (PAGE_SIZE - 1))) { lpfc_printf_log(phba, KERN_INFO, LOG_NVME, "6314 Catching potential buffer " @@ -844,13 +752,12 @@ lpfc_scsi_stat_show(struct device *dev, struct device_attribute *attr, u64 data1, data2, data3; u64 tot, totin, totout; int i; - char tmp[LPFC_MAX_SCSI_INFO_TMP_LEN] = {0}; if (!(vport->cfg_enable_fc4_type & LPFC_ENABLE_FCP) || (phba->sli_rev != LPFC_SLI_REV4)) return 0; - scnprintf(buf, PAGE_SIZE, "SCSI HDWQ Statistics\n"); + len = sysfs_emit(buf, "SCSI HDWQ Statistics\n"); totin = 0; totout = 0; @@ -863,22 +770,20 @@ lpfc_scsi_stat_show(struct device *dev, struct device_attribute *attr, data3 = cstat->control_requests; totout += (data1 + data2 + data3); - scnprintf(tmp, sizeof(tmp), "HDWQ (%d): Rd %016llx Wr %016llx " - "IO %016llx ", i, data1, data2, data3); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "HDWQ (%d): Rd %016llx Wr %016llx IO %016llx ", + i, data1, data2, data3); - scnprintf(tmp, sizeof(tmp), "Cmpl %016llx OutIO %016llx\n", - tot, ((data1 + data2 + data3) - tot)); - if (strlcat(buf, tmp, PAGE_SIZE) >= PAGE_SIZE) - goto buffer_done; + len += sysfs_emit_at(buf, len, + "Cmpl %016llx OutIO %016llx\n", + tot, ((data1 + data2 + data3) - tot)); + + if (len >= PAGE_SIZE - 1) + break; } - scnprintf(tmp, sizeof(tmp), "Total FCP Cmpl %016llx Issue %016llx " - "OutIO %016llx\n", totin, totout, totout - totin); - strlcat(buf, tmp, PAGE_SIZE); - -buffer_done: - len = strnlen(buf, PAGE_SIZE); + len += sysfs_emit_at(buf, len, + "Total FCP Cmpl %016llx Issue %016llx OutIO %016llx\n", + totin, totout, totout - totin); return len; } diff --git a/drivers/scsi/lpfc/lpfc_scsi.h b/drivers/scsi/lpfc/lpfc_scsi.h index a05d203e4777..3e0937cf3ff3 100644 --- a/drivers/scsi/lpfc/lpfc_scsi.h +++ b/drivers/scsi/lpfc/lpfc_scsi.h @@ -158,6 +158,3 @@ struct fcp_cmnd32 { #define TXRDY_PAYLOAD_LEN 12 -/* For sysfs/debugfs tmp string max len */ -#define LPFC_MAX_SCSI_INFO_TMP_LEN 79 - From 271e90eb5f9ff34951647e5ed33c1775eebcca50 Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Fri, 31 Jul 2026 12:42:32 -0300 Subject: [PATCH 175/641] drm: use drm_warn() in validate_blend_mode_for_alpha_formats() Commit 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed") introduced a WARN() to let driver developers know that a previously valid behavior should now be changed. But WARN() should not be used for that, as it's a kernel warning report mechanism for conditions that are not expected to happen. It also produces a stack trace. Instead, a simple warning-level log message should have been used, as drivers were expected to trigger the condition. This is causing problems for fuzzers, as they may stop when encountering a "BUG:" or "WARNING:" in the logs. Replace WARN() with drm_warn() in this function, avoiding these issues. Fixes: 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed") Signed-off-by: Leandro Ribeiro Reviewed-by: Daniel Stone Link: https://patch.msgid.link/20260731154232.37020-2-leandro.ribeiro@collabora.com Signed-off-by: Daniel Stone --- drivers/gpu/drm/drm_mode_config.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/drm_mode_config.c b/drivers/gpu/drm/drm_mode_config.c index 3bcc7bf0900c..366f6d821242 100644 --- a/drivers/gpu/drm/drm_mode_config.c +++ b/drivers/gpu/drm/drm_mode_config.c @@ -869,8 +869,9 @@ static void validate_blend_mode_for_alpha_formats(struct drm_plane *plane) for (i = 0; i < plane->format_count; i++) { fmt = drm_format_info(plane->format_types[i]); if (fmt->has_alpha) { - WARN(1, "[PLANE:%d:%s] pixel format with alpha exposed but blend mode not setup", - plane->base.id, plane->name); + drm_warn(plane->dev, + "[PLANE:%d:%s] pixel format with alpha exposed but blend mode not setup. Please fix.\n", + plane->base.id, plane->name); break; } } From 6620f5e8c11c4f7e41222a86f5c97150cc5f84a5 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sat, 1 Aug 2026 21:09:50 +0200 Subject: [PATCH 176/641] openrisc: drop unneeded semicolon When a function-like macro expands to an expression, that expression doesn't need a semicolon after it. All uses have been verified to have their own semicolons. This was found using the following Coccinelle semantic patch: @r@ identifier i : script:ocaml() { String.lowercase_ascii i = i }; expression e; @@ *#define i(...) e; Signed-off-by: Julia Lawall Signed-off-by: Stafford Horne --- arch/openrisc/mm/tlb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/openrisc/mm/tlb.c b/arch/openrisc/mm/tlb.c index 3115f2e4f864..2ee00bf301a4 100644 --- a/arch/openrisc/mm/tlb.c +++ b/arch/openrisc/mm/tlb.c @@ -74,11 +74,11 @@ void local_flush_tlb_all(void) #define flush_dtlb_page_eir(addr) mtspr(SPR_DTLBEIR, addr) #define flush_dtlb_page_no_eir(addr) \ - mtspr_off(SPR_DTLBMR_BASE(0), DTLB_OFFSET(addr), 0); + mtspr_off(SPR_DTLBMR_BASE(0), DTLB_OFFSET(addr), 0) #define flush_itlb_page_eir(addr) mtspr(SPR_ITLBEIR, addr) #define flush_itlb_page_no_eir(addr) \ - mtspr_off(SPR_ITLBMR_BASE(0), ITLB_OFFSET(addr), 0); + mtspr_off(SPR_ITLBMR_BASE(0), ITLB_OFFSET(addr), 0) void local_flush_tlb_page(struct vm_area_struct *vma, unsigned long addr) { From 3d318fe4e99a627aa3c2980699e2e9f13ad18516 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 13 Aug 2026 10:09:47 +0300 Subject: [PATCH 177/641] drm/xe: tests: fix error message in xe_migrate_sanity_test() This is supposed to print the error code but there is a copy and paste bug so it prints "bo" instead of "err". Fixes: dd08ebf6c352 ("drm/xe: Introduce a new DRM driver for Intel GPUs") Signed-off-by: Dan Carpenter Link: https://patch.msgid.link/an1tu0z3T-qX1ogn@stanley.mountain Signed-off-by: Rodrigo Vivi (cherry picked from commit 28a4198c52a1468fc1b620a9837557ea1dc1766d) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/tests/xe_migrate.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpu/drm/xe/tests/xe_migrate.c b/drivers/gpu/drm/xe/tests/xe_migrate.c index 3c1be809be82..f10d9513747b 100644 --- a/drivers/gpu/drm/xe/tests/xe_migrate.c +++ b/drivers/gpu/drm/xe/tests/xe_migrate.c @@ -198,8 +198,7 @@ static void xe_migrate_sanity_test(struct xe_migrate *m, struct kunit *test, err = xe_bo_vmap(bo); if (err) { - KUNIT_FAIL(test, "Failed to vmap our pagetables: %li\n", - PTR_ERR(bo)); + KUNIT_FAIL(test, "Failed to vmap our pagetables: %d\n", err); return; } From fa487f56efba6344aef67d871688f0908fe08af3 Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Wed, 12 Aug 2026 12:17:54 +0000 Subject: [PATCH 178/641] f2fs: fix to reclaim space in f2fs_allocate_pinning_section() It needs to trigger checkpoint to free space reclaimed by f2fs_gc_range(), otherwise, fallocate() on pinfile will fail easily even there is slash space in conventional zone. [Testcase] nullblk_create.sh 512 2 1024 1024 mkfs.f2fs /dev/nullb0 -f -m mount /dev/nullb0 /mnt/f2fs/ touch /mnt/f2fs/pinfile f2fs_io pinfile set /mnt/f2fs/pinfile mkdir /mnt/f2fs/dir/ for((i=0;i<3934;i++)) do { dd if=/dev/zero of=/mnt/f2fs/dir/$i bs=1M count=1;} done sync for((i=0;i<3934;i+=2)) do { rm /mnt/f2fs/dir/$i;} done for((i=0;i<1950;i++)) do { rm /mnt/f2fs/dir/$i;} done sync f2fs_io fallocate 0 0 $((1024*1024*1024)) /mnt/f2fs/pinfile sync stat /mnt/f2fs/pinfile f2fs_io fiemap 0 $((1024*1024*1024)) /mnt/f2fs/pinfile [Before] fallocate failed: Resource temporarily unavailable File: /mnt/f2fs/pinfile Size: 109051904 Blocks: 213208 IO Block: 4096 regular file Device: 250,0 Inode: 4 Links: 1 Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2026-08-12 20:04:02.264000000 +0800 Modify: 2026-08-12 20:04:26.784000000 +0800 Change: 2026-08-12 20:04:26.784000000 +0800 Birth: - root@localhost:~# root@localhost:~# root@localhost:~# root@localhost:~# f2fs_io fiemap 0 $((1024*1024*1024)) /mnt/f2fs/pinfile Fiemap: offset = 0 len = 1073741824 logical addr. physical addr. length flags 0 0000000000000000 0000000002e00000 0000000000200000 00001000 1 0000000000200000 000000002dc00000 0000000000400000 00001000 2 0000000000600000 000000002e400000 0000000000600000 00001000 3 0000000000c00000 000000007a400000 0000000005c00000 00001001 [After] File: /mnt/f2fs/pinfile Size: 1073741824 Blocks: 2099216 IO Block: 4096 regular file Device: 250,0 Inode: 4 Links: 1 Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2026-08-12 19:47:49.428000000 +0800 Modify: 2026-08-12 19:49:06.808000000 +0800 Change: 2026-08-12 19:49:06.808000000 +0800 Birth: - Fiemap: offset = 0 len = 1073741824 logical addr. physical addr. length flags 0 0000000000000000 0000000002e00000 0000000000200000 00001000 1 0000000000200000 000000003aa00000 0000000000400000 00001000 2 0000000000600000 000000003b400000 0000000000200000 00001000 3 0000000000800000 000000007a200000 0000000005e00000 00001000 4 0000000006600000 0000000002800000 0000000000200000 00001000 5 0000000006800000 0000000003200000 0000000000400000 00001000 6 0000000006c00000 0000000003000000 0000000000200000 00001000 7 0000000006e00000 0000000003600000 0000000037200000 00001000 8 000000003e000000 000000003b200000 0000000000200000 00001000 9 000000003e200000 000000003a800000 0000000000200000 00001000 10 000000003e400000 000000003ae00000 0000000000400000 00001000 11 000000003e800000 000000003b600000 0000000001800000 00001001 Cc: stable@kernel.org Fixes: 9703d69d9d15 ("f2fs: support file pinning for zoned devices") Cc: Daeho Jeong Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/segment.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c index b81245ddd490..7bc779c8e935 100644 --- a/fs/f2fs/segment.c +++ b/fs/f2fs/segment.c @@ -3477,10 +3477,13 @@ retry: err = f2fs_gc_range(sbi, 0, sbi->first_seq_zone_segno - 1, true, ZONED_PIN_SEC_REQUIRED_COUNT); f2fs_up_write_trace(&sbi->gc_lock, &lc); - - gc_required = false; - if (!err) + if (err) + return err; + err = f2fs_sync_fs(sbi->sb, 1); + if (!err) { + gc_required = false; goto retry; + } } return err; From 5d49025a4e596c4c9ac0c519ef9f9a2c91396856 Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Wed, 12 Aug 2026 12:17:55 +0000 Subject: [PATCH 179/641] f2fs: fix to shrink gc_lock coverage in f2fs_gc_range() In f2fs_allocate_pinning_section(), we will hold gc_lock before calling f2fs_gc_range() to migrate section in conventional zone, we may suffer worse case because we may need to traverse and migrate multiple sections if we failed to move blocks in section due to lot of reasons: ENOMEM, fail to migrate block of pinfile, racing on i_gc_rwsem. To avoid hold gc_lock for long time to block checkpoint, let's hold the lock and only try to migrate one section. Cc: Daeho Jeong Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/f2fs.h | 2 +- fs/f2fs/file.c | 2 +- fs/f2fs/gc.c | 34 +++++++++++++++++++++++++--------- fs/f2fs/segment.c | 4 +--- 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index 7fd3a181af61..3e69b94e9465 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -4274,7 +4274,7 @@ int f2fs_gc(struct f2fs_sb_info *sbi, struct f2fs_gc_control *gc_control); void f2fs_build_gc_manager(struct f2fs_sb_info *sbi); int f2fs_gc_range(struct f2fs_sb_info *sbi, unsigned int start_seg, unsigned int end_seg, - bool dry_run, unsigned int dry_run_sections); + bool dry_run, unsigned int dry_run_sections, bool lock); void f2fs_reset_gc_victim_resource(struct f2fs_sb_info *sbi, unsigned int start, unsigned int end); int f2fs_resize_fs(struct file *filp, __u64 block_count); diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index 76316e537f65..a54b3ab52f1a 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -3828,7 +3828,7 @@ static int f2fs_ioc_reserve_dev_alias(struct file *filp) } /* do GC to move out valid blocks in the range all at once! */ - err = f2fs_gc_range(sbi, start, end, false, 0); + err = f2fs_gc_range(sbi, start, end, false, 0, false); if (err) { f2fs_unlock_op(sbi, &lc); goto out_gc_unlock; diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c index 51fc8c69eb25..2f97a7a98517 100644 --- a/fs/f2fs/gc.c +++ b/fs/f2fs/gc.c @@ -2150,8 +2150,9 @@ void f2fs_build_gc_manager(struct f2fs_sb_info *sbi) int f2fs_gc_range(struct f2fs_sb_info *sbi, unsigned int start_seg, unsigned int end_seg, - bool dry_run, unsigned int dry_run_sections) + bool dry_run, unsigned int dry_run_sections, bool lock) { + struct f2fs_lock_context lc; unsigned int segno; unsigned int gc_secs = dry_run_sections; @@ -2164,28 +2165,43 @@ int f2fs_gc_range(struct f2fs_sb_info *sbi, .ilist = LIST_HEAD_INIT(gc_list.ilist), .iroot = RADIX_TREE_INIT(gc_list.iroot, GFP_NOFS), }; + int err = 0; + + if (lock) + f2fs_down_write_trace(&sbi->gc_lock, &lc); /* * avoid migrating empty section, as it can be allocated by * log in parallel. */ if (!get_valid_blocks(sbi, segno, true)) - continue; + goto next; if (is_cursec(sbi, GET_SEC_FROM_SEG(sbi, segno))) - continue; + goto next; do_garbage_collect(sbi, segno, &gc_list, FG_GC, true, false); put_gc_inode(&gc_list); - if (!dry_run && get_valid_blocks(sbi, segno, true)) - return -EAGAIN; + if (!dry_run && get_valid_blocks(sbi, segno, true)) { + err = -EAGAIN; + goto next; + } if (dry_run && dry_run_sections && - !get_valid_blocks(sbi, segno, true) && --gc_secs == 0) - break; + !get_valid_blocks(sbi, segno, true)) { + --gc_secs; + goto next; + } if (fatal_signal_pending(current)) - return -ERESTARTSYS; + err = -ERESTARTSYS; +next: + if (lock) + f2fs_up_write_trace(&sbi->gc_lock, &lc); + if (err) + return err; + if (dry_run && dry_run_sections && !gc_secs) + return 0; } return 0; @@ -2231,7 +2247,7 @@ static int free_segment_range(struct f2fs_sb_info *sbi, } /* do GC to move out valid blocks in the range */ - err = f2fs_gc_range(sbi, start, end, dry_run, 0); + err = f2fs_gc_range(sbi, start, end, dry_run, 0, false); if (err || dry_run) goto out; diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c index 7bc779c8e935..caa46c866443 100644 --- a/fs/f2fs/segment.c +++ b/fs/f2fs/segment.c @@ -3473,10 +3473,8 @@ retry: f2fs_unlock_op(sbi, &lc); if (f2fs_sb_has_blkzoned(sbi) && err == -EAGAIN && gc_required) { - f2fs_down_write_trace(&sbi->gc_lock, &lc); err = f2fs_gc_range(sbi, 0, sbi->first_seq_zone_segno - 1, - true, ZONED_PIN_SEC_REQUIRED_COUNT); - f2fs_up_write_trace(&sbi->gc_lock, &lc); + true, ZONED_PIN_SEC_REQUIRED_COUNT, true); if (err) return err; err = f2fs_sync_fs(sbi->sb, 1); From 6da62bbde487d4678702ef004a911cd1c7a5c746 Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Wed, 12 Aug 2026 12:20:07 +0000 Subject: [PATCH 180/641] f2fs: cleanup w/ f2fs_need_rand_{blk, seg, seg_blk} No logic changes. Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/f2fs.h | 13 +++++++++++-- fs/f2fs/gc.c | 2 +- fs/f2fs/segment.c | 8 ++++---- fs/f2fs/super.c | 4 ++-- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index 3e69b94e9465..2c439ad4f36a 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -4142,10 +4142,19 @@ static inline struct inode *fio_inode(struct f2fs_io_info *fio) #define MIN_FRAGMENT_SIZE 1 #define MAX_FRAGMENT_SIZE 512 +static inline bool f2fs_need_rand_blk(struct f2fs_sb_info *sbi) +{ + return F2FS_OPTION(sbi).fs_mode == FS_MODE_FRAGMENT_BLK; +} + static inline bool f2fs_need_rand_seg(struct f2fs_sb_info *sbi) { - return F2FS_OPTION(sbi).fs_mode == FS_MODE_FRAGMENT_SEG || - F2FS_OPTION(sbi).fs_mode == FS_MODE_FRAGMENT_BLK; + return F2FS_OPTION(sbi).fs_mode == FS_MODE_FRAGMENT_SEG; +} + +static inline bool f2fs_need_rand_seg_blk(struct f2fs_sb_info *sbi) +{ + return f2fs_need_rand_blk(sbi) || f2fs_need_rand_seg(sbi); } /* diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c index 2f97a7a98517..e6758adc5da1 100644 --- a/fs/f2fs/gc.c +++ b/fs/f2fs/gc.c @@ -310,7 +310,7 @@ static void select_policy(struct f2fs_sb_info *sbi, int gc_type, p->max_search = sbi->max_victim_search; /* let's select beginning hot/small space first. */ - if (f2fs_need_rand_seg(sbi)) { + if (f2fs_need_rand_seg_blk(sbi)) { p->offset = get_random_u32_below(MAIN_SECS(sbi) * SEGS_PER_SEC(sbi)); SIT_I(sbi)->last_victim[p->gc_mode] = p->offset; diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c index caa46c866443..76c6f6fe503a 100644 --- a/fs/f2fs/segment.c +++ b/fs/f2fs/segment.c @@ -3052,7 +3052,7 @@ static unsigned int __get_next_segno(struct f2fs_sb_info *sbi, int type) sanity_check_seg_type(sbi, seg_type); if (__is_large_section(sbi)) { - if (f2fs_need_rand_seg(sbi)) { + if (f2fs_need_rand_seg_blk(sbi)) { unsigned int hint = GET_SEC_FROM_SEG(sbi, curseg->segno); if (GET_SEC_FROM_SEG(sbi, curseg->segno + 1) != hint) @@ -3061,7 +3061,7 @@ static unsigned int __get_next_segno(struct f2fs_sb_info *sbi, int type) GET_SEG_FROM_SEC(sbi, hint + 1) - 1); } return curseg->segno; - } else if (f2fs_need_rand_seg(sbi)) { + } else if (f2fs_need_rand_seg_blk(sbi)) { return get_random_u32_below(MAIN_SECS(sbi) * SEGS_PER_SEC(sbi)); } @@ -3117,7 +3117,7 @@ static int new_curseg(struct f2fs_sb_info *sbi, int type, bool new_sec) curseg->next_segno = segno; reset_curseg(sbi, type, 1); curseg->alloc_type = LFS; - if (F2FS_OPTION(sbi).fs_mode == FS_MODE_FRAGMENT_BLK) + if (f2fs_need_rand_blk(sbi)) curseg->fragment_remained_chunk = get_random_u32_inclusive(1, sbi->max_fragment_chunk); return 0; @@ -3936,7 +3936,7 @@ int f2fs_allocate_data_block(struct f2fs_sb_info *sbi, struct folio *folio, curseg->next_blkoff = f2fs_find_next_ssr_block(sbi, curseg); } else { curseg->next_blkoff++; - if (F2FS_OPTION(sbi).fs_mode == FS_MODE_FRAGMENT_BLK) + if (f2fs_need_rand_blk(sbi)) f2fs_randomize_chunk(sbi, curseg); } if (curseg->next_blkoff >= f2fs_usable_blks_in_seg(sbi, curseg->segno)) diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index fa4a7a7288d5..8dd656a7d3dc 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -2457,9 +2457,9 @@ static int f2fs_show_options(struct seq_file *seq, struct dentry *root) seq_puts(seq, "adaptive"); else if (F2FS_OPTION(sbi).fs_mode == FS_MODE_LFS) seq_puts(seq, "lfs"); - else if (F2FS_OPTION(sbi).fs_mode == FS_MODE_FRAGMENT_SEG) + else if (f2fs_need_rand_seg(sbi)) seq_puts(seq, "fragment:segment"); - else if (F2FS_OPTION(sbi).fs_mode == FS_MODE_FRAGMENT_BLK) + else if (f2fs_need_rand_blk(sbi)) seq_puts(seq, "fragment:block"); seq_printf(seq, ",active_logs=%u", F2FS_OPTION(sbi).active_logs); if (test_opt(sbi, RESERVE_ROOT) || test_opt(sbi, RESERVE_NODE)) From 0a1703eba23707e3b2edfa2a2329352e7abc0ffa Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Wed, 12 Aug 2026 12:20:08 +0000 Subject: [PATCH 181/641] f2fs: fix to avoid pinfile fragment on fragment:{block, segment} mode pinfile fallocate() conflicts w/ mode=fragment:{block,segment} mount option, result in fragment blocks in pinfile, it violate semantics of pinfile introduced in commit f5a53edcf01e ("f2fs: support aligned pinned file"). mkfs.f2fs -f /dev/vdb mount -t f2fs -o mode=fragment:block /dev/vdb /mnt/f2fs/ dd if=/dev/zero of=/mnt/f2fs/file bs=1M count=3900 sync touch /mnt/f2fs/pinfile f2fs_io pinfile set /mnt/f2fs/pinfile f2fs_io fallocate 0 0 $((1024*1024*16)) /mnt/f2fs/pinfile sync f2fs_io fiemap 0 $((1024*1024*16)) /mnt/f2fs/pinfile [Before] fallocate failed: No space left on device Fiemap: offset = 0 len = 16777216 logical addr. physical addr. length flags 0 0000000000000000 00000000d7200000 0000000000004000 00001000 1 0000000000004000 00000000d7207000 0000000000001000 00001000 2 0000000000005000 00000000d720c000 0000000000002000 00001000 3 0000000000007000 00000000d7211000 0000000000001000 00001000 4 0000000000008000 00000000d7214000 0000000000001000 00001000 5 0000000000009000 00000000d7218000 0000000000001000 00001000 6 000000000000a000 00000000d721d000 0000000000001000 00001000 7 000000000000b000 00000000d721f000 0000000000004000 00001000 ... 96 00000000000f1000 00000000d73e9000 0000000000004000 00001000 97 00000000000f5000 00000000d73f1000 0000000000003000 00001000 98 00000000000f8000 00000000d73f5000 0000000000004000 00001000 99 00000000000fc000 00000000d73fa000 0000000000001000 00001000 100 00000000000fd000 00000000d73ff000 0000000000001000 00001001 [After] fallocated a file: i_size=16777216, i_blocks=32808 Fiemap: offset = 0 len = 16777216 logical addr. physical addr. length flags 0 0000000000000000 0000000018a00000 0000000000400000 00001000 1 0000000000400000 0000000019000000 0000000000400000 00001000 2 0000000000800000 0000000032400000 0000000000200000 00001000 3 0000000000a00000 0000000038000000 0000000000200000 00001000 4 0000000000c00000 0000000039c00000 0000000000200000 00001000 5 0000000000e00000 0000000044c00000 0000000000200000 00001001 Let's ignore mode=fragment:{block,segment} mount option while fallocate() on pinfile. Fixes: 6691d940b0e0 ("f2fs: introduce fragment allocation mode mount option") Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/f2fs.h | 15 +++++++++++---- fs/f2fs/gc.c | 2 +- fs/f2fs/segment.c | 8 ++++---- fs/f2fs/super.c | 4 ++-- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index 2c439ad4f36a..c925b9207a37 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -4142,19 +4142,26 @@ static inline struct inode *fio_inode(struct f2fs_io_info *fio) #define MIN_FRAGMENT_SIZE 1 #define MAX_FRAGMENT_SIZE 512 -static inline bool f2fs_need_rand_blk(struct f2fs_sb_info *sbi) +static inline bool f2fs_need_rand_blk(struct f2fs_sb_info *sbi, + enum log_type type) { + if (type == CURSEG_COLD_DATA_PINNED) + return false; return F2FS_OPTION(sbi).fs_mode == FS_MODE_FRAGMENT_BLK; } -static inline bool f2fs_need_rand_seg(struct f2fs_sb_info *sbi) +static inline bool f2fs_need_rand_seg(struct f2fs_sb_info *sbi, + enum log_type type) { + if (type == CURSEG_COLD_DATA_PINNED) + return false; return F2FS_OPTION(sbi).fs_mode == FS_MODE_FRAGMENT_SEG; } -static inline bool f2fs_need_rand_seg_blk(struct f2fs_sb_info *sbi) +static inline bool f2fs_need_rand_seg_blk(struct f2fs_sb_info *sbi, + enum log_type type) { - return f2fs_need_rand_blk(sbi) || f2fs_need_rand_seg(sbi); + return f2fs_need_rand_blk(sbi, type) || f2fs_need_rand_seg(sbi, type); } /* diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c index e6758adc5da1..c4da2f31805b 100644 --- a/fs/f2fs/gc.c +++ b/fs/f2fs/gc.c @@ -310,7 +310,7 @@ static void select_policy(struct f2fs_sb_info *sbi, int gc_type, p->max_search = sbi->max_victim_search; /* let's select beginning hot/small space first. */ - if (f2fs_need_rand_seg_blk(sbi)) { + if (f2fs_need_rand_seg_blk(sbi, type)) { p->offset = get_random_u32_below(MAIN_SECS(sbi) * SEGS_PER_SEC(sbi)); SIT_I(sbi)->last_victim[p->gc_mode] = p->offset; diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c index 76c6f6fe503a..56decf9c691c 100644 --- a/fs/f2fs/segment.c +++ b/fs/f2fs/segment.c @@ -3052,7 +3052,7 @@ static unsigned int __get_next_segno(struct f2fs_sb_info *sbi, int type) sanity_check_seg_type(sbi, seg_type); if (__is_large_section(sbi)) { - if (f2fs_need_rand_seg_blk(sbi)) { + if (f2fs_need_rand_seg_blk(sbi, type)) { unsigned int hint = GET_SEC_FROM_SEG(sbi, curseg->segno); if (GET_SEC_FROM_SEG(sbi, curseg->segno + 1) != hint) @@ -3061,7 +3061,7 @@ static unsigned int __get_next_segno(struct f2fs_sb_info *sbi, int type) GET_SEG_FROM_SEC(sbi, hint + 1) - 1); } return curseg->segno; - } else if (f2fs_need_rand_seg_blk(sbi)) { + } else if (f2fs_need_rand_seg_blk(sbi, type)) { return get_random_u32_below(MAIN_SECS(sbi) * SEGS_PER_SEC(sbi)); } @@ -3117,7 +3117,7 @@ static int new_curseg(struct f2fs_sb_info *sbi, int type, bool new_sec) curseg->next_segno = segno; reset_curseg(sbi, type, 1); curseg->alloc_type = LFS; - if (f2fs_need_rand_blk(sbi)) + if (f2fs_need_rand_blk(sbi, type)) curseg->fragment_remained_chunk = get_random_u32_inclusive(1, sbi->max_fragment_chunk); return 0; @@ -3936,7 +3936,7 @@ int f2fs_allocate_data_block(struct f2fs_sb_info *sbi, struct folio *folio, curseg->next_blkoff = f2fs_find_next_ssr_block(sbi, curseg); } else { curseg->next_blkoff++; - if (f2fs_need_rand_blk(sbi)) + if (f2fs_need_rand_blk(sbi, type)) f2fs_randomize_chunk(sbi, curseg); } if (curseg->next_blkoff >= f2fs_usable_blks_in_seg(sbi, curseg->segno)) diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index 8dd656a7d3dc..0c8f60b7242f 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -2457,9 +2457,9 @@ static int f2fs_show_options(struct seq_file *seq, struct dentry *root) seq_puts(seq, "adaptive"); else if (F2FS_OPTION(sbi).fs_mode == FS_MODE_LFS) seq_puts(seq, "lfs"); - else if (f2fs_need_rand_seg(sbi)) + else if (f2fs_need_rand_seg(sbi, NO_CHECK_TYPE)) seq_puts(seq, "fragment:segment"); - else if (f2fs_need_rand_blk(sbi)) + else if (f2fs_need_rand_blk(sbi, NO_CHECK_TYPE)) seq_puts(seq, "fragment:block"); seq_printf(seq, ",active_logs=%u", F2FS_OPTION(sbi).active_logs); if (test_opt(sbi, RESERVE_ROOT) || test_opt(sbi, RESERVE_NODE)) From dd0ae9a2e19a24c532c9aa80a44f572b4907bcdf Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 18 Aug 2026 01:46:29 +0000 Subject: [PATCH 182/641] ASoC: codec: max98926: fixup invert setup mask invert might have both WCI/BCI mask, but regmap_update_bits() masks BCI only. It needs both mask. Fixup it. static int max98926_dai_set_fmt(...) { ... switch (fmt & SND_SOC_DAIFMT_INV_MASK) { case SND_SOC_DAIFMT_NB_NF: break; case SND_SOC_DAIFMT_NB_IF: => invert = MAX98926_DAI_WCI_MASK; break; ^^^^^^^^^^^^^^^^^^^^^ case SND_SOC_DAIFMT_IB_NF: => invert = MAX98926_DAI_BCI_MASK; break; ^^^^^^^^^^^^^^^^^^^^^ case SND_SOC_DAIFMT_IB_IF: => invert = MAX98926_DAI_BCI_MASK | MAX98926_DAI_WCI_MASK; break; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ default: ... } ... regmap_update_bits(max98926->regmap, MAX98926_FORMAT, MAX98926_DAI_BCI_MASK, invert); ... ^^^^^^^^^^^^^^^^^^^^^ } Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87wltomc97.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/max98926.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/max98926.c b/sound/soc/codecs/max98926.c index 5305e1f9d97f..51a30c182167 100644 --- a/sound/soc/codecs/max98926.c +++ b/sound/soc/codecs/max98926.c @@ -360,7 +360,7 @@ static int max98926_dai_set_fmt(struct snd_soc_dai *codec_dai, regmap_write(max98926->regmap, MAX98926_FORMAT, MAX98926_DAI_DLY_MASK); regmap_update_bits(max98926->regmap, MAX98926_FORMAT, - MAX98926_DAI_BCI_MASK, invert); + MAX98926_DAI_BCI_MASK | MAX98926_DAI_WCI_MASK, invert); return 0; } From 046173b98de316211b81d9909cde81ca582604a8 Mon Sep 17 00:00:00 2001 From: Jack Yu Date: Tue, 18 Aug 2026 10:30:18 +0800 Subject: [PATCH 183/641] ASoC: sdw_utils: fix double put_device() on aggregated amps In aggregation mode with two identical amplifiers, a stress test that repeatedly triggers card unbind/rebind can hit a NULL pointer dereference during the exit path. ctx->amp_dev1 / ctx->amp_dev2 are single shared resources, so the release must be safe against being called more than once. Clear each pointer after put_device() so a second invocation becomes a no-op, this could address NULL pointer dereference issue. Signed-off-by: Jack Yu Link: https://patch.msgid.link/20260818023018.2564212-1-jack.yu@realtek.com Signed-off-by: Mark Brown --- sound/soc/sdw_utils/soc_sdw_rt_amp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/sdw_utils/soc_sdw_rt_amp.c b/sound/soc/sdw_utils/soc_sdw_rt_amp.c index 4e9b08cb653d..81d2cbac0ea3 100644 --- a/sound/soc/sdw_utils/soc_sdw_rt_amp.c +++ b/sound/soc/sdw_utils/soc_sdw_rt_amp.c @@ -252,11 +252,13 @@ int asoc_sdw_rt_amp_exit(struct snd_soc_card *card, struct snd_soc_dai_link *dai if (ctx->amp_dev1) { device_remove_software_node(ctx->amp_dev1); put_device(ctx->amp_dev1); + ctx->amp_dev1 = NULL; } if (ctx->amp_dev2) { device_remove_software_node(ctx->amp_dev2); put_device(ctx->amp_dev2); + ctx->amp_dev2 = NULL; } return 0; From ed98f8e27a93dae225a99ac41ad48f967fd19842 Mon Sep 17 00:00:00 2001 From: Michal Clapinski Date: Mon, 10 Aug 2026 09:48:51 +0200 Subject: [PATCH 184/641] ipmi:si: Add async init to ipmi_si Added a new config option to allow offloading individual calls to try_smi_init() using workqueue. Saves 100ms on my system. Signed-off-by: Michal Clapinski Message-ID: <20260810074851.306979-1-mclapinski@google.com> Signed-off-by: Corey Minyard --- drivers/char/ipmi/Kconfig | 9 ++++ drivers/char/ipmi/ipmi_si_intf.c | 79 +++++++++++++++++++++++++------- 2 files changed, 72 insertions(+), 16 deletions(-) diff --git a/drivers/char/ipmi/Kconfig b/drivers/char/ipmi/Kconfig index 669f76000197..538a7d3c65bf 100644 --- a/drivers/char/ipmi/Kconfig +++ b/drivers/char/ipmi/Kconfig @@ -67,6 +67,15 @@ config IPMI_SI Currently, only KCS and SMIC are supported. If you are using IPMI, you should probably say "y" here. +config IPMI_SI_ASYNC_INIT + bool 'Asynchronous initialization of IPMI System Interface' + depends on IPMI_SI + default n + help + Offloads individual SMI inits. It speeds up the boot time. + It also introduces a very small risk that something else might fail + if it depends on synchronous IPMI init. + config IPMI_SSIF tristate 'IPMI SMBus handler (SSIF)' depends on I2C diff --git a/drivers/char/ipmi/ipmi_si_intf.c b/drivers/char/ipmi/ipmi_si_intf.c index 9a9d12be9bf7..6b95a7581328 100644 --- a/drivers/char/ipmi/ipmi_si_intf.c +++ b/drivers/char/ipmi/ipmi_si_intf.c @@ -39,6 +39,7 @@ #include #include #include +#include #include "ipmi_si.h" #include "ipmi_si_sm.h" #include @@ -252,6 +253,8 @@ struct smi_info { struct task_struct *thread; + struct work_struct init_work; + struct list_head link; }; @@ -272,6 +275,7 @@ static bool unload_when_empty = true; static int try_smi_init(struct smi_info *smi); static void cleanup_one_si(struct smi_info *smi_info); static void cleanup_ipmi_si(void); +static void smi_init_work_fn(struct work_struct *work); #ifdef DEBUG_TIMING void debug_timestamp(struct smi_info *smi_info, char *msg) @@ -1970,6 +1974,7 @@ int ipmi_si_add_smi(struct si_sm_io *io) if (!new_smi) return -ENOMEM; spin_lock_init(&new_smi->si_lock); + INIT_WORK(&new_smi->init_work, smi_init_work_fn); new_smi->io = *io; @@ -1982,7 +1987,12 @@ int ipmi_si_add_smi(struct si_sm_io *io) dev_info(dup->io.dev, "Removing SMBIOS-specified %s state machine in favor of ACPI\n", si_to_str[new_smi->io.si_info->type]); + list_del(&dup->link); + mutex_unlock(&smi_infos_lock); + cleanup_one_si(dup); + + mutex_lock(&smi_infos_lock); } else { dev_info(new_smi->io.dev, "%s-specified %s state machine: duplicate\n", @@ -2000,8 +2010,12 @@ int ipmi_si_add_smi(struct si_sm_io *io) list_add_tail(&new_smi->link, &smi_infos); - if (initialized) - rv = try_smi_init(new_smi); + if (initialized) { + if (IS_ENABLED(CONFIG_IPMI_SI_ASYNC_INIT)) + queue_work(system_dfl_wq, &new_smi->init_work); + else + rv = try_smi_init(new_smi); + } out_err: mutex_unlock(&smi_infos_lock); return rv; @@ -2174,6 +2188,15 @@ static bool __init ipmi_smi_info_same(struct smi_info *e1, struct smi_info *e2) e1->io.addr_data == e2->io.addr_data); } +static void smi_init_work_fn(struct work_struct *work) +{ + struct smi_info *smi = container_of(work, struct smi_info, init_work); + + mutex_lock(&smi_infos_lock); + try_smi_init(smi); + mutex_unlock(&smi_infos_lock); +} + static int __init init_ipmi_si(void) { struct smi_info *e, *e2; @@ -2219,8 +2242,12 @@ static int __init init_ipmi_si(void) break; } } - if (!dup) - try_smi_init(e); + if (!dup) { + if (IS_ENABLED(CONFIG_IPMI_SI_ASYNC_INIT)) + queue_work(system_unbound_wq, &e->init_work); + else + try_smi_init(e); + } } /* @@ -2253,8 +2280,12 @@ static int __init init_ipmi_si(void) break; } } - if (!dup) - try_smi_init(e); + if (!dup) { + if (IS_ENABLED(CONFIG_IPMI_SI_ASYNC_INIT)) + queue_work(system_unbound_wq, &e->init_work); + else + try_smi_init(e); + } } initialized = true; @@ -2344,31 +2375,36 @@ static void shutdown_smi(void *send_info) } /* - * Must be called with smi_infos_lock held, to serialize the - * smi_info->intf check. + * Must be called with smi_info unlinked from smi_infos and smi_infos_lock released. */ static void cleanup_one_si(struct smi_info *smi_info) { if (!smi_info) return; - list_del(&smi_info->link); + if (IS_ENABLED(CONFIG_IPMI_SI_ASYNC_INIT)) + cancel_work_sync(&smi_info->init_work); + ipmi_unregister_smi(smi_info->intf); kfree(smi_info); } void ipmi_si_remove_by_dev(struct device *dev) { - struct smi_info *e; + struct smi_info *e = NULL, *tmp; mutex_lock(&smi_infos_lock); - list_for_each_entry(e, &smi_infos, link) { - if (e->io.dev == dev) { - cleanup_one_si(e); + list_for_each_entry(tmp, &smi_infos, link) { + if (tmp->io.dev == dev) { + e = tmp; + list_del(&e->link); break; } } mutex_unlock(&smi_infos_lock); + + if (e) + cleanup_one_si(e); } struct device *ipmi_si_remove_by_data(int addr_space, enum si_type si_type, @@ -2377,6 +2413,7 @@ struct device *ipmi_si_remove_by_data(int addr_space, enum si_type si_type, /* remove */ struct smi_info *e, *tmp_e; struct device *dev = NULL; + LIST_HEAD(to_clean); mutex_lock(&smi_infos_lock); list_for_each_entry_safe(e, tmp_e, &smi_infos, link) { @@ -2386,17 +2423,23 @@ struct device *ipmi_si_remove_by_data(int addr_space, enum si_type si_type, continue; if (e->io.addr_data == addr) { dev = get_device(e->io.dev); - cleanup_one_si(e); + list_move_tail(&e->link, &to_clean); } } mutex_unlock(&smi_infos_lock); + list_for_each_entry_safe(e, tmp_e, &to_clean, link) { + list_del(&e->link); + cleanup_one_si(e); + } + return dev; } static void cleanup_ipmi_si(void) { struct smi_info *e, *tmp_e; + LIST_HEAD(to_clean); if (!initialized) return; @@ -2410,10 +2453,14 @@ static void cleanup_ipmi_si(void) ipmi_si_platform_shutdown(); mutex_lock(&smi_infos_lock); - list_for_each_entry_safe(e, tmp_e, &smi_infos, link) - cleanup_one_si(e); + list_splice_init(&smi_infos, &to_clean); mutex_unlock(&smi_infos_lock); + list_for_each_entry_safe(e, tmp_e, &to_clean, link) { + list_del(&e->link); + cleanup_one_si(e); + } + ipmi_si_hardcode_exit(); ipmi_si_hotmod_exit(); } From 5f01293930d18f8473681b378bb483b7087fc0dd Mon Sep 17 00:00:00 2001 From: Changwoo Min Date: Wed, 19 Aug 2026 01:04:29 +0900 Subject: [PATCH 185/641] sched_ext: Allow ops.cgroup_set_bandwidth() to be sleepable ops.cgroup_set_bandwidth() is delivered from scx_group_set_bandwidth(), which runs from the cpu.max cgroup interface write path (tg_set_bandwidth()) in process context. scx_group_set_bandwidth() holds percpu_down_read(&scx_cgroup_ops_rwsem), whose read side may sleep. The call site is therefore sleepable, like ops.cgroup_init(). bpf_scx_check_member() rejects a sleepable program on any member not on its allow-list, so a BPF scheduler cannot allocate -- which is sleepable -- when a cgroup gains a cpu.max limit at runtime; it must instead pre-reserve memory for a callback that cannot allocate. Add cgroup_set_bandwidth() to the allow-list so the callback can allocate on demand, and document that it may block. A scheduler must decide at load time whether to mark the callback sleepable, but the allow-list entry is a verifier property with no symbol to probe. Add a compatibility marker whose presence in the kernel's BTF lets userspace detect this support: DEFINE_SCX_COMPAT_MARKER() emits an empty, callerless function, here scx_compat_marker_cgroup_set_bandwidth_may_sleep(). It is __used __retain so neither the compiler nor the linker (under CONFIG_LD_DEAD_CODE_DATA_ELIMINATION) drops it. The markers share the scx_compat_marker_ prefix and are collected near the end of ext.c so more can be added as further capabilities appear. Signed-off-by: Changwoo Min Signed-off-by: Tejun Heo --- kernel/sched/ext/ext.c | 14 ++++++++++++++ kernel/sched/ext/internal.h | 23 ++++++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 10af28a9f2c0..b646711a45fe 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -8079,6 +8079,7 @@ static int bpf_scx_check_member(const struct btf_type *t, case offsetof(struct sched_ext_ops, cgroup_init): case offsetof(struct sched_ext_ops, cgroup_exit): case offsetof(struct sched_ext_ops, cgroup_prep_move): + case offsetof(struct sched_ext_ops, cgroup_set_bandwidth): #endif case offsetof(struct sched_ext_ops, cpu_online): case offsetof(struct sched_ext_ops, cpu_offline): @@ -11041,3 +11042,16 @@ static int __init scx_init(void) return 0; } __initcall(scx_init); + +/* + * Compatibility markers for userspace. Existence of a marker function + * represents that the kernel supports that sched-ext feature. + */ + +/* + * scx_compat_marker_cgroup_set_bandwidth_may_sleep: advertises that + * ops.cgroup_set_bandwidth() may be implemented as a sleepable callback. + */ +#ifdef CONFIG_EXT_GROUP_SCHED +DEFINE_SCX_COMPAT_MARKER(cgroup_set_bandwidth_may_sleep); +#endif /* CONFIG_EXT_GROUP_SCHED */ diff --git a/kernel/sched/ext/internal.h b/kernel/sched/ext/internal.h index 27bbf5e04d90..53e136a47924 100644 --- a/kernel/sched/ext/internal.h +++ b/kernel/sched/ext/internal.h @@ -753,7 +753,7 @@ struct sched_ext_ops { * @burst_us: bandwidth control burst * * Update @cgrp's bandwidth control parameters. This is from the cpu.max - * cgroup interface. + * cgroup interface. This operation may block. * * @quota_us / @period_us determines the CPU bandwidth @cgrp is entitled * to. For example, if @period_us is 1_000_000 and @quota_us is @@ -2001,6 +2001,27 @@ struct scx_bstr_buf { char line[SCX_EXIT_MSG_LEN]; }; +/* Internal helper for DEFINE_SCX_COMPAT_MARKER(). */ +#define DECLARE_SCX_COMPAT_MARKER(func) \ + extern void scx_compat_marker_##func(void) + +/** + * DEFINE_SCX_COMPAT_MARKER() - define a userspace capability marker + * @func: marker suffix; the defined symbol is scx_compat_marker_@func + * + * Emit an empty, callerless function that is retained in the kernel's BTF. + * Its presence is part of the kernel<->userspace contract: userspace probes + * scx_compat_marker_@func (e.g. via BTF) to detect that this kernel supports + * the corresponding feature. + * + * The leading declaration suppresses the missing-prototype warning; the + * trailing declaration consumes the semicolon at the use site. + */ +#define DEFINE_SCX_COMPAT_MARKER(func) \ + DECLARE_SCX_COMPAT_MARKER(func); \ + __used __retain void scx_compat_marker_##func(void) {} \ + DECLARE_SCX_COMPAT_MARKER(func) + extern struct scx_sched __rcu *scx_root; DECLARE_PER_CPU(struct rq *, scx_locked_rq_state); From c41ac86802fc0a22a886915a43bcad2e8d482b02 Mon Sep 17 00:00:00 2001 From: Prasad Kumpatla Date: Tue, 18 Aug 2026 14:16:52 +0530 Subject: [PATCH 186/641] ASoC: dt-bindings: qcom,q6dsp-lpass-ports: Rename QAIF clock IDs The QAIF clock IDs were introduced with a LAPSS prefix typo. Rename them to use the existing LPASS naming convention and update all corresponding in-tree users. Since these IDs are new, rename them directly instead of keeping backward-compatible aliases. Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260804070307.117119-1-prasad.kumpatla@oss.qualcomm.com Signed-off-by: Prasad Kumpatla Link: https://patch.msgid.link/20260818084655.3240284-2-prasad.kumpatla@oss.qualcomm.com Signed-off-by: Mark Brown --- .../sound/qcom,q6dsp-lpass-ports.h | 56 +++++++++---------- sound/soc/qcom/qdsp6/q6prm-clocks.c | 56 +++++++++---------- sound/soc/qcom/qdsp6/q6prm.h | 56 +++++++++---------- 3 files changed, 84 insertions(+), 84 deletions(-) diff --git a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h index 2e879a3c09ae..3a99703dbc4a 100644 --- a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h +++ b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h @@ -237,34 +237,34 @@ /* Clock ID for RX CORE MCLK2 2X MCLK */ #define LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK 70 -#define LAPSS_CLK_ID_QAIF_IF0_IBIT 71 -#define LAPSS_CLK_ID_QAIF_IF0_EBIT 72 -#define LAPSS_CLK_ID_QAIF_IF1_IBIT 73 -#define LAPSS_CLK_ID_QAIF_IF1_EBIT 74 -#define LAPSS_CLK_ID_QAIF_IF2_IBIT 75 -#define LAPSS_CLK_ID_QAIF_IF2_EBIT 76 -#define LAPSS_CLK_ID_QAIF_IF3_IBIT 77 -#define LAPSS_CLK_ID_QAIF_IF3_EBIT 78 -#define LAPSS_CLK_ID_QAIF_IF4_IBIT 79 -#define LAPSS_CLK_ID_QAIF_IF4_EBIT 80 -#define LAPSS_CLK_ID_QAIF_IF5_IBIT 81 -#define LAPSS_CLK_ID_QAIF_IF5_EBIT 82 -#define LAPSS_CLK_ID_QAIF_IF6_IBIT 83 -#define LAPSS_CLK_ID_QAIF_IF6_EBIT 84 -#define LAPSS_CLK_ID_QAIF_IF7_IBIT 85 -#define LAPSS_CLK_ID_QAIF_IF7_EBIT 86 -#define LAPSS_CLK_ID_QAIF_IF8_IBIT 87 -#define LAPSS_CLK_ID_QAIF_IF8_EBIT 88 -#define LAPSS_CLK_ID_QAIF_IF9_IBIT 89 -#define LAPSS_CLK_ID_QAIF_IF9_EBIT 90 -#define LAPSS_CLK_ID_QAIF_IF10_IBIT 91 -#define LAPSS_CLK_ID_QAIF_IF10_EBIT 92 -#define LAPSS_CLK_ID_QAIF_IF11_IBIT 93 -#define LAPSS_CLK_ID_QAIF_IF11_EBIT 94 -#define LAPSS_CLK_ID_QAIF_IF12_IBIT 95 -#define LAPSS_CLK_ID_QAIF_IF12_EBIT 96 -#define LAPSS_CLK_ID_VA_QAIF_IF0_IBIT 97 -#define LAPSS_CLK_ID_VA_QAIF_IF0_EBIT 98 +#define LPASS_CLK_ID_QAIF_IF0_IBIT 71 +#define LPASS_CLK_ID_QAIF_IF0_EBIT 72 +#define LPASS_CLK_ID_QAIF_IF1_IBIT 73 +#define LPASS_CLK_ID_QAIF_IF1_EBIT 74 +#define LPASS_CLK_ID_QAIF_IF2_IBIT 75 +#define LPASS_CLK_ID_QAIF_IF2_EBIT 76 +#define LPASS_CLK_ID_QAIF_IF3_IBIT 77 +#define LPASS_CLK_ID_QAIF_IF3_EBIT 78 +#define LPASS_CLK_ID_QAIF_IF4_IBIT 79 +#define LPASS_CLK_ID_QAIF_IF4_EBIT 80 +#define LPASS_CLK_ID_QAIF_IF5_IBIT 81 +#define LPASS_CLK_ID_QAIF_IF5_EBIT 82 +#define LPASS_CLK_ID_QAIF_IF6_IBIT 83 +#define LPASS_CLK_ID_QAIF_IF6_EBIT 84 +#define LPASS_CLK_ID_QAIF_IF7_IBIT 85 +#define LPASS_CLK_ID_QAIF_IF7_EBIT 86 +#define LPASS_CLK_ID_QAIF_IF8_IBIT 87 +#define LPASS_CLK_ID_QAIF_IF8_EBIT 88 +#define LPASS_CLK_ID_QAIF_IF9_IBIT 89 +#define LPASS_CLK_ID_QAIF_IF9_EBIT 90 +#define LPASS_CLK_ID_QAIF_IF10_IBIT 91 +#define LPASS_CLK_ID_QAIF_IF10_EBIT 92 +#define LPASS_CLK_ID_QAIF_IF11_IBIT 93 +#define LPASS_CLK_ID_QAIF_IF11_EBIT 94 +#define LPASS_CLK_ID_QAIF_IF12_IBIT 95 +#define LPASS_CLK_ID_QAIF_IF12_EBIT 96 +#define LPASS_CLK_ID_VA_QAIF_IF0_IBIT 97 +#define LPASS_CLK_ID_VA_QAIF_IF0_EBIT 98 #define LPASS_HW_AVTIMER_VOTE 101 #define LPASS_HW_MACRO_VOTE 102 diff --git a/sound/soc/qcom/qdsp6/q6prm-clocks.c b/sound/soc/qcom/qdsp6/q6prm-clocks.c index f613e2aee75e..b4f9abbe02cb 100644 --- a/sound/soc/qcom/qdsp6/q6prm-clocks.c +++ b/sound/soc/qcom/qdsp6/q6prm-clocks.c @@ -64,34 +64,34 @@ static const struct q6dsp_clk_init q6prm_clks[] = { Q6PRM_CLK(LPASS_CLK_ID_WSA2_CORE_TX_MCLK), Q6PRM_CLK(LPASS_CLK_ID_WSA2_CORE_TX_2X_MCLK), Q6PRM_CLK(LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF0_IBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF0_EBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF1_IBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF1_EBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF2_IBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF2_EBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF3_IBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF3_EBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF4_IBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF4_EBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF5_IBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF5_EBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF6_IBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF6_EBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF7_IBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF7_EBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF8_IBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF8_EBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF9_IBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF9_EBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF10_IBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF10_EBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF11_IBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF11_EBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF12_IBIT), - Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF12_EBIT), - Q6PRM_CLK(LAPSS_CLK_ID_VA_QAIF_IF0_IBIT), - Q6PRM_CLK(LAPSS_CLK_ID_VA_QAIF_IF0_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF0_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF0_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF1_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF1_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF2_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF2_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF3_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF3_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF4_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF4_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF5_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF5_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF6_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF6_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF7_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF7_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF8_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF8_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF9_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF9_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF10_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF10_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF11_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF11_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF12_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF12_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_VA_QAIF_IF0_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_VA_QAIF_IF0_EBIT), Q6DSP_VOTE_CLK(LPASS_HW_MACRO_VOTE, Q6PRM_HW_CORE_ID_LPASS, "LPASS_HW_MACRO"), Q6DSP_VOTE_CLK(LPASS_HW_DCODEC_VOTE, Q6PRM_HW_CORE_ID_DCODEC, diff --git a/sound/soc/qcom/qdsp6/q6prm.h b/sound/soc/qcom/qdsp6/q6prm.h index cca77cd92bc1..cf619ef6561a 100644 --- a/sound/soc/qcom/qdsp6/q6prm.h +++ b/sound/soc/qcom/qdsp6/q6prm.h @@ -97,34 +97,34 @@ /* Clock ID for RX CORE MCLK2 2X MCLK */ #define Q6PRM_LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK 0x318 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF0_IBIT 0x500 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF0_EBIT 0x501 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF1_IBIT 0x502 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF1_EBIT 0x503 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF2_IBIT 0x504 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF2_EBIT 0x505 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF3_IBIT 0x506 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF3_EBIT 0x507 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF4_IBIT 0x508 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF4_EBIT 0x509 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF5_IBIT 0x50A -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF5_EBIT 0x50B -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF6_IBIT 0x50C -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF6_EBIT 0x50D -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF7_IBIT 0x50E -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF7_EBIT 0x50F -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF8_IBIT 0x510 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF8_EBIT 0x511 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF9_IBIT 0x512 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF9_EBIT 0x513 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF10_IBIT 0x514 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF10_EBIT 0x515 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF11_IBIT 0x516 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF11_EBIT 0x517 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF12_IBIT 0x518 -#define Q6PRM_LAPSS_CLK_ID_QAIF_IF12_EBIT 0x519 -#define Q6PRM_LAPSS_CLK_ID_VA_QAIF_IF0_IBIT 0x550 -#define Q6PRM_LAPSS_CLK_ID_VA_QAIF_IF0_EBIT 0x551 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF0_IBIT 0x500 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF0_EBIT 0x501 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF1_IBIT 0x502 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF1_EBIT 0x503 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF2_IBIT 0x504 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF2_EBIT 0x505 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF3_IBIT 0x506 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF3_EBIT 0x507 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF4_IBIT 0x508 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF4_EBIT 0x509 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF5_IBIT 0x50A +#define Q6PRM_LPASS_CLK_ID_QAIF_IF5_EBIT 0x50B +#define Q6PRM_LPASS_CLK_ID_QAIF_IF6_IBIT 0x50C +#define Q6PRM_LPASS_CLK_ID_QAIF_IF6_EBIT 0x50D +#define Q6PRM_LPASS_CLK_ID_QAIF_IF7_IBIT 0x50E +#define Q6PRM_LPASS_CLK_ID_QAIF_IF7_EBIT 0x50F +#define Q6PRM_LPASS_CLK_ID_QAIF_IF8_IBIT 0x510 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF8_EBIT 0x511 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF9_IBIT 0x512 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF9_EBIT 0x513 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF10_IBIT 0x514 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF10_EBIT 0x515 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF11_IBIT 0x516 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF11_EBIT 0x517 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF12_IBIT 0x518 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF12_EBIT 0x519 +#define Q6PRM_LPASS_CLK_ID_VA_QAIF_IF0_IBIT 0x550 +#define Q6PRM_LPASS_CLK_ID_VA_QAIF_IF0_EBIT 0x551 #define Q6PRM_LPASS_CLK_SRC_INTERNAL 1 #define Q6PRM_LPASS_CLK_ROOT_DEFAULT 0 From 9c9fb79f9769ba9042086817ee57aa4eef8b8a97 Mon Sep 17 00:00:00 2001 From: Prasad Kumpatla Date: Tue, 18 Aug 2026 14:16:53 +0530 Subject: [PATCH 187/641] ASoC: qcom: qdsp6: Zero-initialize AudioReach module config q6apm_dai_prepare() and q6apm_dai_compr_set_params() build an AudioReach module config on the stack and populate only the fields needed for the stream type. Audio IF media-format setup treats zero slot_mask, nslots_per_frame and slot_width as a request to use the topology defaults. Leave those fields uninitialized and random stack data can override the topology values sent to the DSP. Initialize the whole config structure so omitted fields reliably retain their zero/default meaning. Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260804070307.117119-1-prasad.kumpatla@oss.qualcomm.com Signed-off-by: Prasad Kumpatla Link: https://patch.msgid.link/20260818084655.3240284-3-prasad.kumpatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/q6apm-dai.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c index bf1f872a09f4..033197bdd870 100644 --- a/sound/soc/qcom/qdsp6/q6apm-dai.c +++ b/sound/soc/qcom/qdsp6/q6apm-dai.c @@ -216,7 +216,7 @@ static int q6apm_dai_prepare(struct snd_soc_component *component, { struct snd_pcm_runtime *runtime = substream->runtime; struct q6apm_dai_rtd *prtd = runtime->private_data; - struct audioreach_module_config cfg; + struct audioreach_module_config cfg = {}; struct device *dev = component->dev; struct q6apm_dai_data *pdata; int ret; @@ -814,7 +814,7 @@ static int q6apm_dai_compr_set_params(struct snd_soc_component *component, struct snd_compr_runtime *runtime = stream->runtime; struct q6apm_dai_rtd *prtd = runtime->private_data; struct q6apm_dai_data *pdata; - struct audioreach_module_config cfg; + struct audioreach_module_config cfg = {}; struct snd_codec *codec = ¶ms->codec; int dir = stream->direction; int ret; From 0a9e00d5ebdfcf460902f463e765f737d3fe935e Mon Sep 17 00:00:00 2001 From: Prasad Kumpatla Date: Tue, 18 Aug 2026 14:16:54 +0530 Subject: [PATCH 188/641] ASoC: qcom: common: Distinguish missing and invalid TDM slot configuration qcom_snd_parse_dai_tdm_slots() uses -EINVAL for both missing DAI-link TDM configuration and malformed TDM slot properties. As a result, qcom_snd_apply_dai_tdm_slots() silently ignores configuration errors. Return -ENOENT for missing DAI-link configuration and preserve -EINVAL for malformed TDM slot properties. Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260804070307.117119-1-prasad.kumpatla@oss.qualcomm.com Signed-off-by: Prasad Kumpatla Link: https://patch.msgid.link/20260818084655.3240284-4-prasad.kumpatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/common.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/soc/qcom/common.c b/sound/soc/qcom/common.c index d231024206db..d9f256d51973 100644 --- a/sound/soc/qcom/common.c +++ b/sound/soc/qcom/common.c @@ -103,14 +103,14 @@ static int qcom_snd_parse_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd, int ret; if (!link_np) - return -EINVAL; + return -ENOENT; struct device_node *cpu_np __free(device_node) = of_get_child_by_name(link_np, "cpu"); struct device_node *codec_np __free(device_node) = of_get_child_by_name(link_np, "codec"); if (!cpu_np || !codec_np) - return -EINVAL; + return -ENOENT; ret = qcom_snd_parse_tdm_slot(cpu_np, cpu_cfg); if (ret) @@ -172,7 +172,7 @@ int qcom_snd_apply_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd) ret = qcom_snd_get_dai_tdm_slots(rtd, &cpu_cfg, &codec_cfg); if (ret) - return ret == -EINVAL ? 0 : ret; + return ret == -ENOENT ? 0 : ret; return qcom_snd_apply_dai_tdm_slots_cfg(rtd, &cpu_cfg, &codec_cfg); } From 8593dc5f052e791748eaa76397ad95b9e32edac3 Mon Sep 17 00:00:00 2001 From: Prasad Kumpatla Date: Tue, 18 Aug 2026 14:16:55 +0530 Subject: [PATCH 189/641] ASoC: qcom: sc8280xp: Fix TDM hw_params error handling Treat -ENOENT from TDM slot parsing as the optional "configuration not present" case and continue to propagate real configuration errors. Also ignore -ENOTSUPP from optional DAI format and codec sysclk callbacks, apply codec_dai_fmt to codec DAIs on TDM links, and use a signed bclk_freq variable so errors from snd_soc_tdm_params_to_bclk() are handled correctly. Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260804070307.117119-1-prasad.kumpatla@oss.qualcomm.com Signed-off-by: Prasad Kumpatla Link: https://patch.msgid.link/20260818084655.3240284-5-prasad.kumpatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/sc8280xp.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/sound/soc/qcom/sc8280xp.c b/sound/soc/qcom/sc8280xp.c index 1322601659d4..4d48e1012cd4 100644 --- a/sound/soc/qcom/sc8280xp.c +++ b/sound/soc/qcom/sc8280xp.c @@ -115,33 +115,42 @@ static int sc8280xp_tdm_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *codec_dai; struct qcom_snd_tdm_slot_cfg cpu_cfg; struct qcom_snd_tdm_slot_cfg codec_cfg; - unsigned int bclk_freq; + int bclk_freq; int ret; int i; ret = qcom_snd_get_dai_tdm_slots(rtd, &cpu_cfg, &codec_cfg); if (ret) - return ret == -EINVAL ? 0 : ret; + return ret == -ENOENT ? 0 : ret; if (!cpu_cfg.slots) return 0; ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP); - if (ret) + if (ret && ret != -ENOTSUPP) return ret; + if (data->priv->codec_dai_fmt) { + for_each_rtd_codec_dais(rtd, i, codec_dai) { + ret = snd_soc_dai_set_fmt(codec_dai, + data->priv->codec_dai_fmt); + if (ret && ret != -ENOTSUPP) + return ret; + } + } + ret = qcom_snd_apply_dai_tdm_slots_cfg(rtd, &cpu_cfg, &codec_cfg); if (ret) return ret; bclk_freq = snd_soc_tdm_params_to_bclk(params, cpu_cfg.slot_width, cpu_cfg.slots, 1); - if (!bclk_freq) + if (bclk_freq <= 0) return -EINVAL; if (data->priv->mi2s_bclk_enable) { ret = snd_soc_dai_set_sysclk(cpu_dai, LPAIF_MI2S_BCLK, bclk_freq, SND_SOC_CLOCK_IN); - if (ret) { + if (ret && ret != -ENOTSUPP) { dev_err(rtd->dev, "%s: failed to set cpu sysclk: %d\n", __func__, ret); return ret; @@ -152,7 +161,7 @@ static int sc8280xp_tdm_hw_params(struct snd_pcm_substream *substream, for_each_rtd_codec_dais(rtd, i, codec_dai) { ret = snd_soc_dai_set_sysclk(codec_dai, 0, bclk_freq, SND_SOC_CLOCK_IN); - if (ret) { + if (ret && ret != -ENOTSUPP) { dev_err(rtd->dev, "%s: failed to set codec sysclk on %s: %d\n", __func__, codec_dai->name, ret); return ret; From 523ee23d4957f3e088e97d1d03ab9a8fc333d72a Mon Sep 17 00:00:00 2001 From: Binbin Zhou Date: Tue, 18 Aug 2026 17:20:52 +0800 Subject: [PATCH 190/641] ASoC: loongson: Fix naming of machine event and DAPM widgets Rename `tegra_machine_event` to `loongson_asoc_machine_event` and `loongson_aosc_dapm_widgets` to `loongson_asoc_dapm_widgets` to accurately reflect the hardware platform. Update all references accordingly. No functional change is introduced. Signed-off-by: Binbin Zhou Reviewed-by: Keguang Zhang Reviewed-by: Huacai Chen Link: https://patch.msgid.link/20260818092052.3596347-1-zhoubinbin@loongson.cn Signed-off-by: Mark Brown --- sound/soc/loongson/loongson_card.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sound/soc/loongson/loongson_card.c b/sound/soc/loongson/loongson_card.c index 25cd12eab4b1..6422cc1703b6 100644 --- a/sound/soc/loongson/loongson_card.c +++ b/sound/soc/loongson/loongson_card.c @@ -55,8 +55,8 @@ static const struct loongson_card_config ls2k0300_dl2k0300b_card_config = { .add_dapm_routes = true, }; -static int tegra_machine_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *k, int event) +static int loongson_asoc_machine_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) { struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm); struct loongson_card_data *priv = snd_soc_card_get_drvdata(card); @@ -72,9 +72,9 @@ static int tegra_machine_event(struct snd_soc_dapm_widget *w, return 0; } -static const struct snd_soc_dapm_widget loongson_aosc_dapm_widgets[] = { - SND_SOC_DAPM_HP("Headphone", tegra_machine_event), - SND_SOC_DAPM_SPK("Speaker", tegra_machine_event), +static const struct snd_soc_dapm_widget loongson_asoc_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone", loongson_asoc_machine_event), + SND_SOC_DAPM_SPK("Speaker", loongson_asoc_machine_event), }; /* Headphones Jack */ @@ -314,8 +314,8 @@ static int loongson_asoc_card_probe(struct platform_device *pdev) card->num_links = ARRAY_SIZE(loongson_dai_links); if (ls_priv->cfg->add_dapm_widgets) { - card->dapm_widgets = loongson_aosc_dapm_widgets; - card->num_dapm_widgets = ARRAY_SIZE(loongson_aosc_dapm_widgets); + card->dapm_widgets = loongson_asoc_dapm_widgets; + card->num_dapm_widgets = ARRAY_SIZE(loongson_asoc_dapm_widgets); } snd_soc_card_set_drvdata(card, ls_priv); From 6eca8f94d84106d3754b9df27f46a14572af9e7f Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Tue, 18 Aug 2026 08:47:21 -1000 Subject: [PATCH 191/641] workqueue: Annotate cb_lock nesting when draining a dead BH pool On PREEMPT_RT, bh_worker() wraps work item execution in pool->cb_lock to provide a handshake for canceling BH work items. When a CPU goes down, drain_dead_softirq_workfn() runs the dead pool's bh_worker() nested inside the local pool's bh_worker(), acquiring the cb_locks of two different pools without a nesting annotation. lockdep reports possible recursive locking: ============================================ WARNING: possible recursive locking detected -------------------------------------------- ktimers/0/16 is trying to acquire lock: ffff8880b873a990 (&pool->cb_lock){+...}-{3:3}, at: bh_worker+0x7d/0x880 but task is already holding lock: ffff8880b863a990 (&pool->cb_lock){+...}-{3:3}, at: bh_worker+0x7d/0x880 Call Trace: bh_worker+0x7d/0x880 kernel/workqueue.c:3688 drain_dead_softirq_workfn+0x95/0x220 kernel/workqueue.c:3763 process_scheduled_works+0xa8e/0x14e0 kernel/workqueue.c:3405 bh_worker+0x46a/0x880 kernel/workqueue.c:3708 tasklet_action+0xc/0x70 kernel/softirq.c:965 The nesting can't deadlock. A pool's bh_worker() runs nested only while the pool's CPU is dead, entered from a live pool's bh_worker() on the draining CPU, so the ordering is always live to dead. CPU hotplug operations are serialized and the drain is synchronous, so the nesting depth never exceeds two. Annotate the inner acquisition with SINGLE_DEPTH_NESTING. Signed-off-by: Tejun Heo Reported-by: syzbot+1bd20115328f8254ed62@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=1bd20115328f8254ed62 Fixes: ad7c7f4b9c6c ("workqueue: Provide a handshake for canceling BH workers") Cc: stable@vger.kernel.org # v6.18+ --- kernel/workqueue.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/kernel/workqueue.c b/kernel/workqueue.c index bfeef512f6dd..c0b72dcc0f03 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -3197,7 +3197,16 @@ restart: #ifdef CONFIG_PREEMPT_RT static void worker_lock_callback(struct worker_pool *pool) { - spin_lock(&pool->cb_lock); + /* + * SINGLE_DEPTH_NESTING is for a dead pool's bh_worker() running from + * drain_dead_softirq_workfn() inside a live pool's bh_worker(). The + * unlocked read is stable: the flag is only set while @pool's CPU is + * dead, inside a serialized hotplug operation. data_race() as the value + * only affects the lockdep annotation and the read can be elided when + * lockdep is disabled. + */ + spin_lock_nested(&pool->cb_lock, + data_race(pool->flags) & POOL_BH_DRAINING ? SINGLE_DEPTH_NESTING : 0); } static void worker_unlock_callback(struct worker_pool *pool) From ae84a2536577057e97f23f75a202e26d0e86cf01 Mon Sep 17 00:00:00 2001 From: Corey Minyard Date: Tue, 18 Aug 2026 12:49:52 -0500 Subject: [PATCH 192/641] ipmi:msghandler: Cancel work cleanly on an error If an error occurs during startup of an IPMI interface, it may have scheduled work to run. The work needs to be canceled before the interface can be freed. Reported-by: Nilay Shroff Closes: https://sourceforge.net/p/openipmi/mailman/message/59375605/ Fixes: 62cd145453d5 ("ipmi:msghandler: Handle error returns from the SMI sender") Cc: stable@vger.kernel.org # 7.0 Tested-by: Nilay Shroff Signed-off-by: Corey Minyard --- drivers/char/ipmi/ipmi_msghandler.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/char/ipmi/ipmi_msghandler.c b/drivers/char/ipmi/ipmi_msghandler.c index 7634dff99f41..c73e9def59a6 100644 --- a/drivers/char/ipmi/ipmi_msghandler.c +++ b/drivers/char/ipmi/ipmi_msghandler.c @@ -3766,6 +3766,7 @@ int ipmi_add_smi(struct module *owner, list_del(&intf->link); mutex_unlock(&ipmi_interfaces_mutex); mutex_unlock(&smi_watchers_mutex); + cancel_work_sync(&intf->smi_work); kref_put(&intf->refcount, intf_free); return rv; From 6c97817e20598e5473094e0e38d1f51f1cf4dfff Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Tue, 18 Aug 2026 22:47:17 +0800 Subject: [PATCH 193/641] ALSA: pcxhr: initialize mutexes before requesting threaded IRQ pcxhr_probe() requests pcxhr_threaded_irq() before initializing mgr->lock, even though the threaded handler takes that mutex. Initialize the manager locks before request_threaded_irq() so an early interrupt cannot run against uninitialized mutex state during probe. Fixes: 9bef72bdb26e ("ALSA: pcxhr: Use nonatomic PCM ops") Cc: stable@vger.kernel.org Signed-off-by: Runyu Xiao Link: https://patch.msgid.link/20260818144717.2269918-1-runyu.xiao@seu.edu.cn Signed-off-by: Takashi Iwai --- sound/pci/pcxhr/pcxhr.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sound/pci/pcxhr/pcxhr.c b/sound/pci/pcxhr/pcxhr.c index 25a640304430..60cc0721d0a6 100644 --- a/sound/pci/pcxhr/pcxhr.c +++ b/sound/pci/pcxhr/pcxhr.c @@ -1535,6 +1535,13 @@ static int pcxhr_probe(struct pci_dev *pci, mgr->pci = pci; mgr->irq = -1; + /* ISR lock */ + mutex_init(&mgr->lock); + mutex_init(&mgr->msg_lock); + + /* init setup mutex*/ + mutex_init(&mgr->setup_mutex); + if (request_threaded_irq(pci->irq, pcxhr_interrupt, pcxhr_threaded_irq, IRQF_SHARED, KBUILD_MODNAME, mgr)) { @@ -1548,13 +1555,6 @@ static int pcxhr_probe(struct pci_dev *pci, "Digigram at 0x%lx & 0x%lx, 0x%lx irq %i", mgr->port[0], mgr->port[1], mgr->port[2], mgr->irq); - /* ISR lock */ - mutex_init(&mgr->lock); - mutex_init(&mgr->msg_lock); - - /* init setup mutex*/ - mutex_init(&mgr->setup_mutex); - mgr->prmh = kmalloc(sizeof(*mgr->prmh) + sizeof(u32) * (PCXHR_SIZE_MAX_LONG_STATUS - PCXHR_SIZE_MAX_STATUS), From 352cef030f9036a658e6eb03d6bb85fa19c7c103 Mon Sep 17 00:00:00 2001 From: Chad Talbott Date: Tue, 18 Aug 2026 14:32:37 -1000 Subject: [PATCH 194/641] ALSA: usb-audio: Add MIN_MUTE quirk for Creative Sound Blaster Play! 3 The Creative Sound Blaster Play! 3 (041e:324d) exposes a Feature Unit volume control with a range of -44.00 dB .. 0.00 dB over raw values 0..88, but raw value 0 is in fact a hard mute rather than -44.00 dB. Tested on hardware. Signed-off-by: Chad Talbott Link: https://patch.msgid.link/20260819003237.452807-1-chad.talbott@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index eb1750def067..f4e1eac639b9 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2219,6 +2219,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_MIXER_GET_CUR_BROKEN), DEVICE_FLG(0x041e, 0x3000, /* Creative SB Extigy */ QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x041e, 0x324d, /* Creative Sound Blaster Play! 3 */ + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), DEVICE_FLG(0x041e, 0x4080, /* Creative Live Cam VF0610 */ QUIRK_FLAG_GET_SAMPLE_RATE), DEVICE_FLG(0x045e, 0x083c, /* MS USB Link headset */ From 9c48a53685040bb0de45a640b34055cbbfc69d4f Mon Sep 17 00:00:00 2001 From: Yi Ding Date: Mon, 1 Jun 2026 20:51:35 -0700 Subject: [PATCH 195/641] rtc: pcf8563: fix clock provider leak on unbind pcf8563_clkout_register_clk() registers the CLKOUT clock provider with of_clk_add_provider(), but nothing ever unwinds it: there is no of_clk_del_provider() call and the driver has no remove callback. Each of_clk_add_provider() allocates a struct of_clk_provider, takes a reference on the OF node and adds an entry to the global of_clk_providers list, none of which is released when the device is unbound. Every bind/unbind (or module reload) therefore leaks a provider structure and an of_node reference. The clock itself is already device-managed (devm_clk_register()); only the provider registration was not. Use devm_of_clk_add_hw_provider() so the provider is removed automatically on unbind. Tie it to the parent i2c device, whose OF node carries the #clock-cells and clock-output-names properties (the RTC class device has no OF node of its own). Fixes: a39a6405d5f9 ("rtc: pcf8563: add CLKOUT to common clock framework") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Yi Ding Link: https://patch.msgid.link/20260602035135.62264-1-yi.s.ding@gmail.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-pcf8563.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/rtc/rtc-pcf8563.c b/drivers/rtc/rtc-pcf8563.c index 81d13733b1e9..a90dc940474b 100644 --- a/drivers/rtc/rtc-pcf8563.c +++ b/drivers/rtc/rtc-pcf8563.c @@ -449,7 +449,9 @@ static struct clk *pcf8563_clkout_register_clk(struct pcf8563 *pcf8563) clk = devm_clk_register(&pcf8563->rtc->dev, &pcf8563->clkout_hw); if (!IS_ERR(clk)) - of_clk_add_provider(node, of_clk_src_simple_get, clk); + devm_of_clk_add_hw_provider(pcf8563->rtc->dev.parent, + of_clk_hw_simple_get, + &pcf8563->clkout_hw); return clk; } From c52feb4365396b6a881b5e8a95540517ffabb3b7 Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Wed, 5 Aug 2026 11:44:54 +0800 Subject: [PATCH 196/641] drm/amdgpu: Disable runtime PM for externally attached dGPUs pci_is_thunderbolt_attached() requires an upstream PCI bridge with is_thunderbolt set from an Intel Thunderbolt VSEC. This does not cover the affected ASM4242 USB4 PCI hierarchy: 00:02.2 \- 0f:00.0 [1b21:2421] +- 10:01.0 [1b21:2423] -> 45:00.0 -> 46:00.0 | -> 47:00.0 -> 48:00.0 -> 49:00.0 [1002:7590] \- 10:03.0 -> 76:00.0 [1b21:2425] USB4 Host Router The host router is outside the GPU upstream bridge chain, leaving no ancestor with is_thunderbolt set. PCI core propagates DEVICE_REMOVABLE below the external-facing PCIe tunnel. Disable Runtime PM when either pci_is_thunderbolt_attached() or dev_is_removable() is true. Cc: stable@vger.kernel.org Signed-off-by: Yang Wang Reviewed-by: Candice Li Reviewed-by: Kenneth Feng Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 520fd59036d5..57c8ce814931 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -609,6 +609,13 @@ void amdgpu_device_detect_runtime_pm_mode(struct amdgpu_device *adev) int bamaco_support; adev->pm.rpm_mode = AMDGPU_RUNPM_NONE; + if (pci_is_thunderbolt_attached(adev->pdev) || + dev_is_removable(&adev->pdev->dev)) { + dev_info(adev->dev, + "Runtime PM disabled for externally attached device\n"); + return; + } + bamaco_support = amdgpu_device_supports_baco(adev); switch (amdgpu_runtime_pm) { From ef5fcf2a6c320676bf8be2dadac93d9023b468b7 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Sat, 8 Aug 2026 20:09:34 +0800 Subject: [PATCH 197/641] drm/amdgpu: fix autosuspend cleanup during removal amdgpu_pci_probe() calls pm_runtime_use_autosuspend(), but amdgpu_pci_remove() does not call the matching pm_runtime_dont_use_autosuspend(). If the autosuspend delay is set to a negative value while autosuspend is enabled, the runtime PM core increments usage_count to prevent runtime suspend. Without calling pm_runtime_dont_use_autosuspend() during teardown, this reference is not dropped and usage_count remains unbalanced. The documentation for pm_runtime_use_autosuspend() also notes that it is important to undo it with pm_runtime_dont_use_autosuspend() at driver exit time, unless runtime PM was initially enabled with devm_pm_runtime_enable(). Add the missing pm_runtime_dont_use_autosuspend() call to the remove path. This issue was found by manual code inspection. Fixes: d38ceaf99ed0 ("drm/amdgpu: add core driver (v4)") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260808120934.2813010-1-lgs201920130244@gmail.com Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c index 0ab380ca7e64..5c33c19fd9bc 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c @@ -2557,6 +2557,7 @@ amdgpu_pci_remove(struct pci_dev *pdev) if (adev->pm.rpm_mode != AMDGPU_RUNPM_NONE) { pm_runtime_get_sync(dev->dev); pm_runtime_forbid(dev->dev); + pm_runtime_dont_use_autosuspend(dev->dev); } amdgpu_driver_unload_kms(dev); From ffdb7a8104f51d552dea4b319c8ce5169f63e724 Mon Sep 17 00:00:00 2001 From: Zhu Lingshan Date: Wed, 22 Jul 2026 18:13:44 +0800 Subject: [PATCH 198/641] drm/amdgpu: ensure all userq VAs mapped before restore amdgpu_userq_buffer_vas_mapped() checks whether all VAs of a queue are mapped before restoring it. So that HW won't access any invalid addresses. Currently, this function assumes all VAs are mapped if any VA of a queue has been mapped, which is wrong. This commit fixes this problem by examining all VAs of a queue and reporting false if any of them is not mapped. Signed-off-by: Zhu Lingshan Reviewed-by: Sunil Khatri Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index bcfbd7213dd6..04639f894903 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -287,22 +287,24 @@ static bool amdgpu_userq_buffer_va_mapped(struct amdgpu_vm *vm, u64 addr) static bool amdgpu_userq_buffer_vas_mapped(struct amdgpu_usermode_queue *queue) { - int i, r = 0; + int i; + bool mapped; for (i = 0; i < ARRAY_SIZE(queue->userq_vas.va_array); i++) { if (!queue->userq_vas.va_array[i]) continue; - r += amdgpu_userq_buffer_va_mapped(queue->vm, + + mapped = amdgpu_userq_buffer_va_mapped(queue->vm, queue->userq_vas.va_array[i]); dev_dbg(queue->userq_mgr->adev->dev, "validate the userq mapping:%p va:%llx r:%d\n", - queue, queue->userq_vas.va_array[i], r); + queue, queue->userq_vas.va_array[i], mapped); + + if (!mapped) + return false; } - if (r != 0) - return true; - - return false; + return true; } From 3438e964c916eb1a51f9f3ac018758e44abe5539 Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Thu, 13 Aug 2026 13:59:07 +0530 Subject: [PATCH 199/641] drm/amdgpu/userq: ignore duplicate BO locks in userq signal ioctl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit amdgpu_userq_signal_ioctl() calls drm_exec_init() without DRM_EXEC_IGNORE_DUPLICATES. When the same GEM object appears more than once across the read/write BO handle lists submitted by userspace, drm_exec_lock_obj() returns -EALREADY the second time it locks that object, which aborts the ioctl instead of treating the repeat as a no-op. Add DRM_EXEC_IGNORE_DUPLICATES so duplicate objects are silently skipped on the second lock attempt, matching the intended semantics of locking a set of (possibly overlapping) BOs before publishing a fence on them. Signed-off-by: Sunil Khatri Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c index 7e80442ec3e5..cd0bd016a24d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c @@ -537,7 +537,7 @@ int amdgpu_userq_signal_ioctl(struct drm_device *dev, void *data, * amdgpu_userq_ensure_ev_fence() can't be called while holding the resv * locks. */ - drm_exec_init(&exec, DRM_EXEC_INTERRUPTIBLE_WAIT, + drm_exec_init(&exec, DRM_EXEC_INTERRUPTIBLE_WAIT | DRM_EXEC_IGNORE_DUPLICATES, (num_read_bo_handles + num_write_bo_handles)); drm_exec_until_all_locked(&exec) { From 2411e499385c7df58afedb59238817579bc2168b Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Thu, 13 Aug 2026 14:02:09 +0530 Subject: [PATCH 200/641] drm/amdgpu/userq: ignore duplicate BO locks when counting wait fences MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit amdgpu_userq_wait_count_fences() calls drm_exec_init() without DRM_EXEC_IGNORE_DUPLICATES. When the same GEM object appears more than once across the read/write BO handle lists submitted by userspace, drm_exec_lock_obj() returns -EALREADY the second time it locks that object, which aborts the fence-counting pass instead of treating the repeat as a no-op. Add DRM_EXEC_IGNORE_DUPLICATES so duplicate objects are silently skipped on the second lock attempt. Signed-off-by: Sunil Khatri Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c index cd0bd016a24d..981cb1dd5dda 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c @@ -643,7 +643,7 @@ amdgpu_userq_wait_count_fences(struct drm_file *filp, /* TODO: It is actually not necessary to lock them */ num_read_bo_handles = wait_info->num_bo_read_handles; num_write_bo_handles = wait_info->num_bo_write_handles; - drm_exec_init(&exec, DRM_EXEC_INTERRUPTIBLE_WAIT, + drm_exec_init(&exec, DRM_EXEC_INTERRUPTIBLE_WAIT | DRM_EXEC_IGNORE_DUPLICATES, num_read_bo_handles + num_write_bo_handles); drm_exec_until_all_locked(&exec) { From 5827c7ad72b6826d9b90be15af139dd2287e3ca3 Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Thu, 13 Aug 2026 14:02:35 +0530 Subject: [PATCH 201/641] drm/amdgpu/userq: ignore duplicate BO locks when returning wait fence info MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit amdgpu_userq_wait_return_fence_info() calls drm_exec_init() without DRM_EXEC_IGNORE_DUPLICATES. When the same GEM object appears more than once across the read/write BO handle lists submitted by userspace, drm_exec_lock_obj() returns -EALREADY the second time it locks that object, which aborts the fence resolution pass instead of treating the repeat as a no-op. Add DRM_EXEC_IGNORE_DUPLICATES so duplicate objects are silently skipped on the second lock attempt. Signed-off-by: Sunil Khatri Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c index 981cb1dd5dda..a33dbe978798 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c @@ -778,7 +778,7 @@ amdgpu_userq_wait_return_fence_info(struct drm_device *dev, struct drm_file *fil /* Lock all the GEM objects */ num_read_bo_handles = wait_info->num_bo_read_handles; num_write_bo_handles = wait_info->num_bo_write_handles; - drm_exec_init(&exec, DRM_EXEC_INTERRUPTIBLE_WAIT, + drm_exec_init(&exec, DRM_EXEC_INTERRUPTIBLE_WAIT | DRM_EXEC_IGNORE_DUPLICATES, num_read_bo_handles + num_write_bo_handles); drm_exec_until_all_locked(&exec) { From 6fd83a1c2cdea48c396f600795217fbdfb8124f6 Mon Sep 17 00:00:00 2001 From: Akhmed Zhitaev Date: Thu, 13 Aug 2026 22:09:59 +0500 Subject: [PATCH 202/641] drm/amd/display: Scale custom brightness curve from full range Custom brightness curves use an 8-bit input signal. After exporting the full PWM range to userspace, the curve normalizer still divides requests by the physical PWM span. On panels with a nonzero minimum PWM level, this can produce a curve input greater than 255 and send an invalid backlight level to DC. Scale the userspace [0..max] range to the curve's [0..255] range instead. This retains the full advertised range and keeps the reverse readback conversion unchanged. Fixes: 8dbd72cb7900 ("drm/amd/display: Export full brightness range to userspace") Cc: stable@vger.kernel.org Signed-off-by: Akhmed Zhitaev Reviewed-by: Mario Limonciello (AMD) (Move to amdgpu_dm_backlight.c) Link: https://patch.msgid.link/20260813170959.22073-1-zhitaevakh@gmail.com Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c index b66ca60e697d..e61bbc310f33 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c @@ -106,10 +106,10 @@ int get_brightness_range(const struct amdgpu_dm_backlight_caps *caps, } EXPORT_IF_KUNIT(get_brightness_range); -/* Rescale from [min..max] to [0..AMDGPU_MAX_BL_LEVEL] */ -static inline u32 scale_input_to_fw(int min, int max, u64 input) +/* Rescale userspace [0..max] to the firmware curve's [0..255]. */ +static inline u32 scale_input_to_fw(int max, u64 input) { - return DIV_ROUND_CLOSEST_ULL(input * AMDGPU_MAX_BL_LEVEL, max - min); + return DIV_ROUND_CLOSEST_ULL(input * AMDGPU_MAX_BL_LEVEL, max); } /* Rescale from [0..AMDGPU_MAX_BL_LEVEL] to [min..max] */ @@ -123,7 +123,7 @@ void convert_custom_brightness(const struct amdgpu_dm_backlight_caps *caps, unsigned int min, unsigned int max, uint32_t *user_brightness) { - u32 brightness = scale_input_to_fw(min, max, *user_brightness); + u32 brightness = scale_input_to_fw(max, *user_brightness); u8 lower_signal, upper_signal, upper_lum, lower_lum, lum; int left, right; From 59db985bc99e0589536a2116b93b0a6c45df41ab Mon Sep 17 00:00:00 2001 From: Mukul Joshi Date: Thu, 13 Aug 2026 11:02:24 -0400 Subject: [PATCH 203/641] drm/amdgpu: fix sysfs ip base addr for 64bit in standalone mode In standalone mode the ip_discovery sysfs tree is built from a verbatim copy of the discovery binary taken before reg_base_init() collapses the 64bit base addresses in place. Decoding as 32bit there yields interleaved zeros. Decode base_address_64[] in that case; keep reading the already collapsed adev->discovery.bin as-is otherwise. Fixes: 402e04f11ff7 ("drm/amdgpu: Export ip_discovery sysfs on probe failure") Cc: stable@vger.kernel.org Signed-off-by: Mukul Joshi Acked-by: Alex Deucher Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c index a404d8aa13ee..164e85b66e2d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c @@ -1307,8 +1307,19 @@ static int amdgpu_discovery_sysfs_ips(struct amdgpu_device *adev, ip_hw_instance->num_instance); ip_hw_instance->num_base_addresses = ip->num_base_address; - for (kk = 0; kk < ip_hw_instance->num_base_addresses; kk++) - ip_hw_instance->base_addr[kk] = ip->base_address[kk]; + for (kk = 0; kk < ip_hw_instance->num_base_addresses; kk++) { + /* + * Standalone mode uses a raw copy of the discovery + * binary; decode 64-bit addresses here. The shared + * bin is already collapsed to 32-bit in place. + */ + if (reg_base_64 && ip_top->standalone_mode) + ip_hw_instance->base_addr[kk] = + lower_32_bits(le64_to_cpu(ip->base_address_64[kk])) & 0x3FFFFFFF; + else + ip_hw_instance->base_addr[kk] = + le32_to_cpu(ip->base_address[kk]); + } kobject_init(&ip_hw_instance->kobj, &ip_hw_instance_ktype); ip_hw_instance->kobj.kset = &ip_hw_id->hw_id_kset; From 48dc279c3010ac8f91b1845b2abb3a1e9943a0f5 Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Thu, 13 Aug 2026 12:27:53 +0800 Subject: [PATCH 204/641] drm/amdgpu: force complete the MES ring fences on reset The MES scheduler ring has no drm scheduler (no_scheduler = true), so it is skipped by the force-completion loop in amdgpu_device_pre_asic_reset(). It uses a polling fence whose hw value lives in wb (GTT) memory and survives a MODE1 reset, while fence_drv.sync_seq keeps advancing for every packet. When the reset is triggered because MES itself stopped responding, the timed-out packets advance sync_seq past the last hw fence value MES wrote. After resume the first MES submission polls forever on a seq that is never written back, failing the resume and wedging the box on a second reset: amdgpu: MES ring buffer is full. amdgpu: *ERROR* ring gfx_0.0.0 test failed (-110) amdgpu: resume of IP block failed -110 amdgpu: GPU reset end with ret = -110 Force complete the MES scheduler ring fences together with the scheduler rings so their hw fence is realigned to sync_seq. v2: cover all XCCs (one scheduler ring each), not just mes.ring[0]. Cc: stable@vger.kernel.org Signed-off-by: Jesse Zhang Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 57c8ce814931..be4c74491554 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -5043,6 +5043,19 @@ int amdgpu_device_pre_asic_reset(struct amdgpu_device *adev, amdgpu_fence_driver_force_completion(ring, fence); } + /* + * MES scheduler rings have no drm scheduler, so they are missed by the + * loop above. Realign their polling fence too (one per XCC), otherwise the + * first post-reset submission polls forever on a stale seq. sched.ready is + * only set while the driver owns the ring. + */ + for (i = 0; i < AMDGPU_MAX_MES_INST_PIPES; i++) { + struct amdgpu_ring *mes_ring = &adev->mes.ring[i]; + + if (mes_ring->fence_drv.initialized && mes_ring->sched.ready) + amdgpu_fence_driver_force_completion(mes_ring, fence); + } + amdgpu_fence_driver_isr_toggle(adev, false); r = amdgpu_reset_prepare_hwcontext(adev, reset_context); From 556488b08638aad240bdb524cebf22fa7569843c Mon Sep 17 00:00:00 2001 From: Zhu Lingshan Date: Thu, 13 Aug 2026 17:36:53 +0800 Subject: [PATCH 205/641] drm/amdgpu: validate rptr and wptr of a userq rptr and wptr of a userq are 8 bytes aligned, and may not placed on a page boundary. This commit checks whether rptr and wptr are 8 bytes aligned, and expectes 8 bytes when validates rptr/wptr VA. With above changes, this commit fixes an regression in amdgpu_userq_input_va_validate, where end_addr is caculated by: check_add_overflow(start_addr, expected_size - 1, &end_addr). Wptr and rptr are very likely not to be page aligned, when validating rptr and wptr, if they are located in the last mapped page(or only one page is mapped) and expected_size is PAGE_SIZE, end_addr will exceed the last mapped page, means (end_addr >> AMDGPU_GPU_PAGE_SHIFT) > va_map->last, and causing an -EINVAL, even it is a valid VA. Signed-off-by: Zhu Lingshan Acked-by: Alex Deucher Fixes: c0122bf2ccb1 ("drm/amdgpu: fix userq VA validation for sub-page buffers") Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index 04639f894903..17cc48d87c4d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -702,10 +702,10 @@ amdgpu_userq_create(struct drm_file *filp, union drm_amdgpu_userq *args) args->in.queue_size, &queue->userq_vas.va.queue_rb) || amdgpu_userq_input_va_validate(adev, queue, args->in.rptr_va, - AMDGPU_GPU_PAGE_SIZE, + sizeof(u64), &queue->userq_vas.va.rptr) || amdgpu_userq_input_va_validate(adev, queue, args->in.wptr_va, - AMDGPU_GPU_PAGE_SIZE, + sizeof(u64), &queue->userq_vas.va.wptr)) { r = -EINVAL; amdgpu_bo_unreserve(fpriv->vm.root.bo); @@ -850,6 +850,12 @@ static int amdgpu_userq_input_args_validate(struct drm_device *dev, drm_file_err(filp, "invalidate userq queue rptr or wptr\n"); return -EINVAL; } + + if (!IS_ALIGNED(args->in.wptr_va, sizeof(u64)) || + !IS_ALIGNED(args->in.rptr_va, sizeof(u64))) { + drm_file_err(filp, "user queue rptr or wptr is not 8-byte aligned\n"); + return -EINVAL; + } break; case AMDGPU_USERQ_OP_FREE: if (args->in.ip_type || From fd65d1742992361fc2201ecb4e43411e6e417fcb Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Thu, 13 Aug 2026 12:28:04 +0800 Subject: [PATCH 206/641] drm/amdgpu: force complete the KIQ ring fences on reset Like the MES scheduler ring, the KIQ ring sets no_scheduler = true and uses a polling fence, so it is skipped by the force-completion loop in amdgpu_device_pre_asic_reset(). Its hw fence value lives in wb (GTT) memory and survives a MODE1 reset while fence_drv.sync_seq keeps advancing, so after a reset the first KIQ submission can poll forever on a seq that is never written back. Force complete the KIQ ring fences too so their hw fence is realigned to sync_seq. Cc: stable@vger.kernel.org Reviewed-by: Alex Deucher Suggested-by: Alex Deucher Signed-off-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index be4c74491554..0b7cdea4b9e0 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -5056,6 +5056,18 @@ int amdgpu_device_pre_asic_reset(struct amdgpu_device *adev, amdgpu_fence_driver_force_completion(mes_ring, fence); } + /* + * KIQ rings are polling-fence/no_scheduler like MES, so realign their + * fence too (one ring per XCC), otherwise the first post-reset KIQ + * submission polls forever on a stale seq. + */ + for (i = 0; i < AMDGPU_MAX_GC_INSTANCES; i++) { + struct amdgpu_ring *kiq_ring = &adev->gfx.kiq[i].ring; + + if (kiq_ring->fence_drv.initialized && kiq_ring->sched.ready) + amdgpu_fence_driver_force_completion(kiq_ring, fence); + } + amdgpu_fence_driver_isr_toggle(adev, false); r = amdgpu_reset_prepare_hwcontext(adev, reset_context); From 8587d48d694da5aca580f92461658ec14470592b Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Wed, 12 Aug 2026 11:08:40 +0800 Subject: [PATCH 207/641] drm/amdgpu: check thunderbolt before switcheroo registration Introduce a helper to consolidate the vga_switcheroo registration condition used by the init and fini paths. Keep the explicit pci_is_thunderbolt_attached() check, as dev_is_removable() does not provide equivalent coverage for Thunderbolt-attached GPUs. This ensures such devices remain excluded from switcheroo registration while preserving the existing PX and Apple gmux handling. Cc: stable@vger.kernel.org Signed-off-by: Yang Wang Reviewed-by: Kenneth Feng Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 0b7cdea4b9e0..ca385ed15bc6 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -3739,6 +3739,14 @@ static void amdgpu_device_sys_interface_fini(struct amdgpu_device *adev) amdgpu_ptl_sysfs_fini(adev); } +static bool +amdgpu_device_should_register_switcheroo(struct amdgpu_device *adev, bool px) +{ + return !pci_is_thunderbolt_attached(adev->pdev) && + (px || (!dev_is_removable(&adev->pdev->dev) && + apple_gmux_detect(NULL, NULL))); +} + /** * amdgpu_device_init - initialize the driver * @@ -4189,8 +4197,7 @@ fence_driver_init: px = amdgpu_device_supports_px(adev); - if (px || (!dev_is_removable(&adev->pdev->dev) && - apple_gmux_detect(NULL, NULL))) + if (amdgpu_device_should_register_switcheroo(adev, px)) vga_switcheroo_register_client(adev->pdev, &amdgpu_switcheroo_ops, px); @@ -4355,8 +4362,7 @@ void amdgpu_device_fini_sw(struct amdgpu_device *adev) px = amdgpu_device_supports_px(adev); - if (px || (!dev_is_removable(&adev->pdev->dev) && - apple_gmux_detect(NULL, NULL))) + if (amdgpu_device_should_register_switcheroo(adev, px)) vga_switcheroo_unregister_client(adev->pdev); if (px) From c675dea86a000e9550077c5bca97c6431786d1b7 Mon Sep 17 00:00:00 2001 From: Pierre-Eric Pelloux-Prayer Date: Mon, 15 Jun 2026 13:48:23 +0200 Subject: [PATCH 208/641] drm/amdgpu: delay ttm buffer func enablement on xgmi When amdgpu_init_minimal_xgmi is used, SDMA engines init is delayed so amdgpu_ttm_enable_buffer_funcs must be called later. Without this, the check for num_buffer_funcs_scheds will fail and using ttm buffer funcs later will fail. Given that amdgpu_ttm_enable_buffer_funcs is a no-op if amdgpu_in_reset() returns true, the call has to occur after the reset lock is dropped. Cc: stable@vger.kernel.org Fixes: e4029f7a9474 ("drm/amdgpu: only use working sdma schedulers for ttm") Signed-off-by: Pierre-Eric Pelloux-Prayer Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 8 +++++--- drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c | 3 +++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index ca385ed15bc6..b61c641b5be4 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -2529,7 +2529,11 @@ static int amdgpu_device_ip_init(struct amdgpu_device *adev) if (r) goto init_failed; - amdgpu_ttm_enable_buffer_funcs(adev); + /* If SDMA is not brought up during hwini, the ttm buffer funcs enablement + * is delayed after reset-on-init completes. + */ + if (amdgpu_ip_member_of_hwini(adev, AMD_IP_BLOCK_TYPE_SDMA)) + amdgpu_ttm_enable_buffer_funcs(adev); /* Don't init kfd if whole hive need to be reset during init */ if (adev->init_lvl->level != AMDGPU_INIT_LEVEL_MINIMAL_XGMI) { @@ -5174,8 +5178,6 @@ int amdgpu_device_reinit_after_reset(struct amdgpu_reset_context *reset_context) if (r) goto out; - amdgpu_ttm_enable_buffer_funcs(tmp_adev); - r = amdgpu_device_ip_resume_phase3(tmp_adev); if (r) goto out; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c index 2725230aa5e3..45e31b3daf06 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c @@ -1380,6 +1380,9 @@ static void amdgpu_xgmi_reset_on_init_work(struct work_struct *work) amdgpu_device_unlock_reset_domain(tmp_adev->reset_domain); list_for_each_entry(tmp_adev, &hive->device_list, gmc.xgmi.head) { + /* Enable ttm buffers funcs after the reset lock has been dropped. */ + amdgpu_ttm_enable_buffer_funcs(tmp_adev); + r = amdgpu_ras_init_badpage_info(tmp_adev); if (r && r != -EHWPOISON) dev_err(tmp_adev->dev, From 8fce9b0f93e222451d3f586c129c7b9f53a53fd2 Mon Sep 17 00:00:00 2001 From: Zhu Lingshan Date: Fri, 14 Aug 2026 14:51:32 +0800 Subject: [PATCH 209/641] amdkfd: let profile_lock_device return an int other than uint32 profile_lock_device() may return negive error code, so the type of the return value should be int, not uint32 Signed-off-by: Zhu Lingshan Reviewed-by: Felix Kuehling Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_chardev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c index 309510e23315..6fd18488d5cf 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c @@ -3329,7 +3329,7 @@ static int kfd_ioctl_create_process(struct file *filep, struct kfd_process *p, v return 0; } -static inline uint32_t profile_lock_device(struct kfd_process *p, +static inline int profile_lock_device(struct kfd_process *p, uint32_t gpu_id, uint32_t op) { struct kfd_process_device *pdd; From 5c082f4cd17601e2357c1c4a686a85a53411c3d0 Mon Sep 17 00:00:00 2001 From: Zhu Lingshan Date: Fri, 14 Aug 2026 16:37:01 +0800 Subject: [PATCH 210/641] drm/amdgpu: fix hang and race in userq destroy When a queue is hung, the hang_detect_work is the only way to recover it. However in amdgpu_userq_destroy(), the hang_detect_work is cancelled too early, resulting in amdgpu_userq_wait_for_last_fence() may never return, leaving an uninterruptible dma_fence_wait() hang there. To fix this problem, this commit moves the cancelling of hang_detect_work after amdgpu_userq_wait_for_last_fence(), and it has to be before the unmap helper, because hang_detect_work resets the queue, so it races with amdgpu_userq_unmap_helper() for MES operations and queue state. This commit splits amdgpu_userq_cleanup() into two parts: 1) amdgpu_userq_detach_doorbell(), which detaches the queue from userq_doorbell_xa. This has to be called before the cancel, otherwise the IRQ handlers (for example amdgpu_userq_process_fence_irq) can re-schedule the hang_detect_work and the cancel is not final. 2) amdgpu_userq_fence_driver_free(), this has to be called after the unmap helper, because it can release the seq64 slot that the GPU writes fence values to. Only one cancel_delayed_work_sync(&queue->hang_detect_work) is needed, so other redundancies are removed. Signed-off-by: Zhu Lingshan Acked-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index 17cc48d87c4d..24adad7be251 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -418,19 +418,12 @@ static void amdgpu_userq_wait_for_last_fence(struct amdgpu_usermode_queue *queue dma_fence_wait(f, false); } -static void amdgpu_userq_cleanup(struct amdgpu_usermode_queue *queue) +static void amdgpu_userq_detach_doorbell(struct amdgpu_usermode_queue *queue) { - struct amdgpu_userq_mgr *uq_mgr = queue->userq_mgr; - struct amdgpu_device *adev = uq_mgr->adev; + struct amdgpu_device *adev = queue->userq_mgr->adev; - /* Wait for mode-1 reset to complete */ down_read(&adev->reset_domain->sem); - - /* Use interrupt-safe locking since IRQ handlers may access these XArrays */ xa_erase_irq(&adev->userq_doorbell_xa, queue->doorbell_index); - amdgpu_userq_fence_driver_free(queue); - queue->fence_drv = NULL; - up_read(&adev->reset_domain->sem); } @@ -551,18 +544,19 @@ amdgpu_userq_destroy(struct amdgpu_userq_mgr *uq_mgr, struct amdgpu_usermode_que cancel_delayed_work_sync(&uq_mgr->resume_work); - /* Cancel any pending hang detection work and cleanup */ - cancel_delayed_work_sync(&queue->hang_detect_work); - mutex_lock(&uq_mgr->userq_mutex); amdgpu_userq_wait_for_last_fence(queue); + amdgpu_userq_detach_doorbell(queue); + cancel_delayed_work_sync(&queue->hang_detect_work); + #if defined(CONFIG_DEBUG_FS) debugfs_remove_recursive(queue->debugfs_queue); #endif r = amdgpu_userq_unmap_helper(queue); atomic_dec(&uq_mgr->userq_count[queue->queue_type]); - amdgpu_userq_cleanup(queue); + amdgpu_userq_fence_driver_free(queue); + queue->fence_drv = NULL; mutex_unlock(&uq_mgr->userq_mutex); /* @@ -574,7 +568,6 @@ amdgpu_userq_destroy(struct amdgpu_userq_mgr *uq_mgr, struct amdgpu_usermode_que if (r) queue_work(adev->reset_domain->wq, &uq_mgr->reset_work); - cancel_delayed_work_sync(&queue->hang_detect_work); uq_funcs->mqd_destroy(queue); queue->userq_mgr = NULL; @@ -748,7 +741,7 @@ amdgpu_userq_create(struct drm_file *filp, union drm_amdgpu_userq *args) ((queue->queue_type != AMDGPU_HW_IP_GFX) && (queue->queue_type != AMDGPU_HW_IP_COMPUTE))) { /* Serialize the map against an in-progress GPU reset (MES is - * unresponsive during recovery), matching amdgpu_userq_cleanup(). + * unresponsive during recovery), matching amdgpu_userq_detach_doorbell(). */ down_read(&adev->reset_domain->sem); r = amdgpu_userq_map_helper(queue); From 275c3332585bbabcefad109a6978cc0cbecf2008 Mon Sep 17 00:00:00 2001 From: Gilles Risch Date: Mon, 17 Aug 2026 02:43:26 +0200 Subject: [PATCH 211/641] drm/radeon: fix internal display on iMac11, 1 (RV770/DCE3.1) The Apple iMac11,1 (27-inch, Late 2009) uses a Mobility Radeon HD 4850 (RV770/DCE3.1) with a 2560x1440 internal panel on an internal DisplayPort path. Without this fix the display stays dark under KMS. This machine suffers from the same issue as iMac10,1 and iMac11,2: Apple routes the internal display through Link B of the DIG encoder instead of Link A. Add iMac11,1 to the existing DMI quirk and move the Apple-specific encoder assignment into its own block, independent of the DCE version check. Additionally, the 2560x1440 panel requires RADEON_PLL_USE_FRAC_FB_DIV and ATOM_ENCODER_CMD_DP_VIDEO_ON, limited to iMac11,1 via dmi_match() to avoid affecting other boards. Reviewed-by: Lukas Wunner Signed-off-by: Gilles Risch Signed-off-by: Alex Deucher --- drivers/gpu/drm/radeon/atombios_crtc.c | 5 ++++- drivers/gpu/drm/radeon/atombios_encoders.c | 23 ++++++++++++---------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/radeon/atombios_crtc.c b/drivers/gpu/drm/radeon/atombios_crtc.c index 2fc0334e0d6c..075eba2d47f3 100644 --- a/drivers/gpu/drm/radeon/atombios_crtc.c +++ b/drivers/gpu/drm/radeon/atombios_crtc.c @@ -24,6 +24,8 @@ * Alex Deucher */ +#include + #include #include #include @@ -594,7 +596,8 @@ static u32 atombios_adjust_pll(struct drm_crtc *crtc, if (((rdev->family == CHIP_RS780) || (rdev->family == CHIP_RS880)) && !radeon_crtc->ss_enabled) radeon_crtc->pll_flags |= RADEON_PLL_USE_FRAC_FB_DIV; - if (ASIC_IS_DCE32(rdev) && mode->clock > 165000) + if ((ASIC_IS_DCE32(rdev) || dmi_match(DMI_PRODUCT_NAME, "iMac11,1")) + && mode->clock > 165000) radeon_crtc->pll_flags |= RADEON_PLL_USE_FRAC_FB_DIV; } else { radeon_crtc->pll_flags |= RADEON_PLL_LEGACY; diff --git a/drivers/gpu/drm/radeon/atombios_encoders.c b/drivers/gpu/drm/radeon/atombios_encoders.c index 5cfd8fcfa5e8..8b3f8303a967 100644 --- a/drivers/gpu/drm/radeon/atombios_encoders.c +++ b/drivers/gpu/drm/radeon/atombios_encoders.c @@ -1707,7 +1707,7 @@ radeon_atom_encoder_dpms_dig(struct drm_encoder *encoder, int mode) if (ENCODER_MODE_IS_DP(atombios_get_encoder_mode(encoder)) && connector) { /* DP_SET_POWER_D0 is set in radeon_dp_link_train */ radeon_dp_link_train(encoder, connector); - if (ASIC_IS_DCE4(rdev)) + if (ASIC_IS_DCE4(rdev) || dmi_match(DMI_PRODUCT_NAME, "iMac11,1")) atombios_dig_encoder_setup(encoder, ATOM_ENCODER_CMD_DP_VIDEO_ON, 0); } if (radeon_encoder->devices & (ATOM_DEVICE_LCD_SUPPORT)) { @@ -2123,17 +2123,20 @@ int radeon_atom_pick_dig_encoder(struct drm_encoder *encoder, int fe_idx) } /* - * On DCE32 any encoder can drive any block so usually just use crtc id, - * but Apple thinks different at least on iMac10,1 and iMac11,2, so there use linkb, - * otherwise the internal eDP panel will stay dark. + * Apple routes the internal eDP panel through Link B of the DIG encoder + * instead of Link A on the iMac10,1, iMac11,1 and iMac11,2. + * Use linkb to avoid a dark display. */ - if (ASIC_IS_DCE32(rdev)) { - if (dmi_match(DMI_PRODUCT_NAME, "iMac10,1") || - dmi_match(DMI_PRODUCT_NAME, "iMac11,2")) - enc_idx = (dig->linkb) ? 1 : 0; - else - enc_idx = radeon_crtc->crtc_id; + if (dmi_match(DMI_PRODUCT_NAME, "iMac10,1") || + dmi_match(DMI_PRODUCT_NAME, "iMac11,1") || + dmi_match(DMI_PRODUCT_NAME, "iMac11,2")) { + enc_idx = (dig->linkb) ? 1 : 0; + goto assigned; + } + /* on DCE32 and encoder can driver any block so just crtc id */ + if (ASIC_IS_DCE32(rdev)) { + enc_idx = radeon_crtc->crtc_id; goto assigned; } From 4d7390530853eb7befda9cc786e4c86e8ad7ac9e Mon Sep 17 00:00:00 2001 From: "David (Ming Qiang) Wu" Date: Fri, 7 Aug 2026 15:12:14 -0400 Subject: [PATCH 212/641] drm/amdgpu/vcn: fix integer overflow in dec_msg buffer count check If the supplied msg[2] (num_buffers) is 0x3FFFFFFF, the expression 6 + num_buffers * 4 wraps to 2 and the bounds check passes, letting the parser loop far past the end of the message BO. Triggering it additionally requires a ~4GiB mapping so that msg[1] survives the earlier "header does not fit in BO" check. Rewrite the test in division form, which is overflow-free by construction. Also update the message to reflect that msg is invalid. Fixes: b193019860d6 ("drm/amdgpu/vcn3: Prevent OOB reads when parsing dec msg") Fixes: 0a78f2bac142 ("drm/amdgpu/vcn4: Prevent OOB reads when parsing dec msg") Cc: stable@vger.kernel.org Signed-off-by: David (Ming Qiang) Wu Reviewed-by: Leo Liu Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/vcn_v3_0.c | 10 +++++++--- drivers/gpu/drm/amd/amdgpu/vcn_v4_0.c | 10 +++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/vcn_v3_0.c b/drivers/gpu/drm/amd/amdgpu/vcn_v3_0.c index 81bba3ec2a93..00d8f35846f2 100644 --- a/drivers/gpu/drm/amd/amdgpu/vcn_v3_0.c +++ b/drivers/gpu/drm/amd/amdgpu/vcn_v3_0.c @@ -1964,9 +1964,13 @@ static int vcn_v3_0_dec_msg(struct amdgpu_cs_parser *p, struct amdgpu_job *job, len_dw = msg[1] / 4; num_buffers = msg[2]; - /* Verify that all indices fit within the claimed length. Each index is 4 DWORDs */ - if (num_buffers > len_dw || 6 + num_buffers * 4 > len_dw) { - DRM_ERROR("VCN message has too many buffers!\n"); + /* Verify that all indices fit within the claimed length. + * There are 6 dwords in the header before the first buffer. + * Each buffer has 4 dwords. Any trailing dwords after the + * last buffer are ignored. + */ + if (len_dw < 6 || num_buffers > (len_dw - 6) / 4) { + DRM_ERROR("Invalid VCN message!\n"); r = -EINVAL; goto out; } diff --git a/drivers/gpu/drm/amd/amdgpu/vcn_v4_0.c b/drivers/gpu/drm/amd/amdgpu/vcn_v4_0.c index 0cce78b205a8..c2ddf3cb368f 100644 --- a/drivers/gpu/drm/amd/amdgpu/vcn_v4_0.c +++ b/drivers/gpu/drm/amd/amdgpu/vcn_v4_0.c @@ -1880,9 +1880,13 @@ static int vcn_v4_0_dec_msg(struct amdgpu_cs_parser *p, struct amdgpu_job *job, len_dw = msg[1] / 4; num_buffers = msg[2]; - /* Verify that all indices fit within the claimed length. Each index is 4 DWORDs */ - if (num_buffers > len_dw || 6 + num_buffers * 4 > len_dw) { - DRM_ERROR("VCN message has too many buffers!\n"); + /* Verify that all indices fit within the claimed length. + * There are 6 dwords in the header before the first buffer. + * Each buffer has 4 dwords. Any trailing dwords after the + * last buffer are ignored. + */ + if (len_dw < 6 || num_buffers > (len_dw - 6) / 4) { + DRM_ERROR("Invalid VCN message!\n"); r = -EINVAL; goto out; } From 0e4ef0ead600e367b4dd431daa97a345a5ff8a86 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Fri, 14 Aug 2026 11:39:16 -0400 Subject: [PATCH 213/641] drm/amdgpu: handle pipeline sync without a VM fence If we end up emitting a VM fence keep pipeline sync associated with that fence. If not, emit them as part of the IB fence. v2: fix need_pipe_sync handling v3: simplify the function Cc: David Rosca Fixes: cb1e657ccac8 ("drm/amdgpu: handle GDS and SPM without a VM fence") Reviewed-by: David Rosca Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c | 6 +++++- drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c | 27 +++++++++++++------------- drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h | 2 +- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c index da4dc489e80b..360e6f00cb7c 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c @@ -222,7 +222,7 @@ int amdgpu_ib_schedule(struct amdgpu_ring *ring, unsigned int num_ibs, vm_af = job->hw_vm_fence; /* VM sequence */ vm_af->ib_wptr = ring->wptr; - amdgpu_vm_flush(ring, job, need_pipe_sync, &emit_spm_needed, + amdgpu_vm_flush(ring, job, &need_pipe_sync, &emit_spm_needed, &emit_gds_needed); vm_af->ib_dw_size = amdgpu_ring_get_dw_distance(ring, vm_af->ib_wptr, ring->wptr); @@ -235,6 +235,10 @@ int amdgpu_ib_schedule(struct amdgpu_ring *ring, unsigned int num_ibs, if (ring->funcs->insert_start) ring->funcs->insert_start(ring); + /* this may have been handled by amdgpu_vm_flush */ + if (need_pipe_sync) + amdgpu_ring_emit_pipeline_sync(ring); + if (emit_spm_needed) adev->gfx.rlc.funcs->update_spm_vmid(adev, ring->xcc_id, ring, job->vmid); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c index 71050a86bcc3..f6c5de63eae6 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c @@ -772,7 +772,7 @@ bool amdgpu_vm_need_pipeline_sync(struct amdgpu_ring *ring, * Emit a VM flush when it is necessary. */ void amdgpu_vm_flush(struct amdgpu_ring *ring, struct amdgpu_job *job, - bool need_pipe_sync, bool *emit_spm_needed, + bool *need_pipe_sync, bool *emit_spm_needed, bool *emit_gds_needed) { struct amdgpu_device *adev = ring->adev; @@ -827,8 +827,7 @@ void amdgpu_vm_flush(struct amdgpu_ring *ring, struct amdgpu_job *job, if (gds_switch_needed && emit_fence) *emit_gds_needed = false; - if (!vm_flush_needed && !gds_switch_needed && !need_pipe_sync && - !cleaner_shader_needed && !spm_update_needed) + if (!emit_fence) return; amdgpu_ring_ib_begin(ring); @@ -847,8 +846,10 @@ void amdgpu_vm_flush(struct amdgpu_ring *ring, struct amdgpu_job *job, patch = amdgpu_ring_init_cond_exec(ring, ring->cond_exe_gpu_addr); - if (need_pipe_sync) + if (*need_pipe_sync) { amdgpu_ring_emit_pipeline_sync(ring); + *need_pipe_sync = false; + } if (cleaner_shader_needed) ring->funcs->emit_cleaner_shader(ring); @@ -861,21 +862,19 @@ void amdgpu_vm_flush(struct amdgpu_ring *ring, struct amdgpu_job *job, if (pasid_mapping_needed) amdgpu_gmc_emit_pasid_mapping(ring, job->vmid, job->pasid); - if (emit_fence) { - if (spm_update_needed) - adev->gfx.rlc.funcs->update_spm_vmid(adev, ring->xcc_id, ring, job->vmid); + if (spm_update_needed) + adev->gfx.rlc.funcs->update_spm_vmid(adev, ring->xcc_id, ring, job->vmid); - if (gds_switch_needed) - amdgpu_ring_emit_gds_switch(ring, job->vmid, job->gds_base, + if (gds_switch_needed) + amdgpu_ring_emit_gds_switch(ring, job->vmid, job->gds_base, job->gds_size, job->gws_base, job->gws_size, job->oa_base, job->oa_size); - amdgpu_fence_emit(ring, job->hw_vm_fence, 0); - fence = &job->hw_vm_fence->base; - /* get a ref for the job */ - dma_fence_get(fence); - } + amdgpu_fence_emit(ring, job->hw_vm_fence, 0); + fence = &job->hw_vm_fence->base; + /* get a ref for the job */ + dma_fence_get(fence); if (vm_flush_needed) { mutex_lock(&id_mgr->lock); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h index 7f2ba728e3ed..d32183cd9e0f 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h @@ -512,7 +512,7 @@ int amdgpu_vm_validate(struct amdgpu_device *adev, struct amdgpu_vm *vm, int (*callback)(void *p, struct amdgpu_bo *bo), void *param); void amdgpu_vm_flush(struct amdgpu_ring *ring, struct amdgpu_job *job, - bool need_pipe_sync, bool *emit_spm_needed, + bool *need_pipe_sync, bool *emit_spm_needed, bool *emit_gds_needed); int amdgpu_vm_update_pdes(struct amdgpu_device *adev, struct amdgpu_vm *vm, bool immediate); From 4f40873f8a4107df2b9c8e68c947c4fd0cd519d2 Mon Sep 17 00:00:00 2001 From: Harry Wentland Date: Tue, 4 Aug 2026 17:04:04 -0400 Subject: [PATCH 214/641] drm/amd/display: avoid divide-by-zero in __is_lut_linear() __is_lut_linear() computes the expected value of each entry with expected = i * MAX_DRM_LUT_VALUE / (size - 1); If it is ever called with a single-entry LUT, size - 1 is zero and the kernel takes a divide error (#DE). A LUT with fewer than two entries cannot describe a linear mapping anyway, so return false early instead of dividing by zero. Fixes: 086247a4b2fb ("drm/amd/display: Use 4096 lut entries") Cc: stable@vger.kernel.org Signed-off-by: Harry Wentland Reviewed-by: Melissa Wen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c index 7b68c6846039..26e5c89375a5 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c @@ -471,6 +471,12 @@ bool __is_lut_linear(const struct drm_color_lut *lut, uint32_t size) uint32_t expected; int delta; + /* A LUT with fewer than two entries can't be interpolated and would + * divide by zero below (size - 1); it can't be treated as linear. + */ + if (size < 2) + return false; + for (i = 0; i < size; i++) { /* All color values should equal */ if ((lut[i].red != lut[i].green) || (lut[i].green != lut[i].blue)) From e4c3ab59021e7c146a84b6671f0d530972bd58b4 Mon Sep 17 00:00:00 2001 From: Harry Wentland Date: Tue, 4 Aug 2026 17:04:05 -0400 Subject: [PATCH 215/641] drm/amd/display: validate plane degamma LUT size for private color prop Unlike the CRTC degamma path, which is guarded by amdgpu_dm_verify_lut_sizes(), the per-plane degamma LUT size was never validated before use. __set_dm_plane_degamma() passed the user-supplied size straight into __is_lut_linear() and, for a non-linear LUT, into __set_input_tf() -> __drm_lut_to_dc_gamma(), the latter always iterating MAX_COLOR_LUT_ENTRIES entries regardless of the actual LUT size. A malformed AMD_PLANE_DEGAMMA_LUT blob (e.g. a single entry) could thus trigger a divide-by-zero in __is_lut_linear() or an out-of-bounds read in __drm_lut_to_dc_gamma(). Reject any plane degamma LUT whose size does not match MAX_COLOR_LUT_ENTRIES, mirroring the invariant the code already asserts a few lines below (and which the CRTC path enforces). The AMD_PLANE_DEGAMMA_LUT property is only exposed on builds with AMD_PRIVATE_COLOR defined. Fixes: 980f8710075a ("drm/amd/display: add plane degamma TF and LUT support") Cc: stable@vger.kernel.org Signed-off-by: Harry Wentland Reviewed-by: Melissa Wen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c index 26e5c89375a5..d55dc06167a8 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c @@ -1495,6 +1495,13 @@ __set_dm_plane_degamma(struct drm_plane_state *plane_state, degamma_lut = __extract_blob_lut(dm_plane_state->degamma_lut, °amma_size); + if (degamma_lut && degamma_size != MAX_COLOR_LUT_ENTRIES) { + drm_dbg(plane_state->state->dev, + "Invalid Plane Degamma LUT size. Should be %u but got %u.\n", + MAX_COLOR_LUT_ENTRIES, degamma_size); + return -EINVAL; + } + has_degamma_lut = degamma_lut && !__is_lut_linear(degamma_lut, degamma_size); From 6fd1b9225de1b09cd8dd79e1ccba8d84b4e94036 Mon Sep 17 00:00:00 2001 From: Andrey Golovko Date: Thu, 13 Aug 2026 19:40:00 +0300 Subject: [PATCH 216/641] ASoC: sdw_utils: prepare the stream again when resuming A peripheral loses its Data Port configuration when the controller is power-gated during system suspend, so the ports have to be prepared again before the stream can be enabled. That happens on its own when userspace calls snd_pcm_prepare() after SNDRV_PCM_STATE_SUSPENDED, but an application is also allowed to restart the stream with SNDRV_PCM_IOCTL_RESUME on a card that advertises SNDRV_PCM_INFO_RESUME, as the AMD ACP platforms do. That path only reaches the trigger callback, sdw_enable_stream() writes the channels of ports that were never prepared, and playback silently produces nothing: the PCM keeps running, no error is reported anywhere, and the speakers stay quiet until the stream is torn down and set up again. Prepare the stream on SNDRV_PCM_TRIGGER_RESUME, before enabling it. The SoundWire core expects exactly this: sdw_prepare_stream() accepts a disabled stream and then reapplies the bus parameters without recomputing them, which it documents as the resume case. Signed-off-by: Andrey Golovko Link: https://patch.msgid.link/20260813194000.10412-2-andrey.golovko@gmail.com Signed-off-by: Mark Brown --- sound/soc/sdw_utils/soc_sdw_utils.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index 8a07ba2a29e5..3e091e78d8c2 100644 --- a/sound/soc/sdw_utils/soc_sdw_utils.c +++ b/sound/soc/sdw_utils/soc_sdw_utils.c @@ -1510,9 +1510,24 @@ int asoc_sdw_trigger(struct snd_pcm_substream *substream, int cmd) } switch (cmd) { + case SNDRV_PCM_TRIGGER_RESUME: + /* + * The peripherals lose their port configuration when the + * controller is power-gated during system suspend, and an + * application that restarts the stream with + * SNDRV_PCM_IOCTL_RESUME - which platforms advertising + * SNDRV_PCM_INFO_RESUME allow - never goes through + * .prepare() again. Prepare the stream here so that the + * ports are reprogrammed before they are enabled; + * sdw_prepare_stream() reapplies the parameters without + * recomputing them when the stream is disabled. + */ + ret = sdw_prepare_stream(sdw_stream); + if (ret) + break; + fallthrough; case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - case SNDRV_PCM_TRIGGER_RESUME: ret = sdw_enable_stream(sdw_stream); break; From 119046319e773ff86b98e3ab67623443f8481d7b Mon Sep 17 00:00:00 2001 From: Andrey Golovko Date: Thu, 13 Aug 2026 19:40:00 +0300 Subject: [PATCH 217/641] ASoC: tas2783-sdw: power the Function up before preparing the port A Data Port cannot complete channel preparation while the SDCA Function is powered down: the peripheral raises the channel's bit in DPn_PrepareStatus and never clears it. tas_sdw_hw_params() takes care of that for a stream that is being set up, and the retry loop there says so - "ensure power on so that port prepare succeeds". Port preparation, however, also happens on a stream that is merely re-prepared, without hw_params() running again. That is what userspace does after a suspend in which the peripheral lost power: snd_pcm_prepare() reaches .prepare and sdw_prepare_stream(), the port is prepared afresh, but PDE23 is still at the PS3 reset default because nothing wrote it since the device came back. The result is silence with no error anywhere. The codec sets simple_ch_prep_sm, so sdw_prep_deprep_slave_port() skips the NOT_PREPARED poll, and a port that never prepares is indistinguishable from a healthy one. Power the Function up in the PRE_PREP callback, immediately before the PrepareCtrl write it already performs, so that preparation has what it needs on every path that prepares a port. Measured on an ASUS ProArt PX13 (AMD ACP7.0, two TAS2783): after s2idle with ~100 s of S0i3 residency, DPn_PrepareStatus stays at the channel mask and there is no audio; writing PDE23 PS0 and re-issuing the prepare clears it within 1 ms and audio returns. Signed-off-by: Andrey Golovko Link: https://patch.msgid.link/20260813194000.10412-3-andrey.golovko@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/tas2783-sdw.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c index c217da5fccdf..d32d8f441651 100644 --- a/sound/soc/codecs/tas2783-sdw.c +++ b/sound/soc/codecs/tas2783-sdw.c @@ -1259,6 +1259,7 @@ static int tas_port_prep(struct sdw_slave *slave, struct sdw_prepare_ch *prep_ch enum sdw_port_prep_ops pre_ops) { struct device *dev = &slave->dev; + struct tas2783_prv *tas_dev = dev_get_drvdata(dev); struct sdw_dpn_prop *dpn_prop; u32 addr; int ret; @@ -1270,6 +1271,25 @@ static int tas_port_prep(struct sdw_slave *slave, struct sdw_prepare_ch *prep_ch addr = SDW_DPN_PREPARECTRL(prep_ch->num); switch (pre_ops) { case SDW_OPS_PORT_PRE_PREP: + /* + * The Function has to be powered before the port can complete + * channel preparation. hw_params() does that when a stream is + * set up, but a stream that is only re-prepared - as it is + * after the peripheral lost power in S0i3 - does not go + * through hw_params() again, and the peripheral is back at its + * PS3 reset default. Power it up here, where it is needed. + */ + scoped_guard(mutex, &tas_dev->pde_lock) + ret = regmap_write(tas_dev->regmap, + SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PDE23, + TAS2783_SDCA_CTL_REQ_POW_STATE, 0), + TAS2783_SDCA_POW_STATE_ON); + if (ret) { + dev_err(dev, "power up failed for port %d, err=%d\n", + prep_ch->num, ret); + return ret; + } + ret = sdw_write_no_pm(slave, addr, prep_ch->ch_mask); if (ret) dev_err(dev, "prep failed for port %d, err=%d\n", From 092755367e1a5fcf0f5405e9425e86907199dfce Mon Sep 17 00:00:00 2001 From: Surendra Singh Chouhan Date: Fri, 24 Jul 2026 19:28:03 +0530 Subject: [PATCH 218/641] rtc: spacemit: handle regmap_test_bits() error return p1_rtc_read_time() called if (!regmap_test_bits(regmap, RTC_CTRL, RTC_EN)) to check if the RTC was enabled. regmap_test_bits() returns 1 if the bit is set, 0 if not set, and a negative error code (e.g. -EIO) if reading the control register fails. Using !regmap_test_bits(...) evaluates a negative error code as boolean false, causing I2C/regmap read failures to be ignored and incorrectly proceeding to read time registers from a failing device. Fix this by capturing the return value of regmap_test_bits() and returning the error code if negative, or -EINVAL if the RTC is disabled. Fixes: a6de182daa2b ("rtc: spacemit: support the SpacemiT P1 RTC") Reviewed-by: Alex Elder Signed-off-by: Surendra Singh Chouhan Link: https://patch.msgid.link/20260724135803.81223-1-kr494167@gmail.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-spacemit-p1.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/rtc/rtc-spacemit-p1.c b/drivers/rtc/rtc-spacemit-p1.c index 43ab62494bb4..1de7bd995d29 100644 --- a/drivers/rtc/rtc-spacemit-p1.c +++ b/drivers/rtc/rtc-spacemit-p1.c @@ -57,8 +57,9 @@ static int p1_rtc_read_time(struct device *dev, struct rtc_time *t) u8 time[6]; int ret; - if (!regmap_test_bits(regmap, RTC_CTRL, RTC_EN)) - return -EINVAL; /* RTC is disabled */ + ret = regmap_test_bits(regmap, RTC_CTRL, RTC_EN); + if (ret <= 0) + return ret ?: -EINVAL; /* RTC is disabled or error */ ret = regmap_bulk_read(regmap, RTC_TIME, time, sizeof(time)); if (ret) From 2943ec93ee6fb4683b73e66ac516b62df091e41b Mon Sep 17 00:00:00 2001 From: Jack Lee Date: Fri, 12 Jun 2026 16:35:34 -0600 Subject: [PATCH 219/641] rtc: ds1343: replace symbolic permissions with octal Symbolic permissions S_IRUGO and S_IWUSR are deprecated in favor of octal permissions. Replace S_IRUGO|S_IWUSR with 0644 and S_IRUGO with 0444. Signed-off-by: Jack Lee Link: https://patch.msgid.link/20260612223534.77412-1-skunkolee@gmail.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-ds1343.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/rtc/rtc-ds1343.c b/drivers/rtc/rtc-ds1343.c index aa9500791b7e..54f6af7886a8 100644 --- a/drivers/rtc/rtc-ds1343.c +++ b/drivers/rtc/rtc-ds1343.c @@ -120,7 +120,7 @@ static ssize_t ds1343_store_glitchfilter(struct device *dev, return count; } -static DEVICE_ATTR(glitch_filter, S_IRUGO | S_IWUSR, ds1343_show_glitchfilter, +static DEVICE_ATTR(glitch_filter, 0644, ds1343_show_glitchfilter, ds1343_store_glitchfilter); static int ds1343_nvram_write(void *priv, unsigned int off, void *val, @@ -183,7 +183,7 @@ static ssize_t ds1343_show_tricklecharger(struct device *dev, return sprintf(buf, "%s %s\n", diodes, resistors); } -static DEVICE_ATTR(trickle_charger, S_IRUGO, ds1343_show_tricklecharger, NULL); +static DEVICE_ATTR(trickle_charger, 0444, ds1343_show_tricklecharger, NULL); static struct attribute *ds1343_attrs[] = { &dev_attr_glitch_filter.attr, From 5904c4109554582ce8ded6a4dcfede48b21dbe4d Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 24 Jun 2026 13:55:24 +0800 Subject: [PATCH 220/641] rtc: zynqmp: Return optional clock lookup errors devm_clk_get_optional() returns NULL when the optional clock is absent, but returns an ERR_PTR when the clock provider lookup fails. Probe currently keeps the ERR_PTR and then passes it to clk_get_rate(). Return the lookup error instead. A truly absent optional clock still reaches the existing calibration fallback through clk_get_rate(NULL). Signed-off-by: Pengpeng Hou Fixes: 07dcc6f9c762 ("rtc: zynqmp: Add calibration set and get support") Reviewed-by: Michal Simek Link: https://patch.msgid.link/20260624055524.38522-1-pengpeng@iscas.ac.cn Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-zynqmp.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/rtc/rtc-zynqmp.c b/drivers/rtc/rtc-zynqmp.c index 2ae54804b87a..5bcb7536e973 100644 --- a/drivers/rtc/rtc-zynqmp.c +++ b/drivers/rtc/rtc-zynqmp.c @@ -334,10 +334,9 @@ static int xlnx_rtc_probe(struct platform_device *pdev) /* Getting the rtc info */ xrtcdev->rtc_clk = devm_clk_get_optional(&pdev->dev, "rtc"); - if (IS_ERR(xrtcdev->rtc_clk)) { - if (PTR_ERR(xrtcdev->rtc_clk) != -EPROBE_DEFER) - dev_warn(&pdev->dev, "Device clock not found.\n"); - } + if (IS_ERR(xrtcdev->rtc_clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(xrtcdev->rtc_clk), + "Failed to get rtc clock\n"); xrtcdev->freq = clk_get_rate(xrtcdev->rtc_clk); if (!xrtcdev->freq) { ret = of_property_read_u32(pdev->dev.of_node, "calibration", From f1daef2834042f2d45e120f55a74bd76a711e5c2 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sat, 4 Jul 2026 20:20:03 +0800 Subject: [PATCH 221/641] rtc: s32g: add missing MODULE_DEVICE_TABLE() The driver has an OF match table wired to .of_match_table, but does not export the table with MODULE_DEVICE_TABLE(). Add the missing MODULE_DEVICE_TABLE(of, ...) entry so module alias information is generated for OF based module autoloading. This is a source-level fix. It does not claim dynamic hardware reproduction; the evidence is the driver-owned match table, its use by the platform driver, and the missing module alias publication. Signed-off-by: Pengpeng Hou Reviewed-by: Matthias Brugger Reviewed-by: Ciprian Marian Costea Link: https://patch.msgid.link/20260704122003.70108-1-pengpeng@iscas.ac.cn Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-s32g.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/rtc/rtc-s32g.c b/drivers/rtc/rtc-s32g.c index 3a0818e972eb..86716192d10f 100644 --- a/drivers/rtc/rtc-s32g.c +++ b/drivers/rtc/rtc-s32g.c @@ -366,6 +366,7 @@ static const struct of_device_id rtc_dt_ids[] = { { .compatible = "nxp,s32g2-rtc", .data = &rtc_s32g2_data }, { /* sentinel */ }, }; +MODULE_DEVICE_TABLE(of, rtc_dt_ids); static DEFINE_SIMPLE_DEV_PM_OPS(s32g_rtc_pm_ops, s32g_rtc_suspend, s32g_rtc_resume); From 402c89d28fecd28652a4bfe997f08f6544836381 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sat, 4 Jul 2026 20:17:34 +0800 Subject: [PATCH 222/641] rtc: brcmstb-waketimer: add missing MODULE_DEVICE_TABLE() The driver has an OF match table wired to .of_match_table, but does not export the table with MODULE_DEVICE_TABLE(). Add the missing MODULE_DEVICE_TABLE(of, ...) entry so module alias information is generated for OF based module autoloading. This is a source-level fix. It does not claim dynamic hardware reproduction; the evidence is the driver-owned match table, its use by the platform driver, and the missing module alias publication. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260704121734.54941-1-pengpeng@iscas.ac.cn Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-brcmstb-waketimer.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/rtc/rtc-brcmstb-waketimer.c b/drivers/rtc/rtc-brcmstb-waketimer.c index fb47c32ab5ff..55c15aad42cf 100644 --- a/drivers/rtc/rtc-brcmstb-waketimer.c +++ b/drivers/rtc/rtc-brcmstb-waketimer.c @@ -413,6 +413,7 @@ static const __maybe_unused struct of_device_id brcmstb_waketmr_of_match[] = { { .compatible = "brcm,brcmstb-waketimer" }, { /* sentinel */ }, }; +MODULE_DEVICE_TABLE(of, brcmstb_waketmr_of_match); static struct platform_driver brcmstb_waketmr_driver = { .probe = brcmstb_waketmr_probe, From 7490b8356e52e09c196645912f2c054f79aa5f3a Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sat, 4 Jul 2026 23:25:28 +0800 Subject: [PATCH 223/641] rtc: ab-eoz9: add missing MODULE_DEVICE_TABLE() The driver has a match table for the i2c bus wired into its driver structure, but the table is not exported with MODULE_DEVICE_TABLE(). Add the missing MODULE_DEVICE_TABLE() entry so module alias information is generated for automatic module loading. This is a source-level fix. It does not claim dynamic hardware reproduction; the evidence is the driver-owned match table, its use by the driver registration structure, and the missing module alias publication. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260704152528.53258-1-pengpeng@iscas.ac.cn Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-ab-eoz9.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/rtc/rtc-ab-eoz9.c b/drivers/rtc/rtc-ab-eoz9.c index b75f4f665076..eb6d267c2ec3 100644 --- a/drivers/rtc/rtc-ab-eoz9.c +++ b/drivers/rtc/rtc-ab-eoz9.c @@ -549,6 +549,7 @@ static const struct i2c_device_id abeoz9_id[] = { { .name = "abeoz9" }, { } }; +MODULE_DEVICE_TABLE(i2c, abeoz9_id); static struct i2c_driver abeoz9_driver = { .driver = { From bcad3667a33750905fbb7470de5727bf8e37186d Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Thu, 25 Jun 2026 15:02:00 +0200 Subject: [PATCH 224/641] rtc: mv: fix potential race condition Since the driver allocates the IRQ using devm_request_irq(), this means the IRQ is going to be automatically unregistered by devres after mv_rtc_remove() returns. However, mv_rtc_remove() explicitly disables the hardware clock before devres teardown happens so the interrupt handler may run while the clock is disabled leading to a possible bus hang when accessing registers. Link: https://patch.msgid.link/20260625130202.1621692-1-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-mv.c | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/drivers/rtc/rtc-mv.c b/drivers/rtc/rtc-mv.c index f88976fd6d5d..c0f1bcd838e3 100644 --- a/drivers/rtc/rtc-mv.c +++ b/drivers/rtc/rtc-mv.c @@ -219,17 +219,15 @@ static int __init mv_rtc_probe(struct platform_device *pdev) if (IS_ERR(pdata->ioaddr)) return PTR_ERR(pdata->ioaddr); - pdata->clk = devm_clk_get(&pdev->dev, NULL); - /* Not all SoCs require a clock.*/ - if (!IS_ERR(pdata->clk)) - clk_prepare_enable(pdata->clk); + pdata->clk = devm_clk_get_optional_prepared(&pdev->dev, NULL); + if (IS_ERR(pdata->clk)) + return PTR_ERR(pdata->clk); /* make sure the 24 hour mode is enabled */ rtc_time = readl(pdata->ioaddr + RTC_TIME_REG_OFFS); if (rtc_time & RTC_HOURS_12H_MODE) { dev_err(&pdev->dev, "12 Hour mode is enabled but not supported.\n"); - ret = -EINVAL; - goto out; + return -EINVAL; } /* make sure it is actually functional */ @@ -238,8 +236,7 @@ static int __init mv_rtc_probe(struct platform_device *pdev) rtc_time = readl(pdata->ioaddr + RTC_TIME_REG_OFFS); if (rtc_time == 0x01000000) { dev_err(&pdev->dev, "internal RTC not ticking\n"); - ret = -ENODEV; - goto out; + return -ENODEV; } } @@ -249,8 +246,7 @@ static int __init mv_rtc_probe(struct platform_device *pdev) pdata->rtc = devm_rtc_allocate_device(&pdev->dev); if (IS_ERR(pdata->rtc)) { - ret = PTR_ERR(pdata->rtc); - goto out; + return PTR_ERR(pdata->rtc); } if (pdata->irq >= 0) { @@ -275,9 +271,6 @@ static int __init mv_rtc_probe(struct platform_device *pdev) ret = devm_rtc_register_device(pdata->rtc); if (!ret) return 0; -out: - if (!IS_ERR(pdata->clk)) - clk_disable_unprepare(pdata->clk); return ret; } @@ -288,9 +281,6 @@ static void __exit mv_rtc_remove(struct platform_device *pdev) if (pdata->irq >= 0) device_init_wakeup(&pdev->dev, false); - - if (!IS_ERR(pdata->clk)) - clk_disable_unprepare(pdata->clk); } #ifdef CONFIG_OF From 12b1843fee1fa7282b800a56faa360ee945630d1 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Thu, 25 Jun 2026 15:02:01 +0200 Subject: [PATCH 225/641] rtc: mv: remove mv_rtc_remove Use devm_device_init_wakeup() so we can avoid having to explicitly teardown of module removal. Link: https://patch.msgid.link/20260625130202.1621692-2-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-mv.c | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/drivers/rtc/rtc-mv.c b/drivers/rtc/rtc-mv.c index c0f1bcd838e3..db64c459ee19 100644 --- a/drivers/rtc/rtc-mv.c +++ b/drivers/rtc/rtc-mv.c @@ -260,7 +260,7 @@ static int __init mv_rtc_probe(struct platform_device *pdev) } if (pdata->irq >= 0) - device_init_wakeup(&pdev->dev, true); + devm_device_init_wakeup(&pdev->dev); else clear_bit(RTC_FEATURE_ALARM, pdata->rtc->features); @@ -275,14 +275,6 @@ static int __init mv_rtc_probe(struct platform_device *pdev) return ret; } -static void __exit mv_rtc_remove(struct platform_device *pdev) -{ - struct rtc_plat_data *pdata = platform_get_drvdata(pdev); - - if (pdata->irq >= 0) - device_init_wakeup(&pdev->dev, false); -} - #ifdef CONFIG_OF static const struct of_device_id rtc_mv_of_match_table[] = { { .compatible = "marvell,orion-rtc", }, @@ -313,14 +305,7 @@ static int mv_rtc_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(mv_rtc_pm_ops, mv_rtc_suspend, mv_rtc_resume); -/* - * mv_rtc_remove() lives in .exit.text. For drivers registered via - * module_platform_driver_probe() this is ok because they cannot get unbound at - * runtime. So mark the driver struct with __refdata to prevent modpost - * triggering a section mismatch warning. - */ -static struct platform_driver mv_rtc_driver __refdata = { - .remove = __exit_p(mv_rtc_remove), +static struct platform_driver mv_rtc_driver = { .driver = { .name = "rtc-mv", .of_match_table = of_match_ptr(rtc_mv_of_match_table), From da5e8713aee51dd73310bccf23d28446b39e7b0e Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Mon, 13 Jul 2026 17:22:59 -0700 Subject: [PATCH 226/641] rtc: st-lpc: get IRQ via platform_get_irq() Replace irq_of_parse_and_map() with platform_get_irq(), which resolves the interrupt from pdev->dev.of_node directly and returns a positive IRQ or a negative errno (it never returns 0). Propagate the error on failure instead of the previous open-coded "IRQ missing or invalid" / -EINVAL message, so -EPROBE_DEFER is handled correctly. The rtc->irq field is a signed short, so the negative error code is preserved. np is still used by of_property_read_u32() for "st,lpc-mode", so it is not removed. Built for ARM (multi_v7_defconfig + CONFIG_RTC_DRV_ST_LPC) with LLVM=1; drivers/rtc/rtc-st-lpc.o compiles cleanly. Assisted-by: opencode:hy3-free Signed-off-by: Rosen Penev Reviewed-by: Patrice Chotard Link: https://patch.msgid.link/20260714002259.1392655-1-rosenp@gmail.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-st-lpc.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/rtc/rtc-st-lpc.c b/drivers/rtc/rtc-st-lpc.c index c6d4522411b3..ae79ddf24f37 100644 --- a/drivers/rtc/rtc-st-lpc.c +++ b/drivers/rtc/rtc-st-lpc.c @@ -18,7 +18,6 @@ #include #include #include -#include #include #include @@ -212,11 +211,9 @@ static int st_rtc_probe(struct platform_device *pdev) if (IS_ERR(rtc->ioaddr)) return PTR_ERR(rtc->ioaddr); - rtc->irq = irq_of_parse_and_map(np, 0); - if (!rtc->irq) { - dev_err(&pdev->dev, "IRQ missing or invalid\n"); - return -EINVAL; - } + rtc->irq = platform_get_irq(pdev, 0); + if (rtc->irq < 0) + return rtc->irq; ret = devm_request_irq(&pdev->dev, rtc->irq, st_rtc_handler, IRQF_NO_AUTOEN, pdev->name, rtc); From 4fb8d6379d2c7ceecb2b3e111954d29089d59492 Mon Sep 17 00:00:00 2001 From: Liang Luo Date: Wed, 19 Aug 2026 11:12:44 +0800 Subject: [PATCH 227/641] sched_ext: Fix nonexistent field in sched-ext.rst example The ops.exit() example in sched-ext.rst reads ei->type, but struct scx_exit_info has never had a type field - the exit reason is exposed as ei->kind since the struct was introduced. A scheduler written following the example fails to compile with error: no member named 'type' in 'struct scx_exit_info' Use ei->kind. Fixes: fa48e8d2c7b5 ("sched_ext: Documentation: scheduler: Document extensible scheduler class") Signed-off-by: Liang Luo Signed-off-by: Tejun Heo --- Documentation/scheduler/sched-ext.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/scheduler/sched-ext.rst b/Documentation/scheduler/sched-ext.rst index 0e97fd019994..35b550671ca7 100644 --- a/Documentation/scheduler/sched-ext.rst +++ b/Documentation/scheduler/sched-ext.rst @@ -230,7 +230,7 @@ optional. The following modified excerpt is from void BPF_STRUCT_OPS(simple_exit, struct scx_exit_info *ei) { - exit_type = ei->type; + exit_type = ei->kind; } SEC(".struct_ops") From 0c893d170ff8efe7b4067552932d26e7defba307 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Wed, 19 Aug 2026 14:06:00 +0530 Subject: [PATCH 228/641] selftests/cgroup: set the test plan after the setup checks The cgroup tests announce their plan before checking whether cgroup v2 is available, so on a host without it they promise a number of results and then skip out after the first one: TAP version 13 1..3 ok 1 # SKIP cgroup v2 isn't mounted # Planned tests != run tests (3 != 1) # Totals: pass:0 fail:0 xfail:0 xpass:0 skip:1 error:0 ksft_exit_skip() can only emit a well formed "1..0 # SKIP" line while no plan has been printed, as the comment above it in kselftest.h points out. Move ksft_set_plan() below the setup checks that can skip, so that a skipped run reports: TAP version 13 1..0 # SKIP cgroup v2 isn't mounted Several of the tests skip more than once while setting up, for a missing or unwritable controller as well, so the plan goes after the last of them. test_core joins its two setup paths at the post_v2_setup label and sets the plan there. Reporting each planned test as skipped instead would keep the plan where it is, but the setup failures here mean the whole test cannot run rather than its individual cases being skipped, which is what "1..0 # SKIP" is for. Fixes: 1dc830ee4c15 ("selftests/cgroup: conform test to KTAP format output") Signed-off-by: Hemanth Selam Reviewed-by: Sarthak Sharma Signed-off-by: Tejun Heo --- tools/testing/selftests/cgroup/test_core.c | 2 +- tools/testing/selftests/cgroup/test_cpu.c | 2 +- tools/testing/selftests/cgroup/test_cpuset.c | 2 +- tools/testing/selftests/cgroup/test_freezer.c | 2 +- tools/testing/selftests/cgroup/test_kill.c | 2 +- tools/testing/selftests/cgroup/test_kmem.c | 2 +- tools/testing/selftests/cgroup/test_memcontrol.c | 2 +- tools/testing/selftests/cgroup/test_pids.c | 2 +- tools/testing/selftests/cgroup/test_zswap.c | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tools/testing/selftests/cgroup/test_core.c b/tools/testing/selftests/cgroup/test_core.c index e9bee164bb70..20d2b63774c3 100644 --- a/tools/testing/selftests/cgroup/test_core.c +++ b/tools/testing/selftests/cgroup/test_core.c @@ -919,7 +919,6 @@ int main(int argc, char *argv[]) int i; ksft_print_header(); - ksft_set_plan(ARRAY_SIZE(tests)); if (cg_find_unified_root(root, sizeof(root), &nsdelegate)) { if (setup_named_v1_root(root, sizeof(root), CG_NAMED_NAME)) ksft_exit_skip("cgroup v2 isn't mounted and could not setup named v1 hierarchy\n"); @@ -932,6 +931,7 @@ int main(int argc, char *argv[]) ksft_exit_skip("Failed to set memory controller\n"); post_v2_setup: + ksft_set_plan(ARRAY_SIZE(tests)); for (i = 0; i < ARRAY_SIZE(tests); i++) { switch (tests[i].fn(root)) { case KSFT_PASS: diff --git a/tools/testing/selftests/cgroup/test_cpu.c b/tools/testing/selftests/cgroup/test_cpu.c index f9f7017d9299..735a53bb222b 100644 --- a/tools/testing/selftests/cgroup/test_cpu.c +++ b/tools/testing/selftests/cgroup/test_cpu.c @@ -832,7 +832,6 @@ int main(int argc, char *argv[]) int i; ksft_print_header(); - ksft_set_plan(ARRAY_SIZE(tests)); if (cg_find_unified_root(root, sizeof(root), NULL)) ksft_exit_skip("cgroup v2 isn't mounted\n"); @@ -840,6 +839,7 @@ int main(int argc, char *argv[]) if (cg_write(root, "cgroup.subtree_control", "+cpu")) ksft_exit_skip("Failed to set cpu controller\n"); + ksft_set_plan(ARRAY_SIZE(tests)); for (i = 0; i < ARRAY_SIZE(tests); i++) { switch (tests[i].fn(root)) { case KSFT_PASS: diff --git a/tools/testing/selftests/cgroup/test_cpuset.c b/tools/testing/selftests/cgroup/test_cpuset.c index 8c2d4d4ef1fc..3dfadd280c1c 100644 --- a/tools/testing/selftests/cgroup/test_cpuset.c +++ b/tools/testing/selftests/cgroup/test_cpuset.c @@ -497,7 +497,6 @@ int main(int argc, char *argv[]) int i; ksft_print_header(); - ksft_set_plan(ARRAY_SIZE(tests)); if (cg_find_unified_root(root, sizeof(root), NULL)) ksft_exit_skip("cgroup v2 isn't mounted\n"); @@ -505,6 +504,7 @@ int main(int argc, char *argv[]) if (cg_write(root, "cgroup.subtree_control", "+cpuset")) ksft_exit_skip("Failed to set cpuset controller\n"); + ksft_set_plan(ARRAY_SIZE(tests)); for (i = 0; i < ARRAY_SIZE(tests); i++) { switch (tests[i].fn(root)) { case KSFT_PASS: diff --git a/tools/testing/selftests/cgroup/test_freezer.c b/tools/testing/selftests/cgroup/test_freezer.c index 0569e93fa6b0..f28bb02e9783 100644 --- a/tools/testing/selftests/cgroup/test_freezer.c +++ b/tools/testing/selftests/cgroup/test_freezer.c @@ -1491,9 +1491,9 @@ int main(int argc, char *argv[]) int i; ksft_print_header(); - ksft_set_plan(ARRAY_SIZE(tests)); if (cg_find_unified_root(root, sizeof(root), NULL)) ksft_exit_skip("cgroup v2 isn't mounted\n"); + ksft_set_plan(ARRAY_SIZE(tests)); for (i = 0; i < ARRAY_SIZE(tests); i++) { switch (tests[i].fn(root)) { case KSFT_PASS: diff --git a/tools/testing/selftests/cgroup/test_kill.c b/tools/testing/selftests/cgroup/test_kill.c index f6cd23a8ecc7..99cafd9dc013 100644 --- a/tools/testing/selftests/cgroup/test_kill.c +++ b/tools/testing/selftests/cgroup/test_kill.c @@ -278,9 +278,9 @@ int main(int argc, char *argv[]) int i; ksft_print_header(); - ksft_set_plan(ARRAY_SIZE(tests)); if (cg_find_unified_root(root, sizeof(root), NULL)) ksft_exit_skip("cgroup v2 isn't mounted\n"); + ksft_set_plan(ARRAY_SIZE(tests)); for (i = 0; i < ARRAY_SIZE(tests); i++) { switch (tests[i].fn(root)) { case KSFT_PASS: diff --git a/tools/testing/selftests/cgroup/test_kmem.c b/tools/testing/selftests/cgroup/test_kmem.c index 1db0ba1226b9..cb47561b4b44 100644 --- a/tools/testing/selftests/cgroup/test_kmem.c +++ b/tools/testing/selftests/cgroup/test_kmem.c @@ -426,7 +426,6 @@ int main(int argc, char **argv) int i; ksft_print_header(); - ksft_set_plan(ARRAY_SIZE(tests)); if (cg_find_unified_root(root, sizeof(root), NULL)) ksft_exit_skip("cgroup v2 isn't mounted\n"); @@ -441,6 +440,7 @@ int main(int argc, char **argv) if (cg_write(root, "cgroup.subtree_control", "+memory")) ksft_exit_skip("Failed to set memory controller\n"); + ksft_set_plan(ARRAY_SIZE(tests)); for (i = 0; i < ARRAY_SIZE(tests); i++) { switch (tests[i].fn(root)) { case KSFT_PASS: diff --git a/tools/testing/selftests/cgroup/test_memcontrol.c b/tools/testing/selftests/cgroup/test_memcontrol.c index 0ebf796f3cff..3a84d068fbf3 100644 --- a/tools/testing/selftests/cgroup/test_memcontrol.c +++ b/tools/testing/selftests/cgroup/test_memcontrol.c @@ -1798,7 +1798,6 @@ int main(int argc, char **argv) page_size = BUF_SIZE; ksft_print_header(); - ksft_set_plan(ARRAY_SIZE(tests)); if (cg_find_unified_root(root, sizeof(root), NULL)) ksft_exit_skip("cgroup v2 isn't mounted\n"); @@ -1823,6 +1822,7 @@ int main(int argc, char **argv) ksft_exit_skip("Failed to query cgroup mount option\n"); has_localevents = proc_status; + ksft_set_plan(ARRAY_SIZE(tests)); for (i = 0; i < ARRAY_SIZE(tests); i++) { switch (tests[i].fn(root)) { case KSFT_PASS: diff --git a/tools/testing/selftests/cgroup/test_pids.c b/tools/testing/selftests/cgroup/test_pids.c index 9a387c815d2c..710109b53dfe 100644 --- a/tools/testing/selftests/cgroup/test_pids.c +++ b/tools/testing/selftests/cgroup/test_pids.c @@ -148,7 +148,6 @@ int main(int argc, char **argv) char root[PATH_MAX]; ksft_print_header(); - ksft_set_plan(ARRAY_SIZE(tests)); if (cg_find_unified_root(root, sizeof(root), NULL)) ksft_exit_skip("cgroup v2 isn't mounted\n"); @@ -163,6 +162,7 @@ int main(int argc, char **argv) if (cg_write(root, "cgroup.subtree_control", "+pids")) ksft_exit_skip("Failed to set pids controller\n"); + ksft_set_plan(ARRAY_SIZE(tests)); for (int i = 0; i < ARRAY_SIZE(tests); i++) { switch (tests[i].fn(root)) { case KSFT_PASS: diff --git a/tools/testing/selftests/cgroup/test_zswap.c b/tools/testing/selftests/cgroup/test_zswap.c index 49b36ee79160..6e7b89315bbf 100644 --- a/tools/testing/selftests/cgroup/test_zswap.c +++ b/tools/testing/selftests/cgroup/test_zswap.c @@ -810,7 +810,6 @@ int main(int argc, char **argv) page_size = BUF_SIZE; ksft_print_header(); - ksft_set_plan(ARRAY_SIZE(tests)); if (cg_find_unified_root(root, sizeof(root), NULL)) ksft_exit_skip("cgroup v2 isn't mounted\n"); @@ -827,6 +826,7 @@ int main(int argc, char **argv) if (cg_write(root, "cgroup.subtree_control", "+memory")) ksft_exit_skip("Failed to set memory controller\n"); + ksft_set_plan(ARRAY_SIZE(tests)); for (i = 0; i < ARRAY_SIZE(tests); i++) { switch (tests[i].fn(root)) { case KSFT_PASS: From 7e2f2a377ac9f50296ad60bd331f0d4def7aee51 Mon Sep 17 00:00:00 2001 From: Zqiang Date: Thu, 16 Jul 2026 17:56:37 +0800 Subject: [PATCH 229/641] workqueue: Use raise_softirq() to trigger softirq in irq_work handler bh_pool_kick_normal() and bh_pool_kick_highpri() are registered via init_irq_work() without the IRQ_WORK_HARD_IRQ flag. On PREEMPT_RT, such irq_work items are processed by the per-CPU irq_workd kthread in preemptible task context with IRQs enabled. However, raise_softirq_irqoff() requires IRQs to be disabled. Calling it from irq_workd trips the lockdep assertion in __raise_softirq_irqoff() and the non-atomic update of the softirq pending mask can lose bits raised by an interrupt on the same CPU. Replace raise_softirq_irqoff() with raise_softirq() in the irq_work handlers. Fixes: 2f34d7337d98 ("workqueue: Fix queue_work_on() with BH workqueues") Cc: stable@vger.kernel.org # v6.9+ Signed-off-by: Zqiang Signed-off-by: Tejun Heo --- kernel/workqueue.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/workqueue.c b/kernel/workqueue.c index c0b72dcc0f03..f2aed36cf7c0 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -8065,12 +8065,12 @@ static inline void wq_watchdog_init(void) { } static void bh_pool_kick_normal(struct irq_work *irq_work) { - raise_softirq_irqoff(TASKLET_SOFTIRQ); + raise_softirq(TASKLET_SOFTIRQ); } static void bh_pool_kick_highpri(struct irq_work *irq_work) { - raise_softirq_irqoff(HI_SOFTIRQ); + raise_softirq(HI_SOFTIRQ); } static void __init restrict_unbound_cpumask(const char *name, const struct cpumask *mask) From 315d11a89147724528d867268382812913bd2ead Mon Sep 17 00:00:00 2001 From: Bhargav Joshi Date: Tue, 4 Aug 2026 22:35:08 +0530 Subject: [PATCH 230/641] dt-bindings: rtc: ti,omap-rtc: Convert to DT schema Convert the Texas Instruments OMAP Real Time Clock (RTC) binding from the legacy text format to the DT schema. Mark 'ti,hwmods' as deprecated as it is no longer used, it is kept to support legacy boards. Signed-off-by: Bhargav Joshi Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260804-ti-omap-rtc-v3-4-ba3bbd8af570@gmail.com Signed-off-by: Alexandre Belloni --- .../devicetree/bindings/rtc/rtc-omap.txt | 53 ------- .../devicetree/bindings/rtc/ti,omap-rtc.yaml | 140 ++++++++++++++++++ 2 files changed, 140 insertions(+), 53 deletions(-) delete mode 100644 Documentation/devicetree/bindings/rtc/rtc-omap.txt create mode 100644 Documentation/devicetree/bindings/rtc/ti,omap-rtc.yaml diff --git a/Documentation/devicetree/bindings/rtc/rtc-omap.txt b/Documentation/devicetree/bindings/rtc/rtc-omap.txt deleted file mode 100644 index 062ebb14cecf..000000000000 --- a/Documentation/devicetree/bindings/rtc/rtc-omap.txt +++ /dev/null @@ -1,53 +0,0 @@ -TI Real Time Clock - -Required properties: -- compatible: - - "ti,da830-rtc" - for RTC IP used similar to that on DA8xx SoC family. - - "ti,am3352-rtc" - for RTC IP used similar to that on AM335x SoC family. - This RTC IP has special WAKE-EN Register to enable - Wakeup generation for event Alarm. It can also be - used to control an external PMIC via the - pmic_power_en pin. - - "ti,am4372-rtc" - for RTC IP used similar to that on AM437X SoC family. -- reg: Address range of rtc register set -- interrupts: rtc timer, alarm interrupts in order - -Optional properties: -- system-power-controller: whether the rtc is controlling the system power - through pmic_power_en -- clocks: Any internal or external clocks feeding in to rtc -- clock-names: Corresponding names of the clocks -- pinctrl-0: a phandle pointing to the pin settings for the device -- pinctrl-names: should be "default" - -Optional subnodes: -- generic pinctrl node - -Required pinctrl subnodes properties: -- pins - Names of ext_wakeup pins to configure - -Optional pinctrl subnodes properties: -- input-enable - Enables ext_wakeup -- ti,active-high - Set input active high (by default active low) - -Example: - -rtc@1c23000 { - compatible = "ti,da830-rtc"; - reg = <0x23000 0x1000>; - interrupts = <19 - 19>; - interrupt-parent = <&intc>; - system-power-controller; - clocks = <&clk_32k_rtc>, <&clk_32768_ck>; - clock-names = "ext-clk", "int-clk"; - - pinctrl-0 = <&ext_wakeup>; - pinctrl-names = "default"; - - ext_wakeup: ext-wakeup { - pins = "ext_wakeup0"; - input-enable; - ti,active-high; - }; -}; diff --git a/Documentation/devicetree/bindings/rtc/ti,omap-rtc.yaml b/Documentation/devicetree/bindings/rtc/ti,omap-rtc.yaml new file mode 100644 index 000000000000..02b3c23cf435 --- /dev/null +++ b/Documentation/devicetree/bindings/rtc/ti,omap-rtc.yaml @@ -0,0 +1,140 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/rtc/ti,omap-rtc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments OMAP Real Time Clock + +maintainers: + - Keerthy + - Afzal Mohammed + +description: + The OMAP RTC is a year/month/day/hours/minutes/seconds BCD clock with + century-range alarm matching, driven by the 32kHz clock. + +properties: + compatible: + oneOf: + - const: ti,da830-rtc + - items: + - const: ti,am3352-rtc + - const: ti,da830-rtc + - items: + - const: ti,am4372-rtc + - const: ti,am3352-rtc + - const: ti,da830-rtc + + reg: + maxItems: 1 + + interrupts: + minItems: 2 + maxItems: 2 + + system-power-controller: + type: boolean + description: + Indicates that this RTC controls system power via the pmic_power_en pin. + + clocks: + minItems: 1 + maxItems: 2 + + clock-names: + minItems: 1 + items: + - enum: [ext-clk, int-clk] + - const: int-clk + + pinctrl-0: + description: + Phandle to pin configuration for the external wakeup pins. + + pinctrl-names: + minItems: 1 + + ti,hwmods: + $ref: /schemas/types.yaml#/definitions/string + description: + Name of the hwmod associated with the RTC. + const: rtc + deprecated: true + +patternProperties: + "^ext-wakeup(-[0-9]+)?$": + type: object + + description: + Child node describing external wakeup pin configuration. + + properties: + pins: + pattern: '^ext_wakeup[0-3]$' + + input-enable: + type: boolean + description: + Enables the external wakeup input on the selected pin. + + ti,active-high: + type: boolean + description: + Sets the wakeup input polarity to active high. By default the + input is active low. + + required: + - pins + + additionalProperties: false + +required: + - compatible + - reg + - interrupts + +allOf: + - $ref: rtc.yaml# + - if: + not: + properties: + compatible: + contains: + const: ti,am3352-rtc + then: + properties: + system-power-controller: false + patternProperties: + "^ext-wakeup(-[0-9]+)?$": false + +unevaluatedProperties: false + +examples: + - | + rtc@23000 { + compatible = "ti,da830-rtc"; + reg = <0x23000 0x1000>; + interrupts = <19>, <19>; + clocks = <&clk_32768_ck>; + clock-names = "int-clk"; + }; + + - | + rtc@0 { + compatible = "ti,am3352-rtc", "ti,da830-rtc"; + reg = <0x0 0x1000>; + interrupts = <75>, <76>; + system-power-controller; + clocks = <&clk_32k_rtc>, <&clk_32768_ck>; + clock-names = "ext-clk", "int-clk"; + + pinctrl-0 = <&ext_wakeup>; + pinctrl-names = "default"; + + ext-wakeup { + pins = "ext_wakeup0"; + input-enable; + ti,active-high; + }; + }; From 1190db8edcd91309e64d8212c1ca8b90a4ed732d Mon Sep 17 00:00:00 2001 From: Udaya Kiran Challa Date: Fri, 3 Jul 2026 16:34:42 +0530 Subject: [PATCH 231/641] dt-bindings: rtc: microchip,pic32mzda-rtc: Convert to DT schema Convert Microchip PIC32 Real Time Clock and Calendar devicetree binding from legacy text format to DT schema. Signed-off-by: Udaya Kiran Challa Acked-by: Conor Dooley Link: https://patch.msgid.link/20260703110442.205026-1-challauday369@gmail.com Signed-off-by: Alexandre Belloni --- .../bindings/rtc/microchip,pic32-rtc.txt | 21 -------- .../bindings/rtc/microchip,pic32mzda-rtc.yaml | 50 +++++++++++++++++++ 2 files changed, 50 insertions(+), 21 deletions(-) delete mode 100644 Documentation/devicetree/bindings/rtc/microchip,pic32-rtc.txt create mode 100644 Documentation/devicetree/bindings/rtc/microchip,pic32mzda-rtc.yaml diff --git a/Documentation/devicetree/bindings/rtc/microchip,pic32-rtc.txt b/Documentation/devicetree/bindings/rtc/microchip,pic32-rtc.txt deleted file mode 100644 index 180b7144bfcc..000000000000 --- a/Documentation/devicetree/bindings/rtc/microchip,pic32-rtc.txt +++ /dev/null @@ -1,21 +0,0 @@ -* Microchip PIC32 Real Time Clock and Calendar - -The RTCC keeps time in hours, minutes, and seconds, and one half second. It -provides a calendar in weekday, date, month, and year. It also provides a -configurable alarm. - -Required properties: -- compatible: should be: "microchip,pic32mzda-rtc" -- reg: physical base address of the controller and length of memory mapped - region. -- interrupts: RTC alarm/event interrupt -- clocks: clock phandle - -Example: - - rtc: rtc@1f8c0000 { - compatible = "microchip,pic32mzda-rtc"; - reg = <0x1f8c0000 0x60>; - interrupts = <166 IRQ_TYPE_EDGE_RISING>; - clocks = <&PBCLK6>; - }; diff --git a/Documentation/devicetree/bindings/rtc/microchip,pic32mzda-rtc.yaml b/Documentation/devicetree/bindings/rtc/microchip,pic32mzda-rtc.yaml new file mode 100644 index 000000000000..481ee28c06e3 --- /dev/null +++ b/Documentation/devicetree/bindings/rtc/microchip,pic32mzda-rtc.yaml @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/rtc/microchip,pic32mzda-rtc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Microchip PIC32 Real Time Clock and Calendar + +maintainers: + - Alexandre Belloni + +description: | + The Microchip PIC32 Real Time Clock and Calendar (RTCC) keeps time in hours, + minutes, seconds, and one half second. It also provides a calendar with + weekday, date, month, and year, along with a configurable alarm. + +allOf: + - $ref: rtc.yaml# + +properties: + compatible: + const: microchip,pic32mzda-rtc + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + maxItems: 1 + +required: + - compatible + - reg + - interrupts + - clocks + +additionalProperties: false + +examples: + - | + #include + + rtc@1f8c0000 { + compatible = "microchip,pic32mzda-rtc"; + reg = <0x1f8c0000 0x60>; + interrupts = <166 IRQ_TYPE_EDGE_RISING>; + clocks = <&PBCLK6>; + }; From 745ca1b959d9f08cbfd15891aa74ef4e3e847952 Mon Sep 17 00:00:00 2001 From: Teja Sai Charan Bellamkonda Date: Fri, 10 Jul 2026 03:49:44 +0530 Subject: [PATCH 232/641] dt-bindings: rtc: Convert rtc-cmos binding to YAML Convert the rtc-cmos devicetree bindings to dt schema. The original text binding documents only the motorola,mc146818 compatible. Existing in-tree Devicetree sources also use the intel,ce4100-rtc compatible together with the motorola,mc146818 fallback, but this was not documented. Document the Intel variant in the schema so that these existing configurations are accepted during schema validation. Signed-off-by: Teja Sai Charan Bellamkonda Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260709221944.159244-1-tejaasaye@gmail.com Signed-off-by: Alexandre Belloni --- .../devicetree/bindings/rtc/rtc-cmos.txt | 27 -------- .../devicetree/bindings/rtc/rtc-cmos.yaml | 63 +++++++++++++++++++ 2 files changed, 63 insertions(+), 27 deletions(-) delete mode 100644 Documentation/devicetree/bindings/rtc/rtc-cmos.txt create mode 100644 Documentation/devicetree/bindings/rtc/rtc-cmos.yaml diff --git a/Documentation/devicetree/bindings/rtc/rtc-cmos.txt b/Documentation/devicetree/bindings/rtc/rtc-cmos.txt deleted file mode 100644 index 7d7b5f6bda65..000000000000 --- a/Documentation/devicetree/bindings/rtc/rtc-cmos.txt +++ /dev/null @@ -1,27 +0,0 @@ - Motorola mc146818 compatible RTC -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Required properties: - - compatible : "motorola,mc146818" - - reg : should contain registers location and length. - -Optional properties: - - interrupts : should contain interrupt. - - ctrl-reg : Contains the initial value of the control register also - called "Register B". - - freq-reg : Contains the initial value of the frequency register also - called "Register A". - -"Register A" and "B" are usually initialized by the firmware (BIOS for -instance). If this is not done, it can be performed by the driver. - -ISA Example: - - rtc@70 { - compatible = "motorola,mc146818"; - interrupts = <8 3>; - interrupt-parent = <&ioapic1>; - ctrl-reg = <2>; - freq-reg = <0x26>; - reg = <1 0x70 2>; - }; diff --git a/Documentation/devicetree/bindings/rtc/rtc-cmos.yaml b/Documentation/devicetree/bindings/rtc/rtc-cmos.yaml new file mode 100644 index 000000000000..e37927e9916c --- /dev/null +++ b/Documentation/devicetree/bindings/rtc/rtc-cmos.yaml @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/rtc/rtc-cmos.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Motorola mc146818 compatible RTC + +maintainers: + - Alexandre Belloni + +properties: + compatible: + oneOf: + - const: motorola,mc146818 + + - items: + - const: intel,ce4100-rtc + - const: motorola,mc146818 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + ctrl-reg: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Initial value of the control register + (also known as Register B). + + freq-reg: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Initial value of the frequency register + (also known as Register A). + +required: + - compatible + - reg + +allOf: + - $ref: rtc.yaml# + +unevaluatedProperties: false + +examples: + - | + bus { + #address-cells = <2>; + #size-cells = <1>; + + rtc@1,70 { + compatible = "motorola,mc146818"; + reg = <0x1 0x70 0x2>; + + interrupts = <8 3>; + + ctrl-reg = <2>; + freq-reg = <0x26>; + }; + }; From 4d31434fea516f10ccff35db4a21773780f08cee Mon Sep 17 00:00:00 2001 From: Eduard Bostina Date: Sun, 19 Jul 2026 14:10:07 +0000 Subject: [PATCH 233/641] dt-bindings: rtc: Convert TI Palmas RTC to DT schema Convert the Texas Instruments Palmas RTC controller bindings to DT schema. As part of the conversion, declare 'wakeup-source: true'. This documents the Palmas PMIC's capability to wake the system. Signed-off-by: Eduard Bostina Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260719141008.3562347-2-egbostina@gmail.com Signed-off-by: Alexandre Belloni --- .../devicetree/bindings/rtc/rtc-palmas.txt | 32 ----------- .../bindings/rtc/ti,palmas-rtc.yaml | 57 +++++++++++++++++++ 2 files changed, 57 insertions(+), 32 deletions(-) delete mode 100644 Documentation/devicetree/bindings/rtc/rtc-palmas.txt create mode 100644 Documentation/devicetree/bindings/rtc/ti,palmas-rtc.yaml diff --git a/Documentation/devicetree/bindings/rtc/rtc-palmas.txt b/Documentation/devicetree/bindings/rtc/rtc-palmas.txt deleted file mode 100644 index c6cf37758a77..000000000000 --- a/Documentation/devicetree/bindings/rtc/rtc-palmas.txt +++ /dev/null @@ -1,32 +0,0 @@ -Palmas RTC controller bindings - -Required properties: -- compatible: - - "ti,palmas-rtc" for palma series of the RTC controller -- interrupts: Interrupt number of RTC submodule on device. - -Optional properties: - -- ti,backup-battery-chargeable: The Palmas series device like TPS65913 or - TPS80036 supports the backup battery for powering the RTC when main - battery is removed or in very low power state. The backup battery - can be chargeable or non-chargeable. This flag will tells whether - battery is chargeable or not. If charging battery then driver can - enable the charging. -- ti,backup-battery-charge-high-current: Enable high current charging in - backup battery. Device supports the < 100uA and > 100uA charging. - The high current will be > 100uA. Absence of this property will - charge battery to lower current i.e. < 100uA. - -Example: - palmas: tps65913@58 { - ... - palmas_rtc: rtc { - compatible = "ti,palmas-rtc"; - interrupt-parent = <&palmas>; - interrupts = <8 0>; - ti,backup-battery-chargeable; - ti,backup-battery-charge-high-current; - }; - ... - }; diff --git a/Documentation/devicetree/bindings/rtc/ti,palmas-rtc.yaml b/Documentation/devicetree/bindings/rtc/ti,palmas-rtc.yaml new file mode 100644 index 000000000000..ac64f0589c84 --- /dev/null +++ b/Documentation/devicetree/bindings/rtc/ti,palmas-rtc.yaml @@ -0,0 +1,57 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/rtc/ti,palmas-rtc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments Palmas RTC + +maintainers: + - Eduard Bostina + +allOf: + - $ref: /schemas/rtc/rtc.yaml# + +properties: + compatible: + const: ti,palmas-rtc + + interrupts: + maxItems: 1 + + wakeup-source: true + + ti,backup-battery-chargeable: + type: boolean + description: + The backup battery can be chargeable or non-chargeable. This flag + indicates whether the battery is chargeable. If present, the driver + can enable charging. + + ti,backup-battery-charge-high-current: + type: boolean + description: + Enable high current charging in the backup battery. + Device supports the < 100uA and > 100uA charging. The high current will + be > 100uA. Absence of this property will charge battery to lower + current i.e. < 100uA. + +required: + - compatible + - interrupts + +unevaluatedProperties: false + +examples: + - | + pmic { + #address-cells = <1>; + #size-cells = <0>; + + rtc { + compatible = "ti,palmas-rtc"; + interrupts = <8 0>; + ti,backup-battery-chargeable; + ti,backup-battery-charge-high-current; + }; + }; From b1407862fb57c6455a04fa5d0d02b58496a38cc9 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Fri, 24 Jul 2026 03:45:31 +0900 Subject: [PATCH 234/641] rtc: pcf2127: remove conditional return with no effect Both branches of the check return the same value, so the check has no effect. Remove it and return the value directly. This is the result of running the Coccinelle script from scripts/coccinelle/misc/cond_return_no_effect.cocci. Signed-off-by: Sang-Heon Jeon Reviewed-by: Bruno Thomsen Link: https://patch.msgid.link/20260723184538.3888637-30-ekffu200098@gmail.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-pcf2127.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/rtc/rtc-pcf2127.c b/drivers/rtc/rtc-pcf2127.c index 1995e9f2756d..ff2023908f5a 100644 --- a/drivers/rtc/rtc-pcf2127.c +++ b/drivers/rtc/rtc-pcf2127.c @@ -1184,12 +1184,7 @@ static int pcf2127_configure_interrupt_pins(struct device *dev) if (ret) return ret; - ret = regmap_write(pcf2127->regmap, - PCF2131_REG_INT_A_MASK2, 0); - if (ret) - return ret; - - return ret; + return regmap_write(pcf2127->regmap, PCF2131_REG_INT_A_MASK2, 0); } static int pcf2127_probe(struct device *dev, struct regmap *regmap, From 878d93aaa596a861dbc8d71f7c38e62a2e805f01 Mon Sep 17 00:00:00 2001 From: Robert Leussler Date: Tue, 21 Jul 2026 08:19:07 +0000 Subject: [PATCH 235/641] rtc: ds1307: fix RX8130 wakeup alarm WADA bit for day-of-month mode The RX8130 wakeup alarm never fired when set via /sys/class/rtc/rtc0/wakealarm. The root cause is that the WADA bit (bit 3) in the Extension register (0x1c) was never set before programming the alarm registers. Per the RX8130 datasheet: WADA=0 - Week alarm: register 0x19 is compared against day-of-week WADA=1 - Day alarm: register 0x19 is compared against day-of-month rx8130_set_alarm() always writes a BCD day-of-month value to alarm register 0x19, so WADA must be 1. With WADA=0 the hardware matched the day-of-month value (e.g. 15) as a day-of-week index, which is always out of range (valid weekdays are 0-6), so the alarm interrupt was never asserted. Fix by setting the WADA bit in rx8130_set_alarm() before writing the Extension register back to the device. This is consistent with rx8130_read_alarm(), which decodes the same register under the same assumption (24-hour and day-of-month mode). Signed-off-by: Robert Leussler Link: https://patch.msgid.link/20260721081907.3518648-1-robert.leussler@leica-geosystems.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-ds1307.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/rtc/rtc-ds1307.c b/drivers/rtc/rtc-ds1307.c index 0707ded5368b..6f3dcb6483b9 100644 --- a/drivers/rtc/rtc-ds1307.c +++ b/drivers/rtc/rtc-ds1307.c @@ -662,7 +662,7 @@ static int rx8130_set_alarm(struct device *dev, struct rtc_wkalrm *t) if (ret < 0) return ret; - ctl[0] &= RX8130_REG_EXTENSION_WADA; + ctl[0] |= RX8130_REG_EXTENSION_WADA; ctl[1] &= ~RX8130_REG_FLAG_AF; ctl[2] &= ~RX8130_REG_CONTROL0_AIE; From ca45cfa74370644d371b552bef57938c19e3c80c Mon Sep 17 00:00:00 2001 From: Linkai Gong Date: Fri, 31 Jul 2026 16:04:58 +0800 Subject: [PATCH 236/641] rtc: gamecube: check return value of devm_rtc_register_device() gamecube_rtc_probe() ignored the return value of devm_rtc_register_device() and always returned success. Propagate the error so probe fails when RTC registration fails. Fixes: 86559400b3ef ("rtc: gamecube: Add a RTC driver for the GameCube, Wii and Wii U") Signed-off-by: Linkai Gong Link: https://patch.msgid.link/20260731080458.417532-1-gonglinkai@kylinos.cn Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-gamecube.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/rtc/rtc-gamecube.c b/drivers/rtc/rtc-gamecube.c index 045d5d45ab4b..889028cecf4a 100644 --- a/drivers/rtc/rtc-gamecube.c +++ b/drivers/rtc/rtc-gamecube.c @@ -355,9 +355,7 @@ static int gamecube_rtc_probe(struct platform_device *pdev) rtc->range_max = U32_MAX; rtc->ops = &gamecube_rtc_ops; - devm_rtc_register_device(rtc); - - return 0; + return devm_rtc_register_device(rtc); } static const struct of_device_id gamecube_rtc_of_match[] = { From d736eba9c453fedce664fdf592c8b71ecff1932b Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Thu, 20 Aug 2026 09:41:17 +0800 Subject: [PATCH 237/641] ALSA: ice1712: Fix the card leak at probe error with the auto-cleanup snd_ice1712_probe() performs multiple initialization steps after snd_card_new(), but directly returns on failures from later steps without releasing the ALSA card, causing resource leaks when probing fails. Use snd_devm_card_new() together with scope-based cleanup via __free(snd_card_unref), and clear the card pointer after successful registration to keep it alive. Fixes: ca642da4b33d ("ALSA: ice1712: Allocate resources with device-managed APIs") Suggested-by: Takashi Iwai Signed-off-by: Haotian Zhang Link: https://patch.msgid.link/20260820014117.14044-1-vulab@iscas.ac.cn Signed-off-by: Takashi Iwai --- sound/pci/ice1712/ice1712.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sound/pci/ice1712/ice1712.c b/sound/pci/ice1712/ice1712.c index 7d1a357ed90d..d6bfd8fb20e6 100644 --- a/sound/pci/ice1712/ice1712.c +++ b/sound/pci/ice1712/ice1712.c @@ -2523,7 +2523,7 @@ static int snd_ice1712_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_unref) = NULL; struct snd_ice1712 *ice; int pcm_dev = 0, err; const struct snd_ice1712_card_info * const *tbl, *c; @@ -2535,8 +2535,8 @@ static int snd_ice1712_probe(struct pci_dev *pci, return -ENOENT; } - err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, - sizeof(*ice), &card); + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(*ice), &card); if (err < 0) return err; ice = card->private_data; @@ -2640,6 +2640,7 @@ static int snd_ice1712_probe(struct pci_dev *pci, if (err < 0) return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } From e711ebfadc9a9b7ee9fcb6b5c8c6b1bfc0cc4a87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Habil=20Eren=20T=C3=BCrker?= Date: Wed, 19 Aug 2026 20:08:59 +0300 Subject: [PATCH 238/641] ALSA: hda/realtek: Add quirk for HP Laptop 15-fd0039nt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds a quirk for the HP Laptop 15-fd0039nt (SSID 103c:8bb6) to fix the micmute LED functionality. The micmute LED is controlled via GPIO 0 with active-low polarity. Tested-by: Habil Eren Türker Signed-off-by: Habil Eren Türker Link: https://patch.msgid.link/20260819170919.3172-1-habilerenturker@hotmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index cae327e1d90d..e3adcd114a36 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -1370,6 +1370,16 @@ static void alc269_fixup_hp_gpio_led(struct hda_codec *codec, alc_fixup_hp_gpio_led(codec, action, 0x08, 0x10); } +static void alc236_fixup_hp_micmute_led_only(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) + spec->micmute_led_polarity = 1; + alc_fixup_hp_gpio_led(codec, action, 0x00, 0x01); +} + static void alc285_fixup_hp_gpio_led(struct hda_codec *codec, const struct hda_fixup *fix, int action) { @@ -4152,6 +4162,7 @@ enum { ALC236_FIXUP_HP_GPIO_LED, ALC236_FIXUP_HP_MUTE_LED, ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF, + ALC236_FIXUP_HP_MICMUTE_LED_ONLY, ALC236_FIXUP_LENOVO_INV_DMIC, ALC298_FIXUP_SAMSUNG_AMP, ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS, @@ -5913,6 +5924,10 @@ static const struct hda_fixup alc269_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc236_fixup_hp_mute_led_micmute_gpio, }, + [ALC236_FIXUP_HP_MICMUTE_LED_ONLY] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc236_fixup_hp_micmute_led_only, + }, [ALC236_FIXUP_LENOVO_INV_DMIC] = { .type = HDA_FIXUP_FUNC, .v.func = alc_fixup_inv_dmic, @@ -7448,6 +7463,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8b97, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), SND_PCI_QUIRK(0x103c, 0x8bb3, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8bb4, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8bb6, "HP Laptop 15-fd0039nt", ALC236_FIXUP_HP_MICMUTE_LED_ONLY), SND_PCI_QUIRK(0x103c, 0x8bbe, "HP Victus 16-r0xxx (MB 8BBE)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8bc8, "HP Victus 15-fa1xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8bcd, "HP Omen 16-xd0xxx", ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT), From 2b366b833f52a37f70be845892c1f5fe1d993051 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Thu, 20 Aug 2026 15:37:44 +0800 Subject: [PATCH 239/641] ALSA: hda/conexant: Simplify headset plugin type handling Both the CTIA and OMTP branches of cx_process_headset_plugin() write the same value to the headset-mic pin, differing only in the debug message. Merge the two branches into a single one, leaving the codec write unchanged. No functional change. Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260820073744.521680-1-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/conexant.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c index c517334b56fd..8e950c796209 100644 --- a/sound/hda/codecs/conexant.c +++ b/sound/hda/codecs/conexant.c @@ -223,11 +223,9 @@ static void cx_process_headset_plugin(struct hda_codec *codec) count++; } while (count < 3); val = snd_hda_codec_read(codec, 0x1c, 0, 0xcb0, 0x0); - if (val & 0x800) { - codec_dbg(codec, "headset plugin, type is CTIA\n"); - snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24); - } else if (val & 0x400) { - codec_dbg(codec, "headset plugin, type is OMTP\n"); + if (val & 0xc00) { + codec_dbg(codec, "headset plugin, type is %s\n", + val & 0x800 ? "CTIA" : "OMTP"); snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24); } else { codec_dbg(codec, "headphone plugin\n"); From dfb8bc1d286fd97b7cf47e3af5df84cfb068d214 Mon Sep 17 00:00:00 2001 From: Jack Yu Date: Thu, 20 Aug 2026 17:24:15 +0800 Subject: [PATCH 240/641] ASoC: rt712-sdca: Pull high JD2 to prevent from entering TV mode Add a patch setting to prevent ALC712 from entering TV mode which may cause unexpected issues. Signed-off-by: Jack Yu Link: https://patch.msgid.link/20260820092415.2979641-1-jack.yu@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt712-sdca.c | 1 + sound/soc/codecs/rt712-sdca.h | 1 + 2 files changed, 2 insertions(+) diff --git a/sound/soc/codecs/rt712-sdca.c b/sound/soc/codecs/rt712-sdca.c index 2218f9918ae3..13574513b181 100644 --- a/sound/soc/codecs/rt712-sdca.c +++ b/sound/soc/codecs/rt712-sdca.c @@ -1779,6 +1779,7 @@ static void rt712_sdca_vb_io_init(struct rt712_sdca_priv *rt712) dev_dbg(dev, "%s jack/mic/amp func_status=0x%x, 0x%x, 0x%x\n", __func__, jack_func_status, mic_func_status, amp_func_status); + rt712_sdca_index_write(rt712, RT712_VENDOR_REG, RT712_JD_CTL3, 0x7778); /* DMIC */ if ((mic_func_status & FUNCTION_NEEDS_INITIALIZATION) || (!rt712->first_hw_init)) { rt712_sdca_index_write(rt712, RT712_VENDOR_HDA_CTL, RT712_DMIC2_FU_IT_FLOAT_CTL, 0x1526); diff --git a/sound/soc/codecs/rt712-sdca.h b/sound/soc/codecs/rt712-sdca.h index 7ab7d5feb50a..46740281a5c1 100644 --- a/sound/soc/codecs/rt712-sdca.h +++ b/sound/soc/codecs/rt712-sdca.h @@ -74,6 +74,7 @@ struct rt712_dmic_kctrl_priv { #define RT712_JD_PRODUCT_NUM 0x00 #define RT712_ANALOG_BIAS_CTL3 0x04 #define RT712_JD_CTL1 0x09 +#define RT712_JD_CTL3 0x0b #define RT712_IO_CTL 0x0c #define RT712_LDO2_3_CTL1 0x0e #define RT712_PARA_VERB_CTL 0x1a From 13087ad7817e6e5f210064518bfc4d6d58a9c2f3 Mon Sep 17 00:00:00 2001 From: Nitin Gote Date: Fri, 14 Aug 2026 13:11:07 +0530 Subject: [PATCH 241/641] drm/xe: don't WARN on kernel job timeout when device already wedged igt@xe_wedged@wedged-at-any-timeout wedges the device in mode 2 (UPON_ANY_HANG_NO_RESET) and then rebinds the driver. During unbind, a GSC proxy kernel submission can still time out; with the device wedged and the GuC CT stopped it can never complete, so its kernel job times out. Tile0: GT1: Kernel-submitted job timed out WARNING: drivers/gpu/drm/xe/xe_guc_submit.c:... at guc_exec_queue_timedout_job() Workqueue: gt-ordered-wq drm_sched_job_timedout Killed queues skip guc_submit_hint_wedged(), leaving 'wedged' false even though the device is already wedged. The timeout handler then treats the kernel queue timeout as unexpected and taints the kernel. Honour an already-wedged device even for killed queues so the expected teardown timeout no longer trips the WARN. Fixes: 5a2f117a80c2 ("drm/xe: Do not wedge device on killed exec queues") Cc: Matthew Brost Signed-off-by: Nitin Gote Reviewed-by: Tejas Upadhyay Link: https://patch.msgid.link/20260814074106.92670-2-nitin.r.gote@intel.com Signed-off-by: Tejas Upadhyay (cherry picked from commit a1c1dbd0f047bb05de6aaf6abe9103031179bf19) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_guc_submit.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_guc_submit.c b/drivers/gpu/drm/xe/xe_guc_submit.c index 8aaed4fd13ea..864859bd0ccc 100644 --- a/drivers/gpu/drm/xe/xe_guc_submit.c +++ b/drivers/gpu/drm/xe/xe_guc_submit.c @@ -1559,8 +1559,14 @@ guc_exec_queue_timedout_job(struct drm_sched_job *drm_job) if (!skip_timeout_check && !check_timeout(q, job)) goto rearm; + /* + * Killed queues must not newly wedge the device, but preserve an + * already-wedged state to avoid warning on teardown timeouts. + */ if (!exec_queue_killed(q)) wedged = guc_submit_hint_wedged(exec_queue_to_guc(q)); + else + wedged = xe_device_wedged(xe); set_exec_queue_banned(q); From c73cb5b7c159246dd572277c668851a56e516019 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Thu, 20 Aug 2026 19:27:49 +0800 Subject: [PATCH 242/641] ALSA: mtpav: shut down output timer before card teardown snd_mtpav_output_timer() rearms chip->timer while holding chip->spinlock and accesses the card-private mtpav state. snd_mtpav_free() currently takes the same lock and calls timer_delete() when the timer is active. This only removes a pending timer; it does not wait for a callback that is already running and does not prevent the callback from rearming the timer. A callback running on another CPU can therefore continue after snd_mtpav_free() releases the lock and access the card-private state while the card is being torn down. It can also rearm the timer after timer_delete() has returned. Call timer_shutdown_sync() without holding chip->spinlock. This waits for any running callback to finish and prevents further rearming before the card-private mtpav state is released. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Runyu Xiao Link: https://patch.msgid.link/20260820112749.9195-1-runyu.xiao@seu.edu.cn Signed-off-by: Takashi Iwai --- sound/drivers/mtpav.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/drivers/mtpav.c b/sound/drivers/mtpav.c index d31eadf4be5f..1ed6d4f0cb62 100644 --- a/sound/drivers/mtpav.c +++ b/sound/drivers/mtpav.c @@ -642,9 +642,7 @@ static void snd_mtpav_free(struct snd_card *card) { struct mtpav *crd = card->private_data; - guard(spinlock_irqsave)(&crd->spinlock); - if (crd->istimer > 0) - snd_mtpav_remove_output_timer(crd); + timer_shutdown_sync(&crd->timer); } /* From 41d60cbfde10b9f01ae6e2d3195463fbad6e54a8 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Thu, 20 Aug 2026 21:06:43 +0800 Subject: [PATCH 243/641] ALSA: hda/realtek: Fix bass speaker DAC routing for Lenovo Yoga Pro 9 16IAH10 The Lenovo Yoga Pro 9 16IAH10 (PCI SSID 17aa:3846, codec SSID 17aa:3920) uses a TAS2781 smart amplifier driven via the ALC287 codec. On this machine the bass speaker pin (NID 0x17) is by default routed to DAC 0x03, which has no volume amplifier capability, resulting in the right-side woofer being barely audible while only the tweeter plays. The Windows driver configures NID 0x17 to DAC 0x02, which is shared with the main speaker and carries proper amp control. Currently this machine has no PCI SSID quirk, so it falls through to the codec SSID fallback and matches the existing 0x17aa:0x3920 entry intended for the Yoga S990-16, applying only the TAS2781 I2C init without the DAC routing fix. Add a dedicated quirk for PCI SSID 17aa:3846 that forces NID 0x17 to DAC 0x02 via alc285_fixup_speaker2_to_dac1() and chains to ALC287_FIXUP_TXNW2781_I2C so the TAS2781 amplifier initialization is preserved. Link: https://bugzilla.kernel.org/show_bug.cgi?id=220540 Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260820130644.76093-1-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index e3adcd114a36..dd57e0b00a85 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -4308,6 +4308,7 @@ enum { ALC287_FIXUP_LENOVO_LEGION_AW88399, ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN_HEADSET, ALC285_LENOVO_DAC_RENAME, + ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1, }; /* A special fixup for Lenovo C940 and Yoga Duet 7; @@ -7025,6 +7026,12 @@ static const struct hda_fixup alc269_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc285_lenovo_dac_rename, }, + [ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_speaker2_to_dac1, + .chained = true, + .chain_id = ALC287_FIXUP_TXNW2781_I2C, + }, }; static const struct hda_quirk alc269_fixup_tbl[] = { @@ -8047,6 +8054,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x3834, "Lenovo IdeaPad Slim 9i 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x383d, "Legion Y9000X 2019", ALC285_FIXUP_LEGION_Y9000X_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x3843, "Lenovo Yoga 9i / Yoga Book 9i", ALC287_FIXUP_LENOVO_YOGA_BOOK_9I), + SND_PCI_QUIRK(0x17aa, 0x3846, "Lenovo Yoga Pro 9 16IAH10", ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1), /* Yoga Pro 7 14IMH9 shares PCI SSID 17aa:3847 with Legion 7 16ACHG6; * use codec SSID to distinguish them */ From 7e77c09e23dae1ff8bd8598c3c96f9db6862eddd Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Thu, 20 Aug 2026 21:06:44 +0800 Subject: [PATCH 244/641] ALSA: hda/realtek: Reorder quirk entries for Lenovo Legion Pro 7 16ARX8H Reorder the HDA_CODEC_QUIRK entries for 0x17aa:0x38a7 and 0x17aa:0x38a8 so that the AMD variant (TAS2781) is matched before the Intel variant's PCI SSID (0x386f) which uses a different amplifier. This ensures the correct fixup (ALC287_FIXUP_TAS2781_I2C) is applied to the AMD model, restoring internal speaker functionality. Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260820130644.76093-2-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index dd57e0b00a85..fe553db5b52f 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -8074,8 +8074,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { HDA_CODEC_QUIRK(0x17aa, 0x386a, "Lenovo Yoga 7 16IAP7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), HDA_CODEC_QUIRK(0x17aa, 0x386e, "Legion Y9000X 2022 IAH7", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x386e, "Yoga Pro 7 14ARP8", ALC285_FIXUP_YOGA_SPEAKER2_TO_DAC1), - HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ HDA_CODEC_QUIRK(0x17aa, 0x38a7, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ + HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ SND_PCI_QUIRK(0x17aa, 0x386f, "Legion Pro 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x3870, "Lenovo Yoga 7 14ARB7", ALC287_FIXUP_YOGA7_14ARB7_I2C), SND_PCI_QUIRK(0x17aa, 0x3874, "Legion 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), From 72c5ae18ebe6588101f2c1e96be61618ce06f182 Mon Sep 17 00:00:00 2001 From: Liang Luo Date: Thu, 20 Aug 2026 10:37:44 +0800 Subject: [PATCH 245/641] Docs/admin-guide/cgroup-v2: document BPF scheduler callbacks for cpu.max and cpu.idle The cpu.weight and cpu.weight.nice entries already state that the files also affect a BPF scheduler through the cgroup_set_weight callback. However, cpu.max, cpu.max.burst and cpu.idle only mention the fair-class scheduler, even though sched_ext implements the cgroup_set_bandwidth (notified with the period/quota from cpu.max and the burst from cpu.max.burst) and cgroup_set_idle callbacks from these interfaces. Mirror the cpu.weight wording for the three entries and generalize the category preamble to refer to the corresponding cgroup_set_* callback so it keeps covering the entries below. Suggested-by: Tejun Heo Signed-off-by: Liang Luo Signed-off-by: Tejun Heo --- Documentation/admin-guide/cgroup-v2.rst | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst index aed195a71cbf..3dc6889ebdb2 100644 --- a/Documentation/admin-guide/cgroup-v2.rst +++ b/Documentation/admin-guide/cgroup-v2.rst @@ -1130,9 +1130,9 @@ policy and the underlying scheduler. From the point of view of the cpu controlle processes can be categorized as follows: * Processes under the fair-class scheduler -* Processes under a BPF scheduler with the ``cgroup_set_weight`` callback +* Processes under a BPF scheduler with the corresponding ``cgroup_set_*`` callback * Everything else: ``SCHED_{FIFO,RR,DEADLINE}`` and processes under a BPF scheduler - without the ``cgroup_set_weight`` callback + without the corresponding ``cgroup_set_*`` callback For details on when a process is under the fair-class scheduler or a BPF scheduler, check out :ref:`Documentation/scheduler/sched-ext.rst `. @@ -1202,7 +1202,9 @@ will be referred to. All time durations are in microseconds. $PERIOD duration. "max" for $MAX indicates no limit. If only one number is written, $MAX is updated. - This file affects only processes under the fair-class scheduler. + This file affects only processes under the fair-class scheduler and a BPF + scheduler with the ``cgroup_set_bandwidth`` callback depending on what + the callback actually does. cpu.max.burst A read-write single value file which exists on non-root @@ -1210,7 +1212,9 @@ will be referred to. All time durations are in microseconds. The burst in the range [0, $MAX]. - This file affects only processes under the fair-class scheduler. + This file affects only processes under the fair-class scheduler and a BPF + scheduler with the ``cgroup_set_bandwidth`` callback depending on what + the callback actually does. cpu.pressure A read-write nested-keyed file. @@ -1262,7 +1266,9 @@ will be referred to. All time durations are in microseconds. own relative priorities, but the cgroup itself will be treated as very low priority relative to its peers. - This file affects only processes under the fair-class scheduler. + This file affects only processes under the fair-class scheduler and a BPF + scheduler with the ``cgroup_set_idle`` callback depending on what the + callback actually does. Memory ------ From a8c6daab4b0e276508b7ffdd66c60fd3020a9178 Mon Sep 17 00:00:00 2001 From: Hongfu Li Date: Thu, 20 Aug 2026 15:09:44 +0800 Subject: [PATCH 246/641] selftests/cgroup: Fix cg_run_in_subcgroups ignoring arg parameter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cg_run_in_subcgroups() discards its arg and always passes NULL to cg_run(), turning the (void *)100 from test_kmem_dead_cgroups() into NULL so no allocation occurs. This makes test_kmem_dead_cgroups() falsely pass without exercising the "dying cgroup with charged slab" scenario it intends to test. Pass the arg through to cg_run() to fix this. Fixes: 933dc80ec262 ("kselftests: cgroup: add kernel memory accounting tests") Signed-off-by: Hongfu Li Reviewed-by: Michal Koutný Signed-off-by: Tejun Heo --- tools/testing/selftests/cgroup/test_kmem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/cgroup/test_kmem.c b/tools/testing/selftests/cgroup/test_kmem.c index cb47561b4b44..437f2d35f205 100644 --- a/tools/testing/selftests/cgroup/test_kmem.c +++ b/tools/testing/selftests/cgroup/test_kmem.c @@ -145,7 +145,7 @@ static int cg_run_in_subcgroups(const char *parent, return -1; } - if (cg_run(child, fn, NULL)) { + if (cg_run(child, fn, arg)) { cg_destroy(child); free(child); return -1; From 258c95150c26a30db1896eacd3bdef26314f2ca1 Mon Sep 17 00:00:00 2001 From: Daeho Jeong Date: Thu, 20 Aug 2026 09:43:40 -0700 Subject: [PATCH 247/641] f2fs: avoid setting SBI_NEED_FSCK on transient resize failure When free_segment_range() fails in f2fs_resize_fs(), no on-disk superblock or filesystem metadata has been modified yet, and free_segment_range() safely restores all in-memory counters before returning. However, the current error recovery path unconditionally sets the SBI_NEED_FSCK flag and prints a scary error message on any error, forcing an unnecessary and time-consuming fsck.f2fs repair on the subsequent mount/reboot. Fix this by separating the error recovery path with a dedicated recover_user_blocks label to bypass setting SBI_NEED_FSCK on free_segment_range() failures. Signed-off-by: Daeho Jeong Signed-off-by: Sunmin Jeong Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/gc.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c index c4da2f31805b..3215630c353d 100644 --- a/fs/f2fs/gc.c +++ b/fs/f2fs/gc.c @@ -2440,7 +2440,7 @@ out_drop_write: set_sbi_flag(sbi, SBI_IS_RESIZEFS); err = free_segment_range(sbi, secs, false); if (err) - goto recover_out; + goto recover_user_blocks; update_sb_metadata(sbi, -secs); @@ -2462,11 +2462,14 @@ out_drop_write: f2fs_commit_super(sbi, false); } recover_out: - clear_sbi_flag(sbi, SBI_IS_RESIZEFS); if (err) { + f2fs_bug_on(sbi, err == -EAGAIN); set_sbi_flag(sbi, SBI_NEED_FSCK); f2fs_err(sbi, "resize_fs failed, should run fsck to repair!"); - + } +recover_user_blocks: + clear_sbi_flag(sbi, SBI_IS_RESIZEFS); + if (err) { spin_lock(&sbi->stat_lock); sbi->user_block_count += shrunk_blocks; spin_unlock(&sbi->stat_lock); From 8ec06f50ddd8d201bd7e55b896ae28ed9d4cb7d1 Mon Sep 17 00:00:00 2001 From: Daeho Jeong Date: Wed, 19 Aug 2026 11:06:35 -0700 Subject: [PATCH 248/641] f2fs: fix to migrate all curseg types during free_segment_range In free_segment_range(), the curseg evacuation loop only iterates up to NR_CURSEG_PERSIST_TYPE (0..5), missing non-persistent in-memory curseg types such as CURSEG_COLD_DATA_PINNED and CURSEG_ALL_DATA_ATGC. Even though these in-memory curseg types are not saved in the on-disk checkpoint header, they still occupy active physical segments at runtime. If an active in-memory curseg happens to be allocated within the segment range being truncated during filesystem shrink, failing to evacuate it will cause subsequent writes to the curseg attempting out-of-bounds I/O on the truncated storage range. Fix this by expanding the curseg evacuation loop upper bound to NR_CURSEG_TYPE to ensure all active curseg types are safely migrated out of the target range. Fixes: d0b9e42ab615 ("f2fs: introduce inmem curseg") Cc: stable@vger.kernel.org Signed-off-by: Daeho Jeong Signed-off-by: Sunmin Jeong Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/gc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c index 3215630c353d..5917ee917d87 100644 --- a/fs/f2fs/gc.c +++ b/fs/f2fs/gc.c @@ -2240,7 +2240,7 @@ static int free_segment_range(struct f2fs_sb_info *sbi, f2fs_reset_gc_victim_resource(sbi, start, end); /* Move out cursegs from the target range */ - for (type = CURSEG_HOT_DATA; type < NR_CURSEG_PERSIST_TYPE; type++) { + for (type = CURSEG_HOT_DATA; type < NR_CURSEG_TYPE; type++) { err = f2fs_allocate_segment_for_resize(sbi, type, start, end); if (err) goto out; From 0f448bb3767ef6119f5cdeabcae3f10d6e75aed6 Mon Sep 17 00:00:00 2001 From: Zhan Xusheng Date: Tue, 18 Aug 2026 22:55:35 +0800 Subject: [PATCH 249/641] f2fs: fix i_size when pinned fallocate partially fails From: Zhan Xusheng Commit 4275b59673eb ("f2fs: fix to round down start offset of fallocate for pin file") moved the allocation loop's start down to a section boundary, but the error path still converts @expanded against @pg_start, which holds the unrounded start. @pg_start exists for that conversion: commit 88f2cfc5fa90 ("f2fs: fix to update last i_size if fallocate partially succeeds") added it as an immutable base because map.m_lblk moves every round. Each round now maps exactly sec_blks blocks starting from rounddown(pg_start, sec_blks), so pg_start + expanded overshoots the last allocated block by pg_start % sec_blks, and a partial failure leaves i_size covering a tail that was never allocated. Nothing corrects that afterwards either, since file_dont_truncate() has already cleared FADVISE_TRUNC_BIT. It needs a start offset that is not section aligned plus a fallocate that hits ENOSPC partway, so the error path runs with expanded > 0. On an 80 MiB image with 2 MiB sections: truncate -s 80M img mkfs.f2fs -s 1 -f img mount -o loop img /mnt touch /mnt/pinned f2fs_io pinfile set /mnt/pinned # 2093056 = block 511, so pg_start % sec_blks = 511 f2fs_io fallocate 0 2093056 536870912 /mnt/pinned stat -c %s /mnt/pinned filefrag -v /mnt/pinned The last extent ends at block 10737 either way. Before, i_size is 46075904, block 11249, so 511 blocks of it were never allocated, and filefrag does not mark the last extent eof. After, i_size is 43982848, block 10738, and eof is back. A kernel from before that commit also shows no overshoot. Keep @pg_start pointing at where allocation actually begins. Fixes: 4275b59673eb ("f2fs: fix to round down start offset of fallocate for pin file") Cc: stable@vger.kernel.org Signed-off-by: Zhan Xusheng Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/file.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index a54b3ab52f1a..d82be8c1502a 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -2001,8 +2001,9 @@ static int f2fs_expand_inode_data(struct inode *inode, loff_t offset, block_t sec_len; if (map.m_lblk % sec_blks) { - map.m_lblk = rounddown(map.m_lblk, sec_blks); - map.m_len = pg_end - map.m_lblk; + pg_start = rounddown(map.m_lblk, sec_blks); + map.m_lblk = pg_start; + map.m_len = pg_end - pg_start; if (off_end) map.m_len++; } From 9a9ee7408a1f8271bd1978baff2dd09457054ef4 Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Wed, 19 Aug 2026 09:31:07 +0800 Subject: [PATCH 250/641] f2fs: reduce memory footprint of ino management Currently, ino entries for APPEND_INO, UPDATE_INO, TRANS_DIR_INO, and XATTR_DIR_INO allocate a 'struct ino_entry' slab object and attach it to both a list and a radix tree solely for existence checks via f2fs_exist_written_data(). Since these ino types only track binary existence status, we can embed the information directly into radix tree value entries as a bitmap: - The Linux radix tree/XArray supports in-place value entries via xa_mk_value() / xa_to_value(), which tag the least significant bit to store an unallocated integer value of BITS_PER_XA_VALUE bits (BITS_PER_LONG - 1) directly in the slot pointer. - For each inode, (ino / BITS_PER_XA_VALUE) serves as the radix tree slot index, and (ino % BITS_PER_XA_VALUE) is used as the bit offset within the slot's bitmap. For example, when tracking ino = 7: - Before: Allocate a 'struct ino_entry' ({ .ino = 7 }), insert its pointer into the radix tree at index = 7, and link it to im->ino_list. - After: Compute slot_index = 7 / BITS_PER_XA_VALUE (index 0) and bit_offset = 7 % BITS_PER_XA_VALUE (bit 7), then set bit 7 in the value entry via xa_mk_value(bitmap) at index 0, without allocating a slab object or linking to a list. Additionally: - In-place slot updates are performed via radix_tree_replace_slot(), and slots are deleted with radix_tree_delete() once the bitmap is zeroed. - Reorder the ino list enum so ORPHAN_INO and FLUSH_INO (which still require struct ino_entry and list traversal) remain separated, while bitmap-based trees are torn down using xa_destroy(). This eliminates 'struct ino_entry' slab allocations and linked-list tracking for these ino types, significantly reducing memory consumption. Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/checkpoint.c | 99 ++++++++++++++++++++++++++++++++++++++++---- fs/f2fs/f2fs.h | 6 ++- 2 files changed, 95 insertions(+), 10 deletions(-) diff --git a/fs/f2fs/checkpoint.c b/fs/f2fs/checkpoint.c index eb2f955b1e2a..c73999e39a39 100644 --- a/fs/f2fs/checkpoint.c +++ b/fs/f2fs/checkpoint.c @@ -766,6 +766,64 @@ static void __remove_ino_entry(struct f2fs_sb_info *sbi, nid_t ino, int type) spin_unlock(&im->ino_lock); } +static void __set_ino_bitmap(struct f2fs_sb_info *sbi, nid_t ino, int type) +{ + struct inode_management *im = &sbi->im[type]; + unsigned long index = INO_SLOT_INDEX(ino); + unsigned int ofs = INO_BIT_OFFSET(ino); + void **slot, *entry; + unsigned long bitmap = 0; + int ret; + + ret = radix_tree_preload(GFP_NOFS | __GFP_NOFAIL); + f2fs_bug_on(sbi, ret); + + spin_lock(&im->ino_lock); + slot = radix_tree_lookup_slot(&im->ino_root, index); + if (slot) { + entry = radix_tree_deref_slot_protected(slot, &im->ino_lock); + bitmap = xa_to_value(entry); + if (!(bitmap & (1UL << ofs))) { + bitmap |= (1UL << ofs); + entry = xa_mk_value(bitmap); + radix_tree_replace_slot(&im->ino_root, slot, entry); + } + } else { + bitmap |= (1UL << ofs); + entry = xa_mk_value(bitmap); + if (unlikely(radix_tree_insert(&im->ino_root, index, entry))) + f2fs_bug_on(sbi, 1); + } + spin_unlock(&im->ino_lock); + radix_tree_preload_end(); +} + +static void __clear_ino_bitmap(struct f2fs_sb_info *sbi, nid_t ino, int type) +{ + struct inode_management *im = &sbi->im[type]; + unsigned long index = INO_SLOT_INDEX(ino); + unsigned int ofs = INO_BIT_OFFSET(ino); + void **slot, *entry; + unsigned long bitmap; + + spin_lock(&im->ino_lock); + slot = radix_tree_lookup_slot(&im->ino_root, index); + if (slot) { + entry = radix_tree_deref_slot_protected(slot, &im->ino_lock); + bitmap = xa_to_value(entry); + if (bitmap & (1UL << ofs)) + bitmap &= ~(1UL << ofs); + + if (bitmap) { + entry = xa_mk_value(bitmap); + radix_tree_replace_slot(&im->ino_root, slot, entry); + } else { + radix_tree_delete(&im->ino_root, index); + } + } + spin_unlock(&im->ino_lock); +} + static void f2fs_wait_for_inode_record(struct f2fs_sb_info *sbi, int mode) { if (mode != APPEND_INO && mode != UPDATE_INO) @@ -778,8 +836,10 @@ static void f2fs_wait_for_inode_record(struct f2fs_sb_info *sbi, int mode) static void __f2fs_add_ino_entry(struct f2fs_sb_info *sbi, nid_t ino, unsigned int devidx, int type) { - /* add new dirty ino entry into list */ - __add_ino_entry(sbi, ino, devidx, type); + if (type <= FLUSH_INO) + __add_ino_entry(sbi, ino, devidx, type); + else + __set_ino_bitmap(sbi, ino, type); } void f2fs_add_ino_entry(struct f2fs_sb_info *sbi, nid_t ino, int type) @@ -789,20 +849,33 @@ void f2fs_add_ino_entry(struct f2fs_sb_info *sbi, nid_t ino, int type) void f2fs_remove_ino_entry(struct f2fs_sb_info *sbi, nid_t ino, int type) { - /* remove dirty ino entry from list */ - __remove_ino_entry(sbi, ino, type); + if (type <= FLUSH_INO) + __remove_ino_entry(sbi, ino, type); + else + __clear_ino_bitmap(sbi, ino, type); } -/* mode should be APPEND_INO, UPDATE_INO or TRANS_DIR_INO */ +/* mode should be APPEND_INO, UPDATE_INO, TRANS_DIR_INO and XATTR_DIR_INO */ bool f2fs_exist_written_data(struct f2fs_sb_info *sbi, nid_t ino, int mode) { struct inode_management *im = &sbi->im[mode]; - struct ino_entry *e; + unsigned long index = INO_SLOT_INDEX(ino); + unsigned int ofs = INO_BIT_OFFSET(ino); + void *entry; + unsigned long bitmap; + + f2fs_bug_on(sbi, mode <= FLUSH_INO); spin_lock(&im->ino_lock); - e = radix_tree_lookup(&im->ino_root, ino); + entry = radix_tree_lookup(&im->ino_root, index); + if (!entry) { + spin_unlock(&im->ino_lock); + return false; + } + bitmap = xa_to_value(entry); spin_unlock(&im->ino_lock); - return e ? true : false; + + return bitmap & (1UL << ofs); } void f2fs_release_ino_entry(struct f2fs_sb_info *sbi, bool all) @@ -810,7 +883,7 @@ void f2fs_release_ino_entry(struct f2fs_sb_info *sbi, bool all) struct ino_entry *e, *tmp; int i; - for (i = all ? ORPHAN_INO : APPEND_INO; i < MAX_INO_ENTRY; i++) { + for (i = all ? ORPHAN_INO : FLUSH_INO; i <= FLUSH_INO; i++) { struct inode_management *im = &sbi->im[i]; f2fs_wait_for_inode_record(sbi, i); @@ -824,6 +897,14 @@ void f2fs_release_ino_entry(struct f2fs_sb_info *sbi, bool all) } spin_unlock(&im->ino_lock); } + + for (i = APPEND_INO; i < MAX_INO_ENTRY; i++) { + struct inode_management *im = &sbi->im[i]; + + spin_lock(&im->ino_lock); + xa_destroy(&im->ino_root); + spin_unlock(&im->ino_lock); + } } void f2fs_set_dirty_device(struct f2fs_sb_info *sbi, nid_t ino, diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index c925b9207a37..1b96d8718c5c 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -388,14 +388,18 @@ enum { /* for the list of ino */ enum { ORPHAN_INO, /* for orphan ino list */ + FLUSH_INO, /* for multiple device flushing */ APPEND_INO, /* for append ino list */ UPDATE_INO, /* for update ino list */ TRANS_DIR_INO, /* for transactions dir ino list */ XATTR_DIR_INO, /* for xattr updated dir ino list */ - FLUSH_INO, /* for multiple device flushing */ MAX_INO_ENTRY, /* max. list */ }; +#define INO_BITS_PER_SLOT BITS_PER_XA_VALUE +#define INO_SLOT_INDEX(ino) ((ino) / INO_BITS_PER_SLOT) +#define INO_BIT_OFFSET(ino) ((ino) % INO_BITS_PER_SLOT) + struct ino_entry { struct list_head list; /* list head */ nid_t ino; /* inode number */ From 41a28c865d1d5843f8cb9e0af17a8f4d9e2961ff Mon Sep 17 00:00:00 2001 From: Yun Zhou Date: Fri, 31 Jul 2026 11:10:10 +0800 Subject: [PATCH 251/641] xfs: don't hold buffer locks across sync transaction commit in xfs_sync_sb_buf xfs_sync_sb_buf() holds sb/rtsb buffer locks across a synchronous xfs_trans_commit(), which flushes the CIL push workqueue internally. If shutdown occurs during the CIL push, xfs_buf_item_unpin() needs to lock these buffers to fail them, causing a deadlock: setlabel: holds buf lock -> flush_workqueue(xfs-cil) CIL push worker: xfs_buf_item_unpin -> xfs_buf_lock(same buf) Remove the xfs_trans_bhold() calls so that commit releases the buffer locks normally. After the sync commit, re-acquire the buffers via mp->m_sb_bp / mp->m_rtsb_bp for the on-disk writeback. Fixes: f7664b31975b ("xfs: implement online get/set fs label") Reported-by: syzbot+837bcd54843dd6262f2f@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=837bcd54843dd6262f2f Cc: stable@vger.kernel.org Signed-off-by: Yun Zhou Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_rtgroup.h | 6 +++++- fs/xfs/libxfs/xfs_sb.c | 37 +++++++++++++++++-------------------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/fs/xfs/libxfs/xfs_rtgroup.h b/fs/xfs/libxfs/xfs_rtgroup.h index c0b9f9f2c413..fca2eb74908c 100644 --- a/fs/xfs/libxfs/xfs_rtgroup.h +++ b/fs/xfs/libxfs/xfs_rtgroup.h @@ -359,7 +359,11 @@ static inline int xfs_initialize_rtgroups(struct xfs_mount *mp, # define xfs_rtgroup_unlock(rtg, gf) ((void)0) # define xfs_rtgroup_trans_join(tp, rtg, gf) ((void)0) # define xfs_update_rtsb(bp, sb_bp) ((void)0) -# define xfs_log_rtsb(tp, sb_bp) (NULL) +static inline struct xfs_buf *xfs_log_rtsb(struct xfs_trans *tp, + const struct xfs_buf *sb_bp) +{ + return NULL; +} # define xfs_rtgroup_get_geometry(rtg, rgeo) (-EOPNOTSUPP) #endif /* CONFIG_XFS_RT */ diff --git a/fs/xfs/libxfs/xfs_sb.c b/fs/xfs/libxfs/xfs_sb.c index 75f2a021ee6d..f0341adbb879 100644 --- a/fs/xfs/libxfs/xfs_sb.c +++ b/fs/xfs/libxfs/xfs_sb.c @@ -1470,36 +1470,33 @@ xfs_sync_sb_buf( bool update_rtsb) { struct xfs_trans *tp; - struct xfs_buf *bp; - struct xfs_buf *rtsb_bp = NULL; int error; error = xfs_trans_alloc(mp, &M_RES(mp)->tr_sb, 0, 0, 0, &tp); if (error) return error; - bp = xfs_trans_getsb(tp); xfs_log_sb(tp); - xfs_trans_bhold(tp, bp); - if (update_rtsb) { - rtsb_bp = xfs_log_rtsb(tp, bp); - if (rtsb_bp) - xfs_trans_bhold(tp, rtsb_bp); - } + if (update_rtsb) + xfs_log_rtsb(tp, xfs_trans_getsb(tp)); xfs_trans_set_sync(tp); error = xfs_trans_commit(tp); if (error) - goto out; - /* - * write out the sb buffer to get the changes to disk - */ - error = xfs_bwrite(bp); - if (!error && rtsb_bp) - error = xfs_bwrite(rtsb_bp); -out: - if (rtsb_bp) - xfs_buf_relse(rtsb_bp); - xfs_buf_relse(bp); + return error; + + /* Re-acquire and write the sb and rtsb to disk. */ + xfs_buf_lock(mp->m_sb_bp); + error = xfs_bwrite(mp->m_sb_bp); + xfs_buf_unlock(mp->m_sb_bp); + if (error) + return error; + + if (update_rtsb && mp->m_rtsb_bp) { + xfs_buf_lock(mp->m_rtsb_bp); + error = xfs_bwrite(mp->m_rtsb_bp); + xfs_buf_unlock(mp->m_rtsb_bp); + } + return error; } From b9b541e70d465a8c9cadf697eec7cb15b6653e7d Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 20 Jul 2026 11:36:12 +0200 Subject: [PATCH 252/641] xfs: split an assert in xfs_trans_log_buf Split the "irst <= last && last < BBTOB(bp->b_length)" assert into two to make it clear which condition fired. Signed-off-by: Christoph Hellwig Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_trans_buf.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/xfs/xfs_trans_buf.c b/fs/xfs/xfs_trans_buf.c index 1e025848811a..a5d25b703dfc 100644 --- a/fs/xfs/xfs_trans_buf.c +++ b/fs/xfs/xfs_trans_buf.c @@ -521,7 +521,8 @@ xfs_trans_log_buf( { struct xfs_buf_log_item *bip = bp->b_log_item; - ASSERT(first <= last && last < BBTOB(bp->b_length)); + ASSERT(first <= last); + ASSERT(last < BBTOB(bp->b_length)); ASSERT(!(bip->bli_flags & XFS_BLI_ORDERED)); xfs_trans_dirty_buf(tp, bp); From 7fc296b379edc0fef83890097af3c9537fbf4364 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 20 Jul 2026 16:09:21 +0200 Subject: [PATCH 253/641] xfs: don't flush and invalidate internal RT device twice in xfs_shutdown_devices Check for an internal RT device to remove a bit of extra work. Fixes: bdc03eb5f98f ("xfs: allow internal RT devices for zoned mode") Signed-off-by: Christoph Hellwig Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_super.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c index 4b2eeb7783f7..b24db75eaedc 100644 --- a/fs/xfs/xfs_super.c +++ b/fs/xfs/xfs_super.c @@ -445,7 +445,7 @@ xfs_shutdown_devices( blkdev_issue_flush(mp->m_logdev_targp->bt_bdev); invalidate_bdev(mp->m_logdev_targp->bt_bdev); } - if (mp->m_rtdev_targp) { + if (mp->m_rtdev_targp && mp->m_rtdev_targp != mp->m_ddev_targp) { blkdev_issue_flush(mp->m_rtdev_targp->bt_bdev); invalidate_bdev(mp->m_rtdev_targp->bt_bdev); } From 750a361bfc8a8c8178872f2aecb7507a6fbf41a4 Mon Sep 17 00:00:00 2001 From: Tal Zussman Date: Fri, 7 Aug 2026 06:58:51 -0400 Subject: [PATCH 254/641] xfs: remove kmem_to_page() kmem_to_page() has been unused since commit 5ced480d4886 ("xfs: simplify building the bio in xlog_write_iclog"), so remove it. This also removes the last instance of 'struct page' in fs/xfs/. Signed-off-by: Tal Zussman Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_platform.h | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/fs/xfs/xfs_platform.h b/fs/xfs/xfs_platform.h index 59a33c60e0ca..5d542e95fe44 100644 --- a/fs/xfs/xfs_platform.h +++ b/fs/xfs/xfs_platform.h @@ -289,15 +289,4 @@ int xfs_rw_bdev(struct block_device *bdev, sector_t sector, unsigned int count, # define PTR_FMT "%p" #endif -/* - * Helper for IO routines to grab backing pages from allocated kernel memory. - */ -static inline struct page * -kmem_to_page(void *addr) -{ - if (is_vmalloc_addr(addr)) - return vmalloc_to_page(addr); - return virt_to_page(addr); -} - #endif /* _XFS_PLATFORM_H */ From 4e07cd78e159a8c6b028e5dc5f4e5bf06067d969 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 10 Aug 2026 08:38:38 -0700 Subject: [PATCH 255/641] xfs: use inode_init_always_gfp with __GFP_NOFAIL in xfs_inode_alloc Just like the inode allocation itself, allocation of the security data inside of inode_init_always(_gfp) must not fail here as we can be inside an already dirty transaction context. Note that we do not have to pass GFP_NOFS explicitly as we are already in a nofs context when in a transaction, as seen by the call to alloc_inode_sb. Also update the comment about this a bit to be more clear. Fixes: bf904248a2ad ("[XFS] Combine the XFS and Linux inodes") Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_icache.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/fs/xfs/xfs_icache.c b/fs/xfs/xfs_icache.c index 9d8dd30bd927..a857b8aa255c 100644 --- a/fs/xfs/xfs_icache.c +++ b/fs/xfs/xfs_icache.c @@ -82,24 +82,20 @@ static inline xa_mark_t ici_tag_to_mark(unsigned int tag) /* * Allocate and initialise an xfs_inode. + * + * This can happen in context of already dirtied transactions, so the memory + * allocations must not fail. */ struct xfs_inode * xfs_inode_alloc( struct xfs_mount *mp, xfs_ino_t ino) { + gfp_t gfp = GFP_KERNEL | __GFP_NOFAIL; struct xfs_inode *ip; - /* - * XXX: If this didn't occur in transactions, we could drop GFP_NOFAIL - * and return NULL here on ENOMEM. - */ - ip = alloc_inode_sb(mp->m_super, xfs_inode_cache, GFP_KERNEL | __GFP_NOFAIL); - - if (inode_init_always(mp->m_super, VFS_I(ip))) { - kmem_cache_free(xfs_inode_cache, ip); - return NULL; - } + ip = alloc_inode_sb(mp->m_super, xfs_inode_cache, gfp); + inode_init_always_gfp(mp->m_super, VFS_I(ip), gfp); VFS_I(ip)->i_ino = ino; /* VFS doesn't initialise i_mode! */ From ae285611891f8d1a691771d14ecb5c9de3319abf Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Tue, 11 Aug 2026 10:48:37 -0600 Subject: [PATCH 256/641] xfs: handle NULL open_zone for merged ioends in xfs_ioend_put_open_zones In theory we could fail multiple ioends before an open zone was assigned to them, and the iomap code could merge them. Check for NULL not only for the main ioend but also all merged ones on ->io_list to handle this case. Fixes: 058dd70c65ab ("xfs: implement buffered writes to zoned RT devices") Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Reviewed-by: Hans Holmberg Reviewed-by: Damien Le Moal Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_aops.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fs/xfs/xfs_aops.c b/fs/xfs/xfs_aops.c index 74a6089abadf..5a444ef04967 100644 --- a/fs/xfs/xfs_aops.c +++ b/fs/xfs/xfs_aops.c @@ -89,8 +89,10 @@ xfs_ioend_put_open_zones( /* * Put the open zone for all ioends merged into this one (if any). */ - list_for_each_entry(tmp, &ioend->io_list, io_list) - xfs_open_zone_put(tmp->io_private); + list_for_each_entry(tmp, &ioend->io_list, io_list) { + if (tmp->io_private) + xfs_open_zone_put(tmp->io_private); + } /* * The main ioend might not have an open zone if the submission failed From 2d829cc76777a5335269768d7caa750e102f74d2 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Tue, 11 Aug 2026 10:48:38 -0600 Subject: [PATCH 257/641] xfs: fix racy open zone caching When testing on very fast storage devices, I've observed writers using io_uring creating many open zones with just a few kiB written to it, which then don't get used. I tracked this down to multiple io_uring helper threads finding a full zone in i_private, and then going on to select a one, with the final one winning the race and leaving it in i_private. Fix this by dropping full zones from i_private as soon we find them, checking cached for a cached zoned when a single writes needs a new zone, and by keeping an existing cached zone in xfs_set_cached_zone when it still has space available, dropping the newly found/allocated one instead. This uses i_flags_lock as a low-level spinlock for short hold times to avoid interactions with the ilock, which is used for completions. Signed-off-by: Christoph Hellwig Reviewed-by: Hans Holmberg Reviewed-by: Darrick J. Wong Reviewed-by: Damien Le Moal Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_zone_alloc.c | 72 ++++++++++++++++++++++++++++++++--------- 1 file changed, 56 insertions(+), 16 deletions(-) diff --git a/fs/xfs/xfs_zone_alloc.c b/fs/xfs/xfs_zone_alloc.c index 7d13fa7ab30a..bdbb60cc5d5b 100644 --- a/fs/xfs/xfs_zone_alloc.c +++ b/fs/xfs/xfs_zone_alloc.c @@ -793,17 +793,35 @@ xfs_get_cached_zone( rcu_read_lock(); oz = VFS_I(ip)->i_private; - if (oz) { - /* - * GC only steals open zones at mount time, so no GC zones - * should end up in the cache. - */ - ASSERT(!oz->oz_is_gc); - if (!atomic_inc_not_zero(&oz->oz_ref)) - oz = NULL; - } - rcu_read_unlock(); + if (!oz) + goto out_unlock; + /* + * GC only steals open zones at mount time, so no GC zones should end up + * in the cache. + */ + ASSERT(!oz->oz_is_gc); + + /* + * Drop the old cached open zone if it is full. + */ + if (oz->oz_allocated == rtg_blocks(oz->oz_rtg)) { + spin_lock(&ip->i_flags_lock); + oz = VFS_I(ip)->i_private; + if (oz && oz->oz_allocated == rtg_blocks(oz->oz_rtg)) { + VFS_I(ip)->i_private = NULL; + spin_unlock(&ip->i_flags_lock); + xfs_open_zone_put(oz); + oz = NULL; + goto out_unlock; + } + spin_unlock(&ip->i_flags_lock); + } + + if (!atomic_inc_not_zero(&oz->oz_ref)) + oz = NULL; +out_unlock: + rcu_read_unlock(); return oz; } @@ -818,18 +836,41 @@ xfs_get_cached_zone( * that were every written to, but significantly simplifies the cached zone * lookup. Because the open_zone is clearly marked as full when all data * in the underlying RTG was written, the caching is always safe. + * + * Called with a reference on @oz held. And returns two references on the + * returned zone: one for the caller and one for pinning the zone in + * inode->i_private. */ -static void +static struct xfs_open_zone * xfs_set_cached_zone( struct xfs_inode *ip, struct xfs_open_zone *oz) { struct xfs_open_zone *old_oz; + /* + * If the open zone cached in the inode still has free space, use that + * instead of the new open zone just selected. This can happen when + * multiple threads race to perform zone selection for an inode. + * io_uring worker threads seem to be good way to trigger this. + * + * We need to grab an extra reference to this open zone as the caller + * owns a reference in addition to the i_private pointer. + */ + spin_lock(&ip->i_flags_lock); + old_oz = VFS_I(ip)->i_private; + if (old_oz && old_oz->oz_allocated < rtg_blocks(old_oz->oz_rtg) && + atomic_inc_not_zero(&old_oz->oz_ref)) { + spin_unlock(&ip->i_flags_lock); + xfs_open_zone_put(oz); + return old_oz; + } + VFS_I(ip)->i_private = oz; atomic_inc(&oz->oz_ref); - old_oz = xchg(&VFS_I(ip)->i_private, oz); + spin_unlock(&ip->i_flags_lock); if (old_oz) xfs_open_zone_put(old_oz); + return oz; } static void @@ -873,14 +914,13 @@ xfs_zone_alloc_and_submit( * the inode is still associated with a zone and use that if so. */ if (!*oz) - *oz = xfs_get_cached_zone(ip); - - if (!*oz) { select_zone: + *oz = xfs_get_cached_zone(ip); + if (!*oz) { *oz = xfs_select_zone(mp, write_hint, pack_tight); if (!*oz) goto out_error; - xfs_set_cached_zone(ip, *oz); + *oz = xfs_set_cached_zone(ip, *oz); } alloc_len = xfs_zone_alloc_blocks(*oz, XFS_B_TO_FSB(mp, ioend->io_size), From 4bc67fc800edcaae8a657e4d2fba12e64e856b9f Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Tue, 11 Aug 2026 10:48:39 -0600 Subject: [PATCH 258/641] xfs: fix zoned write iomap flags assignments Don't overwrite IOMAP_F_DIRTY with IOMAP_F_ANON_WRITE, but ensure both flags are set instead. Note that in practice this is harmless as all zoned writes force a metadata transaction anyway, but incorrectly assigned flags are still a landmine that will cause problems at some point. Fixes: 058dd70c65ab ("xfs: implement buffered writes to zoned RT devices") Fixes: 2e2383405824 ("xfs: implement direct writes to zoned RT devices") Cc: stable@vger.kernel.org # v6.15 Signed-off-by: Christoph Hellwig Reviewed-by: Andrey Albershteyn Reviewed-by: Darrick J. Wong Reviewed-by: Hans Holmberg Reviewed-by: Damien Le Moal Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_aops.c | 3 +-- fs/xfs/xfs_iomap.c | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/fs/xfs/xfs_aops.c b/fs/xfs/xfs_aops.c index 5a444ef04967..53b94af92ac5 100644 --- a/fs/xfs/xfs_aops.c +++ b/fs/xfs/xfs_aops.c @@ -634,11 +634,10 @@ xfs_zoned_map_blocks( xfs_iunlock(ip, XFS_ILOCK_EXCL); wpc->iomap.type = IOMAP_MAPPED; - wpc->iomap.flags = IOMAP_F_DIRTY; wpc->iomap.bdev = mp->m_rtdev_targp->bt_bdev; wpc->iomap.offset = offset; wpc->iomap.length = XFS_FSB_TO_B(mp, count_fsb); - wpc->iomap.flags = IOMAP_F_ANON_WRITE; + wpc->iomap.flags = IOMAP_F_ANON_WRITE | IOMAP_F_DIRTY; trace_xfs_zoned_map_blocks(ip, offset, wpc->iomap.length); return 0; diff --git a/fs/xfs/xfs_iomap.c b/fs/xfs/xfs_iomap.c index 71c45be8c652..d8c3c2be6760 100644 --- a/fs/xfs/xfs_iomap.c +++ b/fs/xfs/xfs_iomap.c @@ -1084,11 +1084,10 @@ xfs_zoned_direct_write_iomap_begin( } iomap->type = IOMAP_MAPPED; - iomap->flags = IOMAP_F_DIRTY; iomap->bdev = ip->i_mount->m_rtdev_targp->bt_bdev; iomap->offset = offset; iomap->length = length; - iomap->flags = IOMAP_F_ANON_WRITE; + iomap->flags = IOMAP_F_ANON_WRITE | IOMAP_F_DIRTY; return 0; } From 0510346e8e308d2e2cb057ea7b408758b5d6f6cd Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Tue, 11 Aug 2026 10:48:40 -0600 Subject: [PATCH 259/641] xfs: factor out a xfs_iomap_set_anon_write helper De-duplicate the iomap setup for zoned writes. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Reviewed-by: Hans Holmberg Reviewed-by: Damien Le Moal Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_aops.c | 8 ++------ fs/xfs/xfs_iomap.c | 6 +----- fs/xfs/xfs_iomap.h | 14 ++++++++++++++ 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/fs/xfs/xfs_aops.c b/fs/xfs/xfs_aops.c index 53b94af92ac5..1dc51235982c 100644 --- a/fs/xfs/xfs_aops.c +++ b/fs/xfs/xfs_aops.c @@ -633,12 +633,8 @@ xfs_zoned_map_blocks( XFS_BMAPI_REMAP); xfs_iunlock(ip, XFS_ILOCK_EXCL); - wpc->iomap.type = IOMAP_MAPPED; - wpc->iomap.bdev = mp->m_rtdev_targp->bt_bdev; - wpc->iomap.offset = offset; - wpc->iomap.length = XFS_FSB_TO_B(mp, count_fsb); - wpc->iomap.flags = IOMAP_F_ANON_WRITE | IOMAP_F_DIRTY; - + xfs_iomap_set_anon_write(ip, &wpc->iomap, offset, + XFS_FSB_TO_B(mp, count_fsb)); trace_xfs_zoned_map_blocks(ip, offset, wpc->iomap.length); return 0; } diff --git a/fs/xfs/xfs_iomap.c b/fs/xfs/xfs_iomap.c index d8c3c2be6760..7c6238fed61e 100644 --- a/fs/xfs/xfs_iomap.c +++ b/fs/xfs/xfs_iomap.c @@ -1083,11 +1083,7 @@ xfs_zoned_direct_write_iomap_begin( return error; } - iomap->type = IOMAP_MAPPED; - iomap->bdev = ip->i_mount->m_rtdev_targp->bt_bdev; - iomap->offset = offset; - iomap->length = length; - iomap->flags = IOMAP_F_ANON_WRITE | IOMAP_F_DIRTY; + xfs_iomap_set_anon_write(ip, iomap, offset, length); return 0; } diff --git a/fs/xfs/xfs_iomap.h b/fs/xfs/xfs_iomap.h index cffcec532ea6..c906c62d46f3 100644 --- a/fs/xfs/xfs_iomap.h +++ b/fs/xfs/xfs_iomap.h @@ -29,6 +29,20 @@ int xfs_zero_range(struct xfs_inode *ip, loff_t pos, loff_t len, int xfs_truncate_page(struct xfs_inode *ip, loff_t pos, struct xfs_zone_alloc_ctx *ac, bool *did_zero); +static inline void +xfs_iomap_set_anon_write( + struct xfs_inode *ip, + struct iomap *iomap, + loff_t offset, + loff_t length) +{ + iomap->type = IOMAP_MAPPED; + iomap->bdev = ip->i_mount->m_rtdev_targp->bt_bdev; + iomap->offset = offset; + iomap->length = length; + iomap->flags = IOMAP_F_ANON_WRITE | IOMAP_F_DIRTY; +} + static inline xfs_filblks_t xfs_aligned_fsb_count( xfs_fileoff_t offset_fsb, From 6b855256eb9e652caf8b14eb1f69b6eb00a9d9d1 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Tue, 11 Aug 2026 10:48:41 -0600 Subject: [PATCH 260/641] xfs: split ioend handling into a separate source file The ioend handling used to be only for buffered writeback, but has been extended to direct I/O and reads. Split it into a new source file. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Reviewed-by: Hans Holmberg Reviewed-by: Damien Le Moal Signed-off-by: Carlos Maiolino --- fs/xfs/Makefile | 1 + fs/xfs/xfs_aops.c | 181 +------------------------------------------- fs/xfs/xfs_aops.h | 1 - fs/xfs/xfs_file.c | 2 +- fs/xfs/xfs_ioend.c | 184 +++++++++++++++++++++++++++++++++++++++++++++ fs/xfs/xfs_ioend.h | 16 ++++ 6 files changed, 203 insertions(+), 182 deletions(-) create mode 100644 fs/xfs/xfs_ioend.c create mode 100644 fs/xfs/xfs_ioend.h diff --git a/fs/xfs/Makefile b/fs/xfs/Makefile index 9f7133e02576..399a207f2d0e 100644 --- a/fs/xfs/Makefile +++ b/fs/xfs/Makefile @@ -91,6 +91,7 @@ xfs-y += xfs_aops.o \ xfs_healthmon.o \ xfs_icache.o \ xfs_ioctl.o \ + xfs_ioend.o \ xfs_iomap.o \ xfs_iops.o \ xfs_inode.o \ diff --git a/fs/xfs/xfs_aops.c b/fs/xfs/xfs_aops.c index 1dc51235982c..8b6119776fb3 100644 --- a/fs/xfs/xfs_aops.c +++ b/fs/xfs/xfs_aops.c @@ -20,6 +20,7 @@ #include "xfs_errortag.h" #include "xfs_error.h" #include "xfs_icache.h" +#include "xfs_ioend.h" #include "xfs_zone_alloc.h" #include "xfs_rtgroup.h" #include @@ -36,15 +37,6 @@ XFS_WPC(struct iomap_writepage_ctx *ctx) return container_of(ctx, struct xfs_writepage_ctx, ctx); } -/* - * Fast and loose check if this write could update the on-disk inode size. - */ -static inline bool xfs_ioend_is_append(struct iomap_ioend *ioend) -{ - return ioend->io_offset + ioend->io_size > - XFS_I(ioend->io_inode)->i_disk_size; -} - /* * Update on-disk file size now that data has been written to disk. */ @@ -80,177 +72,6 @@ xfs_setfilesize( return xfs_trans_commit(tp); } -static void -xfs_ioend_put_open_zones( - struct iomap_ioend *ioend) -{ - struct iomap_ioend *tmp; - - /* - * Put the open zone for all ioends merged into this one (if any). - */ - list_for_each_entry(tmp, &ioend->io_list, io_list) { - if (tmp->io_private) - xfs_open_zone_put(tmp->io_private); - } - - /* - * The main ioend might not have an open zone if the submission failed - * before xfs_zone_alloc_and_submit got called. - */ - if (ioend->io_private) - xfs_open_zone_put(ioend->io_private); -} - -/* - * IO write completion. - */ -STATIC void -xfs_end_ioend_write( - struct iomap_ioend *ioend) -{ - struct xfs_inode *ip = XFS_I(ioend->io_inode); - struct xfs_mount *mp = ip->i_mount; - bool is_zoned = xfs_is_zoned_inode(ip); - xfs_off_t offset = ioend->io_offset; - size_t size = ioend->io_size; - unsigned int nofs_flag; - int error; - - /* - * We can allocate memory here while doing writeback on behalf of - * memory reclaim. To avoid memory allocation deadlocks set the - * task-wide nofs context for the following operations. - */ - nofs_flag = memalloc_nofs_save(); - - /* - * Just clean up the in-memory structures if the fs has been shut down. - */ - if (xfs_is_shutdown(mp)) { - error = -EIO; - goto done; - } - - /* - * Clean up all COW blocks and underlying data fork delalloc blocks on - * I/O error. The delalloc punch is required because this ioend was - * mapped to blocks in the COW fork and the associated pages are no - * longer dirty. If we don't remove delalloc blocks here, they become - * stale and can corrupt free space accounting on unmount. - */ - error = blk_status_to_errno(ioend->io_bio.bi_status); - if (unlikely(error)) { - /* - * Zoned writes update the in-core open zone accounting before - * I/O submission. A failed write leaves that state - * inconsistent, so shut down the filesystem instead of letting - * later writers wait forever for open zone space to become - * available. - */ - if (is_zoned) { - xfs_force_shutdown(mp, SHUTDOWN_META_IO_ERROR); - goto done; - } - if (ioend->io_flags & IOMAP_IOEND_SHARED) { - ASSERT(!is_zoned); - xfs_reflink_cancel_cow_range(ip, offset, size, true); - xfs_bmap_punch_delalloc_range(ip, XFS_DATA_FORK, offset, - offset + size, NULL); - } - goto done; - } - - /* - * Success: commit the COW or unwritten blocks if needed. - */ - if (is_zoned) - error = xfs_zoned_end_io(ip, offset, size, ioend->io_sector, - ioend->io_private, NULLFSBLOCK); - else if (ioend->io_flags & IOMAP_IOEND_SHARED) - error = xfs_reflink_end_cow(ip, offset, size); - else if (ioend->io_flags & IOMAP_IOEND_UNWRITTEN) - error = xfs_iomap_write_unwritten(ip, offset, size, false); - - if (!error && - !(ioend->io_flags & IOMAP_IOEND_DIRECT) && - xfs_ioend_is_append(ioend)) - error = xfs_setfilesize(ip, offset, size); -done: - if (is_zoned) - xfs_ioend_put_open_zones(ioend); - iomap_finish_ioends(ioend, error); - memalloc_nofs_restore(nofs_flag); -} - -/* - * Finish all pending IO completions that require transactional modifications. - * - * We try to merge physical and logically contiguous ioends before completion to - * minimise the number of transactions we need to perform during IO completion. - * Both unwritten extent conversion and COW remapping need to iterate and modify - * one physical extent at a time, so we gain nothing by merging physically - * discontiguous extents here. - * - * The ioend chain length that we can be processing here is largely unbound in - * length and we may have to perform significant amounts of work on each ioend - * to complete it. Hence we have to be careful about holding the CPU for too - * long in this loop. - */ -void -xfs_end_io( - struct work_struct *work) -{ - struct xfs_inode *ip = - container_of(work, struct xfs_inode, i_ioend_work); - struct iomap_ioend *ioend; - struct list_head tmp; - unsigned long flags; - - spin_lock_irqsave(&ip->i_ioend_lock, flags); - list_replace_init(&ip->i_ioend_list, &tmp); - spin_unlock_irqrestore(&ip->i_ioend_lock, flags); - - iomap_sort_ioends(&tmp); - while ((ioend = list_first_entry_or_null(&tmp, struct iomap_ioend, - io_list))) { - list_del_init(&ioend->io_list); - iomap_ioend_try_merge(ioend, &tmp); - if (bio_op(&ioend->io_bio) == REQ_OP_READ) - iomap_finish_ioends(ioend, - blk_status_to_errno(ioend->io_bio.bi_status)); - else - xfs_end_ioend_write(ioend); - cond_resched(); - } -} - -void -xfs_end_bio( - struct bio *bio) -{ - struct iomap_ioend *ioend = iomap_ioend_from_bio(bio); - struct xfs_inode *ip = XFS_I(ioend->io_inode); - struct xfs_mount *mp = ip->i_mount; - unsigned long flags; - - /* - * For Appends record the actually written block number and set the - * boundary flag if needed. - */ - if (IS_ENABLED(CONFIG_XFS_RT) && bio_is_zone_append(bio)) { - ioend->io_sector = bio->bi_iter.bi_sector; - xfs_mark_rtg_boundary(ioend); - } - - spin_lock_irqsave(&ip->i_ioend_lock, flags); - if (list_empty(&ip->i_ioend_list)) - WARN_ON_ONCE(!queue_work(mp->m_unwritten_workqueue, - &ip->i_ioend_work)); - list_add_tail(&ioend->io_list, &ip->i_ioend_list); - spin_unlock_irqrestore(&ip->i_ioend_lock, flags); -} - /* * We cannot cancel the ioend directly on error. We may have already set other * pages under writeback and hence we have to run I/O completion to mark the diff --git a/fs/xfs/xfs_aops.h b/fs/xfs/xfs_aops.h index 5a7a0f1a0b49..d5ae5c9d4c26 100644 --- a/fs/xfs/xfs_aops.h +++ b/fs/xfs/xfs_aops.h @@ -10,6 +10,5 @@ extern const struct address_space_operations xfs_address_space_operations; extern const struct address_space_operations xfs_dax_aops; int xfs_setfilesize(struct xfs_inode *ip, xfs_off_t offset, size_t size); -void xfs_end_bio(struct bio *bio); #endif /* __XFS_AOPS_H__ */ diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c index 7bff07e31cbd..426a67b813a7 100644 --- a/fs/xfs/xfs_file.c +++ b/fs/xfs/xfs_file.c @@ -25,7 +25,7 @@ #include "xfs_iomap.h" #include "xfs_reflink.h" #include "xfs_file.h" -#include "xfs_aops.h" +#include "xfs_ioend.h" #include "xfs_zone_alloc.h" #include "xfs_error.h" #include "xfs_errortag.h" diff --git a/fs/xfs/xfs_ioend.c b/fs/xfs/xfs_ioend.c new file mode 100644 index 000000000000..40695d18dac0 --- /dev/null +++ b/fs/xfs/xfs_ioend.c @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016-2025 Christoph Hellwig. + * All Rights Reserved. + */ +#include "xfs_platform.h" +#include "xfs_shared.h" +#include "xfs_format.h" +#include "xfs_log_format.h" +#include "xfs_trans_resv.h" +#include "xfs_mount.h" +#include "xfs_inode.h" +#include "xfs_iomap.h" +#include "xfs_trace.h" +#include "xfs_bmap_util.h" +#include "xfs_reflink.h" +#include "xfs_zone_alloc.h" +#include "xfs_ioend.h" + +static void +xfs_ioend_put_open_zones( + struct iomap_ioend *ioend) +{ + struct iomap_ioend *tmp; + + /* + * Put the open zone for all ioends merged into this one (if any). + */ + list_for_each_entry(tmp, &ioend->io_list, io_list) + xfs_open_zone_put(tmp->io_private); + + /* + * The main ioend might not have an open zone if the submission failed + * before xfs_zone_alloc_and_submit got called. + */ + if (ioend->io_private) + xfs_open_zone_put(ioend->io_private); +} + +static void +xfs_end_ioend_write( + struct iomap_ioend *ioend) +{ + struct xfs_inode *ip = XFS_I(ioend->io_inode); + struct xfs_mount *mp = ip->i_mount; + bool is_zoned = xfs_is_zoned_inode(ip); + xfs_off_t offset = ioend->io_offset; + size_t size = ioend->io_size; + unsigned int nofs_flag; + int error; + + /* + * We can allocate memory here while doing writeback on behalf of + * memory reclaim. To avoid memory allocation deadlocks set the + * task-wide nofs context for the following operations. + */ + nofs_flag = memalloc_nofs_save(); + + /* + * Just clean up the in-memory structures if the fs has been shut down. + */ + if (xfs_is_shutdown(mp)) { + error = -EIO; + goto done; + } + + /* + * Clean up all COW blocks and underlying data fork delalloc blocks on + * I/O error. The delalloc punch is required because this ioend was + * mapped to blocks in the COW fork and the associated pages are no + * longer dirty. If we don't remove delalloc blocks here, they become + * stale and can corrupt free space accounting on unmount. + */ + error = blk_status_to_errno(ioend->io_bio.bi_status); + if (unlikely(error)) { + /* + * Zoned writes update the in-core open zone accounting before + * I/O submission. A failed write leaves that state + * inconsistent, so shut down the filesystem instead of letting + * later writers wait forever for open zone space to become + * available. + */ + if (is_zoned) { + xfs_force_shutdown(mp, SHUTDOWN_META_IO_ERROR); + goto done; + } + if (ioend->io_flags & IOMAP_IOEND_SHARED) { + ASSERT(!is_zoned); + xfs_reflink_cancel_cow_range(ip, offset, size, true); + xfs_bmap_punch_delalloc_range(ip, XFS_DATA_FORK, offset, + offset + size, NULL); + } + goto done; + } + + /* + * Success: commit the COW or unwritten blocks if needed. + */ + if (is_zoned) + error = xfs_zoned_end_io(ip, offset, size, ioend->io_sector, + ioend->io_private, NULLFSBLOCK); + else if (ioend->io_flags & IOMAP_IOEND_SHARED) + error = xfs_reflink_end_cow(ip, offset, size); + else if (ioend->io_flags & IOMAP_IOEND_UNWRITTEN) + error = xfs_iomap_write_unwritten(ip, offset, size, false); + + if (!error && + !(ioend->io_flags & IOMAP_IOEND_DIRECT) && + xfs_ioend_is_append(ioend)) + error = xfs_setfilesize(ip, offset, size); +done: + if (is_zoned) + xfs_ioend_put_open_zones(ioend); + iomap_finish_ioends(ioend, error); + memalloc_nofs_restore(nofs_flag); +} + +/* + * Finish all pending IO completions that require transactional modifications. + * + * We try to merge physical and logically contiguous ioends before completion to + * minimise the number of transactions we need to perform during IO completion. + * Both unwritten extent conversion and COW remapping need to iterate and modify + * one physical extent at a time, so we gain nothing by merging physically + * discontiguous extents here. + * + * The ioend chain length that we can be processing here is largely unbound in + * length and we may have to perform significant amounts of work on each ioend + * to complete it. Hence we have to be careful about holding the CPU for too + * long in this loop. + */ +void +xfs_end_io( + struct work_struct *work) +{ + struct xfs_inode *ip = + container_of(work, struct xfs_inode, i_ioend_work); + struct iomap_ioend *ioend; + struct list_head tmp; + unsigned long flags; + + spin_lock_irqsave(&ip->i_ioend_lock, flags); + list_replace_init(&ip->i_ioend_list, &tmp); + spin_unlock_irqrestore(&ip->i_ioend_lock, flags); + + iomap_sort_ioends(&tmp); + while ((ioend = list_first_entry_or_null(&tmp, struct iomap_ioend, + io_list))) { + list_del_init(&ioend->io_list); + iomap_ioend_try_merge(ioend, &tmp); + if (bio_op(&ioend->io_bio) == REQ_OP_READ) + iomap_finish_ioends(ioend, + blk_status_to_errno(ioend->io_bio.bi_status)); + else + xfs_end_ioend_write(ioend); + cond_resched(); + } +} + +void +xfs_end_bio( + struct bio *bio) +{ + struct iomap_ioend *ioend = iomap_ioend_from_bio(bio); + struct xfs_inode *ip = XFS_I(ioend->io_inode); + struct xfs_mount *mp = ip->i_mount; + unsigned long flags; + + /* + * For Appends record the actually written block number and set the + * boundary flag if needed. + */ + if (IS_ENABLED(CONFIG_XFS_RT) && bio_is_zone_append(bio)) { + ioend->io_sector = bio->bi_iter.bi_sector; + xfs_mark_rtg_boundary(ioend); + } + + spin_lock_irqsave(&ip->i_ioend_lock, flags); + if (list_empty(&ip->i_ioend_list)) + WARN_ON_ONCE(!queue_work(mp->m_unwritten_workqueue, + &ip->i_ioend_work)); + list_add_tail(&ioend->io_list, &ip->i_ioend_list); + spin_unlock_irqrestore(&ip->i_ioend_lock, flags); +} diff --git a/fs/xfs/xfs_ioend.h b/fs/xfs/xfs_ioend.h new file mode 100644 index 000000000000..525865767fca --- /dev/null +++ b/fs/xfs/xfs_ioend.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __XFS_IOEND_H +#define __XFS_IOEND_H + +/* + * Fast and loose check if this write could update the on-disk inode size. + */ +static inline bool xfs_ioend_is_append(struct iomap_ioend *ioend) +{ + return ioend->io_offset + ioend->io_size > + XFS_I(ioend->io_inode)->i_disk_size; +} + +void xfs_end_bio(struct bio *bio); + +#endif /* __XFS_IOEND_H */ From 885435535bb1d07746916d4c8832f95767bf2d7e Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Thu, 13 Aug 2026 16:56:12 +0200 Subject: [PATCH 261/641] xfs: restore bi_bdev in xfs_zone_gc_write_chunk xfs_zone_gc_write_chunk relies on bi_bdev to still be valid, which is not true when XFS is used on top of a stacked block device. This can lead to misdirected GC writes, writing of plain text when using dm-crypt, or miscalculated I/O limits in xfs_zone_gc_split_write. Fix this by reassigning bi_bdev. Fixes: 080d01c41d44 ("xfs: implement zoned garbage collection") Signed-off-by: Christoph Hellwig Reviewed-by: Damien Le Moal Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_zone_gc.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/xfs/xfs_zone_gc.c b/fs/xfs/xfs_zone_gc.c index d0b85179a3d2..5fdcf98a2133 100644 --- a/fs/xfs/xfs_zone_gc.c +++ b/fs/xfs/xfs_zone_gc.c @@ -869,6 +869,11 @@ xfs_zone_gc_write_chunk( WRITE_ONCE(chunk->state, XFS_GC_BIO_NEW); list_move_tail(&chunk->entry, &data->writing); + /* + * If we run on top of stacked block device, the read I/O might have + * reset bi_bdev, restore it to the one we want. + */ + bio_set_dev(&chunk->bio, mp->m_rtdev_targp->bt_bdev); bio_reuse(&chunk->bio, REQ_OP_WRITE); while ((split_chunk = xfs_zone_gc_split_write(data, chunk))) xfs_zone_gc_submit_write(data, split_chunk); From e2f62a9744ebad3bcb6347a648e615026e9efeff Mon Sep 17 00:00:00 2001 From: Carlos Maiolino Date: Tue, 4 Aug 2026 11:45:51 +0200 Subject: [PATCH 262/641] xfs: fix capability check in xfs An user reported a bug where he managed to evade group's quota by changing a file's gid to a different group id the same user belonged to, even though quotas were enforced on both gids and the file's size was big enough to exceed the quota's hardlimit. Commit eba0549bc7d1 replaced a capable() call by a has_capability_noaudit() to prevent unnecessary selinux audit messages. Turns out that both calls have slightly different semantics even though their documentation seems similar. Where in a nutshell: capable() - Tests the task's effective credentials has_ns_capability_noaudit() - Tests the task's real credentials This most of the time has no practical difference but in some cases like changing attrs (specifically group id in this case) through a NFS client this will allow the quota code to use XFS_QMOPT_FORCE_RES, effectively bypassing quota accounting checks. Using instead ns_capable_noaudit() should fix this issue and prevent selinux audit messages. This also fix the remaining calls to has_capability_noaudit() Fixes: eba0549bc7d1 ("xfs: don't generate selinux audit messages for capability testing") Cc: stable@vger.kernel.org # v5.18 Reported-by: Dr. Thomas Orgis Signed-off-by: Carlos Maiolino Reviewed-by: Darrick J. Wong Reviewed-by: Serge Hallyn Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_fsmap.c | 2 +- fs/xfs/xfs_ioctl.c | 2 +- fs/xfs/xfs_iops.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/xfs/xfs_fsmap.c b/fs/xfs/xfs_fsmap.c index b6a3bc9f143c..7c79fbe0a74c 100644 --- a/fs/xfs/xfs_fsmap.c +++ b/fs/xfs/xfs_fsmap.c @@ -1175,7 +1175,7 @@ xfs_getfsmap( return -EINVAL; use_rmap = xfs_has_rmapbt(mp) && - has_capability_noaudit(current, CAP_SYS_ADMIN); + ns_capable_noaudit(&init_user_ns, CAP_SYS_ADMIN); head->fmh_entries = 0; /* Set up our device handlers. */ diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c index 1b53701bebea..1a8af827dde1 100644 --- a/fs/xfs/xfs_ioctl.c +++ b/fs/xfs/xfs_ioctl.c @@ -647,7 +647,7 @@ xfs_ioctl_setattr_get_trans( goto out_error; error = xfs_trans_alloc_ichange(ip, NULL, NULL, pdqp, - has_capability_noaudit(current, CAP_FOWNER), &tp); + ns_capable_noaudit(&init_user_ns, CAP_FOWNER), &tp); if (error) goto out_error; diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c index 4a3299abf774..36a22d4a8cc4 100644 --- a/fs/xfs/xfs_iops.c +++ b/fs/xfs/xfs_iops.c @@ -834,7 +834,7 @@ xfs_setattr_nonsize( } error = xfs_trans_alloc_ichange(ip, udqp, gdqp, NULL, - has_capability_noaudit(current, CAP_FOWNER), &tp); + ns_capable_noaudit(&init_user_ns, CAP_FOWNER), &tp); if (error) goto out_dqrele; From 1b91724d0bdc470ed8f353d1cc8d3e4123b51ed5 Mon Sep 17 00:00:00 2001 From: Carlos Maiolino Date: Tue, 4 Aug 2026 11:45:52 +0200 Subject: [PATCH 263/641] capability: Add new capable_noaudit In some situations (quota enforcement bypass in this case) we'd like to check for a specific capability without triggering spurious audit messages from security modules like selinux. Add a new helper so we don't need to use ns_capable_noaudit() directly. Signed-off-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Reviewed-by: Serge Hallyn Signed-off-by: Carlos Maiolino --- include/linux/capability.h | 5 +++++ kernel/capability.c | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/include/linux/capability.h b/include/linux/capability.h index 37db92b3d6f8..f8532d92fcad 100644 --- a/include/linux/capability.h +++ b/include/linux/capability.h @@ -145,6 +145,7 @@ extern bool has_capability_noaudit(struct task_struct *t, int cap); extern bool has_ns_capability_noaudit(struct task_struct *t, struct user_namespace *ns, int cap); extern bool capable(int cap); +bool capable_noaudit(int cap); extern bool ns_capable(struct user_namespace *ns, int cap); extern bool ns_capable_noaudit(struct user_namespace *ns, int cap); extern bool ns_capable_setid(struct user_namespace *ns, int cap); @@ -167,6 +168,10 @@ static inline bool capable(int cap) { return true; } +static inline bool capable_noaudit(int cap) +{ + return true; +} static inline bool ns_capable(struct user_namespace *ns, int cap) { return true; diff --git a/kernel/capability.c b/kernel/capability.c index 829f49ae07b9..f4a7f1963c9d 100644 --- a/kernel/capability.c +++ b/kernel/capability.c @@ -416,6 +416,24 @@ bool capable(int cap) return ns_capable(&init_user_ns, cap); } EXPORT_SYMBOL(capable); + +/** + * capable_noaudit - Determine if the current task has a superior + * capability in effect by checking the process's effective + * capabilities (unaudited). + * @cap: The capability to be tested for + * + * This is the same as capable(), except it uses CAP_OPT_NOAUDIT as to prevent + * issuing spurious audit messages. + * + * This sets PF_SUPERPRIV on the task if the capability is available on the + * assumption that it's about to be used. + */ +bool capable_noaudit(int cap) +{ + return ns_capable_noaudit(&init_user_ns, cap); +} +EXPORT_SYMBOL(capable_noaudit); #endif /* CONFIG_MULTIUSER */ /** From 4642259374fc9eb99af4cf8b2d54d54ccb0de08e Mon Sep 17 00:00:00 2001 From: Carlos Maiolino Date: Tue, 4 Aug 2026 11:45:53 +0200 Subject: [PATCH 264/641] quota: Don't issue audit messages on quota enforcing Calling capable() to determine if we can bypass quota enforcement or not can trigger spurious audit messages. We don't really require it here so just use the capable_noaudit() version. Signed-off-by: Carlos Maiolino Reviewed-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Acked-by: Jan Kara Signed-off-by: Carlos Maiolino --- fs/quota/dquot.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c index 204afc5e984b..1c78c695d0dd 100644 --- a/fs/quota/dquot.c +++ b/fs/quota/dquot.c @@ -1240,7 +1240,7 @@ static int ignore_hardlimit(struct dquot *dquot) { struct mem_dqinfo *info = &sb_dqopt(dquot->dq_sb)->info[dquot->dq_id.type]; - return capable(CAP_SYS_RESOURCE) && + return capable_noaudit(CAP_SYS_RESOURCE) && (info->dqi_format->qf_fmt_id != QFMT_VFS_OLD || !(info->dqi_flags & DQF_ROOT_SQUASH)); } From be9c45bdb19461889b16c91c185a284d665ea72d Mon Sep 17 00:00:00 2001 From: Carlos Maiolino Date: Tue, 4 Aug 2026 11:45:54 +0200 Subject: [PATCH 265/641] xfs: replace ns_capable_noaudit Now that capable_noaudit() is available, we don't need to keep using ns_capable_noaudit() and specifying the usernamespace every single time. Signed-off-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_fsmap.c | 3 +-- fs/xfs/xfs_ioctl.c | 2 +- fs/xfs/xfs_iops.c | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/fs/xfs/xfs_fsmap.c b/fs/xfs/xfs_fsmap.c index 7c79fbe0a74c..041bb2105ec6 100644 --- a/fs/xfs/xfs_fsmap.c +++ b/fs/xfs/xfs_fsmap.c @@ -1174,8 +1174,7 @@ xfs_getfsmap( if (!xfs_getfsmap_check_keys(&head->fmh_keys[0], &head->fmh_keys[1])) return -EINVAL; - use_rmap = xfs_has_rmapbt(mp) && - ns_capable_noaudit(&init_user_ns, CAP_SYS_ADMIN); + use_rmap = xfs_has_rmapbt(mp) && capable_noaudit(CAP_SYS_ADMIN); head->fmh_entries = 0; /* Set up our device handlers. */ diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c index 1a8af827dde1..96ca3e480cb9 100644 --- a/fs/xfs/xfs_ioctl.c +++ b/fs/xfs/xfs_ioctl.c @@ -647,7 +647,7 @@ xfs_ioctl_setattr_get_trans( goto out_error; error = xfs_trans_alloc_ichange(ip, NULL, NULL, pdqp, - ns_capable_noaudit(&init_user_ns, CAP_FOWNER), &tp); + capable_noaudit(CAP_FOWNER), &tp); if (error) goto out_error; diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c index 36a22d4a8cc4..d1306e723899 100644 --- a/fs/xfs/xfs_iops.c +++ b/fs/xfs/xfs_iops.c @@ -834,7 +834,7 @@ xfs_setattr_nonsize( } error = xfs_trans_alloc_ichange(ip, udqp, gdqp, NULL, - ns_capable_noaudit(&init_user_ns, CAP_FOWNER), &tp); + capable_noaudit(CAP_FOWNER), &tp); if (error) goto out_dqrele; From 412f89fb3988a344175899776c8bc7073524ad84 Mon Sep 17 00:00:00 2001 From: Carlos Maiolino Date: Tue, 4 Aug 2026 11:45:55 +0200 Subject: [PATCH 266/641] capability: unexport has_capability_noaudit This has been originally exported to be used in xfs. Giving we are not using it anymore, unexport for consistency. Signed-off-by: Carlos Maiolino Reviewed-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Reviewed-by: Serge Hallyn Signed-off-by: Carlos Maiolino --- kernel/capability.c | 1 - 1 file changed, 1 deletion(-) diff --git a/kernel/capability.c b/kernel/capability.c index f4a7f1963c9d..90e6ab62f6db 100644 --- a/kernel/capability.c +++ b/kernel/capability.c @@ -326,7 +326,6 @@ bool has_capability_noaudit(struct task_struct *t, int cap) { return has_ns_capability_noaudit(t, &init_user_ns, cap); } -EXPORT_SYMBOL(has_capability_noaudit); static bool ns_capable_common(struct user_namespace *ns, int cap, From 78acddfde75177a27000f076e3e828743e38877e Mon Sep 17 00:00:00 2001 From: Cosmo Chou Date: Sat, 18 Jul 2026 03:37:05 +0800 Subject: [PATCH 267/641] rtc: pcf85363: Add error checking to regmap calls in probe() The probe() function ignores errors returned by regmap operations. If an I2C transport error occurs (e.g., -ENXIO), the driver continues probing and may register a non-functional RTC device. Propagate errors from all unchecked regmap calls in probe() using dev_err_probe(). Fixes: fd9a6a13949a ("rtc: pcf85363: add support for the quartz-load-femtofarads property") Signed-off-by: Cosmo Chou Link: https://lore.kernel.org/linux-rtc/20260716125142.1801599-1-chou.cosmo@gmail.com/ Link: https://patch.msgid.link/20260717193705.2003175-1-chou.cosmo@gmail.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-pcf85363.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/drivers/rtc/rtc-pcf85363.c b/drivers/rtc/rtc-pcf85363.c index 540042b9eec8..ccc7834e5759 100644 --- a/drivers/rtc/rtc-pcf85363.c +++ b/drivers/rtc/rtc-pcf85363.c @@ -426,8 +426,8 @@ static int pcf85363_probe(struct i2c_client *client) err = pcf85363_load_capacitance(pcf85363, client->dev.of_node); if (err < 0) - dev_warn(&client->dev, "failed to set xtal load capacitance: %d", - err); + return dev_err_probe(&client->dev, err, + "failed to set xtal load capacitance\n"); pcf85363->rtc->ops = &rtc_ops; pcf85363->rtc->range_min = RTC_TIMESTAMP_BEGIN_2000; @@ -436,9 +436,16 @@ static int pcf85363_probe(struct i2c_client *client) wakeup_source = device_property_read_bool(&client->dev, "wakeup-source"); if (client->irq > 0 || wakeup_source) { - regmap_write(pcf85363->regmap, CTRL_FLAGS, 0); - regmap_update_bits(pcf85363->regmap, CTRL_PIN_IO, - PIN_IO_INTAPM, PIN_IO_INTA_OUT); + err = regmap_write(pcf85363->regmap, CTRL_FLAGS, 0); + if (err) + return dev_err_probe(&client->dev, err, + "failed to clear flags\n"); + + err = regmap_update_bits(pcf85363->regmap, CTRL_PIN_IO, + PIN_IO_INTAPM, PIN_IO_INTA_OUT); + if (err) + return dev_err_probe(&client->dev, err, + "failed to set interrupt pin mode\n"); } if (client->irq > 0) { From 53dd1c1de851280c71385f49a24715e5615c926c Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Fri, 21 Aug 2026 16:37:31 +0100 Subject: [PATCH 268/641] ASoC: tegra: Fix the I2S enable default value Commit 4b05ccb17f92 ("regcache: Sort the local copy of an unsorted reg_defaults array") exposed an issue in the Tegra I2S driver where the register default for the TEGRA210_I2S_ENABLE is specified as 1, but the hardware default is actually 0. After this commit was added the I2S driver is no longer working and so fix this by correcting the default value for this register and explicitly configuring the I2S_ENABLE register when runtime resuming the I2S device. The I2S_ENABLE register offset is different on Tegra264 devices than other Tegra devices and so add a 'enable_reg' variable to the SoC data structure to specify the offset for different SoC devices. Fixes: c0bfa98349d1 ("ASoC: tegra: Add Tegra210 based I2S driver") Cc: stable@vger.kernel.org Signed-off-by: Jon Hunter Link: https://patch.msgid.link/20260821153734.158426-2-jonathanh@nvidia.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra210_i2s.c | 20 +++++++++++++++++--- sound/soc/tegra/tegra210_i2s.h | 1 + 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/sound/soc/tegra/tegra210_i2s.c b/sound/soc/tegra/tegra210_i2s.c index 84506576437d..79a2f898ea28 100644 --- a/sound/soc/tegra/tegra210_i2s.c +++ b/sound/soc/tegra/tegra210_i2s.c @@ -23,7 +23,7 @@ static const struct reg_default tegra210_i2s_reg_defaults[] = { { TEGRA210_I2S_RX_CIF_CTRL, 0x00007700 }, { TEGRA210_I2S_TX_INT_MASK, 0x00000003 }, { TEGRA210_I2S_TX_CIF_CTRL, 0x00007700 }, - { TEGRA210_I2S_ENABLE, 0x1 }, + { TEGRA210_I2S_ENABLE, 0x0 }, { TEGRA210_I2S_CG, 0x1 }, { TEGRA210_I2S_TIMING, 0x0000001f }, /* @@ -42,7 +42,7 @@ static const struct reg_default tegra264_i2s_reg_defaults[] = { { TEGRA264_I2S_TX_INT_MASK, 0x00000003 }, { TEGRA264_I2S_TX_CIF_CTRL, 0x00003f00 }, { TEGRA264_I2S_TX_FIFO_RD_ACCESS_MODE, 0x1 }, - { TEGRA264_I2S_ENABLE, 0x1 }, + { TEGRA264_I2S_ENABLE, 0x0 }, { TEGRA264_I2S_CG, 0x1 }, { TEGRA264_I2S_TIMING, 0x0000001f }, }; @@ -201,9 +201,21 @@ static int tegra210_i2s_runtime_resume(struct device *dev) } regcache_cache_only(i2s->regmap, false); - regcache_sync(i2s->regmap); + err = regcache_sync(i2s->regmap); + if (err) + goto err; + + err = regmap_write(i2s->regmap, i2s->soc_data->enable_reg, I2S_EN); + if (err) + goto err; return 0; + +err: + regcache_cache_only(i2s->regmap, true); + clk_disable_unprepare(i2s->clk_i2s); + + return err; } static void tegra210_i2s_set_data_offset(struct tegra210_i2s *i2s, @@ -1133,6 +1145,7 @@ static const struct tegra_i2s_soc_data soc_data_tegra210 = { .regmap_conf = &tegra210_regmap_conf, .i2s_cmpnt = &tegra210_i2s_cmpnt, .max_ch = TEGRA210_I2S_MAX_CHANNEL, + .enable_reg = TEGRA210_I2S_ENABLE, .tx_offset = TEGRA210_I2S_TX_OFFSET, .i2s_ctrl_offset = TEGRA210_I2S_CTRL_OFFSET, .fsync_width_mask = I2S_CTRL_FSYNC_WIDTH_MASK, @@ -1144,6 +1157,7 @@ static const struct tegra_i2s_soc_data soc_data_tegra264 = { .regmap_conf = &tegra264_regmap_conf, .i2s_cmpnt = &tegra264_i2s_cmpnt, .max_ch = TEGRA264_I2S_MAX_CHANNEL, + .enable_reg = TEGRA264_I2S_ENABLE, .tx_offset = TEGRA264_I2S_TX_OFFSET, .i2s_ctrl_offset = TEGRA264_I2S_CTRL_OFFSET, .fsync_width_mask = TEGRA264_I2S_CTRL_FSYNC_WIDTH_MASK, diff --git a/sound/soc/tegra/tegra210_i2s.h b/sound/soc/tegra/tegra210_i2s.h index 42be2137342c..82292f96ab36 100644 --- a/sound/soc/tegra/tegra210_i2s.h +++ b/sound/soc/tegra/tegra210_i2s.h @@ -150,6 +150,7 @@ struct tegra_i2s_soc_data { const struct regmap_config *regmap_conf; const struct snd_soc_component_driver *i2s_cmpnt; unsigned int max_ch; + unsigned int enable_reg; unsigned int tx_offset; unsigned int i2s_ctrl_offset; unsigned int fsync_width_mask; From 5442b8093a2f94ecd4696b3875194be09e2676c5 Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Fri, 21 Aug 2026 16:37:32 +0100 Subject: [PATCH 269/641] ASoC: tegra: Fix the MIXER enable default value Commit 4b05ccb17f92 ("regcache: Sort the local copy of an unsorted reg_defaults array") exposed an issue in the Tegra MIXER driver where the register default for the TEGRA210_MIXER_ENABLE is specified as 1, but the hardware default is actually 0. After this commit was added the MIXER driver is no longer working and so fix this by correcting the default value for this register and explicitly configuring the MIXER_ENABLE register when runtime resuming the MIXER device. Fixes: 05bb3d5ec64a ("ASoC: tegra: Add Tegra210 based Mixer driver") Cc: stable@vger.kernel.org Signed-off-by: Jon Hunter Link: https://patch.msgid.link/20260821153734.158426-3-jonathanh@nvidia.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra210_mixer.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/sound/soc/tegra/tegra210_mixer.c b/sound/soc/tegra/tegra210_mixer.c index 8eb4e54b954b..96259a6cbc59 100644 --- a/sound/soc/tegra/tegra210_mixer.c +++ b/sound/soc/tegra/tegra210_mixer.c @@ -57,7 +57,7 @@ static const struct reg_default tegra210_mixer_reg_defaults[] = { MIXER_TX_REG_DEFAULTS(3), MIXER_TX_REG_DEFAULTS(4), - { TEGRA210_MIXER_ENABLE, 0x1 }, + { TEGRA210_MIXER_ENABLE, 0x0 }, { TEGRA210_MIXER_CG, 0x00000001}, { TEGRA210_MIXER_GAIN_CFG_RAM_CTRL, 0x00004000}, { TEGRA210_MIXER_PEAKM_RAM_CTRL, 0x00004000}, @@ -86,11 +86,15 @@ static int tegra210_mixer_runtime_suspend(struct device *dev) static int tegra210_mixer_runtime_resume(struct device *dev) { struct tegra210_mixer *mixer = dev_get_drvdata(dev); + int err; regcache_cache_only(mixer->regmap, false); - regcache_sync(mixer->regmap); + err = regcache_sync(mixer->regmap); + if (err) + return err; - return 0; + return regmap_write(mixer->regmap, TEGRA210_MIXER_ENABLE, + TEGRA210_MIXER_EN); } static int tegra210_mixer_write_ram(struct tegra210_mixer *mixer, From 18d48bfd1bc178da7cfc7a1be3eaa1c8cb10f05c Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Fri, 21 Aug 2026 16:37:33 +0100 Subject: [PATCH 270/641] ASoC: tegra: Sort ADMAIF register defaults Commit 4b05ccb17f92 ("regcache: Sort the local copy of an unsorted reg_defaults array") exposed an issue in the Tegra ADMAIF driver and now the following warning is observed: tegra210-admaif 290f000.admaif: Driver needs fixing: Unsorted reg_defaults, sorting the copy This warning occurs because register defaults in the structures tegra186_admaif_reg_defaults and tegra264_admaif_reg_defaults are not specified in ascending order which is required by regmap. Fix this by sorting the register defaults according to their address. Note that in order to do this it is necessary to split the macro ADMAIF_REG_DEFAULTS into separate RX and TX macros to the RX and TX registers. Fixes: f74028e159bb ("ASoC: tegra: Add Tegra210 based ADMAIF driver") Cc: stable@vger.kernel.org Signed-off-by: Jon Hunter Link: https://patch.msgid.link/20260821153734.158426-4-jonathanh@nvidia.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra210_admaif.c | 210 ++++++++++++++++++++---------- 1 file changed, 139 insertions(+), 71 deletions(-) diff --git a/sound/soc/tegra/tegra210_admaif.c b/sound/soc/tegra/tegra210_admaif.c index 7299c6bfcf15..87e898cc35e4 100644 --- a/sound/soc/tegra/tegra210_admaif.c +++ b/sound/soc/tegra/tegra210_admaif.c @@ -26,94 +26,162 @@ #define CH_RX_REG(reg, id) CH_REG(admaif->soc_data->rx_base, reg, id) -#define REG_DEFAULTS(id, rx_ctrl, tx_ctrl, tx_base, rx_base, cif_ctrl) \ +#define RX_REG_DEFAULTS(id, rx_ctrl, rx_base, cif_ctrl) \ { CH_REG(rx_base, TEGRA_ADMAIF_RX_INT_MASK, id), 0x00000001 }, \ { CH_REG(rx_base, TEGRA_ADMAIF_CH_ACIF_RX_CTRL, id), cif_ctrl }, \ - { CH_REG(rx_base, TEGRA_ADMAIF_RX_FIFO_CTRL, id), rx_ctrl }, \ + { CH_REG(rx_base, TEGRA_ADMAIF_RX_FIFO_CTRL, id), rx_ctrl } + +#define TX_REG_DEFAULTS(id, tx_ctrl, tx_base, cif_ctrl) \ { CH_REG(tx_base, TEGRA_ADMAIF_TX_INT_MASK, id), 0x00000001 }, \ { CH_REG(tx_base, TEGRA_ADMAIF_CH_ACIF_TX_CTRL, id), cif_ctrl }, \ { CH_REG(tx_base, TEGRA_ADMAIF_TX_FIFO_CTRL, id), tx_ctrl } -#define ADMAIF_REG_DEFAULTS(id, chip) \ - REG_DEFAULTS((id) - 1, \ +#define ADMAIF_RX_REG_DEFAULTS(id, chip) \ + RX_REG_DEFAULTS((id) - 1, \ chip ## _ADMAIF_RX ## id ## _FIFO_CTRL_REG_DEFAULT, \ - chip ## _ADMAIF_TX ## id ## _FIFO_CTRL_REG_DEFAULT, \ - chip ## _ADMAIF_TX_BASE, \ chip ## _ADMAIF_RX_BASE, \ chip ## _ADMAIF_CIF_REG_DEFAULT) +#define ADMAIF_TX_REG_DEFAULTS(id, chip) \ + TX_REG_DEFAULTS((id) - 1, \ + chip ## _ADMAIF_TX ## id ## _FIFO_CTRL_REG_DEFAULT, \ + chip ## _ADMAIF_TX_BASE, \ + chip ## _ADMAIF_CIF_REG_DEFAULT) + static const struct reg_default tegra186_admaif_reg_defaults[] = { - {(TEGRA_ADMAIF_GLOBAL_CG_0 + TEGRA186_ADMAIF_GLOBAL_BASE), 0x00000003}, - ADMAIF_REG_DEFAULTS(1, TEGRA186), - ADMAIF_REG_DEFAULTS(2, TEGRA186), - ADMAIF_REG_DEFAULTS(3, TEGRA186), - ADMAIF_REG_DEFAULTS(4, TEGRA186), - ADMAIF_REG_DEFAULTS(5, TEGRA186), - ADMAIF_REG_DEFAULTS(6, TEGRA186), - ADMAIF_REG_DEFAULTS(7, TEGRA186), - ADMAIF_REG_DEFAULTS(8, TEGRA186), - ADMAIF_REG_DEFAULTS(9, TEGRA186), - ADMAIF_REG_DEFAULTS(10, TEGRA186), - ADMAIF_REG_DEFAULTS(11, TEGRA186), - ADMAIF_REG_DEFAULTS(12, TEGRA186), - ADMAIF_REG_DEFAULTS(13, TEGRA186), - ADMAIF_REG_DEFAULTS(14, TEGRA186), - ADMAIF_REG_DEFAULTS(15, TEGRA186), - ADMAIF_REG_DEFAULTS(16, TEGRA186), - ADMAIF_REG_DEFAULTS(17, TEGRA186), - ADMAIF_REG_DEFAULTS(18, TEGRA186), - ADMAIF_REG_DEFAULTS(19, TEGRA186), - ADMAIF_REG_DEFAULTS(20, TEGRA186) + ADMAIF_RX_REG_DEFAULTS(1, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(2, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(3, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(4, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(5, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(6, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(7, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(8, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(9, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(10, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(11, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(12, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(13, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(14, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(15, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(16, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(17, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(18, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(19, TEGRA186), + ADMAIF_RX_REG_DEFAULTS(20, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(1, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(2, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(3, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(4, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(5, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(6, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(7, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(8, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(9, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(10, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(11, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(12, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(13, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(14, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(15, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(16, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(17, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(18, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(19, TEGRA186), + ADMAIF_TX_REG_DEFAULTS(20, TEGRA186), + {(TEGRA_ADMAIF_GLOBAL_CG_0 + TEGRA186_ADMAIF_GLOBAL_BASE), 0x00000003} }; static const struct reg_default tegra210_admaif_reg_defaults[] = { - {(TEGRA_ADMAIF_GLOBAL_CG_0 + TEGRA210_ADMAIF_GLOBAL_BASE), 0x00000003}, - ADMAIF_REG_DEFAULTS(1, TEGRA210), - ADMAIF_REG_DEFAULTS(2, TEGRA210), - ADMAIF_REG_DEFAULTS(3, TEGRA210), - ADMAIF_REG_DEFAULTS(4, TEGRA210), - ADMAIF_REG_DEFAULTS(5, TEGRA210), - ADMAIF_REG_DEFAULTS(6, TEGRA210), - ADMAIF_REG_DEFAULTS(7, TEGRA210), - ADMAIF_REG_DEFAULTS(8, TEGRA210), - ADMAIF_REG_DEFAULTS(9, TEGRA210), - ADMAIF_REG_DEFAULTS(10, TEGRA210) + ADMAIF_RX_REG_DEFAULTS(1, TEGRA210), + ADMAIF_RX_REG_DEFAULTS(2, TEGRA210), + ADMAIF_RX_REG_DEFAULTS(3, TEGRA210), + ADMAIF_RX_REG_DEFAULTS(4, TEGRA210), + ADMAIF_RX_REG_DEFAULTS(5, TEGRA210), + ADMAIF_RX_REG_DEFAULTS(6, TEGRA210), + ADMAIF_RX_REG_DEFAULTS(7, TEGRA210), + ADMAIF_RX_REG_DEFAULTS(8, TEGRA210), + ADMAIF_RX_REG_DEFAULTS(9, TEGRA210), + ADMAIF_RX_REG_DEFAULTS(10, TEGRA210), + ADMAIF_TX_REG_DEFAULTS(1, TEGRA210), + ADMAIF_TX_REG_DEFAULTS(2, TEGRA210), + ADMAIF_TX_REG_DEFAULTS(3, TEGRA210), + ADMAIF_TX_REG_DEFAULTS(4, TEGRA210), + ADMAIF_TX_REG_DEFAULTS(5, TEGRA210), + ADMAIF_TX_REG_DEFAULTS(6, TEGRA210), + ADMAIF_TX_REG_DEFAULTS(7, TEGRA210), + ADMAIF_TX_REG_DEFAULTS(8, TEGRA210), + ADMAIF_TX_REG_DEFAULTS(9, TEGRA210), + ADMAIF_TX_REG_DEFAULTS(10, TEGRA210), + {(TEGRA_ADMAIF_GLOBAL_CG_0 + TEGRA210_ADMAIF_GLOBAL_BASE), 0x00000003} }; static const struct reg_default tegra264_admaif_reg_defaults[] = { - {(TEGRA_ADMAIF_GLOBAL_CG_0 + TEGRA264_ADMAIF_GLOBAL_BASE), 0x00000003}, - ADMAIF_REG_DEFAULTS(1, TEGRA264), - ADMAIF_REG_DEFAULTS(2, TEGRA264), - ADMAIF_REG_DEFAULTS(3, TEGRA264), - ADMAIF_REG_DEFAULTS(4, TEGRA264), - ADMAIF_REG_DEFAULTS(5, TEGRA264), - ADMAIF_REG_DEFAULTS(6, TEGRA264), - ADMAIF_REG_DEFAULTS(7, TEGRA264), - ADMAIF_REG_DEFAULTS(8, TEGRA264), - ADMAIF_REG_DEFAULTS(9, TEGRA264), - ADMAIF_REG_DEFAULTS(10, TEGRA264), - ADMAIF_REG_DEFAULTS(11, TEGRA264), - ADMAIF_REG_DEFAULTS(12, TEGRA264), - ADMAIF_REG_DEFAULTS(13, TEGRA264), - ADMAIF_REG_DEFAULTS(14, TEGRA264), - ADMAIF_REG_DEFAULTS(15, TEGRA264), - ADMAIF_REG_DEFAULTS(16, TEGRA264), - ADMAIF_REG_DEFAULTS(17, TEGRA264), - ADMAIF_REG_DEFAULTS(18, TEGRA264), - ADMAIF_REG_DEFAULTS(19, TEGRA264), - ADMAIF_REG_DEFAULTS(20, TEGRA264), - ADMAIF_REG_DEFAULTS(21, TEGRA264), - ADMAIF_REG_DEFAULTS(22, TEGRA264), - ADMAIF_REG_DEFAULTS(23, TEGRA264), - ADMAIF_REG_DEFAULTS(24, TEGRA264), - ADMAIF_REG_DEFAULTS(25, TEGRA264), - ADMAIF_REG_DEFAULTS(26, TEGRA264), - ADMAIF_REG_DEFAULTS(27, TEGRA264), - ADMAIF_REG_DEFAULTS(28, TEGRA264), - ADMAIF_REG_DEFAULTS(29, TEGRA264), - ADMAIF_REG_DEFAULTS(30, TEGRA264), - ADMAIF_REG_DEFAULTS(31, TEGRA264), - ADMAIF_REG_DEFAULTS(32, TEGRA264) + ADMAIF_RX_REG_DEFAULTS(1, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(2, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(3, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(4, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(5, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(6, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(7, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(8, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(9, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(10, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(11, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(12, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(13, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(14, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(15, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(16, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(17, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(18, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(19, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(20, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(21, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(22, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(23, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(24, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(25, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(26, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(27, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(28, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(29, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(30, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(31, TEGRA264), + ADMAIF_RX_REG_DEFAULTS(32, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(1, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(2, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(3, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(4, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(5, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(6, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(7, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(8, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(9, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(10, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(11, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(12, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(13, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(14, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(15, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(16, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(17, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(18, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(19, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(20, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(21, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(22, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(23, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(24, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(25, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(26, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(27, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(28, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(29, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(30, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(31, TEGRA264), + ADMAIF_TX_REG_DEFAULTS(32, TEGRA264), + {(TEGRA_ADMAIF_GLOBAL_CG_0 + TEGRA264_ADMAIF_GLOBAL_BASE), 0x00000003} }; static bool tegra_admaif_wr_reg(struct device *dev, unsigned int reg) From abb5bed6f48d54d3b82724a430fd462579ecd876 Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Fri, 21 Aug 2026 16:37:34 +0100 Subject: [PATCH 271/641] ASoC: tegra: Sort MBDRC register defaults Commit 4b05ccb17f92 ("regcache: Sort the local copy of an unsorted reg_defaults array") exposed an issue with the Tegra MBDRC driver and now the following warning is observed: tegra210-ope 2908000.processing-engine: Driver needs fixing: Unsorted reg_defaults, sorting the copy This warning occurs because register defaults in the structure tegra210_mbdrc_reg_defaults are not specified in ascending order which is required by regmap. Fix this by sorting the register defaults according to their address. Note that in order to do this it is necessary to replace the macro MBDRC_FILTER_REG_DEFAULTS with a per register macro MBDRC_FILTER_PARAM_REG_DEFAULTS. Fixes: 7358a803c778 ("ASoC: tegra: Add Tegra210 based OPE driver") Cc: stable@vger.kernel.org Signed-off-by: Jon Hunter Link: https://patch.msgid.link/20260821153734.158426-5-jonathanh@nvidia.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra210_mbdrc.c | 42 ++++++++++++++++---------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/sound/soc/tegra/tegra210_mbdrc.c b/sound/soc/tegra/tegra210_mbdrc.c index f5d4a93dd6f1..1d78b4c1cc88 100644 --- a/sound/soc/tegra/tegra210_mbdrc.c +++ b/sound/soc/tegra/tegra210_mbdrc.c @@ -20,33 +20,33 @@ #define MBDRC_FILTER_REG(reg, id) \ ((reg) + ((id) * TEGRA210_MBDRC_FILTER_PARAM_STRIDE)) -#define MBDRC_FILTER_REG_DEFAULTS(id) \ - { MBDRC_FILTER_REG(TEGRA210_MBDRC_IIR_CFG, id), 0x00000005}, \ - { MBDRC_FILTER_REG(TEGRA210_MBDRC_IN_ATTACK, id), 0x3e48590c}, \ - { MBDRC_FILTER_REG(TEGRA210_MBDRC_IN_RELEASE, id), 0x08414e9f}, \ - { MBDRC_FILTER_REG(TEGRA210_MBDRC_FAST_ATTACK, id), 0x7fffffff}, \ - { MBDRC_FILTER_REG(TEGRA210_MBDRC_IN_THRESHOLD, id), 0x06145082}, \ - { MBDRC_FILTER_REG(TEGRA210_MBDRC_OUT_THRESHOLD, id), 0x060d379b}, \ - { MBDRC_FILTER_REG(TEGRA210_MBDRC_RATIO_1ST, id), 0x0000a000}, \ - { MBDRC_FILTER_REG(TEGRA210_MBDRC_RATIO_2ND, id), 0x00002000}, \ - { MBDRC_FILTER_REG(TEGRA210_MBDRC_RATIO_3RD, id), 0x00000b33}, \ - { MBDRC_FILTER_REG(TEGRA210_MBDRC_RATIO_4TH, id), 0x00000800}, \ - { MBDRC_FILTER_REG(TEGRA210_MBDRC_RATIO_5TH, id), 0x0000019a}, \ - { MBDRC_FILTER_REG(TEGRA210_MBDRC_MAKEUP_GAIN, id), 0x00000002}, \ - { MBDRC_FILTER_REG(TEGRA210_MBDRC_INIT_GAIN, id), 0x00066666}, \ - { MBDRC_FILTER_REG(TEGRA210_MBDRC_GAIN_ATTACK, id), 0x00d9ba0e}, \ - { MBDRC_FILTER_REG(TEGRA210_MBDRC_GAIN_RELEASE, id), 0x3e48590c}, \ - { MBDRC_FILTER_REG(TEGRA210_MBDRC_FAST_RELEASE, id), 0x7ffff26a}, \ - { MBDRC_FILTER_REG(TEGRA210_MBDRC_CFG_RAM_CTRL, id), 0x4000} +#define MBDRC_FILTER_PARAM_REG_DEFAULTS(reg, val) \ + { MBDRC_FILTER_REG(reg, 0), val}, \ + { MBDRC_FILTER_REG(reg, 1), val}, \ + { MBDRC_FILTER_REG(reg, 2), val} static const struct reg_default tegra210_mbdrc_reg_defaults[] = { { TEGRA210_MBDRC_CFG, 0x0030de51}, { TEGRA210_MBDRC_CHANNEL_MASK, 0x00000003}, { TEGRA210_MBDRC_FAST_FACTOR, 0x30000800}, - MBDRC_FILTER_REG_DEFAULTS(0), - MBDRC_FILTER_REG_DEFAULTS(1), - MBDRC_FILTER_REG_DEFAULTS(2), + MBDRC_FILTER_PARAM_REG_DEFAULTS(TEGRA210_MBDRC_IIR_CFG, 0x00000005), + MBDRC_FILTER_PARAM_REG_DEFAULTS(TEGRA210_MBDRC_IN_ATTACK, 0x3e48590c), + MBDRC_FILTER_PARAM_REG_DEFAULTS(TEGRA210_MBDRC_IN_RELEASE, 0x08414e9f), + MBDRC_FILTER_PARAM_REG_DEFAULTS(TEGRA210_MBDRC_FAST_ATTACK, 0x7fffffff), + MBDRC_FILTER_PARAM_REG_DEFAULTS(TEGRA210_MBDRC_IN_THRESHOLD, 0x06145082), + MBDRC_FILTER_PARAM_REG_DEFAULTS(TEGRA210_MBDRC_OUT_THRESHOLD, 0x060d379b), + MBDRC_FILTER_PARAM_REG_DEFAULTS(TEGRA210_MBDRC_RATIO_1ST, 0x0000a000), + MBDRC_FILTER_PARAM_REG_DEFAULTS(TEGRA210_MBDRC_RATIO_2ND, 0x00002000), + MBDRC_FILTER_PARAM_REG_DEFAULTS(TEGRA210_MBDRC_RATIO_3RD, 0x00000b33), + MBDRC_FILTER_PARAM_REG_DEFAULTS(TEGRA210_MBDRC_RATIO_4TH, 0x00000800), + MBDRC_FILTER_PARAM_REG_DEFAULTS(TEGRA210_MBDRC_RATIO_5TH, 0x0000019a), + MBDRC_FILTER_PARAM_REG_DEFAULTS(TEGRA210_MBDRC_MAKEUP_GAIN, 0x00000002), + MBDRC_FILTER_PARAM_REG_DEFAULTS(TEGRA210_MBDRC_INIT_GAIN, 0x00066666), + MBDRC_FILTER_PARAM_REG_DEFAULTS(TEGRA210_MBDRC_GAIN_ATTACK, 0x00d9ba0e), + MBDRC_FILTER_PARAM_REG_DEFAULTS(TEGRA210_MBDRC_GAIN_RELEASE, 0x3e48590c), + MBDRC_FILTER_PARAM_REG_DEFAULTS(TEGRA210_MBDRC_FAST_RELEASE, 0x7ffff26a), + MBDRC_FILTER_PARAM_REG_DEFAULTS(TEGRA210_MBDRC_CFG_RAM_CTRL, 0x4000), }; /* Default MBDRC parameters */ From 9d4843ac81d15be27e0b03392a99a8bd88a2d86a Mon Sep 17 00:00:00 2001 From: Niranjan H Y Date: Thu, 20 Aug 2026 17:21:12 +0530 Subject: [PATCH 272/641] ASoC: sdw_utils: Set component_name for tac5xx2 codec family * With component_name set in the codec_info_list DAI entries, "amixer -D hw: info" includes the 'spk' identifier for each tac5xx2 speaker component. This allows userspace to identify the speaker codec at runtime and enables UCM card matching for the tac5xx2 family. * Set component_name for the speaker, mic, and UAJ DAI entries for all tac5xx2 family variants. Signed-off-by: Niranjan H Y Link: https://patch.msgid.link/20260820115114.1203-1-niranjan.hy@ti.com Signed-off-by: Mark Brown --- sound/soc/sdw_utils/soc_sdw_utils.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index 3e091e78d8c2..a66dcc02fb59 100644 --- a/sound/soc/sdw_utils/soc_sdw_utils.c +++ b/sound/soc/sdw_utils/soc_sdw_utils.c @@ -81,6 +81,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { /* speaker */ .direction = {true, false}, .dai_name = "tac5xx2-aif1", + .component_name = "tac5572", .dai_type = SOC_SDW_DAI_TYPE_AMP, .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, .init = asoc_sdw_ti_amp_init, @@ -94,6 +95,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { /* mic */ .direction = {false, true}, .dai_name = "tac5xx2-aif2", + .component_name = "tac5572", .dai_type = SOC_SDW_DAI_TYPE_MIC, .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, .rtd_init = asoc_sdw_ti_dmic_rtd_init, @@ -102,6 +104,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { /* UAJ */ .direction = {true, true}, .dai_name = "tac5xx2-aif3", + .component_name = "tac5572", .dai_type = SOC_SDW_DAI_TYPE_JACK, .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, .controls = generic_jack_controls, @@ -122,6 +125,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { /* speaker with IV sense feedback */ .direction = {true, true}, .dai_name = "tac5xx2-aif1", + .component_name = "tac5672", .dai_type = SOC_SDW_DAI_TYPE_AMP, .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, .init = asoc_sdw_ti_amp_init, @@ -135,6 +139,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { /* mic */ .direction = {false, true}, .dai_name = "tac5xx2-aif2", + .component_name = "tac5672", .dai_type = SOC_SDW_DAI_TYPE_MIC, .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, .rtd_init = asoc_sdw_ti_dmic_rtd_init, @@ -143,6 +148,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { /* UAJ */ .direction = {true, true}, .dai_name = "tac5xx2-aif3", + .component_name = "tac5672", .dai_type = SOC_SDW_DAI_TYPE_JACK, .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, .controls = generic_jack_controls, @@ -163,6 +169,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { /* speaker with echo reference feedback */ .direction = {true, true}, .dai_name = "tac5xx2-aif1", + .component_name = "tac5682", .dai_type = SOC_SDW_DAI_TYPE_AMP, .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, .init = asoc_sdw_ti_amp_init, @@ -176,6 +183,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { /* mic */ .direction = {false, true}, .dai_name = "tac5xx2-aif2", + .component_name = "tac5682", .dai_type = SOC_SDW_DAI_TYPE_MIC, .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, .rtd_init = asoc_sdw_ti_dmic_rtd_init, @@ -184,6 +192,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { /* UAJ */ .direction = {true, true}, .dai_name = "tac5xx2-aif3", + .component_name = "tac5682", .dai_type = SOC_SDW_DAI_TYPE_JACK, .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, .controls = generic_jack_controls, @@ -203,6 +212,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { { .direction = {true, false}, .dai_name = "tac5xx2-aif1", + .component_name = "tas2883", .dai_type = SOC_SDW_DAI_TYPE_AMP, .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, .init = asoc_sdw_ti_amp_init, @@ -216,6 +226,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { /* mic */ .direction = {false, true}, .dai_name = "tac5xx2-aif2", + .component_name = "tas2883", .dai_type = SOC_SDW_DAI_TYPE_MIC, .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, .rtd_init = asoc_sdw_ti_dmic_rtd_init, From 04145998fa20f321fd7090089c7f86dba3d36a13 Mon Sep 17 00:00:00 2001 From: Niranjan H Y Date: Thu, 20 Aug 2026 17:21:13 +0530 Subject: [PATCH 273/641] ASoC: tac5xx2-sdw: Rename gain and volume macros Rename gain and volume macros to match the hardware intent. Currently, both TAC_SDCA_CHANNEL_GAIN and TAC_SDCA_CHANNEL_VOLUME are defined as 0x02. Keep only TAC_SDCA_CHANNEL_VOLUME and reposition TAC_SDCA_CHANNEL_GAIN to 0x0B. Remove the now-redundant TAC_SDCA_MASTER_GAIN alias (also 0x0B), replacing its single use with TAC_SDCA_CHANNEL_GAIN. Signed-off-by: Niranjan H Y Link: https://patch.msgid.link/20260820115114.1203-2-niranjan.hy@ti.com Signed-off-by: Mark Brown --- sound/soc/codecs/tac5xx2-sdw.c | 48 +++++++++++++++++----------------- sound/soc/codecs/tac5xx2.h | 5 ++-- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/sound/soc/codecs/tac5xx2-sdw.c b/sound/soc/codecs/tac5xx2-sdw.c index c062065a807d..065e7d456240 100644 --- a/sound/soc/codecs/tac5xx2-sdw.c +++ b/sound/soc/codecs/tac5xx2-sdw.c @@ -123,9 +123,9 @@ static const DECLARE_TLV_DB_SCALE(tac5xx2_dvc_tlv, -7200, 50, 0); #define TAC_DOUBLE_Q78_TLV(name, func_id, ent_id) \ SDCA_DOUBLE_Q78_TLV(name, \ SDW_SDCA_CTL(TAC_FUNCTION_ID_##func_id, TAC_SDCA_ENT_##ent_id, \ - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), \ + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT), \ SDW_SDCA_CTL(TAC_FUNCTION_ID_##func_id, TAC_SDCA_ENT_##ent_id, \ - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), \ + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT), \ TAC_DVC_MIN, TAC_DVC_MAX, TAC_DVC_STEP, tac5xx2_dvc_tlv) struct tac5xx2_prv { @@ -385,33 +385,33 @@ static const struct reg_default tac_reg_default[] = { static const struct reg_sequence tac_spk_seq[] = { REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU21, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0), + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT), 0), REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU21, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0), + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT), 0), REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0), + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT), 0), REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0), + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT), 0), }; static const struct reg_sequence tac_sm_seq[] = { REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU113, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0), + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT), 0), REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU113, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0), + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT), 0), REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU11, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0), + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT), 0), REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU11, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0), + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT), 0), }; static const struct reg_sequence tac_uaj_seq[] = { REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU41, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0), + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT), 0), REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU41, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0), + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT), 0), REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU36, - TAC_SDCA_CHANNEL_GAIN, TAC_JACK_MONO_CS), 0), + TAC_SDCA_CHANNEL_VOLUME, TAC_JACK_MONO_CS), 0), }; static bool tac_volatile_reg(struct device *dev, unsigned int reg) @@ -468,25 +468,25 @@ static int tac_sdca_mbq_size(struct device *dev, unsigned int reg) case SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU21, TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT): case SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT): + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT): case SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT): + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT): case SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23, - TAC_SDCA_MASTER_GAIN, 0): + TAC_SDCA_CHANNEL_GAIN, 0): case SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU113, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT): + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT): case SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU113, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT): + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT): case SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU11, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT): + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT): case SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU11, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT): + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT): case SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU41, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT): + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT): case SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU41, - TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT): + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT): case SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU36, - TAC_SDCA_CHANNEL_GAIN, TAC_JACK_MONO_CS): + TAC_SDCA_CHANNEL_VOLUME, TAC_JACK_MONO_CS): return 2; default: @@ -531,7 +531,7 @@ static const struct snd_kcontrol_new tac_uaj_controls[] = { TAC_DOUBLE_Q78_TLV("UAJ Playback Volume", UAJ, FU41), SDCA_SINGLE_Q78_TLV("UAJ Capture Volume", SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU36, - TAC_SDCA_CHANNEL_GAIN, TAC_JACK_MONO_CS), + TAC_SDCA_CHANNEL_VOLUME, TAC_JACK_MONO_CS), TAC_DVC_MIN, TAC_DVC_MAX, TAC_DVC_STEP, tac5xx2_dvc_tlv), }; diff --git a/sound/soc/codecs/tac5xx2.h b/sound/soc/codecs/tac5xx2.h index eed8e6cf3498..a84c5ca515a7 100644 --- a/sound/soc/codecs/tac5xx2.h +++ b/sound/soc/codecs/tac5xx2.h @@ -174,14 +174,13 @@ #define TAC_SDCA_ENT_SAPU29 0x35 /* Control selector definitions */ -#define TAC_SDCA_MASTER_GAIN 0x0B #define TAC_SDCA_MASTER_MUTE 0x01 #define TAC_SDCA_CHANNEL_MUTE 0x01 -#define TAC_SDCA_CHANNEL_GAIN 0x02 +#define TAC_SDCA_CHANNEL_VOLUME 0x02 #define TAC_SDCA_POSTURENUMBER 0x10 #define TAC_SDCA_REQUESTED_PS 0x01 #define TAC_SDCA_ACTUAL_PS 0x10 -#define TAC_SDCA_CHANNEL_VOLUME 0x02 +#define TAC_SDCA_CHANNEL_GAIN 0x0B /* 2. smart mic function */ #define TAC_FUNCTION_ID_SM 0x2 From cf82dd2183cb0c224816f7d23d32a7a05be4858b Mon Sep 17 00:00:00 2001 From: Niranjan H Y Date: Thu, 20 Aug 2026 17:21:14 +0530 Subject: [PATCH 274/641] ASoC: tac5xx2-sdw: update default regmap registers * At boot time, when the registers are accessed by asoc layer while trying to enable a DAPM path, while the device is still being attached, -EBUSY error will be thown by regmap_read and regmap_udpate_bits. Because the regmap will be in cache only mode. Add default values for these registers so that, the cached values are returned instead of error. * Also remove writing default values once again for first attach. Signed-off-by: Niranjan H Y Link: https://patch.msgid.link/20260820115114.1203-3-niranjan.hy@ti.com Signed-off-by: Mark Brown --- sound/soc/codecs/tac5xx2-sdw.c | 88 ++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 42 deletions(-) diff --git a/sound/soc/codecs/tac5xx2-sdw.c b/sound/soc/codecs/tac5xx2-sdw.c index 065e7d456240..a5f654cde69f 100644 --- a/sound/soc/codecs/tac5xx2-sdw.c +++ b/sound/soc/codecs/tac5xx2-sdw.c @@ -55,6 +55,12 @@ #define TAC_XU_BYPASS_REG(func, xu) \ SDW_SDCA_CTL(TAC_FUNCTION_ID_##func, TAC_SDCA_ENT_##xu, \ TAC_SDCA_CTL_XU_BYPASS, 0) +#define TAC_VOLUME_REG(func, fu, ch) \ + SDW_SDCA_CTL(TAC_FUNCTION_ID_##func, TAC_SDCA_ENT_##fu, \ + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_##ch) +#define TAC_GAIN_REG(func, fu, ch) \ + SDW_SDCA_CTL(TAC_FUNCTION_ID_##func, TAC_SDCA_ENT_##fu, \ + TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_##ch) /* mute registers */ #define FU21_L_MUTE_REG TAC_MUTE_REG(SA, FU21, LEFT) @@ -381,6 +387,46 @@ static const struct reg_default tac_reg_default[] = { {TAC_REG_SDW(0, 1, 0x71), 0x5}, {TAC_REG_SDW(0, 1, 0x72), 0x6}, {TAC_REG_SDW(0, 1, 0x73), 0x7}, + /* SA */ + {TAC_MUTE_REG(SA, FU21, LEFT), 0x1}, + {TAC_MUTE_REG(SA, FU21, RIGHT), 0x1}, + {TAC_VOLUME_REG(SA, FU21, LEFT), 0x9c00}, + {TAC_VOLUME_REG(SA, FU21, RIGHT), 0x9c00}, + {TAC_MUTE_REG(SA, FU23, LEFT), 0x1}, + {TAC_MUTE_REG(SA, FU23, RIGHT), 0x1}, + {TAC_GAIN_REG(SA, FU23, LEFT), 0x0}, + {TAC_GAIN_REG(SA, FU23, RIGHT), 0x0}, + /* SM */ + {TAC_USAGE_REG(SM, IT11), 0x0}, + {TAC_USAGE_REG(SM, OT113), 0x0}, + {TAC_MUTE_REG(SM, FU113, LEFT), 0x1}, + {TAC_MUTE_REG(SM, FU113, RIGHT), 0x1}, + {TAC_GAIN_REG(SM, FU113, LEFT), 0x0}, + {TAC_GAIN_REG(SM, FU113, RIGHT), 0x0}, + {TAC_MUTE_REG(SM, FU11, LEFT), 0x1}, + {TAC_MUTE_REG(SM, FU11, RIGHT), 0x1}, + {TAC_GAIN_REG(SM, FU11, LEFT), 0x0}, + {TAC_GAIN_REG(SM, FU11, RIGHT), 0x0}, + {TAC_XU_BYPASS_REG(SM, XU12), 0x1}, + {SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_CS113, + TAC_SDCA_CTL_CS_SAMP_RATE_IDX, 0), 0x0}, + /* UAJ */ + {TAC_USAGE_REG(UAJ, IT33), 0x0}, + {TAC_USAGE_REG(UAJ, IT41), 0x0}, + {TAC_USAGE_REG(UAJ, OT36), 0x0}, + {TAC_USAGE_REG(UAJ, OT45), 0x0}, + {TAC_MUTE_REG(UAJ, FU41, LEFT), 0x1}, + {TAC_MUTE_REG(UAJ, FU41, RIGHT), 0x1}, + {TAC_VOLUME_REG(UAJ, FU41, LEFT), 0x0}, + {TAC_VOLUME_REG(UAJ, FU41, RIGHT), 0x0}, + {TAC_MUTE_REG(UAJ, FU36, RIGHT), 0x1}, + {SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU36, + TAC_SDCA_CHANNEL_VOLUME, TAC_JACK_MONO_CS), 0x0}, + {TAC_XU_BYPASS_REG(UAJ, XU42), 0x0}, + {SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_CS36, + TAC_SDCA_CTL_CS_SAMP_RATE_IDX, 0), 0x0}, + {SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_CS41, + TAC_SDCA_CTL_CS_SAMP_RATE_IDX, 0), 0x0}, }; static const struct reg_sequence tac_spk_seq[] = { @@ -388,30 +434,6 @@ static const struct reg_sequence tac_spk_seq[] = { TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT), 0), REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU21, TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT), 0), - REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23, - TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT), 0), - REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23, - TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT), 0), -}; - -static const struct reg_sequence tac_sm_seq[] = { - REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU113, - TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT), 0), - REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU113, - TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT), 0), - REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU11, - TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT), 0), - REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU11, - TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT), 0), -}; - -static const struct reg_sequence tac_uaj_seq[] = { - REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU41, - TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT), 0), - REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU41, - TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT), 0), - REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU36, - TAC_SDCA_CHANNEL_VOLUME, TAC_JACK_MONO_CS), 0), }; static bool tac_volatile_reg(struct device *dev, unsigned int reg) @@ -1761,16 +1783,6 @@ static int tac_io_init(struct device *dev, struct sdw_slave *slave, bool first) goto io_init_err; } dev_dbg(dev, "smartmic init done\n"); - - if (first) { - ret = regmap_multi_reg_write(tac_dev->regmap, tac_sm_seq, - ARRAY_SIZE(tac_sm_seq)); - if (ret) { - dev_err(tac_dev->dev, - "init writes failed, err=%d", ret); - goto io_init_err; - } - } } if (tac_dev->uaj_func_data) { @@ -1783,14 +1795,6 @@ static int tac_io_init(struct device *dev, struct sdw_slave *slave, bool first) dev_dbg(dev, "uaj init done\n"); if (first) { - ret = regmap_multi_reg_write(tac_dev->regmap, tac_uaj_seq, - ARRAY_SIZE(tac_uaj_seq)); - if (ret) { - dev_err(tac_dev->dev, - "init writes failed, err=%d", ret); - goto io_init_err; - } - if (tac_dev->hs_jack) { ret = tac5xx2_jack_init(tac_dev); if (ret) { From 72cc574aa3791aa4f05d247b4e884cdbd135819a Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Fri, 21 Aug 2026 18:54:10 +0100 Subject: [PATCH 275/641] ASoC: fix unmet dependencies on PPC_BESTCOMM and SND_SOC_AC97_BUS SND_MPC52xx_SOC_PCM030 and SND_MPC52xx_SOC_EFIKA both select SND_SOC_MPC5200_AC97 without ensuring its dependency on PPC_BESTCOMM is met. This causes unmet dependencies such as: WARNING: unmet direct dependencies detected for SND_SOC_MPC5200_AC97 Depends on [n]: SOUND [=m] && SND [=m] && SND_SOC [=m] && SND_POWERPC_SOC [=m] && PPC_BESTCOMM [=n] Selected by [m]: - SND_MPC52xx_SOC_PCM030 [=m] && SOUND [=m] && SND [=m] && SND_SOC [=m] && SND_POWERPC_SOC [=m] && PPC_MPC5200_SIMPLE [=y] In v1, Rosen pointed out that the import of "mpc5200_dma.h" is actually unnecessary, and Arnd suggested that the 'select' of SND_SOC_MPC5200_AC97 be changed to 'depends on'. This resolves all 3 unmet dependencies. These unmet dependency bugs were detected by kconfirm, a static analysis tool for Kconfig. Fixes: a9262c4fd404 ("ASoC: Support for AC97 on Phytec pmc030 base board.") Fixes: 6ffee43ecf8b ("ASoC: Fabric bindings for STAC9766 on the Efika") Suggested-by: Arnd Bergmann Suggested-by: Rosen Penev Signed-off-by: Julian Braha Link: https://patch.msgid.link/20260821175410.179631-1-julianbraha@gmail.com Signed-off-by: Mark Brown --- sound/soc/fsl/Kconfig | 4 ++-- sound/soc/fsl/efika-audio-fabric.c | 2 -- sound/soc/fsl/pcm030-audio-fabric.c | 2 -- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 8ae59c094878..04940879dfd8 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -244,7 +244,7 @@ config SND_SOC_MPC5200_AC97 config SND_MPC52xx_SOC_PCM030 tristate "SoC AC97 Audio support for Phytec pcm030 and WM9712" depends on PPC_MPC5200_SIMPLE - select SND_SOC_MPC5200_AC97 + depends on SND_SOC_MPC5200_AC97 select SND_SOC_WM9712 help Say Y if you want to add support for sound on the Phytec pcm030 @@ -253,7 +253,7 @@ config SND_MPC52xx_SOC_PCM030 config SND_MPC52xx_SOC_EFIKA tristate "SoC AC97 Audio support for bbplan Efika and STAC9766" depends on PPC_EFIKA - select SND_SOC_MPC5200_AC97 + depends on SND_SOC_MPC5200_AC97 select SND_SOC_STAC9766 help Say Y if you want to add support for sound on the Efika. diff --git a/sound/soc/fsl/efika-audio-fabric.c b/sound/soc/fsl/efika-audio-fabric.c index 4d3266d9cbb7..e9c728bae8b9 100644 --- a/sound/soc/fsl/efika-audio-fabric.c +++ b/sound/soc/fsl/efika-audio-fabric.c @@ -22,8 +22,6 @@ #include #include -#include "mpc5200_dma.h" - #define DRV_NAME "efika-audio-fabric" SND_SOC_DAILINK_DEFS(analog, diff --git a/sound/soc/fsl/pcm030-audio-fabric.c b/sound/soc/fsl/pcm030-audio-fabric.c index 5542c4ee6d12..a7a5dba83a2f 100644 --- a/sound/soc/fsl/pcm030-audio-fabric.c +++ b/sound/soc/fsl/pcm030-audio-fabric.c @@ -13,8 +13,6 @@ #include -#include "mpc5200_dma.h" - #define DRV_NAME "pcm030-audio-fabric" struct pcm030_audio_data { From 46d4246d8dcde75aff707976cddc546f22184cac Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Mon, 17 Aug 2026 11:19:16 +0000 Subject: [PATCH 276/641] f2fs: use f2fs_{down, up}_(read, write}_trace() for nat_tree_lock Under heavy workloads or during background GC/fallocate operations, nat_tree_lock can experience high lock contention between background readers (e.g. f2fs_get_node_info() in gc_data_segment) and writers (e.g. flush_nat_entries, set_node_addr, shrinker). [375067.327986][T13777] schedule+0x4c/0x114 [375067.327997][T13777] f2fs_get_node_info+0x438/0x5c4 [375067.328002][T13777] f2fs_get_inode_page+0x1e0/0x3f0 [375067.328013][T13777] f2fs_iget+0x88/0x1180 [375067.328024][T13777] f2fs_lookup+0x168/0x3a8 [375067.328035][T13777] path_openat+0xa28/0x1b04 [375067.328046][T13777] do_filp_open+0xac/0x130 [375067.328056][T13777] do_sys_openat2+0x140/0x21c [375067.328066][T13777] __arm64_sys_openat+0x70/0x9c [375067.330299][T13777] schedule+0x4c/0x114 [375067.330310][T13777] schedule_preempt_disabled+0x24/0x40 [375067.330321][T13777] rwsem_down_write_slowpath+0x3b4/0x9d0 [375067.330332][T13777] down_write+0x98/0x170 [375067.330343][T13777] set_node_addr+0x74/0x4b4 [375067.330354][T13777] f2fs_new_node_page+0xb0/0x280 [375067.330444][T13777] f2fs_new_inode_page+0x3c/0x64 [375067.330455][T13777] f2fs_init_inode_metadata+0x4c/0x47c [375067.330461][T13777] f2fs_add_regular_entry+0x258/0x5b8 [375067.330471][T13777] f2fs_add_dentry+0x100/0x158 [375067.330476][T13777] f2fs_do_add_link+0x84/0x140 [375067.330487][T13777] f2fs_create+0xec/0x250 [375067.331759][T13777] schedule+0x4c/0x114 [375067.331770][T13777] f2fs_down_read+0x9c/0xc4 [375067.331781][T13777] f2fs_need_inode_block_update+0x20/0x10c [375067.331792][T13777] f2fs_do_sync_file+0x478/0x830 [375067.331802][T13777] f2fs_sync_file+0x2c/0x40 This patch converts nat_tree_lock to use the f2fs_{down,up}_{read,write}_trace infrastructure. Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- Documentation/ABI/testing/sysfs-fs-f2fs | 1 + fs/f2fs/checkpoint.c | 1 + fs/f2fs/f2fs.h | 1 + fs/f2fs/node.c | 71 ++++++++++++++----------- include/trace/events/f2fs.h | 3 +- 5 files changed, 46 insertions(+), 31 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-fs-f2fs b/Documentation/ABI/testing/sysfs-fs-f2fs index f4e6a7415cde..85194e4c7f01 100644 --- a/Documentation/ABI/testing/sysfs-fs-f2fs +++ b/Documentation/ABI/testing/sysfs-fs-f2fs @@ -986,6 +986,7 @@ Description: This sysfs entry can be used to enable/disable to adjust priority f 0x00000008 gc_lock 0x00000010 cp_global 0x00000020 io_rwsem + 0x00000040 nat_tree_lock ========== ================== What: /sys/fs/f2fs//lock_duration_priority diff --git a/fs/f2fs/checkpoint.c b/fs/f2fs/checkpoint.c index c73999e39a39..4b59f30ef45d 100644 --- a/fs/f2fs/checkpoint.c +++ b/fs/f2fs/checkpoint.c @@ -107,6 +107,7 @@ static bool need_uplift_priority(struct f2fs_rwsem *sem, bool is_write) case LOCK_NAME_GC_LOCK: case LOCK_NAME_CP_GLOBAL: case LOCK_NAME_IO_RWSEM: + case LOCK_NAME_NAT_TREE_LOCK: return true; default: f2fs_bug_on(sem->sbi, 1); diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index 1b96d8718c5c..a1f5f375045a 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -193,6 +193,7 @@ enum f2fs_lock_name { LOCK_NAME_GC_LOCK, LOCK_NAME_CP_GLOBAL, LOCK_NAME_IO_RWSEM, + LOCK_NAME_NAT_TREE_LOCK, LOCK_NAME_MAX, }; diff --git a/fs/f2fs/node.c b/fs/f2fs/node.c index 46bea52e35c3..968e5ed38816 100644 --- a/fs/f2fs/node.c +++ b/fs/f2fs/node.c @@ -401,15 +401,16 @@ bool f2fs_need_dentry_mark(struct f2fs_sb_info *sbi, nid_t nid) struct f2fs_nm_info *nm_i = NM_I(sbi); struct nat_entry *e; bool need = false; + struct f2fs_lock_context lc; - f2fs_down_read(&nm_i->nat_tree_lock); + f2fs_down_read_trace(&nm_i->nat_tree_lock, &lc); e = __lookup_nat_cache(nm_i, nid, false); if (e) { if (!get_nat_flag(e, IS_CHECKPOINTED) && !get_nat_flag(e, HAS_FSYNCED_INODE)) need = true; } - f2fs_up_read(&nm_i->nat_tree_lock); + f2fs_up_read_trace(&nm_i->nat_tree_lock, &lc); return need; } @@ -418,12 +419,13 @@ bool f2fs_is_checkpointed_node(struct f2fs_sb_info *sbi, nid_t nid) struct f2fs_nm_info *nm_i = NM_I(sbi); struct nat_entry *e; bool is_cp = true; + struct f2fs_lock_context lc; - f2fs_down_read(&nm_i->nat_tree_lock); + f2fs_down_read_trace(&nm_i->nat_tree_lock, &lc); e = __lookup_nat_cache(nm_i, nid, false); if (e && !get_nat_flag(e, IS_CHECKPOINTED)) is_cp = false; - f2fs_up_read(&nm_i->nat_tree_lock); + f2fs_up_read_trace(&nm_i->nat_tree_lock, &lc); return is_cp; } @@ -432,16 +434,16 @@ bool f2fs_need_inode_block_update(struct f2fs_sb_info *sbi, nid_t ino) struct f2fs_nm_info *nm_i = NM_I(sbi); struct nat_entry *e; bool need_update = true; - struct f2fs_lock_context lc; + struct f2fs_lock_context lc, nlc; f2fs_down_read_trace(&sbi->node_write, &lc); - f2fs_down_read(&nm_i->nat_tree_lock); + f2fs_down_read_trace(&nm_i->nat_tree_lock, &nlc); e = __lookup_nat_cache(nm_i, ino, false); if (e && get_nat_flag(e, HAS_LAST_FSYNC) && (get_nat_flag(e, IS_CHECKPOINTED) || get_nat_flag(e, HAS_FSYNCED_INODE))) need_update = false; - f2fs_up_read(&nm_i->nat_tree_lock); + f2fs_up_read_trace(&nm_i->nat_tree_lock, &nlc); f2fs_up_read_trace(&sbi->node_write, &lc); return need_update; } @@ -452,6 +454,7 @@ static void cache_nat_entry(struct f2fs_sb_info *sbi, nid_t nid, { struct f2fs_nm_info *nm_i = NM_I(sbi); struct nat_entry *new, *e; + struct f2fs_lock_context lc; /* Let's mitigate lock contention of nat_tree_lock during checkpoint */ if (f2fs_rwsem_is_locked(&sbi->cp_global_sem)) @@ -461,7 +464,7 @@ static void cache_nat_entry(struct f2fs_sb_info *sbi, nid_t nid, if (!new) return; - f2fs_down_write(&nm_i->nat_tree_lock); + f2fs_down_write_trace(&nm_i->nat_tree_lock, &lc); e = __lookup_nat_cache(nm_i, nid, false); if (!e) e = __init_nat_entry(nm_i, new, ne, false, false); @@ -470,7 +473,7 @@ static void cache_nat_entry(struct f2fs_sb_info *sbi, nid_t nid, nat_get_blkaddr(e) != le32_to_cpu(ne->block_addr) || nat_get_version(e) != ne->version); - f2fs_up_write(&nm_i->nat_tree_lock); + f2fs_up_write_trace(&nm_i->nat_tree_lock, &lc); if (e != new) __free_nat_entry(new); } @@ -482,8 +485,9 @@ static void set_node_addr(struct f2fs_sb_info *sbi, struct node_info *ni, struct nat_entry *e; struct nat_entry *new = __alloc_nat_entry(sbi, ni->nid, true); bool init_dirty = false; + struct f2fs_lock_context lc; - f2fs_down_write(&nm_i->nat_tree_lock); + f2fs_down_write_trace(&nm_i->nat_tree_lock, &lc); e = __lookup_nat_cache(nm_i, ni->nid, true); if (!e) { init_dirty = true; @@ -533,15 +537,16 @@ static void set_node_addr(struct f2fs_sb_info *sbi, struct node_info *ni, set_nat_flag(e, HAS_FSYNCED_INODE, true); set_nat_flag(e, HAS_LAST_FSYNC, fsync_done); } - f2fs_up_write(&nm_i->nat_tree_lock); + f2fs_up_write_trace(&nm_i->nat_tree_lock, &lc); } int f2fs_try_to_free_nats(struct f2fs_sb_info *sbi, int nr_shrink) { struct f2fs_nm_info *nm_i = NM_I(sbi); int nr = nr_shrink; + struct f2fs_lock_context lc; - if (!f2fs_down_write_trylock(&nm_i->nat_tree_lock)) + if (!f2fs_down_write_trylock_trace(&nm_i->nat_tree_lock, &lc)) return 0; spin_lock(&nm_i->nat_list_lock); @@ -563,7 +568,7 @@ int f2fs_try_to_free_nats(struct f2fs_sb_info *sbi, int nr_shrink) } spin_unlock(&nm_i->nat_list_lock); - f2fs_up_write(&nm_i->nat_tree_lock); + f2fs_up_write_trace(&nm_i->nat_tree_lock, &lc); return nr - nr_shrink; } @@ -581,18 +586,19 @@ int f2fs_get_node_info(struct f2fs_sb_info *sbi, nid_t nid, pgoff_t index; int i; bool need_cache = true; + struct f2fs_lock_context lc; ni->flag = 0; ni->nid = nid; retry: /* Check nat cache */ - f2fs_down_read(&nm_i->nat_tree_lock); + f2fs_down_read_trace(&nm_i->nat_tree_lock, &lc); e = __lookup_nat_cache(nm_i, nid, false); if (e) { ni->ino = nat_get_ino(e); ni->blk_addr = nat_get_blkaddr(e); ni->version = nat_get_version(e); - f2fs_up_read(&nm_i->nat_tree_lock); + f2fs_up_read_trace(&nm_i->nat_tree_lock, &lc); if (IS_ENABLED(CONFIG_F2FS_CHECK_FS)) { need_cache = false; goto sanity_check; @@ -610,7 +616,7 @@ retry: down_read(&curseg->journal_rwsem); } else if (f2fs_rwsem_is_contended(&nm_i->nat_tree_lock) || !down_read_trylock(&curseg->journal_rwsem)) { - f2fs_up_read(&nm_i->nat_tree_lock); + f2fs_up_read_trace(&nm_i->nat_tree_lock, &lc); goto retry; } @@ -621,13 +627,13 @@ retry: } up_read(&curseg->journal_rwsem); if (i >= 0) { - f2fs_up_read(&nm_i->nat_tree_lock); + f2fs_up_read_trace(&nm_i->nat_tree_lock, &lc); goto sanity_check; } /* Fill node_info from nat page */ index = current_nat_addr(sbi, nid); - f2fs_up_read(&nm_i->nat_tree_lock); + f2fs_up_read_trace(&nm_i->nat_tree_lock, &lc); folio = f2fs_get_meta_folio(sbi, index); if (IS_ERR(folio)) @@ -2567,8 +2573,9 @@ static void scan_free_nid_bits(struct f2fs_sb_info *sbi) struct f2fs_nm_info *nm_i = NM_I(sbi); unsigned int i, idx; nid_t nid; + struct f2fs_lock_context lc; - f2fs_down_read(&nm_i->nat_tree_lock); + f2fs_down_read_trace(&nm_i->nat_tree_lock, &lc); for (i = 0; i < nm_i->nat_blocks; i++) { if (!test_bit_le(i, nm_i->nat_block_bitmap)) @@ -2591,7 +2598,7 @@ static void scan_free_nid_bits(struct f2fs_sb_info *sbi) out: scan_curseg_cache(sbi); - f2fs_up_read(&nm_i->nat_tree_lock); + f2fs_up_read_trace(&nm_i->nat_tree_lock, &lc); } static int __f2fs_build_free_nids(struct f2fs_sb_info *sbi, @@ -2600,6 +2607,7 @@ static int __f2fs_build_free_nids(struct f2fs_sb_info *sbi, struct f2fs_nm_info *nm_i = NM_I(sbi); int i = 0, ret; nid_t nid = nm_i->next_scan_nid; + struct f2fs_lock_context lc; if (unlikely(nid >= nm_i->max_nid)) nid = 0; @@ -2626,7 +2634,7 @@ static int __f2fs_build_free_nids(struct f2fs_sb_info *sbi, f2fs_ra_meta_pages(sbi, NAT_BLOCK_OFFSET(nid), FREE_NID_PAGES, META_NAT, true); - f2fs_down_read(&nm_i->nat_tree_lock); + f2fs_down_read_trace(&nm_i->nat_tree_lock, &lc); while (1) { if (!test_bit_le(NAT_BLOCK_OFFSET(nid), @@ -2642,7 +2650,7 @@ static int __f2fs_build_free_nids(struct f2fs_sb_info *sbi, } if (ret) { - f2fs_up_read(&nm_i->nat_tree_lock); + f2fs_up_read_trace(&nm_i->nat_tree_lock, &lc); if (ret == -EFSCORRUPTED) { f2fs_err(sbi, "NAT is corrupt, run fsck to fix it"); @@ -2669,7 +2677,7 @@ static int __f2fs_build_free_nids(struct f2fs_sb_info *sbi, /* find free nids from current sum_pages */ scan_curseg_cache(sbi); - f2fs_up_read(&nm_i->nat_tree_lock); + f2fs_up_read_trace(&nm_i->nat_tree_lock, &lc); f2fs_ra_meta_pages(sbi, NAT_BLOCK_OFFSET(nm_i->next_scan_nid), nm_i->ra_nid_pages, META_NAT, false); @@ -3206,21 +3214,22 @@ int f2fs_flush_nat_entries(struct f2fs_sb_info *sbi, struct cp_control *cpc) nid_t set_idx = 0; LIST_HEAD(sets); int err = 0; + struct f2fs_lock_context lc; /* * during unmount, let's flush nat_bits before checking * nat_cnt[DIRTY_NAT]. */ if (enabled_nat_bits(sbi, cpc)) { - f2fs_down_write(&nm_i->nat_tree_lock); + f2fs_down_write_trace(&nm_i->nat_tree_lock, &lc); remove_nats_in_journal(sbi); - f2fs_up_write(&nm_i->nat_tree_lock); + f2fs_up_write_trace(&nm_i->nat_tree_lock, &lc); } if (!nm_i->nat_cnt[DIRTY_NAT]) return 0; - f2fs_down_write(&nm_i->nat_tree_lock); + f2fs_down_write_trace(&nm_i->nat_tree_lock, &lc); /* * if there are no enough space in journal to store dirty nat @@ -3261,7 +3270,7 @@ int f2fs_flush_nat_entries(struct f2fs_sb_info *sbi, struct cp_control *cpc) break; } - f2fs_up_write(&nm_i->nat_tree_lock); + f2fs_up_write_trace(&nm_i->nat_tree_lock, &lc); /* Allow dirty nats by node block allocation in write_begin */ return err; @@ -3380,7 +3389,8 @@ static int init_node_manager(struct f2fs_sb_info *sbi) mutex_init(&nm_i->build_lock); spin_lock_init(&nm_i->nid_list_lock); - init_f2fs_rwsem(&nm_i->nat_tree_lock); + init_f2fs_rwsem_trace(&nm_i->nat_tree_lock, sbi, + LOCK_NAME_NAT_TREE_LOCK); nm_i->next_scan_nid = le32_to_cpu(sbi->ckpt->next_free_nid); nm_i->bitmap_size = __bitmap_size(sbi, NAT_BITMAP); @@ -3472,6 +3482,7 @@ void f2fs_destroy_node_manager(struct f2fs_sb_info *sbi) struct nat_entry_set **setvec = (struct nat_entry_set **)vec; nid_t nid = 0; unsigned int found; + struct f2fs_lock_context lc; if (!nm_i) return; @@ -3490,7 +3501,7 @@ void f2fs_destroy_node_manager(struct f2fs_sb_info *sbi) spin_unlock(&nm_i->nid_list_lock); /* destroy nat cache */ - f2fs_down_write(&nm_i->nat_tree_lock); + f2fs_down_write_trace(&nm_i->nat_tree_lock, &lc); while ((found = __gang_lookup_nat_cache(nm_i, nid, NAT_VEC_SIZE, natvec))) { unsigned idx; @@ -3521,7 +3532,7 @@ void f2fs_destroy_node_manager(struct f2fs_sb_info *sbi) kmem_cache_free(nat_entry_set_slab, setvec[idx]); } } - f2fs_up_write(&nm_i->nat_tree_lock); + f2fs_up_write_trace(&nm_i->nat_tree_lock, &lc); kvfree(nm_i->nat_block_bitmap); if (nm_i->free_nid_bitmap) { diff --git a/include/trace/events/f2fs.h b/include/trace/events/f2fs.h index 1dd9fc5afc46..d53be932df01 100644 --- a/include/trace/events/f2fs.h +++ b/include/trace/events/f2fs.h @@ -191,7 +191,8 @@ TRACE_DEFINE_ENUM(CP_PHASE_FINISH_CHECKPOINT); { LOCK_NAME_NODE_WRITE, "node_write" }, \ { LOCK_NAME_GC_LOCK, "gc_lock" }, \ { LOCK_NAME_CP_GLOBAL, "cp_global" }, \ - { LOCK_NAME_IO_RWSEM, "io_rwsem" }) + { LOCK_NAME_IO_RWSEM, "io_rwsem" }, \ + { LOCK_NAME_NAT_TREE_LOCK, "nat_tree_lock" }) struct f2fs_sb_info; struct f2fs_io_info; From 2b8704b6a8b2896ccad1f5941d9a3e2c5031a470 Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Mon, 17 Aug 2026 02:39:27 +0000 Subject: [PATCH 277/641] f2fs: fix to reset all pinned status during fggc Otherwise, the pinned status may affect latter flow of fggc. Cc: stable@kernel.org Fixes: 9703d69d9d15 ("f2fs: support file pinning for zoned devices") Cc: Daeho Jeong Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/gc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c index 5917ee917d87..0c17038fcfd7 100644 --- a/fs/f2fs/gc.c +++ b/fs/f2fs/gc.c @@ -2183,6 +2183,9 @@ int f2fs_gc_range(struct f2fs_sb_info *sbi, do_garbage_collect(sbi, segno, &gc_list, FG_GC, true, false); put_gc_inode(&gc_list); + /* reset all pinned status during fggc */ + f2fs_unpin_all_sections(sbi, true); + if (!dry_run && get_valid_blocks(sbi, segno, true)) { err = -EAGAIN; goto next; From 11d56d7a8e60d7ee4969b56403da700745aea1af Mon Sep 17 00:00:00 2001 From: Daeho Jeong Date: Fri, 21 Aug 2026 07:17:42 -0700 Subject: [PATCH 278/641] f2fs: fix error handling on device alias check in rename and unlink In f2fs_rename() and f2fs_unlink(), directly returning -EPERM when encountering a device aliasing file bypasses the cleanup path. Fix this by setting err to -EPERM and jumping to the proper cleanup labels (out_dir and out) instead of returning immediately. Reported-by: Christophe JAILLET Signed-off-by: Daeho Jeong Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/namei.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c index 784f636244e1..b9b15c5d28de 100644 --- a/fs/f2fs/namei.c +++ b/fs/f2fs/namei.c @@ -571,8 +571,10 @@ static int f2fs_unlink(struct inode *dir, struct dentry *dentry) trace_f2fs_unlink_enter(dir, dentry); - if (IS_DEVICE_ALIASING(inode)) - return -EPERM; + if (IS_DEVICE_ALIASING(inode)) { + err = -EPERM; + goto out; + } if (unlikely(f2fs_cp_error(sbi))) { err = -EIO; @@ -1025,8 +1027,10 @@ static int f2fs_rename(struct mnt_idmap *idmap, struct inode *old_dir, } if (new_inode) { - if (IS_DEVICE_ALIASING(new_inode)) - return -EPERM; + if (IS_DEVICE_ALIASING(new_inode)) { + err = -EPERM; + goto out_dir; + } err = -ENOTEMPTY; if (old_is_dir && !f2fs_empty_dir(new_inode)) From a2c73a7a677afdaa8b16d775188f9ef5cfbfd8b2 Mon Sep 17 00:00:00 2001 From: Wenjie Qi Date: Mon, 10 Aug 2026 21:38:32 +0800 Subject: [PATCH 279/641] f2fs: return symlink writeback errors F2FS writes long symlink data with page_symlink() and then flushes the symlink mapping to reduce the chance of exposing a broken symlink. That flush result is currently ignored. If the writeback fails, symlink() still returns success even though the symlink is not durable and the same operation can already surface -EIO through syncfs(). Return the writeback error to userspace and skip the dirsync flush once the symlink data flush has failed. Fixes: d0cae97cb600 ("f2fs: flush symlink path to avoid broken symlink after POR") Cc: stable@kernel.org Signed-off-by: Wenjie Qi Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/namei.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c index b9b15c5d28de..a0e902895dd3 100644 --- a/fs/f2fs/namei.c +++ b/fs/f2fs/namei.c @@ -715,15 +715,16 @@ err_out: * performance regression. */ if (!err) { - filemap_write_and_wait_range(inode->i_mapping, 0, - disk_link.len - 1); + err = filemap_write_and_wait_range(inode->i_mapping, 0, + disk_link.len - 1); - if (IS_DIRSYNC(dir)) + if (!err && IS_DIRSYNC(dir)) f2fs_sync_fs(sbi->sb, 1); - } else { - f2fs_unlink(dir, dentry); } + if (err) + f2fs_unlink(dir, dentry); + f2fs_balance_fs(sbi, true); goto out_free_encrypted_link; From dafb84f092a6387748b2df4c8f647d46873bc1a0 Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Wed, 12 Aug 2026 12:20:43 +0000 Subject: [PATCH 280/641] f2fs: fix to propagate error from f2fs_sync_fs() So that caller can detect any failure from f2fs_sync_fs(). Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/namei.c | 51 ++++++++++++++++++++++++++++++++++--------------- fs/f2fs/super.c | 4 +++- 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c index a0e902895dd3..7d9f8e92de06 100644 --- a/fs/f2fs/namei.c +++ b/fs/f2fs/namei.c @@ -402,8 +402,11 @@ static int f2fs_create(struct mnt_idmap *idmap, struct inode *dir, d_instantiate_new(dentry, inode); - if (IS_DIRSYNC(dir)) - f2fs_sync_fs(sbi->sb, 1); + if (IS_DIRSYNC(dir)) { + err = f2fs_sync_fs(sbi->sb, 1); + if (err) + return err; + } f2fs_balance_fs(sbi, true); return 0; @@ -455,8 +458,11 @@ static int f2fs_link(struct dentry *old_dentry, struct inode *dir, d_instantiate(dentry, inode); - if (IS_DIRSYNC(dir)) - f2fs_sync_fs(sbi->sb, 1); + if (IS_DIRSYNC(dir)) { + err = f2fs_sync_fs(sbi->sb, 1); + if (err) + return err; + } return 0; out: clear_inode_flag(inode, FI_INC_LINK); @@ -626,8 +632,11 @@ static int f2fs_unlink(struct inode *dir, struct dentry *dentry) if (IS_ENABLED(CONFIG_UNICODE) && IS_CASEFOLDED(dir)) d_invalidate(dentry); - if (IS_DIRSYNC(dir)) - f2fs_sync_fs(sbi->sb, 1); + if (IS_DIRSYNC(dir)) { + err = f2fs_sync_fs(sbi->sb, 1); + if (err) + goto out; + } goto out; corrupted: @@ -719,7 +728,7 @@ err_out: disk_link.len - 1); if (!err && IS_DIRSYNC(dir)) - f2fs_sync_fs(sbi->sb, 1); + err = f2fs_sync_fs(sbi->sb, 1); } if (err) @@ -771,8 +780,11 @@ static struct dentry *f2fs_mkdir(struct mnt_idmap *idmap, struct inode *dir, d_instantiate_new(dentry, inode); - if (IS_DIRSYNC(dir)) - f2fs_sync_fs(sbi->sb, 1); + if (IS_DIRSYNC(dir)) { + err = f2fs_sync_fs(sbi->sb, 1); + if (err) + return ERR_PTR(err); + } f2fs_balance_fs(sbi, true); return NULL; @@ -826,8 +838,11 @@ static int f2fs_mknod(struct mnt_idmap *idmap, struct inode *dir, d_instantiate_new(dentry, inode); - if (IS_DIRSYNC(dir)) - f2fs_sync_fs(sbi->sb, 1); + if (IS_DIRSYNC(dir)) { + err = f2fs_sync_fs(sbi->sb, 1); + if (err) + return err; + } f2fs_balance_fs(sbi, true); return 0; @@ -1126,8 +1141,11 @@ static int f2fs_rename(struct mnt_idmap *idmap, struct inode *old_dir, f2fs_unlock_op(sbi, &lc); - if (IS_DIRSYNC(old_dir) || IS_DIRSYNC(new_dir)) - f2fs_sync_fs(sbi->sb, 1); + if (IS_DIRSYNC(old_dir) || IS_DIRSYNC(new_dir)) { + err = f2fs_sync_fs(sbi->sb, 1); + if (err) + return err; + } f2fs_update_time(sbi, REQ_TIME); return 0; @@ -1293,8 +1311,11 @@ static int f2fs_cross_rename(struct inode *old_dir, struct dentry *old_dentry, f2fs_unlock_op(sbi, &lc); - if (IS_DIRSYNC(old_dir) || IS_DIRSYNC(new_dir)) - f2fs_sync_fs(sbi->sb, 1); + if (IS_DIRSYNC(old_dir) || IS_DIRSYNC(new_dir)) { + err = f2fs_sync_fs(sbi->sb, 1); + if (err) + return err; + } f2fs_update_time(sbi, REQ_TIME); return 0; diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index 0c8f60b7242f..3bdb0f891c35 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -2960,7 +2960,9 @@ static int __f2fs_remount(struct fs_context *fc, struct super_block *sb) set_sbi_flag(sbi, SBI_IS_DIRTY); set_sbi_flag(sbi, SBI_IS_CLOSE); - f2fs_sync_fs(sb, 1); + err = f2fs_sync_fs(sb, 1); + if (err) + goto restore_gc; clear_sbi_flag(sbi, SBI_IS_CLOSE); } From e10b8b4931e10dbcce5b369583461d81c69187e8 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Tue, 18 Aug 2026 09:48:40 -1000 Subject: [PATCH 281/641] sched_ext: Sync tools autogen enum headers from the scx repo Regenerate enum_defs.autogen.h against the current tree, picking up the dispatch verdict enums and dropping the marker for the removed SCX_RQ_IN_BALANCE. Add enums_abi.autogen.h, a table of 64-bit scx enumerator values generated from vmlinux.h, used as the substitution source when the running kernel's BTF truncates 64-bit enum values to 32 bits. Signed-off-by: Tejun Heo --- .../sched_ext/include/scx/enum_defs.autogen.h | 5 +- .../sched_ext/include/scx/enums_abi.autogen.h | 223 ++++++++++++++++++ 2 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 tools/sched_ext/include/scx/enums_abi.autogen.h diff --git a/tools/sched_ext/include/scx/enum_defs.autogen.h b/tools/sched_ext/include/scx/enum_defs.autogen.h index 19aa1de3e700..63b6b14b19bd 100644 --- a/tools/sched_ext/include/scx/enum_defs.autogen.h +++ b/tools/sched_ext/include/scx/enum_defs.autogen.h @@ -56,6 +56,10 @@ #define HAVE_SCX_DEQ_SLEEP #define HAVE_SCX_DEQ_CORE_SCHED_EXEC #define HAVE_SCX_DEQ_SCHED_CHANGE +#define HAVE_SCX_DSP_NONE +#define HAVE_SCX_DSP_LOCAL +#define HAVE_SCX_DSP_PREV +#define HAVE_SCX_DSP_RETRY #define HAVE_SCX_DSQ_FLAG_BUILTIN #define HAVE_SCX_DSQ_FLAG_LOCAL_ON #define HAVE_SCX_DSQ_INVALID @@ -188,7 +192,6 @@ #define HAVE_SCX_RQ_SUB_IDLE_RENOTIFY #define HAVE_SCX_RQ_ROOT_IDLE_RENOTIFY #define HAVE_SCX_RQ_IN_WAKEUP -#define HAVE_SCX_RQ_IN_BALANCE #define HAVE_SCX_RQ_IN_DISPATCH #define HAVE_SCX_SCHED_PCPU_BYPASSING #define HAVE_SCX_SLICE_OOB_DUR_BITS diff --git a/tools/sched_ext/include/scx/enums_abi.autogen.h b/tools/sched_ext/include/scx/enums_abi.autogen.h new file mode 100644 index 000000000000..d53899764f5a --- /dev/null +++ b/tools/sched_ext/include/scx/enums_abi.autogen.h @@ -0,0 +1,223 @@ +/* + * WARNING: This file is autogenerated from gen_enum_defs.py [1]. + * + * scx enumerator values from the vmlinux.h this tree is built against. + * Used as the substitution source when the running kernel's BTF lacks + * BTF_KIND_ENUM64 encoding and 64-bit enum values are truncated. + * + * [1] https://github.com/sched-ext/scx/blob/main/scripts/gen_enum_defs.py + */ + +#ifndef __ENUMS_ABI_AUTOGEN_H__ +#define __ENUMS_ABI_AUTOGEN_H__ + +struct __scx_enum_abi_val { + const char *type; + const char *name; + u64 val; +}; + +static const struct __scx_enum_abi_val __scx_enum_abi_vals[] + __attribute__((unused)) = { + { "scx_arena_consts", "SCX_ARENA_MIN_ORDER", 0x3LLU }, + { "scx_arena_consts", "SCX_ARENA_GROW_PAGES", 0x4LLU }, + { "scx_cap_flags", "__SCX_CAP_ENQ_IMMED", 0x0LLU }, + { "scx_cap_flags", "__SCX_CAP_ENQ", 0x1LLU }, + { "scx_cap_flags", "__SCX_CAP_PREEMPT", 0x2LLU }, + { "scx_cap_flags", "__SCX_CAP_PERF", 0x3LLU }, + { "scx_cap_flags", "__SCX_NR_CAPS", 0x4LLU }, + { "scx_cap_flags", "__SCX_CAP_ALL", 0xfLLU }, + { "scx_cap_flags", "SCX_CAP_ENQ_IMMED", 0x1LLU }, + { "scx_cap_flags", "SCX_CAP_ENQ", 0x2LLU }, + { "scx_cap_flags", "SCX_CAP_PREEMPT", 0x4LLU }, + { "scx_cap_flags", "SCX_CAP_PERF", 0x8LLU }, + { "scx_cap_flags", "SCX_CAP_BASE", 0x1LLU }, + { "scx_cap_flags", "SCX_CAPS_REENQ_ON_LOSS", 0x3LLU }, + { "scx_cid_consts", "SCX_CID_SHARD_SIZE_DFL", 0x18LLU }, + { "scx_cid_consts", "SCX_CID_SHARD_MAX_CPUS", 0x200LLU }, + { "scx_consts", "SCX_DSP_DFL_MAX_BATCH", 0x20LLU }, + { "scx_consts", "SCX_DSP_MAX_LOOPS", 0x20LLU }, + { "scx_consts", "SCX_WATCHDOG_MAX_TIMEOUT", 0x7530LLU }, + { "scx_consts", "SCX_RESCUE_DFL_BW_PPT", 0x14LLU }, + { "scx_consts", "SCX_RESCUE_MAX_BW_PPT", 0xfaLLU }, + { "scx_consts", "SCX_RESCUE_DISABLE", 0xffffffffLLU }, + { "scx_consts", "SCX_RESCUE_DFL_QUANTUM_US", 0x1388LLU }, + { "scx_consts", "SCX_RESCUE_MIN_QUANTUM_US", 0x3e8LLU }, + { "scx_consts", "SCX_RESCUE_MAX_QUANTUM_US", 0x186a0LLU }, + { "scx_consts", "SCX_RESCUE_MIN_SLICE_US", 0x3e8LLU }, + { "scx_consts", "SCX_RESCUE_OVERLOAD_MULT", 0x10LLU }, + { "scx_consts", "SCX_RESCUE_MIN_OVERLOAD_MS", 0x3e8LLU }, + { "scx_consts", "SCX_RESCUE_MAX_OVERLOAD_MS", 0x3a98LLU }, + { "scx_consts", "SCX_TID_CHUNK", 0x400LLU }, + { "scx_consts", "SCX_EXIT_BT_LEN", 0x40LLU }, + { "scx_consts", "SCX_EXIT_MSG_LEN", 0x400LLU }, + { "scx_consts", "SCX_EXIT_DUMP_DFL_LEN", 0x8000LLU }, + { "scx_consts", "SCX_CPUPERF_ONE", 0x400LLU }, + { "scx_consts", "SCX_TASK_ITER_BATCH", 0x20LLU }, + { "scx_consts", "SCX_BYPASS_HOST_NTH", 0x2LLU }, + { "scx_consts", "SCX_BYPASS_LB_DFL_INTV_US", 0x7a120LLU }, + { "scx_consts", "SCX_BYPASS_LB_DONOR_PCT", 0x7dLLU }, + { "scx_consts", "SCX_BYPASS_LB_MIN_DELTA_DIV", 0x4LLU }, + { "scx_consts", "SCX_BYPASS_LB_BATCH", 0x100LLU }, + { "scx_consts", "SCX_REENQ_MAX_REPEAT", 0x100LLU }, + { "scx_consts", "SCX_SUB_MAX_DEPTH", 0x4LLU }, + { "scx_cpu_preempt_reason", "SCX_CPU_PREEMPT_RT", 0x0LLU }, + { "scx_cpu_preempt_reason", "SCX_CPU_PREEMPT_DL", 0x1LLU }, + { "scx_cpu_preempt_reason", "SCX_CPU_PREEMPT_STOP", 0x2LLU }, + { "scx_cpu_preempt_reason", "SCX_CPU_PREEMPT_UNKNOWN", 0x3LLU }, + { "scx_deq_flags", "SCX_DEQ_SLEEP", 0x1LLU }, + { "scx_deq_flags", "SCX_DEQ_CORE_SCHED_EXEC", 0x100000000LLU }, + { "scx_deq_flags", "SCX_DEQ_SCHED_CHANGE", 0x200000000LLU }, + { "scx_dsp_verdict", "SCX_DSP_NONE", 0x0LLU }, + { "scx_dsp_verdict", "SCX_DSP_LOCAL", 0x1LLU }, + { "scx_dsp_verdict", "SCX_DSP_PREV", 0x2LLU }, + { "scx_dsp_verdict", "SCX_DSP_RETRY", 0x3LLU }, + { "scx_dsq_id_flags", "SCX_DSQ_FLAG_BUILTIN", 0x8000000000000000LLU }, + { "scx_dsq_id_flags", "SCX_DSQ_FLAG_LOCAL_ON", 0x4000000000000000LLU }, + { "scx_dsq_id_flags", "SCX_DSQ_INVALID", 0x8000000000000000LLU }, + { "scx_dsq_id_flags", "SCX_DSQ_GLOBAL", 0x8000000000000001LLU }, + { "scx_dsq_id_flags", "SCX_DSQ_LOCAL", 0x8000000000000002LLU }, + { "scx_dsq_id_flags", "SCX_DSQ_BYPASS", 0x8000000000000003LLU }, + { "scx_dsq_id_flags", "SCX_DSQ_REJECT", 0x8000000000000004LLU }, + { "scx_dsq_id_flags", "SCX_DSQ_RESCUE", 0x8000000000000005LLU }, + { "scx_dsq_id_flags", "SCX_DSQ_LOCAL_ON", 0xc000000000000000LLU }, + { "scx_dsq_id_flags", "SCX_DSQ_LOCAL_CPU_MASK", 0xffffffffLLU }, + { "scx_dsq_iter_flags", "SCX_DSQ_ITER_REV", 0x10000LLU }, + { "scx_dsq_iter_flags", "__SCX_DSQ_ITER_HAS_SLICE", 0x40000000LLU }, + { "scx_dsq_iter_flags", "__SCX_DSQ_ITER_HAS_VTIME", 0x80000000LLU }, + { "scx_dsq_iter_flags", "__SCX_DSQ_ITER_USER_FLAGS", 0x10000LLU }, + { "scx_dsq_iter_flags", "__SCX_DSQ_ITER_ALL_FLAGS", 0xc0010000LLU }, + { "scx_dsq_lnode_flags", "SCX_DSQ_LNODE_ITER_CURSOR", 0x1LLU }, + { "scx_dsq_lnode_flags", "__SCX_DSQ_LNODE_PRIV_SHIFT", 0x10LLU }, + { "scx_enable_state", "SCX_ENABLING", 0x0LLU }, + { "scx_enable_state", "SCX_ENABLED", 0x1LLU }, + { "scx_enable_state", "SCX_DISABLING", 0x2LLU }, + { "scx_enable_state", "SCX_DISABLED", 0x3LLU }, + { "scx_enq_flags", "SCX_ENQ_WAKEUP", 0x1LLU }, + { "scx_enq_flags", "SCX_ENQ_HEAD", 0x10000LLU }, + { "scx_enq_flags", "SCX_ENQ_CPU_SELECTED", 0x100000LLU }, + { "scx_enq_flags", "SCX_ENQ_PREEMPT", 0x100000000LLU }, + { "scx_enq_flags", "SCX_ENQ_IMMED", 0x200000000LLU }, + { "scx_enq_flags", "SCX_ENQ_RESCUE", 0x400000000LLU }, + { "scx_enq_flags", "SCX_ENQ_REENQ", 0x10000000000LLU }, + { "scx_enq_flags", "SCX_ENQ_LAST", 0x20000000000LLU }, + { "scx_enq_flags", "__SCX_ENQ_INTERNAL_MASK", 0xff00000000000000LLU }, + { "scx_enq_flags", "SCX_ENQ_CLEAR_OPSS", 0x100000000000000LLU }, + { "scx_enq_flags", "SCX_ENQ_DSQ_PRIQ", 0x200000000000000LLU }, + { "scx_enq_flags", "SCX_ENQ_NESTED", 0x400000000000000LLU }, + { "scx_enq_flags", "SCX_ENQ_GDSQ_FALLBACK", 0x800000000000000LLU }, + { "scx_enq_flags", "SCX_ENQ_IGNORE_CAPS", 0x1000000000000000LLU }, + { "scx_enq_flags", "SCX_ENQ_APPLY_SLICE", 0x2000000000000000LLU }, + { "scx_enq_flags", "SCX_ENQ_SLICE_DFL", 0x4000000000000000LLU }, + { "scx_ent_dsq_flags", "SCX_TASK_DSQ_ON_PRIQ", 0x1LLU }, + { "scx_ent_flags", "SCX_TASK_QUEUED", 0x1LLU }, + { "scx_ent_flags", "SCX_TASK_IN_CUSTODY", 0x2LLU }, + { "scx_ent_flags", "SCX_TASK_RESET_RUNNABLE_AT", 0x4LLU }, + { "scx_ent_flags", "SCX_TASK_DEQD_FOR_SLEEP", 0x8LLU }, + { "scx_ent_flags", "SCX_TASK_SUB_INIT", 0x10LLU }, + { "scx_ent_flags", "SCX_TASK_IMMED", 0x20LLU }, + { "scx_ent_flags", "SCX_TASK_PROTECTED", 0x40LLU }, + { "scx_ent_flags", "SCX_TASK_STATE_SHIFT", 0x8LLU }, + { "scx_ent_flags", "SCX_TASK_STATE_BITS", 0x3LLU }, + { "scx_ent_flags", "SCX_TASK_STATE_MASK", 0x700LLU }, + { "scx_ent_flags", "SCX_TASK_NONE", 0x0LLU }, + { "scx_ent_flags", "SCX_TASK_INIT_BEGIN", 0x100LLU }, + { "scx_ent_flags", "SCX_TASK_INIT", 0x200LLU }, + { "scx_ent_flags", "SCX_TASK_READY", 0x300LLU }, + { "scx_ent_flags", "SCX_TASK_ENABLED", 0x400LLU }, + { "scx_ent_flags", "SCX_TASK_DEAD", 0x500LLU }, + { "scx_ent_flags", "SCX_TASK_REENQ_REASON_SHIFT", 0xcLLU }, + { "scx_ent_flags", "SCX_TASK_REENQ_REASON_BITS", 0x3LLU }, + { "scx_ent_flags", "SCX_TASK_REENQ_REASON_MASK", 0x7000LLU }, + { "scx_ent_flags", "SCX_TASK_REENQ_NONE", 0x0LLU }, + { "scx_ent_flags", "SCX_TASK_REENQ_KFUNC", 0x1000LLU }, + { "scx_ent_flags", "SCX_TASK_REENQ_IMMED", 0x2000LLU }, + { "scx_ent_flags", "SCX_TASK_REENQ_PREEMPTED", 0x3000LLU }, + { "scx_ent_flags", "SCX_TASK_REENQ_CAP", 0x4000LLU }, + { "scx_ent_flags", "SCX_TASK_CURSOR", 0xffffffff80000000LLU }, + { "scx_exit_code", "SCX_ECODE_RSN_HOTPLUG", 0x100000000LLU }, + { "scx_exit_code", "SCX_ECODE_RSN_CGROUP_OFFLINE", 0x200000000LLU }, + { "scx_exit_code", "SCX_ECODE_ACT_RESTART", 0x1000000000000LLU }, + { "scx_exit_flags", "SCX_EFLAG_INITIALIZED", 0x1LLU }, + { "scx_exit_kind", "SCX_EXIT_NONE", 0x0LLU }, + { "scx_exit_kind", "SCX_EXIT_DONE", 0x1LLU }, + { "scx_exit_kind", "SCX_EXIT_UNREG", 0x40LLU }, + { "scx_exit_kind", "SCX_EXIT_UNREG_BPF", 0x41LLU }, + { "scx_exit_kind", "SCX_EXIT_UNREG_KERN", 0x42LLU }, + { "scx_exit_kind", "SCX_EXIT_SYSRQ", 0x43LLU }, + { "scx_exit_kind", "SCX_EXIT_PARENT", 0x44LLU }, + { "scx_exit_kind", "SCX_EXIT_PARENT_KILL", 0x45LLU }, + { "scx_exit_kind", "SCX_EXIT_ERROR", 0x400LLU }, + { "scx_exit_kind", "SCX_EXIT_ERROR_BPF", 0x401LLU }, + { "scx_exit_kind", "SCX_EXIT_ERROR_STALL", 0x402LLU }, + { "scx_exit_kind", "SCX_EXIT_ERROR_REENQ", 0x403LLU }, + { "scx_exit_kind", "SCX_EXIT_ERROR_RESCUE", 0x404LLU }, + { "scx_kf_allow_flags", "SCX_KF_ALLOW_UNLOCKED", 0x1LLU }, + { "scx_kf_allow_flags", "SCX_KF_ALLOW_INIT_CIDS", 0x2LLU }, + { "scx_kf_allow_flags", "SCX_KF_ALLOW_CPU_RELEASE", 0x4LLU }, + { "scx_kf_allow_flags", "SCX_KF_ALLOW_DISPATCH", 0x8LLU }, + { "scx_kf_allow_flags", "SCX_KF_ALLOW_ENQUEUE", 0x10LLU }, + { "scx_kf_allow_flags", "SCX_KF_ALLOW_SELECT_CPU", 0x20LLU }, + { "scx_kick_flags", "SCX_KICK_IDLE", 0x1LLU }, + { "scx_kick_flags", "SCX_KICK_PREEMPT", 0x2LLU }, + { "scx_kick_flags", "SCX_KICK_WAIT", 0x4LLU }, + { "scx_opi", "SCX_OPI_BEGIN", 0x0LLU }, + { "scx_opi", "SCX_OPI_NORMAL_BEGIN", 0x0LLU }, + { "scx_opi", "SCX_OPI_NORMAL_END", 0x21LLU }, + { "scx_opi", "SCX_OPI_CPU_HOTPLUG_BEGIN", 0x21LLU }, + { "scx_opi", "SCX_OPI_CPU_HOTPLUG_END", 0x23LLU }, + { "scx_opi", "SCX_OPI_END", 0x23LLU }, + { "scx_ops_flags", "SCX_OPS_KEEP_BUILTIN_IDLE", 0x1LLU }, + { "scx_ops_flags", "SCX_OPS_ENQ_LAST", 0x2LLU }, + { "scx_ops_flags", "SCX_OPS_ENQ_EXITING", 0x4LLU }, + { "scx_ops_flags", "SCX_OPS_SWITCH_PARTIAL", 0x8LLU }, + { "scx_ops_flags", "SCX_OPS_ENQ_MIGRATION_DISABLED", 0x10LLU }, + { "scx_ops_flags", "SCX_OPS_ALLOW_QUEUED_WAKEUP", 0x20LLU }, + { "scx_ops_flags", "SCX_OPS_BUILTIN_IDLE_PER_NODE", 0x40LLU }, + { "scx_ops_flags", "SCX_OPS_ALWAYS_ENQ_IMMED", 0x80LLU }, + { "scx_ops_flags", "SCX_OPS_TID_TO_TASK", 0x100LLU }, + { "scx_ops_flags", "SCX_OPS_ALL_FLAGS", 0x1ffLLU }, + { "scx_ops_flags", "__SCX_OPS_INTERNAL_MASK", 0xff00000000000000LLU }, + { "scx_ops_flags", "SCX_OPS_HAS_CPU_PREEMPT", 0x100000000000000LLU }, + { "scx_ops_state", "SCX_OPSS_NONE", 0x0LLU }, + { "scx_ops_state", "SCX_OPSS_QUEUEING", 0x1LLU }, + { "scx_ops_state", "SCX_OPSS_QUEUED", 0x2LLU }, + { "scx_ops_state", "SCX_OPSS_DISPATCHING", 0x3LLU }, + { "scx_ops_state", "SCX_OPSS_QSEQ_SHIFT", 0x2LLU }, + { "scx_pick_idle_cpu_flags", "SCX_PICK_IDLE_CORE", 0x1LLU }, + { "scx_pick_idle_cpu_flags", "SCX_PICK_IDLE_IN_NODE", 0x2LLU }, + { "scx_public_consts", "SCX_OPS_NAME_LEN", 0x80LLU }, + { "scx_public_consts", "SCX_SLICE_DFL", 0x1312d00LLU }, + { "scx_public_consts", "SCX_SLICE_BYPASS", 0x4c4b40LLU }, + { "scx_public_consts", "SCX_SLICE_INF", 0xffffffffffffffffLLU }, + { "scx_reenq_flags", "SCX_REENQ_ANY", 0x1LLU }, + { "scx_reenq_flags", "SCX_REENQ_CAP_REVOKE", 0x2LLU }, + { "scx_reenq_flags", "__SCX_REENQ_FILTER_MASK", 0xffffLLU }, + { "scx_reenq_flags", "__SCX_REENQ_USER_MASK", 0x1LLU }, + { "scx_reenq_flags", "SCX_REENQ_TSR_RQ_OPEN", 0x100000000LLU }, + { "scx_reenq_flags", "SCX_REENQ_TSR_NOT_FIRST", 0x200000000LLU }, + { "scx_reenq_flags", "__SCX_REENQ_TSR_MASK", 0xf00000000LLU }, + { "scx_rq_flags", "SCX_RQ_ONLINE", 0x1LLU }, + { "scx_rq_flags", "SCX_RQ_CAN_STOP_TICK", 0x2LLU }, + { "scx_rq_flags", "SCX_RQ_CLK_VALID", 0x20LLU }, + { "scx_rq_flags", "SCX_RQ_BAL_CB_PENDING", 0x40LLU }, + { "scx_rq_flags", "SCX_RQ_SUB_IDLE_RENOTIFY", 0x80LLU }, + { "scx_rq_flags", "SCX_RQ_ROOT_IDLE_RENOTIFY", 0x100LLU }, + { "scx_rq_flags", "SCX_RQ_IN_WAKEUP", 0x10000LLU }, + { "scx_rq_flags", "SCX_RQ_IN_DISPATCH", 0x20000LLU }, + { "scx_sched_pcpu_flags", "SCX_SCHED_PCPU_BYPASSING", 0x1LLU }, + { "scx_slice_oob_consts", "SCX_SLICE_OOB_DUR_BITS", 0x2bLLU }, + { "scx_slice_oob_consts", "SCX_SLICE_OOB_ID_BITS", 0x14LLU }, + { "scx_slice_oob_consts", "SCX_SLICE_OOB_DUR_MASK", 0x7ffffffffffLLU }, + { "scx_slice_oob_consts", "SCX_SLICE_OOB_ID_SHIFT", 0x2bLLU }, + { "scx_slice_oob_consts", "SCX_SLICE_OOB_ID_MASK", 0xfffffLLU }, + { "scx_slice_oob_consts", "SCX_SLICE_OOB_PENDING", 0x8000000000000000LLU }, + { "scx_tg_flags", "SCX_TG_ONLINE", 0x1LLU }, + { "scx_tg_flags", "SCX_TG_INITED", 0x2LLU }, + { "scx_tg_flags", "SCX_TG_SUB_INIT", 0x4LLU }, + { "scx_wake_flags", "SCX_WAKE_FORK", 0x4LLU }, + { "scx_wake_flags", "SCX_WAKE_TTWU", 0x8LLU }, + { "scx_wake_flags", "SCX_WAKE_SYNC", 0x10LLU }, +}; + +#endif /* __ENUMS_ABI_AUTOGEN_H__ */ From 9e8581a090c02ffa35e8439b90b024956a735de9 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Tue, 18 Aug 2026 09:48:40 -1000 Subject: [PATCH 282/641] sched_ext: Sync common and compat headers from the scx repo Sync common.bpf.h, compat.bpf.h and compat.h with the scx repo, which accumulated the following: - __COMPAT_read_enum() can now recover 64-bit scx enum values from kernel BTF generated without BTF_KIND_ENUM64 support (pahole < 1.24 or --skip_encoding_btf_enum64, e.g. COS/GKE kernels), substituting values from the build-time vmlinux.h cross-checked against the low 32 bits the kernel does provide. - is_migration_disabled() no longer assumes the BPF prolog always disables migration. Since 8e4f0b1ebcf2 ("bpf: use rcu_read_lock_dont_migrate() for trampoline.c") the prolog only does so under CONFIG_PREEMPT_RCU, so the old current-task test under-reported on v6.18+ !PREEMPT_RCU kernels. A runtime probe on bpf_scx_reg() handles older kernels with backported trampoline behavior. - __COMPAT_scx_bpf_dsq_peek() is gated behind kernel v7.1 where 2f2ea7709266 ("sched_ext: Use dsq->first_task instead of list_empty() in dispatch_enqueue() FIFO-tail") fixed the kfunc spuriously returning NULL on non-empty FIFO DSQs, and the new scx_bpf_reenqueue_local_from_anywhere() provides a callable-from-anywhere reenqueue which prefers the generic scx_bpf_dsq_reenq(). Both were first posted by Gavin Guo and Changwoo Min and are picked up here with the review feedback folded in. - __COMPAT_scx_bpf_cpu_curr() and the scx_bpf_cpu_rq() declaration are restored. Schedulers built from these headers still run on pre-v6.18 kernels where scx_bpf_cpu_curr() does not resolve and the scx_bpf_cpu_rq() fallback still exists. - scx_clock_task() and scx_clock_pelt() document their stale-read behavior for remote idle CPUs under NO_HZ_IDLE. Link: https://lore.kernel.org/all/20260817143126.562923-1-changwoo@igalia.com Signed-off-by: Tejun Heo --- tools/sched_ext/include/scx/common.bpf.h | 140 +++++++++++++++++++---- tools/sched_ext/include/scx/compat.bpf.h | 68 +++++++++-- tools/sched_ext/include/scx/compat.h | 97 ++++++++++++++++ 3 files changed, 274 insertions(+), 31 deletions(-) diff --git a/tools/sched_ext/include/scx/common.bpf.h b/tools/sched_ext/include/scx/common.bpf.h index 979d4cabfaf9..76f5e025e107 100644 --- a/tools/sched_ext/include/scx/common.bpf.h +++ b/tools/sched_ext/include/scx/common.bpf.h @@ -48,6 +48,7 @@ extern int LINUX_KERNEL_VERSION __kconfig; extern const char CONFIG_CC_VERSION_TEXT[64] __kconfig __weak; extern const char CONFIG_LOCALVERSION[64] __kconfig __weak; +extern bool CONFIG_PREEMPT_RCU __kconfig __weak; /* * Earlier versions of clang/pahole lost upper 32bits in 64bit enums which can @@ -97,6 +98,7 @@ s32 scx_bpf_pick_any_cpu_node(const cpumask_t *cpus_allowed, int node, u64 flags s32 scx_bpf_pick_any_cpu(const cpumask_t *cpus_allowed, u64 flags) __ksym; bool scx_bpf_task_running(const struct task_struct *p) __ksym; s32 scx_bpf_task_cpu(const struct task_struct *p) __ksym; +struct rq *scx_bpf_cpu_rq(s32 cpu) __ksym __weak; struct rq *scx_bpf_locked_rq(void) __ksym; struct task_struct *scx_bpf_cpu_curr(s32 cpu) __ksym __weak; struct task_struct *scx_bpf_tid_to_task(u64 tid) __ksym __weak; @@ -527,32 +529,103 @@ static __always_inline const struct cpumask *cast_mask(struct bpf_cpumask *mask) return (const struct cpumask *)mask; } +/* + * True if the non-sleepable BPF trampoline prolog (__bpf_prog_enter) calls + * migrate_disable() for the current task. Recorded once by + * scx_lib_init_probe, an fentry program on bpf_scx_reg() that fires during + * the natural scheduler-attach call chain (auto-attached by scx_ops_attach!). + * + * Defaults to true (conservative). Over-reporting in is_migration_disabled() + * causes local-only dispatch, which is safe. Under-reporting can crash the + * scheduler, so we err high if the probe somehow fails to run. + */ +bool __scx_prolog_disables_migration __weak = true; + +/* + * scx_lib_init_probe - non-sleepable prolog probe. + * + * Attached to bpf_scx_reg(), the .reg callback in bpf_sched_ext_ops + * (kernel/sched/ext.c). The kernel's struct_ops machinery invokes + * bpf_scx_reg when userspace creates the scheduler link, before + * ops.init() fires. Its address is taken in the vtable, so the symbol + * is non-inlinable and has been stable since introduction. + * + * Entering via fentry runs us through __bpf_prog_enter -- the + * non-sleepable prolog that consumers of is_migration_disabled() live + * under. + * + * Loud warning: the prolog adds at most 1 to migration_disabled. + * Reading > 1 means something upstream in the + * bpf_struct_ops_link_create -> bpf_scx_reg path disabled migration + * before the prolog ran, invalidating the probe; audit and adjust. + */ +SEC("fentry/bpf_scx_reg") __weak +int scx_lib_init_probe(void *ctx) +{ + if (bpf_core_field_exists(((struct task_struct *)0)->migration_disabled)) { + const struct task_struct *p = bpf_get_current_task_btf(); + unsigned int md = p->migration_disabled; + + if (md > 1) + bpf_printk("scx_lib_init_probe: unexpected migration_disabled=%u " + "upstream of BPF prolog; probe result unreliable", + md); + + __scx_prolog_disables_migration = md > 0; + } + return 0; +} + /* * Return true if task @p cannot migrate to a different CPU, false * otherwise. + * + * IMPORTANT: designed for NON-SLEEPABLE BPF contexts only. Sleepable + * contexts (BPF_STRUCT_OPS_SLEEPABLE, SEC("syscall"), + * SEC("fentry.s/...")) enter via __bpf_prog_enter_sleepable() or + * __bpf_prog_enter_sleepable_recur(), both of which unconditionally + * call migrate_disable(); this helper can yield a false negative for + * p == current there, which can crash the scheduler. */ static inline bool is_migration_disabled(const struct task_struct *p) { /* - * Testing p->migration_disabled in a BPF code is tricky because the - * migration is _always_ disabled while running the BPF code. - * The prolog (__bpf_prog_enter) and epilog (__bpf_prog_exit) for BPF - * code execution disable and re-enable the migration of the current - * task, respectively. So, the _current_ task of the sched_ext ops is - * always migration-disabled. Moreover, p->migration_disabled could be - * two or greater when a sched_ext ops BPF code (e.g., ops.tick) is - * executed in the middle of the other BPF code execution. + * Testing p->migration_disabled in BPF is tricky because the BPF prolog + * (__bpf_prog_enter) may call migrate_disable() for the current task, + * making migration_disabled == 1 even for tasks that are not truly + * migration-disabled. * - * Therefore, we should decide that the _current_ task is - * migration-disabled only when its migration_disabled count is greater - * than one. In other words, when p->migration_disabled == 1, there is - * an ambiguity, so we should check if @p is the current task or not. + * Since commit 8e4f0b1ebcf2 ("bpf: use rcu_read_lock_dont_migrate() for + * trampoline.c"), the BPF prolog calls migrate_disable() only when + * CONFIG_PREEMPT_RCU is enabled. Two fast paths cover the common cases: + * + * 1) CONFIG_PREEMPT_RCU: prolog always calls migrate_disable(), so + * migration_disabled == 1 for the current task is ambiguous. + * Disambiguate by checking p == current. + * + * 2) v6.18+ without CONFIG_PREEMPT_RCU: prolog never calls + * migrate_disable(), so migration_disabled == 1 is unambiguously + * a real migrate_disable() call. + * + * A slow path handles pre-v6.18 kernels without CONFIG_PREEMPT_RCU, + * where the prolog historically called migrate_disable() unconditionally + * but a cherry-picked downstream kernel may not. The runtime-probed flag + * __scx_prolog_disables_migration (set by scx_lib_init_probe) distinguishes + * the two cases without relying on the kernel version alone. */ if (bpf_core_field_exists(p->migration_disabled)) { - if (p->migration_disabled == 1) - return bpf_get_current_task_btf() != p; - else - return p->migration_disabled; + if (p->migration_disabled == 1) { + /* Fast path: prolog always disables migration */ + if (CONFIG_PREEMPT_RCU) + return bpf_get_current_task_btf() != p; + /* Fast path: prolog never disables migration */ + if (LINUX_KERNEL_VERSION >= KERNEL_VERSION(6, 18, 0)) + return true; + /* Slow path: pre-v6.18, !PREEMPT_RCU - use runtime flag */ + return __scx_prolog_disables_migration ? + bpf_get_current_task_btf() != p : true; + } + return p->migration_disabled; } return false; } @@ -1021,7 +1094,20 @@ static inline u64 scx_clock_task(u32 cpu) { struct rq___local *rq = get_current_rq(cpu); - /* Equivalent to the kernel's rq_clock_task(). */ + /* + * Equivalent to the kernel's rq_clock_task(): wall-clock time minus + * cumulative IRQ time (CONFIG_IRQ_TIME_ACCOUNTING) and hypervisor + * steal time (CONFIG_PARAVIRT_TIME_ACCOUNTING). Without those configs, + * it equals rq->clock. + * + * Conceptually this clock advances during idle (the idle task counts + * as a running task), but rq->clock_task is only updated on scheduling + * events. With NO_HZ_IDLE (the default), the periodic tick is stopped + * on idle CPUs, so rq->clock_task is not refreshed while a CPU is + * idle. Reading this clock for a remote idle CPU from a BPF timer + * callback returns the value from when the CPU last went idle, making + * the delta over an idle interval effectively zero. + */ return rq ? rq->clock_task : 0; } @@ -1032,9 +1118,23 @@ static inline u64 scx_clock_pelt(u32 cpu) /* * Equivalent to the kernel's rq_clock_pelt(): subtracts * lost_idle_time from clock_pelt to absorb the jump that occurs - * when clock_pelt resyncs with clock_task at idle exit. The result - * is a continuous, capacity-invariant clock safe for both task - * execution time stamping and cross-idle measurements. + * when clock_pelt resyncs with clock_task at idle exit. The intent + * is a continuous, capacity- and frequency-invariant clock that is + * frozen during idle, IRQ, and hypervisor steal. + * + * However, like scx_clock_task(), this clock has a stale-read issue + * for remote idle CPUs with NO_HZ_IDLE (the default). clock_pelt + * itself advances at wall-clock rate (hardware-clock based), but + * lost_idle_time is only updated via update_rq_clock_pelt(), which + * requires update_rq_clock() to be called. With NO_HZ_IDLE, the + * periodic tick is stopped on idle CPUs, so lost_idle_time is not + * refreshed during idle. Reading this clock for a remote idle CPU + * from a BPF timer callback therefore returns a value that drifts + * at wall-clock rate -- the same stale behaviour as scx_clock_task(). + * + * Without NO_HZ_IDLE, periodic ticks keep lost_idle_time nearly in + * sync (stale by at most one tick period, ~1 ms), so the result is + * accurate. */ return rq ? (rq->clock_pelt - rq->lost_idle_time) : 0; } diff --git a/tools/sched_ext/include/scx/compat.bpf.h b/tools/sched_ext/include/scx/compat.bpf.h index 3ab642f92c8a..6944221f96cc 100644 --- a/tools/sched_ext/include/scx/compat.bpf.h +++ b/tools/sched_ext/include/scx/compat.bpf.h @@ -92,15 +92,20 @@ int bpf_cpumask_populate(struct bpf_cpumask *dst, void *src, size_t src__sz) __k /* * v6.19: Introduce lockless peek API for user DSQs. + * v7.1: Fix scx_bpf_dsq_peek() spuriously returning NULL on non-empty + * FIFO DSQs (2f2ea7709266). * - * Preserve the following macro until v6.21. + * The kfunc exists from v6.19 but can return NULL for a non-empty FIFO DSQ + * before the v7.1 fix. Require kernel version >= 7.1.0 before calling it; + * otherwise fall through to the bpf_iter_scx_dsq fallback below. */ static inline struct task_struct *__COMPAT_scx_bpf_dsq_peek(u64 dsq_id) { struct task_struct *p = NULL; struct bpf_iter_scx_dsq it; - if (bpf_ksym_exists(scx_bpf_dsq_peek)) + if (bpf_ksym_exists(scx_bpf_dsq_peek) && + LINUX_KERNEL_VERSION >= KERNEL_VERSION(7, 1, 0)) return scx_bpf_dsq_peek(dsq_id); if (!bpf_iter_scx_dsq_new(&it, dsq_id, 0)) p = bpf_iter_scx_dsq_next(&it); @@ -238,6 +243,26 @@ static inline bool __COMPAT_is_enq_cpu_selected(u64 enq_flags) scx_bpf_pick_any_cpu_node(cpus_allowed, node, flags) : \ scx_bpf_pick_any_cpu(cpus_allowed, flags)) +/* + * v6.18: Add a helper to retrieve the current task running on a CPU. + * + * The kernel tree dropped this helper and scx_bpf_cpu_rq(), but schedulers in + * this tree still support pre-v6.18 kernels where scx_bpf_cpu_curr() doesn't + * resolve and the scx_bpf_cpu_rq() fallback still exists. Keep it until + * pre-v6.18 kernels fall out of the support window. + */ +static inline struct task_struct *__COMPAT_scx_bpf_cpu_curr(int cpu) +{ + struct rq *rq; + + if (bpf_ksym_exists(scx_bpf_cpu_curr)) + return scx_bpf_cpu_curr(cpu); + + rq = scx_bpf_cpu_rq(cpu); + + return rq ? rq->curr : NULL; +} + /* * v6.19: To work around BPF maximum parameter limit, the following kfuncs are * replaced with variants that pack scalar arguments in a struct. Wrappers are @@ -378,6 +403,17 @@ static inline void scx_bpf_task_set_dsq_vtime(struct task_struct *p, u64 vtime) p->scx.dsq_vtime = vtime; } +/* + * v7.1: New scx_bpf_dsq_reenq() that allows re-enqueues on more DSQs. This + * will eventually deprecate scx_bpf_reenqueue_local(). + */ +void scx_bpf_dsq_reenq___compat(u64 dsq_id, u64 reenq_flags) __ksym __weak; + +static inline bool __COMPAT_has_generic_reenq(void) +{ + return bpf_ksym_exists(scx_bpf_dsq_reenq___compat); +} + /* * v6.19: The new void variant can be called from anywhere while the older v1 * variant can only be called from ops.cpu_release(). The double ___ prefixes on @@ -395,21 +431,31 @@ static inline bool __COMPAT_scx_bpf_reenqueue_local_from_anywhere(void) static inline void scx_bpf_reenqueue_local(void) { - if (__COMPAT_scx_bpf_reenqueue_local_from_anywhere()) + if (__COMPAT_has_generic_reenq()) + scx_bpf_dsq_reenq___compat(SCX_DSQ_LOCAL, 0); + else if (__COMPAT_scx_bpf_reenqueue_local_from_anywhere()) scx_bpf_reenqueue_local___v2___compat(); else scx_bpf_reenqueue_local___v1(); } -/* - * v7.1: New scx_bpf_dsq_reenq() that allows re-enqueues on more DSQs. This - * will eventually deprecate scx_bpf_reenqueue_local(). - */ -void scx_bpf_dsq_reenq___compat(u64 dsq_id, u64 reenq_flags) __ksym __weak; - -static inline bool __COMPAT_has_generic_reenq(void) +static inline int scx_bpf_reenqueue_local_from_anywhere(void) { - return bpf_ksym_exists(scx_bpf_dsq_reenq___compat); + /* + * The generic reenq kfunc and the v2 reenqueue-local variant can both be + * called from anywhere; v1 cannot. Test each ksym in its own branch with a + * distinct call: combining them with || would fold into a bitwise OR of the + * two ksym addresses, which the verifier rejects. + */ + if (__COMPAT_has_generic_reenq()) { + scx_bpf_dsq_reenq___compat(SCX_DSQ_LOCAL, 0); + return 0; + } + if (__COMPAT_scx_bpf_reenqueue_local_from_anywhere()) { + scx_bpf_reenqueue_local___v2___compat(); + return 0; + } + return -EOPNOTSUPP; } static inline void scx_bpf_dsq_reenq(u64 dsq_id, u64 reenq_flags) diff --git a/tools/sched_ext/include/scx/compat.h b/tools/sched_ext/include/scx/compat.h index d2e4384df5af..7c12df45fdba 100644 --- a/tools/sched_ext/include/scx/compat.h +++ b/tools/sched_ext/include/scx/compat.h @@ -10,9 +10,14 @@ #include #include #include +#include +#include #include +#include #include +#include "enums_abi.autogen.h" + struct btf *__COMPAT_vmlinux_btf __attribute__((weak)); static inline void __COMPAT_load_vmlinux_btf(void) @@ -23,6 +28,85 @@ static inline void __COMPAT_load_vmlinux_btf(void) } } +/* + * Recover the true value of a 64-bit enum enumerator whose kernel BTF entry + * was truncated to its low 32 bits. + * + * Kernels whose BTF was generated without BTF_KIND_ENUM64 support encode + * 64-bit enums as 8-byte BTF_KIND_ENUM entries whose enumerator values only + * carry the low 32 bits. This happens with pahole < 1.24, which predates + * ENUM64, and with pahole passing --skip_encoding_btf_enum64 (e.g. Google's + * Container-Optimized OS / GKE kernels deliberately pass it for backward + * compatibility with older BTF consumers). The high bits + * can't be recovered from kernel BTF, so substitute the value from the + * vmlinux.h this tree was built against, cross-checked against the low 32 + * bits the kernel did provide. + * + * Note that this is a best-effort recovery, not a ground truth. The + * substitution assumes the running kernel agrees with this tree's vmlinux.h + * on the high 32 bits, but only the low 32 bits can actually be verified. + * The cross-check is vacuous for enumerators whose value has no low bits + * set (e.g. SCX_DSQ_FLAG_BUILTIN, __SCX_ENQ_INTERNAL_MASK, + * SCX_ENQ_CLEAR_OPSS, SCX_ECODE_*): their lo32 is 0 and matches anything, + * so those substitutions rest entirely on the high bits never moving. An + * enumerator missing from the table (a kernel newer than this tree's + * vmlinux.h, or a stale autogen table) can't be recovered at all. If a + * substitution is ever wrong, the scheduler operates on bogus values (e.g. + * dispatching to nonexistent DSQ ids or silently dropping flags) and can + * wildly malfunction, which is why the mismatch and table-miss paths refuse + * instead of guessing. + */ +static inline bool __COMPAT_recover_truncated_enum64(const char *type, + const char *name, + u32 lo32, u64 *v) +{ + static bool warned; + size_t i; + + for (i = 0; i < sizeof(__scx_enum_abi_vals) / sizeof(__scx_enum_abi_vals[0]); i++) { + const struct __scx_enum_abi_val *e = &__scx_enum_abi_vals[i]; + + if (strcmp(e->type, type) || strcmp(e->name, name)) + continue; + + if (e->val <= (u64)UINT32_MAX) { + *v = lo32; + return true; + } + + if ((u32)e->val != lo32) { + fprintf(stderr, "ERROR: kernel BTF value of %s::%s (0x%x) doesn't match the low 32 bits of the vmlinux.h value (0x%llx); refusing to substitute\n", + type, name, lo32, (unsigned long long)e->val); + return false; + } + + if (!warned) { + fprintf(stderr, + "WARNING: kernel BTF lacks BTF_KIND_ENUM64 encoding (generated by\n" + "WARNING: pahole < 1.24 or with --skip_encoding_btf_enum64), so 64-bit\n" + "WARNING: scx enum values are truncated to their low 32 bits in kernel\n" + "WARNING: BTF. Substituting the full 64-bit values from the vmlinux.h\n" + "WARNING: this binary was built against, cross-checked against the low\n" + "WARNING: 32 bits the kernel does provide. The high 32 bits cannot be\n" + "WARNING: verified: if the running kernel's actual values differ from\n" + "WARNING: the build-time vmlinux.h (e.g. an enum that moved in a newer\n" + "WARNING: kernel), the scheduler will operate on bogus values, such as\n" + "WARNING: dispatching to nonexistent DSQ ids, and can wildly malfunction.\n"); + warned = true; + } + *v = e->val; + return true; + } + + /* + * Unknown enumerator (likely a stale autogen table). Fail + * pessimistically to avoid returning an invalid value. + */ + fprintf(stderr, "ERROR: kernel BTF truncates 64-bit enum %s::%s to 0x%x; 64-bit variant not found in vmlinux.h\n", + type, name, lo32); + return false; +} + static inline bool __COMPAT_read_enum(const char *type, const char *name, u64 *v) { const struct btf_type *t; @@ -46,6 +130,19 @@ static inline bool __COMPAT_read_enum(const char *type, const char *name, u64 *v n = btf__name_by_offset(__COMPAT_vmlinux_btf, e[i].name_off); SCX_BUG_ON(!n, "btf__name_by_offset()"); if (!strcmp(n, name)) { + /* + * Try to recover a 64-bit enum from an 8-byte + * BTF_KIND_ENUM that was encoded without ENUM64 + * support (old pahole or + * --skip_encoding_btf_enum64). Only scx_* + * types are covered by the substitution table; + * non-scx types fall through to the raw value + * so this generic utility keeps working for + * them. + */ + if (t->size == 8 && !strncmp(type, "scx_", 4)) + return __COMPAT_recover_truncated_enum64(type, name, + (u32)e[i].val, v); *v = e[i].val; return true; } From a9a01be2834a529cbd490ccbab02643f0c1735f2 Mon Sep 17 00:00:00 2001 From: Vincent Donnefort Date: Fri, 7 Aug 2026 09:54:23 +0100 Subject: [PATCH 283/641] tracing: Fix logged instance name on creation failure When boot instance creation fails, the kernel incorrectly logs "(null)" as the instance name because strsep() consumes curr_str entirely during parsing. Print the properly parsed name variable instead. And while at it log the error code. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260807085423.4175161-1-vdonnefort@google.com Fixes: cb1f98c5e574 ("tracing: Add creation of instances at boot command line") Acked-by: Masami Hiramatsu (Google) Signed-off-by: Vincent Donnefort Signed-off-by: Steven Rostedt --- kernel/trace/trace.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index 89dc1c0ebb90..740c5f358b75 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -9726,7 +9726,8 @@ __init static void enable_instances(void) tr = trace_array_create_systems(name, NULL, addr, size); if (IS_ERR(tr)) { - pr_warn("Tracing: Failed to create instance buffer %s\n", curr_str); + pr_warn("Tracing: Failed to create instance buffer '%s' (%ld)\n", name, + PTR_ERR(tr)); continue; } From cca061dccf563907061766191b2ce3f66b7c285a Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Fri, 21 Aug 2026 09:05:52 -1000 Subject: [PATCH 284/641] sched_ext: Fix spurious aborts in scx_bpf_dsq_move() on ownership change races scx_dsq_move() verifies that the task belongs to the calling scheduler before taking any locks and aborts the scheduler on mismatch. The task can lose the sched association at any point: It can run and fully exit, which clears the association, or get rehomed to a different sub-sched. Both are benign races, but the early ownership check escalates them into scheduler aborts. Move the ownership check below the cursor-lost check. Every ownership change dequeues the task first, so a task that is still on the iterated DSQ under the lock while owned elsewhere indicates a genuine violation and should abort. Also fix two stale comments still referencing sched_ext_free(), which has been renamed to sched_ext_dead(). Fixes: bb4d9fd55158 ("sched_ext: scx_dsq_move() should validate the task belongs to the right scheduler") Signed-off-by: Tejun Heo --- kernel/sched/ext/ext.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index b646711a45fe..c539d15cda63 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -7694,7 +7694,7 @@ static void scx_root_enable_workfn(struct kthread_work *work) /* * Enable ops for every task. Fork is excluded by scx_fork_rwsem * preventing new tasks from being added. No need to exclude tasks - * leaving as sched_ext_free() can handle both prepped and enabled + * leaving as sched_ext_dead() can handle both prepped and enabled * tasks. Prep all tasks first and then enable them with preemption * disabled. * @@ -7786,7 +7786,7 @@ static void scx_root_enable_workfn(struct kthread_work *work) /* * We're fully committed and can't fail. The task READY -> ENABLED - * transitions here are synchronized against sched_ext_free() through + * transitions here are synchronized against sched_ext_dead() through * scx_tasks_lock. */ percpu_down_write(&scx_fork_rwsem); @@ -9004,12 +9004,6 @@ static bool scx_dsq_move(struct bpf_iter_scx_dsq_kern *kit, if (unlikely(READ_ONCE(sch->aborting))) return false; - if (unlikely(!scx_task_on_sched(sch, p))) { - scx_error(sch, "scx_bpf_dsq_move[_vtime]() on %s[%d] but the task belongs to a different scheduler", - p->comm, p->pid); - return false; - } - /* * Can be called from either ops.dispatch() holding the dispatched rq's * lock or any context where no rq lock is held. If latter, lock @p's @@ -9041,6 +9035,17 @@ static bool scx_dsq_move(struct bpf_iter_scx_dsq_kern *kit, goto out; } + /* + * @p has been on $src_dsq and can't move anymore. If @p is not on @sch, + * the caller didn't have authority over @p at the time of the call. + */ + if (unlikely(!scx_task_on_sched(sch, p))) { + scx_error(sch, "scx_bpf_dsq_move[_vtime]() on %s[%d] but the task belongs to a different scheduler", + p->comm, p->pid); + raw_spin_unlock(&src_dsq->lock); + goto out; + } + /* @p is still on $src_dsq and stable, determine the destination */ dst_dsq = find_dsq_for_dispatch(sch, locked_rq ?: this_rq(), dsq_id, task_cpu(p)); From a7318172aa332a161fb9618286e64454c827f8fd Mon Sep 17 00:00:00 2001 From: Hui Su Date: Sun, 16 Aug 2026 18:04:29 +0800 Subject: [PATCH 285/641] tracing: Fix use-after-free with same-name named triggers When two hist triggers on different events are registered with the same name=, the second one reuses the first as named_data. Both are added to tr->hist_vars by save_hist_vars() during event_hist_trigger_parse(), because save_hist_vars() is called before event_trigger_register() while the named reuse is only detected later, in hist_register_trigger(). In the named-data branch hist_register_trigger() then frees the second histogram's hist_data via destroy_hist_data(), but never removes its tr->hist_vars list entry, leaving a dangling pointer and leaking the trace_array reference it holds. A later hist trigger that references a variable makes find_var_file() walk tr->hist_vars and dereference the freed hist_data. The bug is reproducible from userspace by writing three hist triggers to tracefs: cd /sys/kernel/tracing echo 'hist:keys=common_pid:x=common_pid:name=mh' > events/sched/sched_switch/trigger echo 'hist:keys=common_pid:x=common_pid:name=mh' > events/sched/sched_process_fork/trigger echo 'hist:keys=common_pid:vals=$x' > events/sched/sched_process_exit/trigger The third write panics the kernel: BUG: KASAN: slab-use-after-free in find_var_file.part.0+0x272/0x290 Read of size 8 at addr ffff888001f8a0e0 by task sh/1 CPU: 1 UID: 0 PID: 1 Comm: sh Tainted: G D N Call Trace: find_var_file.part.0 find_event_var parse_atom parse_expr __create_val_field event_hist_trigger_parse trigger_process_regex event_trigger_write vfs_write ksys_write do_syscall_64 entry_SYSCALL_64_after_hwframe Allocated by task 1: event_hist_trigger_parse Freed by task 1: hist_register_trigger+0x618/0xa30 event_hist_trigger_parse The buggy address belongs to freed 2048-byte region Oops: general protection fault ... RIP: find_var_file.part.0 Kernel panic - not syncing: Attempted to kill init! exitcode=0x0000000b Fix by removing the hist_data from tr->hist_vars and releasing the trace_array reference in the named-data branch of hist_register_trigger() before freeing the hist_data. Cc: stable@vger.kernel.org Fixes: 6f86bdeab633 ("tracing: Fix bad hist from corrupting named_triggers list") Link: https://patch.msgid.link/20260816100427.33642-3-sh_def@163.com Signed-off-by: Hui Su Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_hist.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index 893bd8b0e48a..963e0d6b61fd 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -6661,8 +6661,10 @@ static int hist_register_trigger(char *glob, tracing_set_filter_buffering(file->tr, true); } - if (named_data) + if (named_data) { + remove_hist_vars(hist_data); destroy_hist_data(hist_data); + } out: return ret; } From 649bc7df3e5d7be6f7996a95084037dbf3cad1e5 Mon Sep 17 00:00:00 2001 From: Hui Su Date: Mon, 17 Aug 2026 20:06:44 +0800 Subject: [PATCH 286/641] tracing: Fix crash passing ERR_PTR to kthread_stop() event_test_stuff() calls kthread_run() and unconditionally passes the returned task_struct pointer to kthread_stop(). kthread_run() returns an error pointer such as ERR_PTR(-ENOMEM) when kthread creation fails, for example under memory pressure during the boot-time event self-test. kthread_stop() then dereferences the invalid pointer, crashing the kernel. Check the result of kthread_run() before passing it to kthread_stop(). Use WARN_ON() so that a failure to create the self-test thread does not go unnoticed, matching the ring-buffer self-test fix in commit 91542863abad ("ring-buffer: Fix crash passing ERR_PTR to kthread_stop()"). Cc: stable@vger.kernel.org Fixes: e6187007d6c3 ("tracing/events: add startup tests for events") Link: https://patch.msgid.link/20260817120642.668375-3-sh_def@163.com Signed-off-by: Hui Su Signed-off-by: Steven Rostedt --- kernel/trace/trace_events.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c index 9f8f2d02276c..1d39eaf6a0f7 100644 --- a/kernel/trace/trace_events.c +++ b/kernel/trace/trace_events.c @@ -5019,6 +5019,8 @@ static __init void event_test_stuff(void) struct task_struct *test_thread; test_thread = kthread_run(event_test_thread, NULL, "test-events"); + if (WARN_ON(IS_ERR(test_thread))) + return; msleep(1); kthread_stop(test_thread); } From 372f8534244d632ad5118e8a87a11291b01712d3 Mon Sep 17 00:00:00 2001 From: Deepanshu Kartikey Date: Mon, 17 Aug 2026 19:36:55 +0530 Subject: [PATCH 287/641] tracing: Fix use-after-free in trace_pipe read on sub-buffer order change Writing to buffer_subbuf_size_kb calls ring_buffer_subbuf_order_set(), which frees every sub-buffer of the ring buffer, including the reader page, and replaces them with newly allocated ones. Readers of trace_pipe hold pointers into those pages. ring_buffer_peek() looks up an event under cpu_buffer->reader_lock but returns the event pointer after dropping the lock, and peek_next_entry() then calls ring_buffer_event_length() and ring_buffer_event_data() on it. If the sub-buffer order is changed in that window, the reader dereferences freed memory: BUG: KASAN: use-after-free in ring_buffer_peek+0x3e0/0x430 Read of size 1 at addr ffff88802a4cf010 by task syz-executor989/6002 Freed by: free_buffer_page kernel/trace/ring_buffer.c:398 [inline] ring_buffer_subbuf_order_set+0x1325/0x18e0 kernel/trace/ring_buffer.c:7444 buffer_subbuf_size_write+0x182/0x280 kernel/trace/trace.c:8221 Take trace_access_lock(RING_BUFFER_ALL_CPUS) around the order change. This is the lock trace_pipe readers already hold across their entire peek-and-print loop, so the swap can no longer race with a reader that is dereferencing a peeked event. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260817140655.5694-1-kartikey406@gmail.com Fixes: f9b94daa542a ("ring-buffer: Set new size of the ring buffer sub page") Reported-by: syzbot+685955db58555575fdd2@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=685955db58555575fdd2 Tested-by: syzbot+685955db58555575fdd2@syzkaller.appspotmail.com Reviewed-by: Bradley Morgan Signed-off-by: Deepanshu Kartikey Signed-off-by: Steven Rostedt --- kernel/trace/trace.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index 740c5f358b75..60c87977f1e3 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -8214,6 +8214,8 @@ buffer_subbuf_size_write(struct file *filp, const char __user *ubuf, /* Do not allow tracing while changing the order of the ring buffer */ tracing_stop_tr(tr); + trace_access_lock(RING_BUFFER_ALL_CPUS); + old_order = ring_buffer_subbuf_order_get(tr->array_buffer.buffer); if (old_order == order) goto out; @@ -8253,6 +8255,7 @@ buffer_subbuf_size_write(struct file *filp, const char __user *ubuf, #endif (*ppos)++; out: + trace_access_unlock(RING_BUFFER_ALL_CPUS); if (ret) cnt = ret; tracing_start_tr(tr); From 1fcccdc20a76b4639b00124cf2a379b837bd2bf7 Mon Sep 17 00:00:00 2001 From: Csaba Budai Date: Fri, 21 Aug 2026 12:25:14 +0200 Subject: [PATCH 288/641] ALSA: hda/realtek: Fix ALC700 audio on Intel Hades Canyon On Intel Hades Canyon systems with an ALC700 codec, analog audio can become unusable after rebooting from Windows into Linux. The affected system uses codec subsystem ID 8086:2073. In the broken state, Realtek coefficient 0x1b has value 0x4a4b. Setting bit 0x0400 changes it to 0x4e4b and immediately restores normal analog playback. The BIOS also sets this bit after disabling and re-enabling onboard audio, while Windows may leave it cleared. Linux currently does not restore it during codec initialization. Add a machine-specific fixup for Intel Hades Canyon which sets only bit 0x0400 of coefficient 0x1b during HDA_FIXUP_ACT_INIT. The fix has been verified across Windows -> Linux reboots on an Intel NUC8i7HNB with an ALC700 codec. Signed-off-by: Csaba Budai Link: https://patch.msgid.link/20260821102514.11194-1-ilyr73@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index fe553db5b52f..5c4ea24e9651 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -4099,6 +4099,7 @@ enum { ALC225_FIXUP_DELL_WYSE_MIC_NO_PRESENCE, ALC225_FIXUP_S3_POP_NOISE, ALC700_FIXUP_INTEL_REFERENCE, + ALC700_FIXUP_INTEL_HADES_CANYON, ALC274_FIXUP_DELL_BIND_DACS, ALC274_FIXUP_DELL_AIO_LINEOUT_VERB, ALC298_FIXUP_TPT470_DOCK_FIX, @@ -4345,6 +4346,19 @@ static void alc287_fixup_lenovo_yoga_book_9i(struct hda_codec *codec, __snd_hda_apply_fixup(codec, id, action, 0); } +static void alc700_fixup_intel_hades_canyon(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + /* + * Windows may leave coef 0x1b bit 0x0400 cleared, causing broken + * analog playback after rebooting into Linux. Restore the bit + * during codec initialization. + */ + if (action == HDA_FIXUP_ACT_INIT) + alc_update_coef_idx(codec, 0x1b, 0x0400, 0x0400); +} + static const struct hda_fixup alc269_fixups[] = { [ALC298_FIXUP_RAZER_BLADE16_2025_PINS] = { .type = HDA_FIXUP_PINS, @@ -5423,6 +5437,10 @@ static const struct hda_fixup alc269_fixups[] = { {} } }, + [ALC700_FIXUP_INTEL_HADES_CANYON] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc700_fixup_intel_hades_canyon, + }, [ALC274_FIXUP_DELL_BIND_DACS] = { .type = HDA_FIXUP_FUNC, .v.func = alc274_fixup_bind_dacs, @@ -8254,6 +8272,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x2782, 0xa128, "Positivo N15RPE-S", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), SND_PCI_QUIRK(0x2782, 0xa212, "Lunnen Ground 14", ALC269VC_FIXUP_LUNNEN_GROUND_14), SND_PCI_QUIRK(0x7017, 0x2014, "Star Labs StarFighter", ALC233_FIXUP_STARLABS_STARFIGHTER), + SND_PCI_QUIRK(0x8086, 0x2073, "Intel NUC 8 Hades Canyon", + ALC700_FIXUP_INTEL_HADES_CANYON), SND_PCI_QUIRK(0x8086, 0x2074, "Intel NUC 8", ALC233_FIXUP_INTEL_NUC8_DMIC), SND_PCI_QUIRK(0x8086, 0x2080, "Intel NUC 8 Rugged", ALC256_FIXUP_INTEL_NUC8_RUGGED), SND_PCI_QUIRK(0x8086, 0x2081, "Intel NUC 10", ALC256_FIXUP_INTEL_NUC10), From af79b081ce332a3cb0e5c11b7666c6af5d5f4cbc Mon Sep 17 00:00:00 2001 From: Edward Blair Date: Sat, 22 Aug 2026 12:41:10 +0100 Subject: [PATCH 289/641] ALSA: usb-audio: Add quirk flags for SMSL USB DAC The SMSL USB DAC with USB ID 152a:85dd produces an audible pop when snd-usb-audio performs its redundant probe-time interface setup. It also pops when returning from runtime suspend. Skip the probe-time interface setup and disable runtime autosuspend for this device. Both flags have been verified through the quirk_flags module parameter. Keep QUIRK_FLAG_DSD_RAW in the device entry because the exact match takes precedence over the generic 0x152a vendor entry from which the device currently inherits that flag. Signed-off-by: Edward Blair Link: https://patch.msgid.link/20260822114110.1069541-1-edward.blair@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index f4e1eac639b9..0dc5695160eb 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2395,6 +2395,9 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_IFB_SILENCE_ON_EMPTY), DEVICE_FLG(0x13e5, 0x0001, /* Serato Phono */ QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x152a, 0x85dd, /* SMSL USB DAC */ + QUIRK_FLAG_DSD_RAW | QUIRK_FLAG_DISABLE_AUTOSUSPEND | + QUIRK_FLAG_SKIP_IFACE_SETUP), DEVICE_FLG(0x152a, 0x880a, /* NeuralDSP Quad Cortex */ 0), /* Doesn't have the vendor quirk which would otherwise apply */ DEVICE_FLG(0x1532, 0x055e, /* Razer Nommo V2 X */ From f3d14f0a8dc30eb9755ecd24c853e6eff882fb80 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Sun, 23 Aug 2026 02:52:28 +0800 Subject: [PATCH 290/641] ALSA: doc: usb-audio: Add doc for QUIRK_FLAG_ALWAYS_SET_RATE QUIRK_FLAG_ALWAYS_SET_RATE was introduced into usb-audio before without appropriate documentation, so add it. There is an odd colon in its comments, so remove it too. As this is often forgotten, also add a reminder to the end of the enumeration. Fixes: 786f91da8535 ("ALSA: usb-audio: add QUIRK_FLAG_ALWAYS_SET_RATE for Mackie DLZ Creator XS") Signed-off-by: Rong Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260823-uac-demote-sticky-check-v1-1-a8cbe572edff@rong.moe --- Documentation/sound/alsa-configuration.rst | 5 +++++ sound/usb/usbaudio.h | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Documentation/sound/alsa-configuration.rst b/Documentation/sound/alsa-configuration.rst index b2171472e6cb..71733e807a7c 100644 --- a/Documentation/sound/alsa-configuration.rst +++ b/Documentation/sound/alsa-configuration.rst @@ -2406,6 +2406,11 @@ quirk_flags works around it by enforcing the fixed max URBs (12) instead of the dynamic calculation from the buffer size, and passing the `URB_ISO_ASAP` URB flag. + * bit 32: ``always_set_rate`` + Issue SET_CUR for the sample rate even when the clock already reports + the requested rate. A device advertising a single rate is otherwise + never sent the request at all, and some require it before streaming + will start. This module supports multiple devices, autoprobe and hotplugging. diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index c49709d7ad25..abc2d7e579de 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -260,7 +260,7 @@ extern bool snd_usb_skip_validation; * to insufficient buffer depth combined with xHCI scheduling variability. * The larger buffer (MAX_URBS = 12, ~64ms) absorbs system scheduling * jitter during boot, while URB_ISO_ASAP ensures consistent xHCI scheduling. - * QUIRK_FLAG_ALWAYS_SET_RATE: + * QUIRK_FLAG_ALWAYS_SET_RATE * Issue SET_CUR for the sample rate even when the clock already reports the * requested rate. A device advertising a single rate is otherwise never sent * the request at all, and some require it before streaming will start. @@ -300,7 +300,7 @@ enum { QUIRK_TYPE_MIXER_GET_CUR_BROKEN = 30, QUIRK_TYPE_PLAYBACK_URB_FIXUP = 31, QUIRK_TYPE_ALWAYS_SET_RATE = 32, -/* Please also edit snd_usb_audio_quirk_flag_names */ +/* Please also edit snd_usb_audio_quirk_flag_names and alsa-configuration.rst */ }; #define QUIRK_FLAG(x) BIT_U64(QUIRK_TYPE_ ## x) From 3c87a903a6820a0789bbab61681dd570d6030260 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Sun, 23 Aug 2026 02:52:29 +0800 Subject: [PATCH 291/641] ALSA: usb-audio: Demote the severity of sticky GET_CUR Currently, a mixer is disabled when its GET_CUR is sticky, causing userspace to fall back to soft mixers, unless QUIRK_FLAG_MIXER_GET_CUR_BROKEN is set. This leads to issues on some wireless headphones with broken GET_CUR but effective SET_CUR, which use poorly-performed lossy codecs and are prone to audible distortion at low volume. They have to set the quirk flag to reeanble the mixer. Considering that users can always opt into soft mixers if they need it, i.e., when SET_CUR is stubbed, demote the severity of sticky GET_CUR by marking GET_CUR as broken and only provide mixer values from the cache. The mixer itself is still registered. Signed-off-by: Rong Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260823-uac-demote-sticky-check-v1-2-a8cbe572edff@rong.moe --- sound/usb/mixer.c | 76 +++++++++++++---------------------------------- 1 file changed, 21 insertions(+), 55 deletions(-) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 5de182181ede..3aa9a9c12557 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1237,21 +1237,27 @@ static void init_cur_mix_raw(struct usb_mixer_elem_info *cval, int ch, int idx) } /* - * Additional checks for sticky mixers + * Additional checks for sticky GET_CUR * - * Some devices' volume control mixers are sticky, which accept SET_CUR but - * do absolutely nothing. + * Some devices' volume control mixers have sticky GET_CUR, which implies either + * stubbed SET_CUR or broken GET_CUR. For the former case, the mixer accepts + * SET_CUR but do absolutely nothing, so falling back to soft mixer is the only + * way to control the volume. For the latter case, the mixer has effective + * SET_CUR despite GET_CUR being constant, and the mixer is usable as long as we + * always provide mixer value from the ceche. * * Check the return values of GET_CUR with different SET_CUR values. Consider - * the mixer as sticky if GET_CUR always returns a constant value. + * GET_CUR as sticky if GET_CUR always returns a constant value. * - * Some devices have effective SET_CUR despite GET_CUR being constant. Do not - * consider the mixer as sticky if a quirk flag indicates that. + * Unfortunately, we can't distinguish between stubbed SET_CUR and broken + * GET_CUR with simple read-back tests. Disabling the mixer regardless and + * forcing userspace to use soft mixer instead can lead to audible distortion at + * low volume on some wireless headphones, probably due to their poorly- + * performed lossy codec. * - * Gate the registration of sticky mixers to prevent confusing userspace, so - * that they won't cause ineffective volume control. However, for mixers with - * effective SET_CUR but broken GET_CUR, the registration can continue normally - * but further GET_CUR requests will be gated. + * Instead, mark GET_CUR as broken regardless and only provide mixer value from + * the cache. Users may opt into soft mixer in userspace audio stack if they + * need it. */ static int check_sticky_volume_control(struct usb_mixer_elem_info *cval, int channel, int saved) @@ -1271,24 +1277,13 @@ static int check_sticky_volume_control(struct usb_mixer_elem_info *cval, return 0; } - if (cval->head.mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_GET_CUR_BROKEN) { - usb_audio_info(cval->head.mixer->chip, - "%d:%d: broken mixer GET_CUR (%d/%d/%d => %d)\n", - cval->head.id, mixer_ctrl_intf(cval->head.mixer), - cval->min, cval->max, cval->res, saved); - - cval->get_cur_broken = 1; - return -ENXIO; - } - - usb_audio_err(cval->head.mixer->chip, - "%d:%d: sticky mixer values (%d/%d/%d => %d), disabling\n", - cval->head.id, mixer_ctrl_intf(cval->head.mixer), - cval->min, cval->max, cval->res, saved); usb_audio_info(cval->head.mixer->chip, - "check MIXER_GET_CUR_BROKEN if you believe the mixer is non-sticky"); + "%d:%d: broken mixer GET_CUR (%d/%d/%d => %d)\n", + cval->head.id, mixer_ctrl_intf(cval->head.mixer), + cval->min, cval->max, cval->res, saved); - return -ENODEV; + cval->get_cur_broken = 1; + return -ENXIO; } /* @@ -1385,8 +1380,6 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, goto no_checks; ret = check_sticky_volume_control(cval, minchn, saved); - if (ret == -ENODEV) - goto sticky; if (ret) goto no_checks; @@ -1455,33 +1448,6 @@ no_checks: } return 0; - -sticky: - /* - * It makes no sense to restore the saved value for a sticky mixer, - * since setting any value is a no-op. - * - * However, in some rare cases, SET_CUR is effective despite GET_CUR - * always returns a constant value. These mixers are not sticky, but - * there's no way to distinguish them. Without any additional - * information, the best thing we can do is to set the mixer value to - * the maximum before bailing out, so that a soft mixer can still reach - * the maximum hardware volume if the mixer turns out to be non-sticky. - * Meanwhile, all channels must be synchronized to prevent imbalance - * volume. - */ - if (!cval->cmask) { - snd_usb_set_cur_mix_value(cval, 0, 0, cval->max); - } else { - idx = 0; - for (i = 0; i < MAX_CHANNELS; i++) { - if (cval->cmask & BIT(i)) { - snd_usb_set_cur_mix_value(cval, i + 1, idx, cval->max); - idx++; - } - } - } - return ret; } #define get_min_max(cval, def) get_min_max_with_quirks(cval, def, NULL) From 9e8a6fc3b1a5ee7e3ccbd11ba7d5bfeb7eaf8ebf Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Sun, 23 Aug 2026 02:52:30 +0800 Subject: [PATCH 292/641] ALSA: usb-audio: Reverse MIXER_GET_CUR_BROKEN as MIXER_GET_CUR_OK The default behavior of sticky check now becomes what QUIRK_FLAG_MIXER_GET_CUR_BROKEN originally does, so the quirk flag is no longer needed. On some devices, whether their GET_CUR being sticky depends on whether hotpluggable components are present. When the hotpluggable components are missing on probe, their GET_CUR behavior is classified as broken. Therefore, reverse QUIRK_FLAG_MIXER_GET_CUR_BROKEN as QUIRK_FLAG_MIXER_GET_CUR_OK, so that it can be set to prevent the heuristics from gating GET_CUR. Note that even if the quirk flag is set, init_cur_mix_raw() should still initialize the mixer value to cval->min, otherwise restoring the bogus saved value on the first channel could lead to unbalanced channels. Signed-off-by: Rong Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260823-uac-demote-sticky-check-v1-3-a8cbe572edff@rong.moe --- Documentation/sound/alsa-configuration.rst | 20 ++++++++------------ sound/usb/mixer.c | 8 ++++++++ sound/usb/quirks.c | 16 +++------------- sound/usb/usbaudio.h | 20 +++++++------------- 4 files changed, 26 insertions(+), 38 deletions(-) diff --git a/Documentation/sound/alsa-configuration.rst b/Documentation/sound/alsa-configuration.rst index 71733e807a7c..e500ec84e3ef 100644 --- a/Documentation/sound/alsa-configuration.rst +++ b/Documentation/sound/alsa-configuration.rst @@ -2316,6 +2316,9 @@ quirk_flags applies the ``mixer_playback_min_mute`` flag and clears the ``ignore_ctl_error`` flag for the device 1234:abcd, and applies the ``skip_sample_rate`` flag for all devices. + New quirk flags may replace old ones by reusing the latter's bits, so the + new usage is preferred. Despite that, depending on the order of probing is + fragile, so it'd better migrate to the new usage anyway. * bit 0: ``get_sample_rate`` Skip reading sample rate for devices @@ -2389,18 +2392,11 @@ quirk_flags from snd_usb_handle_sync_urb. Instead fall through and enqueue a packet_info containing only size-0 packets, so the OUT ring keeps moving (emits silence). Needed by Behringer Flow 8 (1397:050c). - * bit 30: ``mixer_get_cur_broken`` - Some mixers are sticky, which means that setting their current volume - is a no-op, and reading the current volume returns a constant value. - The sticky check disables these mixers to prevent confusing userspace. - However, some devices do have a tunable volume despite the reported - current volume being constant. As the sticky check can't distinguish - between the two categories, setting this flag tells that the device - should fall into the second category when GET_CUR returns a constant - value, resulting in the sticky check being non-fatal and only - disabling GET_CUR instead of the whole mixer. The current volume will - then be provided by the internal cache that stores the last set - volume + * bit 30: ``mixer_get_cur_ok`` + On some devices, whether their GET_CUR being sticky depends on whether + hotpluggable components are present. When the hotpluggable components + are missing on probe, their GET_CUR behavior is classified as broken. + Set the flag to prevent the heuristics from gating GET_CUR. * bit 31: ``playback_urb_fixup`` Some devices show the stuttering at playback, and this quirk works around it by enforcing the fixed max URBs (12) instead of diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 3aa9a9c12557..ecaa8bc08d7c 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1447,6 +1447,14 @@ no_checks: } } + /* + * When GET_CUR is sticky, the saved value is bogus, so mixer values set + * by the sanity checks must be discarded through init_cur_mix_raw(). + * After that, we can clear the flag as per QUIRK_FLAG_MIXER_GET_CUR_OK. + */ + if (cval->head.mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_GET_CUR_OK) + cval->get_cur_broken = 0; + return 0; } diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 0dc5695160eb..2f5276cf7339 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2215,8 +2215,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), DEVICE_FLG(0x03f0, 0x654a, /* HP 320 FHD Webcam */ QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), - DEVICE_FLG(0x040b, 0x0897, /* Weltrend Semiconductor, sold as Redragon H510-PRO Wireless headset */ - QUIRK_FLAG_MIXER_GET_CUR_BROKEN), DEVICE_FLG(0x041e, 0x3000, /* Creative SB Extigy */ QUIRK_FLAG_IGNORE_CTL_ERROR), DEVICE_FLG(0x041e, 0x324d, /* Creative Sound Blaster Play! 3 */ @@ -2258,8 +2256,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { DEVICE_FLG(0x046d, 0x0a8f, /* Logitech H390 headset */ QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), - DEVICE_FLG(0x046d, 0x0af7, /* Logitech PRO X 2 LIGHTSPEED */ - QUIRK_FLAG_MIXER_GET_CUR_BROKEN), DEVICE_FLG(0x0499, 0x1506, /* Yamaha THR5 */ QUIRK_FLAG_GENERIC_IMPLICIT_FB), DEVICE_FLG(0x0499, 0x1509, /* Steinberg UR22 */ @@ -2331,9 +2327,9 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { DEVICE_FLG(0x0763, 0x2031, /* M-Audio Fast Track C600 */ QUIRK_FLAG_GENERIC_IMPLICIT_FB), DEVICE_FLG(0x0763, 0x2080, /* M-Audio Fast Track Ultra */ - QUIRK_FLAG_MIXER_GET_CUR_BROKEN | QUIRK_FLAG_GENERIC_IMPLICIT_FB), + QUIRK_FLAG_GENERIC_IMPLICIT_FB), DEVICE_FLG(0x0763, 0x2081, /* M-Audio Fast Track Ultra */ - QUIRK_FLAG_MIXER_GET_CUR_BROKEN | QUIRK_FLAG_GENERIC_IMPLICIT_FB), + QUIRK_FLAG_GENERIC_IMPLICIT_FB), DEVICE_FLG(0x07fd, 0x000b, /* MOTU M Series 2nd hardware revision */ QUIRK_FLAG_CTL_MSG_DELAY_1M), DEVICE_FLG(0x08bb, 0x2702, /* LineX FM Transmitter */ @@ -2372,8 +2368,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_SHARE_MEDIA_DEVICE | QUIRK_FLAG_ALIGN_TRANSFER), DEVICE_FLG(0x1038, 0x1294, /* SteelSeries Arctis Pro Wireless */ QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), - DEVICE_FLG(0x1038, 0x2232, /* SteelSeries Arctis Nova 5 */ - QUIRK_FLAG_MIXER_GET_CUR_BROKEN), DEVICE_FLG(0x1101, 0x0003, /* Audioengine D1 */ QUIRK_FLAG_GET_SAMPLE_RATE), DEVICE_FLG(0x12d1, 0x3a07, /* HUAWEI USB-C HEADSET */ @@ -2381,8 +2375,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), DEVICE_FLG(0x1224, 0x2a25, /* Jieli Technology USB PHY 2.0 */ QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), - DEVICE_FLG(0x1377, 0x6004, /* Sennheiser MOMENTUM 3 */ - QUIRK_FLAG_MIXER_GET_CUR_BROKEN), DEVICE_FLG(0x1395, 0x740a, /* Sennheiser DECT */ QUIRK_FLAG_GET_SAMPLE_RATE), DEVICE_FLG(0x1397, 0x0507, /* Behringer UMC202HD */ @@ -2510,8 +2502,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_CTL_MSG_DELAY_1M), DEVICE_FLG(0x2d99, 0x0026, /* HECATE G2 GAMING HEADSET */ QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), - DEVICE_FLG(0x2d99, 0xa024, /* Edifier MF200 */ - QUIRK_FLAG_MIXER_GET_CUR_BROKEN), DEVICE_FLG(0x2fc6, 0xf06b, /* MOONDROP Moonriver2 Ti */ QUIRK_FLAG_CTL_MSG_DELAY), DEVICE_FLG(0x2fc6, 0xf0b5, /* iBasso DC-Elite */ @@ -2652,7 +2642,7 @@ static const char *const snd_usb_audio_quirk_flag_names[] = { QUIRK_STRING_ENTRY(MIXER_PLAYBACK_LINEAR_VOL), QUIRK_STRING_ENTRY(MIXER_CAPTURE_LINEAR_VOL), QUIRK_STRING_ENTRY(IFB_SILENCE_ON_EMPTY), - QUIRK_STRING_ENTRY(MIXER_GET_CUR_BROKEN), + QUIRK_STRING_ENTRY(MIXER_GET_CUR_OK), QUIRK_STRING_ENTRY(PLAYBACK_URB_FIXUP), QUIRK_STRING_ENTRY(ALWAYS_SET_RATE), NULL diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index abc2d7e579de..24436473931d 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -243,17 +243,11 @@ extern bool snd_usb_skip_validation; * from snd_usb_handle_sync_urb. Instead fall through and enqueue a * packet_info containing only size-0 packets, so the OUT ring keeps * moving (emits silence). Needed by Behringer Flow 8 (1397:050c). - * QUIRK_FLAG_MIXER_GET_CUR_BROKEN - * Some mixers are sticky, which means that setting their current volume is a - * no-op, and reading the current volume returns a constant value. The sticky - * check disables these mixers to prevent confusing userspace. However, some - * devices do have a tunable volume despite the reported current volume being - * constant. As the sticky check can't distinguish between the two categories, - * setting this flag tells that the device should fall into the second - * category when GET_CUR returns a constant value, resulting in the sticky - * check being non-fatal and only disabling GET_CUR instead of the whole mixer. - * The current volume will then be provided by the internal cache that stores - * the last set volume + * QUIRK_FLAG_MIXER_GET_CUR_OK + * On some devices, whether their GET_CUR being sticky depends on whether + * hotpluggable components are present. When the hotpluggable components are + * missing on probe, their GET_CUR behavior is classified as broken. Set the + * flag to prevent the heuristics from gating GET_CUR. * QUIRK_FLAG_PLAYBACK_URB_FIXUP * Set URB_ISO_ASAP flag for isochronous URBs and force nurbs to MAX_URBS. * This is needed for devices that exhibit boot-time audio stuttering due @@ -297,7 +291,7 @@ enum { QUIRK_TYPE_MIXER_PLAYBACK_LINEAR_VOL = 27, QUIRK_TYPE_MIXER_CAPTURE_LINEAR_VOL = 28, QUIRK_TYPE_IFB_SILENCE_ON_EMPTY = 29, - QUIRK_TYPE_MIXER_GET_CUR_BROKEN = 30, + QUIRK_TYPE_MIXER_GET_CUR_OK = 30, QUIRK_TYPE_PLAYBACK_URB_FIXUP = 31, QUIRK_TYPE_ALWAYS_SET_RATE = 32, /* Please also edit snd_usb_audio_quirk_flag_names and alsa-configuration.rst */ @@ -335,7 +329,7 @@ enum { #define QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL QUIRK_FLAG(MIXER_PLAYBACK_LINEAR_VOL) #define QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL QUIRK_FLAG(MIXER_CAPTURE_LINEAR_VOL) #define QUIRK_FLAG_IFB_SILENCE_ON_EMPTY QUIRK_FLAG(IFB_SILENCE_ON_EMPTY) -#define QUIRK_FLAG_MIXER_GET_CUR_BROKEN QUIRK_FLAG(MIXER_GET_CUR_BROKEN) +#define QUIRK_FLAG_MIXER_GET_CUR_OK QUIRK_FLAG(MIXER_GET_CUR_OK) #define QUIRK_FLAG_PLAYBACK_URB_FIXUP QUIRK_FLAG(PLAYBACK_URB_FIXUP) #define QUIRK_FLAG_ALWAYS_SET_RATE QUIRK_FLAG(ALWAYS_SET_RATE) From 3ac761b613c744aa22e269d31801053e38bb4968 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Sun, 23 Aug 2026 02:52:31 +0800 Subject: [PATCH 293/641] ALSA: usb-audio: Add quirk flags for Logitech PRO X Wireless The Logitech PRO X Wireless is a wireless headset with a hotpluggable microphone. Its Playback mixer's GET_CUR somehow becomes broken when the microphone is detached, so set QUIRK_FLAG_MIXER_GET_CUR_OK to prevent the mixer behavior from depending on whether the microphone is attached. Meanwhile, the Playback mixer's minimum value doesn't work properly, thus set QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE to prevent userspace audio stack from using the minimum mixer value to tune volume (setting it when muted is OK). Reported-by: Alexander Niemeyer Closes: https://msgid.link/6262cbbd-d1f2-4c9d-a1c7-9c5d12636f4b@gmx.de Closes: https://msgid.link/7984832b-86f6-4934-bfc0-1ed70218973a@gmx.de Signed-off-by: Rong Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260823-uac-demote-sticky-check-v1-4-a8cbe572edff@rong.moe --- sound/usb/quirks.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 2f5276cf7339..5c544bd88728 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2256,6 +2256,9 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { DEVICE_FLG(0x046d, 0x0a8f, /* Logitech H390 headset */ QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), + DEVICE_FLG(0x046d, 0x0aba, /* Logitech PRO X Wireless */ + QUIRK_FLAG_MIXER_GET_CUR_OK | + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), DEVICE_FLG(0x0499, 0x1506, /* Yamaha THR5 */ QUIRK_FLAG_GENERIC_IMPLICIT_FB), DEVICE_FLG(0x0499, 0x1509, /* Steinberg UR22 */ From 17056a36d276a740f09dca723de1bc6aa3eb4ef9 Mon Sep 17 00:00:00 2001 From: Will Porter Date: Sat, 22 Aug 2026 20:53:53 -0500 Subject: [PATCH 294/641] ALSA: usb-audio: Add iD14 MkI to monitor-mix volume quirk Add the iD14 MkI USB ID to the existing audient_id14_map. This names feature unit 12 Monitor Mix Playback Volume. Live meter A/B testing on MkI USB ID 2708:0002 reproduced the same asymmetric attenuation seen on MkII. At ALSA value 104, the right channel was 23.07 dB below the left. At 127, both channels were equal. The misleading Speaker Playback Volume name causes PipeWire to adopt FU12 as the stream master. Reuse the existing map to keep the control available while preventing that selection. Hardware-tested on Bazzite 7.2.0-ogc4.1.fc44.x86_64 with this change backported. ALSA exposed Monitor Mix Playback Volume after module load and USB reconnect. PipeWire volume changes from 100% to 35% and back left all four FU12 values at 127. Channel-isolated left/right playback remained balanced after reconnect. Build-tested with CONFIG_SND_USB_AUDIO=m using the focused sound/usb target. Assisted-by: Codex:gpt-5 Signed-off-by: Will Porter Link: https://patch.msgid.link/20260823015353.686080-1-mrwillporter@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/mixer_maps.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c index 8046d5987d5b..ace4ccad8f51 100644 --- a/sound/usb/mixer_maps.c +++ b/sound/usb/mixer_maps.c @@ -505,13 +505,13 @@ static const struct usbmix_connector_map gigabyte_b450_connector_map[] = { {} }; -/* Audient iD14: FU 12 advertises Volume on only 4 of its 6 logical channels - * and sits on the monitor mixer branch, but it is traced through to the - * Speaker output terminal and gets named "Speaker Playback Volume". Userspace - * then adopts it as the stream's hardware volume, and any setting below 0 dB - * attenuates some channels but not others (20 dB imbalance at 80%). Give it a - * non-standard name so that it is no longer taken for the stream's master - * volume, while remaining reachable for anyone who wants the monitor gain. +/* Audient iD14 MkI and MkII: FU 12 sits on the monitor mixer branch but is + * traced through to the Speaker output terminal, so it is named "Speaker + * Playback Volume". On MkII it controls only 4 of 6 playback channels. MkI + * testing found asymmetric attenuation within the main stereo pair. Userspace + * adopts this control as the stream's hardware volume, causing imbalance below + * 0 dB. Give it a non-standard name so that userspace no longer treats it as + * the stream master, while keeping the monitor gain reachable. */ static const struct usbmix_name_map audient_id14_map[] = { { 12, "Monitor Mix Playback" }, /* FU, partial coverage */ @@ -602,7 +602,12 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { .map = maya44_map, }, { - /* Audient iD14 */ + /* Audient iD14 MkI */ + .id = USB_ID(0x2708, 0x0002), + .map = audient_id14_map, + }, + { + /* Audient iD14 MkII */ .id = USB_ID(0x2708, 0x0008), .map = audient_id14_map, }, From 1ffb5be3e4be37a8f8d88861d3e1a1a1539b4c97 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 21 Aug 2026 22:10:16 +0100 Subject: [PATCH 295/641] dt-bindings: rtc: renesas,rzn1-rtc: Add RZ/T2H and RZ/N2H support Add compatible strings for the RTC block found on the Renesas RZ/T2H (R9A09G077) and RZ/N2H (R9A09G087) SoCs. These SoCs integrate a closely related variant of the RZ/N1 RTC IP. Unlike RZ/N1, they do not implement the RTCA0SUBU register. This is not a limitation for Linux support, as these registers are not used when the RTC operates in "scmp" clock mode, which is required on RZ/T2H and RZ/N2H due to their 195.3 kHz input clock. The RZ/T2H RTC variant also supports a 1Hz output signal on the RTCAT1HZ pin, controlled by the RTCA0CTL1[RTCA01HZE] bit. This bit is marked as reserved in the RZ/N1 hardware manual. Update the binding schema to require the additional clock inputs used by these SoCs. Signed-off-by: Lad Prabhakar Acked-by: Conor Dooley Reviewed-by: Wolfram Sang Link: https://patch.msgid.link/20260821211032.13554-2-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Alexandre Belloni --- .../bindings/rtc/renesas,rzn1-rtc.yaml | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/Documentation/devicetree/bindings/rtc/renesas,rzn1-rtc.yaml b/Documentation/devicetree/bindings/rtc/renesas,rzn1-rtc.yaml index 1860f0e4c31a..ea7b039a91e7 100644 --- a/Documentation/devicetree/bindings/rtc/renesas,rzn1-rtc.yaml +++ b/Documentation/devicetree/bindings/rtc/renesas,rzn1-rtc.yaml @@ -9,15 +9,19 @@ title: Renesas RZ/N1 SoCs Real-Time Clock maintainers: - Miquel Raynal -allOf: - - $ref: rtc.yaml# - properties: compatible: - items: - - enum: - - renesas,r9a06g032-rtc - - const: renesas,rzn1-rtc + oneOf: + - items: + - enum: + - renesas,r9a06g032-rtc + - const: renesas,rzn1-rtc + + - const: renesas,r9a09g077-rtc + + - items: + - const: renesas,r9a09g087-rtc + - const: renesas,r9a09g077-rtc reg: maxItems: 1 @@ -54,6 +58,23 @@ required: - clock-names - power-domains +allOf: + - $ref: rtc.yaml# + + - if: + properties: + compatible: + contains: + enum: + - renesas,r9a09g077-rtc + - renesas,r9a09g087-rtc + then: + properties: + clocks: + minItems: 2 + clock-names: + minItems: 2 + unevaluatedProperties: false examples: From 708546aa39560a11cf44c7ba99492c8395a6c2fb Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 21 Aug 2026 22:10:17 +0100 Subject: [PATCH 296/641] rtc: rzn1: Handle EPROBE_DEFER for optional pps interrupt Check for -EPROBE_DEFER from platform_get_irq_byname_optional() and handle the deferred probe request properly. Although the "pps" interrupt is optional, an error code of -EPROBE_DEFER indicates that the interrupt subsystem is not yet ready. Intercept this specific error condition, assign it to the return value, and jump to the dis_runtime_pm label to avoid ignoring a valid probe deferral. Fixes: eea7791e00f33 ("rtc: rzn1: implement one-second accuracy for alarms") Cc: stable@vger.kernel.org Signed-off-by: Lad Prabhakar Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Link: https://patch.msgid.link/20260821211032.13554-3-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rzn1.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c index c4ed43735457..f81d691c8b9a 100644 --- a/drivers/rtc/rtc-rzn1.c +++ b/drivers/rtc/rtc-rzn1.c @@ -465,6 +465,10 @@ static int rzn1_rtc_probe(struct platform_device *pdev) } irq = platform_get_irq_byname_optional(pdev, "pps"); + if (irq == -EPROBE_DEFER) { + ret = irq; + goto dis_runtime_pm; + } if (irq >= 0) ret = devm_request_irq(&pdev->dev, irq, rzn1_rtc_1s_irq, 0, "RZN1 RTC 1s", rtc); From 022a2839a52006531804a8db55d3228084400b48 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 21 Aug 2026 22:10:18 +0100 Subject: [PATCH 297/641] rtc: rzn1: Fix weekday underflow when alarm crosses month boundary rzn1_rtc_set_alarm() calculates the alarm weekday from the difference between the alarm day and the current day of the month. When the alarm crosses a month boundary, this difference can become negative. Since days_ahead is unsigned, it underflows and results in an incorrect weekday being programmed into RZN1_RTC_ALW. The RTC core already provides a fully populated struct rtc_time for the alarm, including the correct tm_wday. Use tm->tm_wday directly instead of recalculating the weekday from the day-of-month. This avoids the underflow and ensures alarms scheduled across a month boundary use the correct weekday. Fixes: b5ad1bf00d2c4 ("rtc: rzn1: Add alarm support") Cc: stable@vger.kernel.org Signed-off-by: Lad Prabhakar Suggested-by: Wolfram Sang Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Link: https://patch.msgid.link/20260821211032.13554-4-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rzn1.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c index f81d691c8b9a..56284a4320ae 100644 --- a/drivers/rtc/rtc-rzn1.c +++ b/drivers/rtc/rtc-rzn1.c @@ -261,7 +261,6 @@ static int rzn1_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) struct rzn1_rtc *rtc = dev_get_drvdata(dev); struct rtc_time *tm = &alrm->time, tm_now; unsigned long alarm, farest; - unsigned int days_ahead, wday; int ret; ret = rzn1_rtc_read_time(dev, &tm_now); @@ -274,13 +273,9 @@ static int rzn1_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) if (time_after(alarm, farest)) return -ERANGE; - /* Convert alarm day into week day */ - days_ahead = tm->tm_mday - tm_now.tm_mday; - wday = (tm_now.tm_wday + days_ahead) % 7; - writel(bin2bcd(tm->tm_min), rtc->base + RZN1_RTC_ALM); writel(bin2bcd(tm->tm_hour), rtc->base + RZN1_RTC_ALH); - writel(BIT(wday), rtc->base + RZN1_RTC_ALW); + writel(BIT(tm->tm_wday), rtc->base + RZN1_RTC_ALW); rtc->tm_alarm = alrm->time; From 457b5dbce31209e65e1184716ed3af59cb1c0372 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 21 Aug 2026 22:10:19 +0100 Subject: [PATCH 298/641] rtc: rzn1: Handle unset alarm weekday in rzn1_rtc_read_alarm RZN1_RTC_ALW is a weekday bitmask where bit N represents weekday N. When no alarm has been configured, the register has its power-on-reset value of zero. rzn1_rtc_read_alarm() uses fls() to convert the weekday bitmask into a weekday number. When RZN1_RTC_ALW is zero, fls(0) returns zero and fls(wday) - 1 evaluates to -1. This invalid weekday is then used to calculate the alarm date and can either leave tm_wday set to -1 or produce a fabricated alarm date. Treat a zero RZN1_RTC_ALW value as an unset alarm weekday and return without calculating the alarm date. Move reading RZN1_RTC_CTL1 before this check so that alrm->enabled is updated for both configured and unconfigured alarms. Fixes: b5ad1bf00d2c4 ("rtc: rzn1: Add alarm support") Cc: stable@vger.kernel.org Signed-off-by: Lad Prabhakar Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Link: https://patch.msgid.link/20260821211032.13554-5-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rzn1.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c index 56284a4320ae..d4cba0d415b6 100644 --- a/drivers/rtc/rtc-rzn1.c +++ b/drivers/rtc/rtc-rzn1.c @@ -235,13 +235,24 @@ static int rzn1_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) if (ret) return ret; + ctl1 = readl(rtc->base + RZN1_RTC_CTL1); + alrm->enabled = !!(ctl1 & (RZN1_RTC_CTL1_ALME | RZN1_RTC_CTL1_1SE)); + min = readl(rtc->base + RZN1_RTC_ALM); hour = readl(rtc->base + RZN1_RTC_ALH); - wday = readl(rtc->base + RZN1_RTC_ALW); tm->tm_sec = 0; tm->tm_min = bcd2bin(min); tm->tm_hour = bcd2bin(hour); + + /* + * If wday is zero, no bit is set in RZN1_RTC_ALW. This is the + * register's power-on reset value. + */ + wday = readl(rtc->base + RZN1_RTC_ALW); + if (!wday) + return 0; + delta_days = ((fls(wday) - 1) - tm->tm_wday + 7) % 7; tm->tm_wday = fls(wday) - 1; @@ -250,9 +261,6 @@ static int rzn1_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) rtc_time64_to_tm(alarm, tm); } - ctl1 = readl(rtc->base + RZN1_RTC_CTL1); - alrm->enabled = !!(ctl1 & (RZN1_RTC_CTL1_ALME | RZN1_RTC_CTL1_1SE)); - return 0; } From c3e735e9f62022354fc63dbf193cb5614188a33b Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 21 Aug 2026 22:10:20 +0100 Subject: [PATCH 299/641] rtc: rzn1: Fix alarm range check truncation on 32-bit systems alarm and farest were declared as unsigned long, but rtc_tm_to_time64() returns time64_t (s64). On 32-bit systems where unsigned long is 32 bits, the assignment silently truncates the upper 32 bits of the timestamp. Fix by declaring alarm and farest as time64_t and replacing time_after() with a direct signed comparison, which is correct for time64_t values that will never realistically overflow. Signed-off-by: Lad Prabhakar Reviewed-by: Geert Uytterhoeven Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Link: https://patch.msgid.link/20260821211032.13554-6-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rzn1.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c index d4cba0d415b6..f3268655fd37 100644 --- a/drivers/rtc/rtc-rzn1.c +++ b/drivers/rtc/rtc-rzn1.c @@ -268,7 +268,7 @@ static int rzn1_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) { struct rzn1_rtc *rtc = dev_get_drvdata(dev); struct rtc_time *tm = &alrm->time, tm_now; - unsigned long alarm, farest; + time64_t alarm, farest; int ret; ret = rzn1_rtc_read_time(dev, &tm_now); @@ -278,7 +278,7 @@ static int rzn1_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) /* We cannot set alarms more than one week ahead */ farest = rtc_tm_to_time64(&tm_now) + rtc->rtcdev->alarm_offset_max; alarm = rtc_tm_to_time64(tm); - if (time_after(alarm, farest)) + if (alarm > farest) return -ERANGE; writel(bin2bcd(tm->tm_min), rtc->base + RZN1_RTC_ALM); From 51458d5b0a1cfb1b6013400abc95aadf16ed2a57 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 21 Aug 2026 22:10:21 +0100 Subject: [PATCH 300/641] rtc: rzn1: Disable alarm interrupt before reprogramming alarm registers rzn1_rtc_set_alarm() updates RZN1_RTC_ALM, RZN1_RTC_ALH and RZN1_RTC_ALW using separate MMIO writes without first disabling the alarm interrupt. If a previous alarm is still enabled, the interrupt can fire while the alarm registers contain a mixture of old and newly written values. Fix this by disabling the alarm interrupt before reprogramming ALM, ALH and ALW with a call to rzn1_rtc_alarm_irq_enable(). Fixes: b5ad1bf00d2c4 ("rtc: rzn1: Add alarm support") Cc: stable@vger.kernel.org Signed-off-by: Lad Prabhakar Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Link: https://patch.msgid.link/20260821211032.13554-7-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rzn1.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c index f3268655fd37..42e57bf0b4aa 100644 --- a/drivers/rtc/rtc-rzn1.c +++ b/drivers/rtc/rtc-rzn1.c @@ -281,6 +281,11 @@ static int rzn1_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) if (alarm > farest) return -ERANGE; + /* Disable alarm interrupts before reprogramming the alarm. */ + ret = rzn1_rtc_alarm_irq_enable(dev, 0); + if (ret) + return ret; + writel(bin2bcd(tm->tm_min), rtc->base + RZN1_RTC_ALM); writel(bin2bcd(tm->tm_hour), rtc->base + RZN1_RTC_ALH); writel(BIT(tm->tm_wday), rtc->base + RZN1_RTC_ALW); From cd54d8f09d9b61c67e7fc662f4e4e2ffd65a54f2 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 21 Aug 2026 22:10:22 +0100 Subject: [PATCH 301/641] rtc: rzn1: Fix malformed MODULE_AUTHOR string Fix a malformed MODULE_AUTHOR macro in the rtc-rzn1 driver where a missing closing angle bracket on the second author entry creates an invalid format. Correct it to the standard "Name " format. Signed-off-by: Lad Prabhakar Reviewed-by: Geert Uytterhoeven Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Link: https://patch.msgid.link/20260821211032.13554-8-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rzn1.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c index 42e57bf0b4aa..12c52003a7dc 100644 --- a/drivers/rtc/rtc-rzn1.c +++ b/drivers/rtc/rtc-rzn1.c @@ -525,6 +525,6 @@ static struct platform_driver rzn1_rtc_driver = { module_platform_driver(rzn1_rtc_driver); MODULE_AUTHOR("Michel Pollet "); -MODULE_AUTHOR("Miquel Raynal "); MODULE_DESCRIPTION("RZ/N1 RTC driver"); MODULE_LICENSE("GPL"); From 960987abe58d175b94869fb7aca72d7b931b584f Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 21 Aug 2026 22:10:23 +0100 Subject: [PATCH 302/641] rtc: Kconfig: Broaden RTC_DRV_RZN1 dependency to ARCH_RENESAS Replace the ARCH_RZN1 dependency with ARCH_RENESAS for the RTC_DRV_RZN1 config option to make the driver available across both ARM32 and ARM64 Renesas architectures. The newer RZ/T2H and RZ/N2H ARM64 SoCs integrate a closely related variant of the RTC IP block found on the RZ/N1 SoCs. Update the build dependency and expand the Kconfig help text to allow this driver to be selected for these additional platforms. Signed-off-by: Lad Prabhakar Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Link: https://patch.msgid.link/20260821211032.13554-9-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/rtc/Kconfig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 01def8231873..d23a0fbe8d89 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -1635,10 +1635,11 @@ config RTC_DRV_RS5C313 config RTC_DRV_RZN1 tristate "Renesas RZ/N1 RTC" - depends on ARCH_RZN1 || COMPILE_TEST + depends on ARCH_RENESAS || COMPILE_TEST depends on OF && HAS_IOMEM help - If you say yes here you get support for the Renesas RZ/N1 RTC. + If you say yes here you get support for the RTC initially found on + Renesas RZ/N1 SoCs. config RTC_DRV_GENERIC tristate "Generic RTC support" From ea0ef2e7f83be647be114d8cfaf701a5146eaa90 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 21 Aug 2026 22:10:24 +0100 Subject: [PATCH 303/641] rtc: rzn1: Use pm_runtime_put_sync() 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. Signed-off-by: Lad Prabhakar Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Link: https://patch.msgid.link/20260821211032.13554-10-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rzn1.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c index 12c52003a7dc..84ec8dc397e5 100644 --- a/drivers/rtc/rtc-rzn1.c +++ b/drivers/rtc/rtc-rzn1.c @@ -493,7 +493,7 @@ static int rzn1_rtc_probe(struct platform_device *pdev) return 0; dis_runtime_pm: - pm_runtime_put(&pdev->dev); + pm_runtime_put_sync(&pdev->dev); return ret; } @@ -505,7 +505,7 @@ static void rzn1_rtc_remove(struct platform_device *pdev) /* Disable all interrupts */ writel(0, rtc->base + RZN1_RTC_CTL1); - pm_runtime_put(&pdev->dev); + pm_runtime_put_sync(&pdev->dev); } static const struct of_device_id rzn1_rtc_of_match[] = { From 6e0d7d480f2df83dba82e2fa8f6cd453e9a8326f Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 21 Aug 2026 22:10:25 +0100 Subject: [PATCH 304/641] rtc: rzn1: Replace remove callback with devm_add_action_or_reset() Simplify the driver teardown by registering a managed cleanup action with devm_add_action_or_reset(). This eliminates the explicit probe error path and allows the .remove() callback to be dropped. Signed-off-by: Lad Prabhakar Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Link: https://patch.msgid.link/20260821211032.13554-11-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rzn1.c | 56 +++++++++++++++++------------------------- 1 file changed, 23 insertions(+), 33 deletions(-) diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c index 84ec8dc397e5..f90785827945 100644 --- a/drivers/rtc/rtc-rzn1.c +++ b/drivers/rtc/rtc-rzn1.c @@ -385,6 +385,17 @@ static const struct rtc_class_ops rzn1_rtc_ops_scmp = { .alarm_irq_enable = rzn1_rtc_alarm_irq_enable, }; +static void rzn1_rtc_disable_hardware(void *data) +{ + struct device *dev = data; + struct rzn1_rtc *rtc = dev_get_drvdata(dev); + + /* Disable all interrupts */ + writel(0, rtc->base + RZN1_RTC_CTL1); + + pm_runtime_put_sync(dev); +} + static int rzn1_rtc_probe(struct platform_device *pdev) { struct rzn1_rtc *rtc; @@ -422,18 +433,19 @@ static int rzn1_rtc_probe(struct platform_device *pdev) if (ret < 0) return ret; + ret = devm_add_action_or_reset(&pdev->dev, rzn1_rtc_disable_hardware, &pdev->dev); + if (ret) + return ret; + /* Only switch to scmp if we have an xtal clock with a valid rate and != 32768 */ xtal = devm_clk_get_optional(&pdev->dev, "xtal"); if (IS_ERR(xtal)) { - ret = PTR_ERR(xtal); - goto dis_runtime_pm; + return PTR_ERR(xtal); } else if (xtal) { rate = clk_get_rate(xtal); - if (rate < 32000 || rate > BIT(22)) { - ret = -EOPNOTSUPP; - goto dis_runtime_pm; - } + if (rate < 32000 || rate > BIT(22)) + return -EOPNOTSUPP; if (rate != 32768) scmp_val = RZN1_RTC_CTL0_SLSB_SCMP; @@ -446,7 +458,7 @@ static int rzn1_rtc_probe(struct platform_device *pdev) ret = readl_poll_timeout(rtc->base + RZN1_RTC_CTL0, val, !(val & RZN1_RTC_CTL0_CEST), 62, 123); if (ret) - goto dis_runtime_pm; + return ret; /* Set desired modes leaving the controller disabled */ writel(RZN1_RTC_CTL0_AMPM | scmp_val, rtc->base + RZN1_RTC_CTL0); @@ -469,14 +481,12 @@ static int rzn1_rtc_probe(struct platform_device *pdev) ret = devm_request_irq(&pdev->dev, irq, rzn1_rtc_alarm_irq, 0, "RZN1 RTC Alarm", rtc); if (ret) { dev_err(&pdev->dev, "RTC alarm interrupt not available\n"); - goto dis_runtime_pm; + return ret; } irq = platform_get_irq_byname_optional(pdev, "pps"); - if (irq == -EPROBE_DEFER) { - ret = irq; - goto dis_runtime_pm; - } + if (irq == -EPROBE_DEFER) + return irq; if (irq >= 0) ret = devm_request_irq(&pdev->dev, irq, rzn1_rtc_1s_irq, 0, "RZN1 RTC 1s", rtc); @@ -486,26 +496,7 @@ static int rzn1_rtc_probe(struct platform_device *pdev) dev_warn(&pdev->dev, "RTC pps interrupt not available. Alarm has only minute accuracy\n"); } - ret = devm_rtc_register_device(rtc->rtcdev); - if (ret) - goto dis_runtime_pm; - - return 0; - -dis_runtime_pm: - pm_runtime_put_sync(&pdev->dev); - - return ret; -} - -static void rzn1_rtc_remove(struct platform_device *pdev) -{ - struct rzn1_rtc *rtc = platform_get_drvdata(pdev); - - /* Disable all interrupts */ - writel(0, rtc->base + RZN1_RTC_CTL1); - - pm_runtime_put_sync(&pdev->dev); + return devm_rtc_register_device(rtc->rtcdev); } static const struct of_device_id rzn1_rtc_of_match[] = { @@ -516,7 +507,6 @@ MODULE_DEVICE_TABLE(of, rzn1_rtc_of_match); static struct platform_driver rzn1_rtc_driver = { .probe = rzn1_rtc_probe, - .remove = rzn1_rtc_remove, .driver = { .name = "rzn1-rtc", .of_match_table = rzn1_rtc_of_match, From afb8972b9e36cd519bc30fb63081fe9ae96adec2 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 21 Aug 2026 22:10:26 +0100 Subject: [PATCH 305/641] rtc: rzn1: Dynamically calculate synchronization delay based on clock rate Replace the hardcoded hardware synchronization delays with a calculated time window derived from the operating sub-clock frequency. The driver currently hardcodes microsecond ranges assuming a fixed sub-clock frequency of 32.768 kHz. Newer SoC variants, such as the RZ/T2H, drive this hardware block using a much faster clock rate (~195.3 kHz). Hardcoding these wait windows forces faster blocks to over-sleep, introducing unnecessary delays during clock initialization and register configuration. Calculate the duration of the required clock cycles in microseconds based on the runtime clock rate, and store this value in the driver private structure to adjust the usleep_range() and readl_poll_timeout() boundaries dynamically. Signed-off-by: Lad Prabhakar Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Link: https://patch.msgid.link/20260821211032.13554-12-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rzn1.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c index f90785827945..4e62f7bc2e34 100644 --- a/drivers/rtc/rtc-rzn1.c +++ b/drivers/rtc/rtc-rzn1.c @@ -70,6 +70,7 @@ struct rzn1_rtc { */ spinlock_t ctl1_access_lock; struct rtc_time tm_alarm; + unsigned long sync_time; }; static void rzn1_rtc_get_time_snapshot(struct rzn1_rtc *rtc, struct rtc_time *tm) @@ -120,8 +121,8 @@ static int rzn1_rtc_set_time(struct device *dev, struct rtc_time *tm) /* Hold the counter if it was counting up */ writel(RZN1_RTC_CTL2_WAIT, rtc->base + RZN1_RTC_CTL2); - /* Wait for the counter to stop: two 32k clock cycles */ - usleep_range(61, 100); + /* Wait 2-4 RTC_PCLK clock cycles for the counter to stop */ + usleep_range(rtc->sync_time, rtc->sync_time * 2); ret = readl_poll_timeout(rtc->base + RZN1_RTC_CTL2, val, val & RZN1_RTC_CTL2_WST, 0, 100); if (ret) @@ -398,10 +399,10 @@ static void rzn1_rtc_disable_hardware(void *data) static int rzn1_rtc_probe(struct platform_device *pdev) { + unsigned long rate = 32768; struct rzn1_rtc *rtc; u32 val, scmp_val = 0; struct clk *xtal; - unsigned long rate; int irq, ret; rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL); @@ -451,12 +452,16 @@ static int rzn1_rtc_probe(struct platform_device *pdev) scmp_val = RZN1_RTC_CTL0_SLSB_SCMP; } + /* Calculate the duration of two RTC_PCLK clock cycles */ + rtc->sync_time = DIV_ROUND_UP(2 * USEC_PER_SEC, rate); + /* Disable controller during SUBU/SCMP setup */ val = readl(rtc->base + RZN1_RTC_CTL0) & ~RZN1_RTC_CTL0_CE; writel(val, rtc->base + RZN1_RTC_CTL0); - /* Wait 2-4 32k clock cycles for the disabled controller */ + /* Wait 2-4 RTC_PCLK clock cycles for the disabled controller to stop */ ret = readl_poll_timeout(rtc->base + RZN1_RTC_CTL0, val, - !(val & RZN1_RTC_CTL0_CEST), 62, 123); + !(val & RZN1_RTC_CTL0_CEST), rtc->sync_time, + rtc->sync_time * 2); if (ret) return ret; From b587001387c16b3a75b8ed9399feee157ad3d3b4 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 21 Aug 2026 22:10:27 +0100 Subject: [PATCH 306/641] rtc: rzn1: Use temporary variable for struct device Use a temporary variable for the struct device pointers to avoid dereferencing. Signed-off-by: Lad Prabhakar Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Link: https://patch.msgid.link/20260821211032.13554-13-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rzn1.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c index 4e62f7bc2e34..7598efc31e09 100644 --- a/drivers/rtc/rtc-rzn1.c +++ b/drivers/rtc/rtc-rzn1.c @@ -399,13 +399,14 @@ static void rzn1_rtc_disable_hardware(void *data) static int rzn1_rtc_probe(struct platform_device *pdev) { + struct device *dev = &pdev->dev; unsigned long rate = 32768; struct rzn1_rtc *rtc; u32 val, scmp_val = 0; struct clk *xtal; int irq, ret; - rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL); + rtc = devm_kzalloc(dev, sizeof(*rtc), GFP_KERNEL); if (!rtc) return -ENOMEM; @@ -413,13 +414,13 @@ static int rzn1_rtc_probe(struct platform_device *pdev) rtc->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(rtc->base)) - return dev_err_probe(&pdev->dev, PTR_ERR(rtc->base), "Missing reg\n"); + return dev_err_probe(dev, PTR_ERR(rtc->base), "Missing reg\n"); irq = platform_get_irq_byname(pdev, "alarm"); if (irq < 0) return irq; - rtc->rtcdev = devm_rtc_allocate_device(&pdev->dev); + rtc->rtcdev = devm_rtc_allocate_device(dev); if (IS_ERR(rtc->rtcdev)) return PTR_ERR(rtc->rtcdev); @@ -427,19 +428,19 @@ static int rzn1_rtc_probe(struct platform_device *pdev) rtc->rtcdev->range_max = RTC_TIMESTAMP_END_2099; rtc->rtcdev->alarm_offset_max = 7 * 86400; - ret = devm_pm_runtime_enable(&pdev->dev); + ret = devm_pm_runtime_enable(dev); if (ret < 0) return ret; - ret = pm_runtime_resume_and_get(&pdev->dev); + ret = pm_runtime_resume_and_get(dev); if (ret < 0) return ret; - ret = devm_add_action_or_reset(&pdev->dev, rzn1_rtc_disable_hardware, &pdev->dev); + ret = devm_add_action_or_reset(dev, rzn1_rtc_disable_hardware, dev); if (ret) return ret; /* Only switch to scmp if we have an xtal clock with a valid rate and != 32768 */ - xtal = devm_clk_get_optional(&pdev->dev, "xtal"); + xtal = devm_clk_get_optional(dev, "xtal"); if (IS_ERR(xtal)) { return PTR_ERR(xtal); } else if (xtal) { @@ -483,9 +484,9 @@ static int rzn1_rtc_probe(struct platform_device *pdev) spin_lock_init(&rtc->ctl1_access_lock); - ret = devm_request_irq(&pdev->dev, irq, rzn1_rtc_alarm_irq, 0, "RZN1 RTC Alarm", rtc); + ret = devm_request_irq(dev, irq, rzn1_rtc_alarm_irq, 0, "RZN1 RTC Alarm", rtc); if (ret) { - dev_err(&pdev->dev, "RTC alarm interrupt not available\n"); + dev_err(dev, "RTC alarm interrupt not available\n"); return ret; } @@ -493,12 +494,12 @@ static int rzn1_rtc_probe(struct platform_device *pdev) if (irq == -EPROBE_DEFER) return irq; if (irq >= 0) - ret = devm_request_irq(&pdev->dev, irq, rzn1_rtc_1s_irq, 0, "RZN1 RTC 1s", rtc); + ret = devm_request_irq(dev, irq, rzn1_rtc_1s_irq, 0, "RZN1 RTC 1s", rtc); if (irq < 0 || ret) { set_bit(RTC_FEATURE_ALARM_RES_MINUTE, rtc->rtcdev->features); clear_bit(RTC_FEATURE_UPDATE_INTERRUPT, rtc->rtcdev->features); - dev_warn(&pdev->dev, "RTC pps interrupt not available. Alarm has only minute accuracy\n"); + dev_warn(dev, "RTC pps interrupt not available. Alarm has only minute accuracy\n"); } return devm_rtc_register_device(rtc->rtcdev); From 0e1ae8c654f05d813457bee308f864acea7b5cc1 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 21 Aug 2026 22:10:28 +0100 Subject: [PATCH 307/641] rtc: rzn1: Consistently use dev_err_probe() Use dev_err_probe() in the IRQ request error path to make error handling consistent with the rest of rzn1_rtc_probe(). Signed-off-by: Lad Prabhakar Reviewed-by: Geert Uytterhoeven Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Link: https://patch.msgid.link/20260821211032.13554-14-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rzn1.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c index 7598efc31e09..9fc93afee1e5 100644 --- a/drivers/rtc/rtc-rzn1.c +++ b/drivers/rtc/rtc-rzn1.c @@ -485,10 +485,8 @@ static int rzn1_rtc_probe(struct platform_device *pdev) spin_lock_init(&rtc->ctl1_access_lock); ret = devm_request_irq(dev, irq, rzn1_rtc_alarm_irq, 0, "RZN1 RTC Alarm", rtc); - if (ret) { - dev_err(dev, "RTC alarm interrupt not available\n"); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "RTC alarm interrupt not available\n"); irq = platform_get_irq_byname_optional(pdev, "pps"); if (irq == -EPROBE_DEFER) From 5ae93269d3e51db730ca3ee96f8833e4d3befd95 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 21 Aug 2026 22:10:29 +0100 Subject: [PATCH 308/641] rtc: rzn1: use FIELD_PREP/FIELD_GET and GENMASK for register access Replace open-coded shift and mask operations with the bitfield API. Note that the weekday field is changed from an explicit 0x0f mask to an 8-bit field definition, matching the hardware manual. This does not change behaviour, as valid weekday values cannot exceed 7. Signed-off-by: Lad Prabhakar Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Link: https://patch.msgid.link/20260821211032.13554-15-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rzn1.c | 50 +++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c index 9fc93afee1e5..33c7ef5c1eba 100644 --- a/drivers/rtc/rtc-rzn1.c +++ b/drivers/rtc/rtc-rzn1.c @@ -12,6 +12,8 @@ */ #include +#include +#include #include #include #include @@ -39,14 +41,18 @@ #define RZN1_RTC_CTL2_STOPPED (RZN1_RTC_CTL2_WAIT | RZN1_RTC_CTL2_WST) #define RZN1_RTC_TIME 0x30 -#define RZN1_RTC_TIME_MIN_SHIFT 8 -#define RZN1_RTC_TIME_HOUR_SHIFT 16 +#define RZN1_RTC_TIME_SEC GENMASK(7, 0) +#define RZN1_RTC_TIME_MIN GENMASK(15, 8) +#define RZN1_RTC_TIME_HOUR GENMASK(23, 16) + #define RZN1_RTC_CAL 0x34 -#define RZN1_RTC_CAL_DAY_SHIFT 8 -#define RZN1_RTC_CAL_MON_SHIFT 16 -#define RZN1_RTC_CAL_YEAR_SHIFT 24 +#define RZN1_RTC_CAL_WDAY GENMASK(7, 0) +#define RZN1_RTC_CAL_DAY GENMASK(15, 8) +#define RZN1_RTC_CAL_MON GENMASK(23, 16) +#define RZN1_RTC_CAL_YEAR GENMASK(31, 24) #define RZN1_RTC_SUBU 0x38 +#define RZN1_RTC_SUBU_RTCA0FX GENMASK(5, 0) #define RZN1_RTC_SUBU_DEV BIT(7) #define RZN1_RTC_SUBU_DECR BIT(6) @@ -78,15 +84,15 @@ static void rzn1_rtc_get_time_snapshot(struct rzn1_rtc *rtc, struct rtc_time *tm u32 val; val = readl(rtc->base + RZN1_RTC_TIMEC); - tm->tm_sec = bcd2bin(val); - tm->tm_min = bcd2bin(val >> RZN1_RTC_TIME_MIN_SHIFT); - tm->tm_hour = bcd2bin(val >> RZN1_RTC_TIME_HOUR_SHIFT); + tm->tm_sec = bcd2bin(FIELD_GET(RZN1_RTC_TIME_SEC, val)); + tm->tm_min = bcd2bin(FIELD_GET(RZN1_RTC_TIME_MIN, val)); + tm->tm_hour = bcd2bin(FIELD_GET(RZN1_RTC_TIME_HOUR, val)); val = readl(rtc->base + RZN1_RTC_CALC); - tm->tm_wday = val & 0x0f; - tm->tm_mday = bcd2bin(val >> RZN1_RTC_CAL_DAY_SHIFT); - tm->tm_mon = bcd2bin(val >> RZN1_RTC_CAL_MON_SHIFT) - 1; - tm->tm_year = bcd2bin(val >> RZN1_RTC_CAL_YEAR_SHIFT) + 100; + tm->tm_wday = FIELD_GET(RZN1_RTC_CAL_WDAY, val); + tm->tm_mday = bcd2bin(FIELD_GET(RZN1_RTC_CAL_DAY, val)); + tm->tm_mon = bcd2bin(FIELD_GET(RZN1_RTC_CAL_MON, val)) - 1; + tm->tm_year = bcd2bin(FIELD_GET(RZN1_RTC_CAL_YEAR, val)) + 100; } static int rzn1_rtc_read_time(struct device *dev, struct rtc_time *tm) @@ -129,15 +135,15 @@ static int rzn1_rtc_set_time(struct device *dev, struct rtc_time *tm) return ret; } - val = bin2bcd(tm->tm_sec); - val |= bin2bcd(tm->tm_min) << RZN1_RTC_TIME_MIN_SHIFT; - val |= bin2bcd(tm->tm_hour) << RZN1_RTC_TIME_HOUR_SHIFT; + val = FIELD_PREP(RZN1_RTC_TIME_SEC, bin2bcd(tm->tm_sec)) | + FIELD_PREP(RZN1_RTC_TIME_MIN, bin2bcd(tm->tm_min)) | + FIELD_PREP(RZN1_RTC_TIME_HOUR, bin2bcd(tm->tm_hour)); writel(val, rtc->base + RZN1_RTC_TIME); - val = tm->tm_wday; - val |= bin2bcd(tm->tm_mday) << RZN1_RTC_CAL_DAY_SHIFT; - val |= bin2bcd(tm->tm_mon + 1) << RZN1_RTC_CAL_MON_SHIFT; - val |= bin2bcd(tm->tm_year - 100) << RZN1_RTC_CAL_YEAR_SHIFT; + val = FIELD_PREP(RZN1_RTC_CAL_WDAY, tm->tm_wday) | + FIELD_PREP(RZN1_RTC_CAL_DAY, bin2bcd(tm->tm_mday)) | + FIELD_PREP(RZN1_RTC_CAL_MON, bin2bcd(tm->tm_mon + 1)) | + FIELD_PREP(RZN1_RTC_CAL_YEAR, bin2bcd(tm->tm_year - 100)); writel(val, rtc->base + RZN1_RTC_CAL); writel(0, rtc->base + RZN1_RTC_CTL2); @@ -308,12 +314,12 @@ static int rzn1_rtc_read_offset(struct device *dev, long *offset) val = readl(rtc->base + RZN1_RTC_SUBU); ppb_per_step = val & RZN1_RTC_SUBU_DEV ? 1017 : 3051; subtract = val & RZN1_RTC_SUBU_DECR; - val &= 0x3F; + val = FIELD_GET(RZN1_RTC_SUBU_RTCA0FX, val); if (!val) *offset = 0; else if (subtract) - *offset = -(((~val) & 0x3F) + 1) * ppb_per_step; + *offset = -(((~val) & RZN1_RTC_SUBU_RTCA0FX) + 1) * ppb_per_step; else *offset = (val - 1) * ppb_per_step; @@ -355,7 +361,7 @@ static int rzn1_rtc_set_offset(struct device *dev, long offset) subu |= steps + 1; } else { subu |= RZN1_RTC_SUBU_DECR; - subu |= (~(-steps - 1)) & 0x3F; + subu |= (~(-steps - 1)) & RZN1_RTC_SUBU_RTCA0FX; } ret = readl_poll_timeout(rtc->base + RZN1_RTC_CTL2, ctl2, From 38a3a3b09958d9f2d2a2c72b90a0437cbe8618bf Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 21 Aug 2026 22:10:30 +0100 Subject: [PATCH 309/641] rtc: rzn1: Add OF match data to gate SUBU register access The RZ/N1 RTC driver selects SCMP mode only when an optional xtal clock is provided at a valid rate other than 32768 Hz. Without an xtal clock, or when it runs at 32768 Hz, the driver uses SUBU mode. However, the RTCA0SUBU register used by SUBU mode is not present on all SoCs that integrate a similar variant of the RTC block. Allowing SUBU mode on those variants would expose RTC offset operations that access a non-existent register. Add OF match data to describe whether the RTC supports the SUBU register. Reject probe with -EOPNOTSUPP when SUBU mode would be selected on a variant without SUBU support. Signed-off-by: Lad Prabhakar Suggested-by: Wolfram Sang Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Link: https://patch.msgid.link/20260821211032.13554-16-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rzn1.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c index 33c7ef5c1eba..8c3dbbc34717 100644 --- a/drivers/rtc/rtc-rzn1.c +++ b/drivers/rtc/rtc-rzn1.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -66,6 +67,10 @@ #define RZN1_RTC_TIMEC 0x68 #define RZN1_RTC_CALC 0x6c +struct rzn1_rtc_data { + bool has_subu; +}; + struct rzn1_rtc { struct rtc_device *rtcdev; void __iomem *base; @@ -405,6 +410,7 @@ static void rzn1_rtc_disable_hardware(void *data) static int rzn1_rtc_probe(struct platform_device *pdev) { + const struct rzn1_rtc_data *data; struct device *dev = &pdev->dev; unsigned long rate = 32768; struct rzn1_rtc *rtc; @@ -412,6 +418,10 @@ static int rzn1_rtc_probe(struct platform_device *pdev) struct clk *xtal; int irq, ret; + data = of_device_get_match_data(dev); + if (!data) + return -ENODEV; + rtc = devm_kzalloc(dev, sizeof(*rtc), GFP_KERNEL); if (!rtc) return -ENOMEM; @@ -455,8 +465,12 @@ static int rzn1_rtc_probe(struct platform_device *pdev) if (rate < 32000 || rate > BIT(22)) return -EOPNOTSUPP; - if (rate != 32768) + if (rate != 32768 || !data->has_subu) scmp_val = RZN1_RTC_CTL0_SLSB_SCMP; + } else if (!data->has_subu) { + /* xtal is NULL here */ + return dev_err_probe(dev, -EOPNOTSUPP, + "No valid XTAL provided and SUBU mode not supported\n"); } /* Calculate the duration of two RTC_PCLK clock cycles */ @@ -509,8 +523,12 @@ static int rzn1_rtc_probe(struct platform_device *pdev) return devm_rtc_register_device(rtc->rtcdev); } +static const struct rzn1_rtc_data rzn1_rtc_rzn1_data = { + .has_subu = true, +}; + static const struct of_device_id rzn1_rtc_of_match[] = { - { .compatible = "renesas,rzn1-rtc" }, + { .compatible = "renesas,rzn1-rtc", .data = &rzn1_rtc_rzn1_data }, {}, }; MODULE_DEVICE_TABLE(of, rzn1_rtc_of_match); From 8af6331c6e7aaec3453b8a158bedb44b271314d7 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 21 Aug 2026 22:10:31 +0100 Subject: [PATCH 310/641] rtc: rzn1: Drop trailing comma from OF match table sentinel Drop the trailing comma from the final empty entry in the RZN1 RTC OF match table and mark it explicitly as the sentinel. Signed-off-by: Lad Prabhakar Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Link: https://patch.msgid.link/20260821211032.13554-17-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rzn1.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c index 8c3dbbc34717..906d03c65df3 100644 --- a/drivers/rtc/rtc-rzn1.c +++ b/drivers/rtc/rtc-rzn1.c @@ -529,7 +529,7 @@ static const struct rzn1_rtc_data rzn1_rtc_rzn1_data = { static const struct of_device_id rzn1_rtc_of_match[] = { { .compatible = "renesas,rzn1-rtc", .data = &rzn1_rtc_rzn1_data }, - {}, + { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, rzn1_rtc_of_match); From b418fa370ea69fc5824e70b63d9f4c93249762e7 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 21 Aug 2026 22:10:32 +0100 Subject: [PATCH 311/641] rtc: rzn1: Add support for Renesas RZ/T2H and RZ/N2H SoCs Add support for the RTC block found on the Renesas RZ/T2H (R9A09G077) and RZ/N2H (R9A09G087) SoCs. These SoCs integrate a closely related variant of the RZ/N1 RTC IP, but do not implement the RTCA0SUBU register. Use variant-specific match data to disable SUBU support for these SoCs. Signed-off-by: Lad Prabhakar Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Link: https://patch.msgid.link/20260821211032.13554-18-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rzn1.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c index 906d03c65df3..1e1676a7f3cc 100644 --- a/drivers/rtc/rtc-rzn1.c +++ b/drivers/rtc/rtc-rzn1.c @@ -523,11 +523,16 @@ static int rzn1_rtc_probe(struct platform_device *pdev) return devm_rtc_register_device(rtc->rtcdev); } +static const struct rzn1_rtc_data rzn1_rtc_rzt2h_data = { + .has_subu = false, +}; + static const struct rzn1_rtc_data rzn1_rtc_rzn1_data = { .has_subu = true, }; static const struct of_device_id rzn1_rtc_of_match[] = { + { .compatible = "renesas,r9a09g077-rtc", .data = &rzn1_rtc_rzt2h_data }, { .compatible = "renesas,rzn1-rtc", .data = &rzn1_rtc_rzn1_data }, { /* sentinel */ } }; From a5c7d35e2fd3e24c411816c91f8f6cc78e652e0c Mon Sep 17 00:00:00 2001 From: Nikolai Grlica Date: Mon, 10 Aug 2026 15:03:35 +0000 Subject: [PATCH 312/641] rust: kernel: list: fix incorrect pop_back example comment The example uses pop_back(), but the accompanying comment says pop_front(). Update the comment to match the example. Signed-off-by: Nikolai Grlica Cc: stable@vger.kernel.org Fixes: bf87a41b85d6 ("rust: list: Add an example for `ListLinksSelfPtr` usage") Link: https://patch.msgid.link/20260810150322.61809-1-nikolai@nikolaigrlica.dev Signed-off-by: Miguel Ojeda --- rust/kernel/list.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/kernel/list.rs b/rust/kernel/list.rs index 406e3a028c55..0f367264ee2e 100644 --- a/rust/kernel/list.rs +++ b/rust/kernel/list.rs @@ -249,7 +249,7 @@ pub use self::arc_field::{ /// assert_eq!(list.iter().count(), 3); /// } /// -/// // Pop the items from the list using `pop_front()` and verify the content. +/// // Pop the items from the list using `pop_back()` and verify the content. /// { /// assert_eq!(list.pop_back().ok_or(EINVAL)?.value.foo(), ("a", 15)); /// assert_eq!(list.pop_back().ok_or(EINVAL)?.value.foo(), ("a", 32)); From 993f235c4af49d0e912cc28abe6d733531600237 Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Wed, 5 Aug 2026 16:45:24 +0200 Subject: [PATCH 313/641] objtool/rust: add one more `noreturn` Rust function When the pointer formatting series [1] is applied and KUnit tests are enabled, `objtool` would report an error with any of our supported Rust versions. For instance, with Rust 1.97.1: rust/kernel.o: error: objtool: _R..._4core3fmt7Pointer3fmtB7_() falls through to next function _R..._4core7convert5AsRefNtB5_4BStrE6as_ref() Or, with Rust 1.85.0: rust/kernel.o: error: objtool: _R..._4core3fmt7Pointer3fmtB7_() falls through to next function _R..._4core3ffi5c_str4CStrENtNtBS_3fmt7Display3fmtB7_() This happens due to calls to the `noreturn` symbol: core::str::slice_error_fail Thus add the mangled one to the list so that `objtool` knows it is actually `noreturn`. See commit 56d680dd23c3 ("objtool/rust: list `noreturn` Rust functions") for more details. [ While the series [1] mentioned above was not applied in a way that triggered this, Gary found another case [2]: I am seeing this function causing issue on my 1.85.1 -Os build, although the error message is different this time: rust/kernel.o: warning: objtool: _R..._5range9RangeFromjEE5indexCsldbe0TGTmNx_6kernel+0x1d: stack state mismatch: cfa1=4+8 cfa2=4+16 Disassembly shows that the function call before the warning location is a call to _4core3str16slice_error_fail, and objtool thought it can return (which will end up with a unbalanced stack if it does return). Thus apply it. - Miguel ] Cc: stable@vger.kernel.org # Needed in 6.12.y and later (Rust is pinned in older LTSs). Cc: Josh Poimboeuf Cc: Peter Zijlstra Reported-by: Alice Ryhl Link: https://lore.kernel.org/rust-for-linux/anLxXvCKBcAVf91_@google.com/ Link: https://lore.kernel.org/rust-for-linux/20260706-hashedptr-v13-0-377a07f2f78d@kylinos.cn/ [1] Link: https://lore.kernel.org/rust-for-linux/DKS7U9UHY6XR.2JC0EBIPCI06Z@garyguo.net/ [2] Link: https://patch.msgid.link/20260805144524.233362-1-ojeda@kernel.org Signed-off-by: Miguel Ojeda --- tools/objtool/check.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 87db9f4ed9e2..3ab5b9f1c6a4 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -195,6 +195,7 @@ static bool is_rust_noreturn(const struct symbol *func) return str_ends_with(func->name, "_4core3num20from_str_radix_panic") || str_ends_with(func->name, "_4core3num22from_ascii_radix_panic") || str_ends_with(func->name, "_4core3num28from_ascii_bytes_radix_panic") || + str_ends_with(func->name, "_4core3str16slice_error_fail") || str_ends_with(func->name, "_4core5sliceSp15copy_from_slice17len_mismatch_fail") || str_ends_with(func->name, "_4core6option13expect_failed") || str_ends_with(func->name, "_4core6option13unwrap_failed") || From dea1bf38143f8505747c19b953ef12fb809d5d77 Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori Date: Sun, 9 Aug 2026 22:48:58 +0900 Subject: [PATCH 314/641] rust: jump_label: skip arch-specific asm in `testlib` builds Running `make rusttest` with `ARCH=` set to an architecture other than the host's may fail in the future, e.g. `ARCH=arm64` on an x86_64 host: error: alignment must be a power of 2 --> rust/kernel/jump_label.rs:51:13 | 51 | include!(concat!(env!("OBJTREE"), "/rust/kernel/generated_arch_static_branch_asm.rs")); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: instantiated into assembly here --> :3:10 | 3 | .align 3 | ^ The reason is that `rusttest` builds the kernel crate as a host library: it passes the `CONFIG_*` cfgs of the configured architecture, but not `--target`, so code generation happens for the host. `arch_static_branch!` then selects the arch-specific inline asm arm based on CONFIG_*, and the host assembler rejects it. This does not happen with the current master because `arch_static_branch!` has no user inside the kernel crate itself yet, but fix it now to avoid surprises later. Signed-off-by: FUJITA Tomonori Link: https://patch.msgid.link/20260809134858.1219036-1-tomo@flapping.org [ Reworded slightly to clarify it "may fail in the future". - Miguel ] Signed-off-by: Miguel Ojeda --- rust/kernel/jump_label.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/rust/kernel/jump_label.rs b/rust/kernel/jump_label.rs index 4e974c768dbd..f54cedcb6fd5 100644 --- a/rust/kernel/jump_label.rs +++ b/rust/kernel/jump_label.rs @@ -44,6 +44,7 @@ const _: &str = include!(concat!( #[macro_export] #[doc(hidden)] +#[cfg(not(testlib))] #[cfg(CONFIG_JUMP_LABEL)] macro_rules! arch_static_branch { ($key:path, $keytyp:ty, $field:ident, $branch:expr) => {'my_label: { @@ -61,6 +62,17 @@ macro_rules! arch_static_branch { }}; } +#[macro_export] +#[doc(hidden)] +#[cfg(testlib)] +#[cfg(CONFIG_JUMP_LABEL)] +macro_rules! arch_static_branch { + ($key:path, $keytyp:ty, $field:ident, $branch:expr) => { + // The asm falls through until patched, which never happens on the host. + false + }; +} + #[cfg(CONFIG_JUMP_LABEL)] pub use arch_static_branch; From 29b03d1de967a177176d12811d970cac7882afcf Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Thu, 20 Aug 2026 14:57:32 +0100 Subject: [PATCH 315/641] rust: cfi: disable function merging if CFI is enabled In Rust doc tests, there is a dummy `__module_firmware_test_init` function generated by the example in `module_firmware!`'s documentation, which just returns zero. Many other documentation examples generate functions that produce zero. LKP test robot reports [1] a `Flags::zeroed` instance; my local reproduction has a `Bounded::new::<0>`. LLVM's MergeFunctionsPass incorrectly merges functions with different KCFI types, causing `__module_firmware_test_init` to be merged into one of the zero-returning functions. As module init is invoked via an indirect function call, KCFI is checked and this produces a KCFI failure. I've reported this bug to upstream LLVM [2]; in the meantime, disable function merging if CFI is enabled. No separate treatment is needed for CONFIG_RUST_INLINE_HELPERS, as Clang does not enable function merging by default. [ LLVM already has a pending PR: https://github.com/llvm/llvm-project/pull/217665 which solves the issue. In addition, I asked upstream Rust if the unstable `-Zmerge-functions=disabled` flag will remain around: https://rust-lang.zulipchat.com/#narrow/channel/425075-rust-for-linux/topic/.60-Zmerge-functions.3Ddisabled.60/ and it does indeed look like that will be the case. - Miguel ] Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-lkp/202608201017.100a4511-lkp@intel.com [1] Link: https://github.com/llvm/llvm-project/issues/217629 [2] Signed-off-by: Gary Guo Cc: stable@vger.kernel.org Fixes: ca627e636551 ("rust: cfi: add support for CFI_CLANG with Rust") Reviewed-by: Sami Tolvanen Link: https://patch.msgid.link/20260820135733.37121-1-gary@kernel.org [ Fixed typos as discussed. Reworded slightly for other typos. - Miguel ] Signed-off-by: Miguel Ojeda --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 130926fa592e..c2344755a1bb 100644 --- a/Makefile +++ b/Makefile @@ -1118,7 +1118,8 @@ endif ifdef CONFIG_RUST # Always pass -Zsanitizer-cfi-normalize-integers as CONFIG_RUST selects # CONFIG_CFI_ICALL_NORMALIZE_INTEGERS. - RUSTC_FLAGS_CFI := -Zsanitizer=kcfi -Zsanitizer-cfi-normalize-integers + # Disable function merging as LLVM incorrectly merges functions with different KCFI types. + RUSTC_FLAGS_CFI := -Zsanitizer=kcfi -Zsanitizer-cfi-normalize-integers -Zmerge-functions=disabled KBUILD_RUSTFLAGS += $(RUSTC_FLAGS_CFI) export RUSTC_FLAGS_CFI endif From 0bff7711c19ba05ff3b686554c5a2d503c3e9797 Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Sun, 16 Aug 2026 15:32:32 +0200 Subject: [PATCH 316/641] kbuild: rust: preserve unreachable traps with inline helpers When `CONFIG_RUST_INLINE_HELPERS` is enabled, it is possible to hit `objtool` warnings like: vmlinux.o: warning: objtool: _R..._4cmdq12CommandToGsp4init() falls through to next function _R..._4core5array4iter8IntoIterRShKj3_EEEBa_() `rustc` normally emits traps for unreachable paths. However, under `CONFIG_RUST_INLINE_HELPERS=y`, `rustc` emits LLVM bitcode and Clang performs final code generation after the helper bitcode is linked, but Clang does not trap unreachable IR by default. In turn, this means `objtool` follows compiler-generated impossible Rust `enum` paths through alignment padding into the next function, resulting in fallthrough warnings. Thus pass the LLVM `trap-unreachable` option to the final Clang invocation and suppress traps immediately after `noreturn` calls, which `objtool` already recognizes as dead ends. The combination of both flags makes it match `rustc`'s behavior. Rust 1.85.0 (the minimum supported one) supports LLVM >= 18, and both flags are available in LLVM 18. Assisted-by: LLM Cc: Gary Guo Cc: Boqun Feng Cc: Alice Ryhl Cc: Matthew Maurer Cc: Josh Poimboeuf Cc: Peter Zijlstra Cc: stable@vger.kernel.org Fixes: 3a2486cc1da5 ("kbuild: rust: provide an option to inline C helpers into Rust") Acked-by: Gary Guo Link: https://patch.msgid.link/20260816133233.197500-1-ojeda@kernel.org Signed-off-by: Miguel Ojeda --- Makefile | 10 ++++++++++ rust/Makefile | 3 ++- scripts/Makefile.build | 3 ++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index c2344755a1bb..960f296e4a4d 100644 --- a/Makefile +++ b/Makefile @@ -1083,6 +1083,16 @@ endif export CC_FLAGS_SCS endif +ifdef CONFIG_RUST_INLINE_HELPERS +# `rustc` normally emits traps for unreachable paths during code generation. +# With inline helpers, Clang performs code generation from the linked bitcode +# instead, so request the same behavior explicitly. Otherwise `objtool` may +# follow an impossible Rust path into the next function. +CC_FLAGS_RUST_INLINE_HELPERS := -mllvm -trap-unreachable \ + -mllvm -no-trap-after-noreturn +export CC_FLAGS_RUST_INLINE_HELPERS +endif + ifdef CONFIG_LTO_CLANG ifdef CONFIG_LTO_CLANG_FULL CC_FLAGS_LTO := -flto diff --git a/rust/Makefile b/rust/Makefile index fbe0accc51a3..f871d94f6af2 100644 --- a/rust/Makefile +++ b/rust/Makefile @@ -644,7 +644,8 @@ quiet_cmd_rustc_library = $(if $(skip_clippy),RUSTC,$(RUSTC_OR_CLIPPY_QUIET)) L -Zunstable-options \ $(if $(link_helper),;$(LLVM_LINK) --internalize --suppress-warnings $(patsubst %.o,%.bc,$@) \ $(obj)/helpers/helpers$(if $(part-of-module),_module).bc -o $(patsubst %.o,%.m.bc,$@); \ - $(CC) $(CLANG_FLAGS) $(KBUILD_CFLAGS) -Wno-override-module -c $(patsubst %.o,%.m.bc,$@) -o $@ \ + $(CC) $(CLANG_FLAGS) $(KBUILD_CFLAGS) \ + $(CC_FLAGS_RUST_INLINE_HELPERS) -Wno-override-module -c $(patsubst %.o,%.m.bc,$@) -o $@ \ $(cmd_ld_single)) \ $(if $(rustc_objcopy),;$(OBJCOPY) $(rustc_objcopy) $@) \ $(cmd_objtool) diff --git a/scripts/Makefile.build b/scripts/Makefile.build index a48209591dee..0ac326e43fea 100644 --- a/scripts/Makefile.build +++ b/scripts/Makefile.build @@ -347,7 +347,8 @@ quiet_cmd_rustc_o_rs = $(RUSTC_OR_CLIPPY_QUIET) $(quiet_modtag) $@ cmd_rustc_o_rs = $(rust_common_cmd) --emit=$(if $(CONFIG_RUST_INLINE_HELPERS),llvm-bc=$(patsubst %.o,%.bc,$@),obj=$@) $< \ $(if $(CONFIG_RUST_INLINE_HELPERS),;$(LLVM_LINK) --internalize --suppress-warnings $(patsubst %.o,%.bc,$@) \ $(objtree)/rust/helpers/helpers$(if $(part-of-module),_module).bc -o $(patsubst %.o,%.m.bc,$@); \ - $(CC) $(CLANG_FLAGS) $(KBUILD_CFLAGS) -Wno-override-module -c $(patsubst %.o,%.m.bc,$@) -o $@ \ + $(CC) $(CLANG_FLAGS) $(KBUILD_CFLAGS) \ + $(CC_FLAGS_RUST_INLINE_HELPERS) -Wno-override-module -c $(patsubst %.o,%.m.bc,$@) -o $@ \ $(cmd_ld_single)) \ $(cmd_objtool) From 5febf432df1cfa5b25d99b54c32103fafbdd0eb9 Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Sun, 16 Aug 2026 15:32:33 +0200 Subject: [PATCH 317/641] kbuild: rust: keep Rust objects out of Clang LTO with inline helpers Under `CONFIG_LTO_CLANG` + `CONFIG_RUST_INLINE_HELPERS`, one may hit `objtool` errors such as: vmlinux.o: error: objtool: _R..._3Gsp4boot+0xd6a: can't find jump dest instruction at .text._R..._3Gsp4boot+0x1dfd The reason is that in such builds, the Clang invocation that compiles the combined Rust plus helpers bitcode emits LLVM bitcode (again) -- the final code generation happens in the linker's LTO step, which the `-mllvm` trap options passed to Clang do not reach. This, in turn, means that unreachable traps are missing, and the impossible paths do not merely fallthrough to the next symbol, but past the end of their own section, since LTO builds place each function in its own section. Thus filter `CC_FLAGS_LTO` out of the Clang invocation, so that it always emits machine code directly, with the traps in place. Assisted-by: LLM Cc: Gary Guo Cc: Boqun Feng Cc: Alice Ryhl Cc: Matthew Maurer Cc: Josh Poimboeuf Cc: Peter Zijlstra Cc: stable@vger.kernel.org Fixes: 3a2486cc1da5 ("kbuild: rust: provide an option to inline C helpers into Rust") Acked-by: Gary Guo Link: https://patch.msgid.link/20260816133233.197500-2-ojeda@kernel.org Signed-off-by: Miguel Ojeda --- rust/Makefile | 2 +- scripts/Makefile.build | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/Makefile b/rust/Makefile index f871d94f6af2..3afaad4a4a3a 100644 --- a/rust/Makefile +++ b/rust/Makefile @@ -644,7 +644,7 @@ quiet_cmd_rustc_library = $(if $(skip_clippy),RUSTC,$(RUSTC_OR_CLIPPY_QUIET)) L -Zunstable-options \ $(if $(link_helper),;$(LLVM_LINK) --internalize --suppress-warnings $(patsubst %.o,%.bc,$@) \ $(obj)/helpers/helpers$(if $(part-of-module),_module).bc -o $(patsubst %.o,%.m.bc,$@); \ - $(CC) $(CLANG_FLAGS) $(KBUILD_CFLAGS) \ + $(CC) $(CLANG_FLAGS) $(filter-out $(CC_FLAGS_LTO),$(KBUILD_CFLAGS)) \ $(CC_FLAGS_RUST_INLINE_HELPERS) -Wno-override-module -c $(patsubst %.o,%.m.bc,$@) -o $@ \ $(cmd_ld_single)) \ $(if $(rustc_objcopy),;$(OBJCOPY) $(rustc_objcopy) $@) \ diff --git a/scripts/Makefile.build b/scripts/Makefile.build index 0ac326e43fea..4349108e75e1 100644 --- a/scripts/Makefile.build +++ b/scripts/Makefile.build @@ -347,7 +347,7 @@ quiet_cmd_rustc_o_rs = $(RUSTC_OR_CLIPPY_QUIET) $(quiet_modtag) $@ cmd_rustc_o_rs = $(rust_common_cmd) --emit=$(if $(CONFIG_RUST_INLINE_HELPERS),llvm-bc=$(patsubst %.o,%.bc,$@),obj=$@) $< \ $(if $(CONFIG_RUST_INLINE_HELPERS),;$(LLVM_LINK) --internalize --suppress-warnings $(patsubst %.o,%.bc,$@) \ $(objtree)/rust/helpers/helpers$(if $(part-of-module),_module).bc -o $(patsubst %.o,%.m.bc,$@); \ - $(CC) $(CLANG_FLAGS) $(KBUILD_CFLAGS) \ + $(CC) $(CLANG_FLAGS) $(filter-out $(CC_FLAGS_LTO),$(KBUILD_CFLAGS)) \ $(CC_FLAGS_RUST_INLINE_HELPERS) -Wno-override-module -c $(patsubst %.o,%.m.bc,$@) -o $@ \ $(cmd_ld_single)) \ $(cmd_objtool) From db005bdaa5ff4ea52095aa213507134dcaff42cc Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 21 Aug 2026 12:09:02 +0200 Subject: [PATCH 318/641] rtc: hym8563: Make sure clk_init_data is fully initialized The clk_init_data structure contains several mutually-exclusive members for different methods to specify the possible parents of a clock, prompting drivers to initialize only the members they need. However, not initializing all members may cause subtle issues, which are only exposed when CONFIG_INIT_STACK_ALL_PATTERN or CONFIG_INIT_STACK_NONE is enabled. Make sure all members are fully initialized, to avoid such bugs, and to prevent future breakage when converting drivers to a different method for specifying the parents. Signed-off-by: Geert Uytterhoeven Link: https://patch.msgid.link/657f2b3a871074087aee0b7a70bf527ab0f48da8.1787241693.git.geert+renesas@glider.be Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-hym8563.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/rtc/rtc-hym8563.c b/drivers/rtc/rtc-hym8563.c index 3156aa5f2d9f..1d52cc3def02 100644 --- a/drivers/rtc/rtc-hym8563.c +++ b/drivers/rtc/rtc-hym8563.c @@ -376,8 +376,8 @@ static struct clk *hym8563_clkout_register_clk(struct hym8563 *hym8563) { struct i2c_client *client = hym8563->client; struct device_node *node = client->dev.of_node; + struct clk_init_data init = {}; struct clk *clk; - struct clk_init_data init; int ret; ret = i2c_smbus_write_byte_data(client, HYM8563_CLKOUT, From bdcedfee8b87067dbc907e9e94310c5be4311133 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 21 Aug 2026 12:09:03 +0200 Subject: [PATCH 319/641] rtc: m41t80: Make sure clk_init_data is fully initialized The clk_init_data structure contains several mutually-exclusive members for different methods to specify the possible parents of a clock, prompting drivers to initialize only the members they need. However, not initializing all members may cause subtle issues, which are only exposed when CONFIG_INIT_STACK_ALL_PATTERN or CONFIG_INIT_STACK_NONE is enabled. Make sure all members are fully initialized, to avoid such bugs, and to prevent future breakage when converting drivers to a different method for specifying the parents. Signed-off-by: Geert Uytterhoeven Link: https://patch.msgid.link/c5290fb9a28b45d6bcec724d5897f8bcbf96ea50.1787241693.git.geert+renesas@glider.be Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-m41t80.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/rtc/rtc-m41t80.c b/drivers/rtc/rtc-m41t80.c index 3c8c379392c1..ec86ca3f2c4c 100644 --- a/drivers/rtc/rtc-m41t80.c +++ b/drivers/rtc/rtc-m41t80.c @@ -574,8 +574,8 @@ static struct clk *m41t80_sqw_register_clk(struct m41t80_data *m41t80) struct i2c_client *client = m41t80->client; struct device_node *node = client->dev.of_node; struct device_node *fixed_clock; + struct clk_init_data init = {}; struct clk *clk; - struct clk_init_data init; int ret; fixed_clock = of_get_child_by_name(node, "clock"); From 096c7ef4ef6670e64ebfc22f8989cb48f560fe10 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 21 Aug 2026 12:09:04 +0200 Subject: [PATCH 320/641] rtc: nct3018y: Make sure clk_init_data is fully initialized The clk_init_data structure contains several mutually-exclusive members for different methods to specify the possible parents of a clock, prompting drivers to initialize only the members they need. However, not initializing all members may cause subtle issues, which are only exposed when CONFIG_INIT_STACK_ALL_PATTERN or CONFIG_INIT_STACK_NONE is enabled. Make sure all members are fully initialized, to avoid such bugs, and to prevent future breakage when converting drivers to a different method for specifying the parents. Signed-off-by: Geert Uytterhoeven Link: https://patch.msgid.link/6dba18b645aeea279a67f4625d5ec48037c76f0c.1787241693.git.geert+renesas@glider.be Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-nct3018y.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/rtc/rtc-nct3018y.c b/drivers/rtc/rtc-nct3018y.c index 700a395fad3a..2f7ad57057a4 100644 --- a/drivers/rtc/rtc-nct3018y.c +++ b/drivers/rtc/rtc-nct3018y.c @@ -459,8 +459,8 @@ static struct clk *nct3018y_clkout_register_clk(struct nct3018y *nct3018y) { struct i2c_client *client = nct3018y->client; struct device_node *node = client->dev.of_node; + struct clk_init_data init = {}; struct clk *clk; - struct clk_init_data init; init.name = "nct3018y-clkout"; init.ops = &nct3018y_clkout_ops; From 5ef3d51a3aaae642f8e5515a2c67c17f6b7cb01c Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 21 Aug 2026 12:09:05 +0200 Subject: [PATCH 321/641] rtc: philips: Make sure clk_init_data is fully initialized The clk_init_data structure contains several mutually-exclusive members for different methods to specify the possible parents of a clock, prompting drivers to initialize only the members they need. However, not initializing all members may cause subtle issues, which are only exposed when CONFIG_INIT_STACK_ALL_PATTERN or CONFIG_INIT_STACK_NONE is enabled. Make sure all members are fully initialized, to avoid such bugs, and to prevent future breakage when converting drivers to a different method for specifying the parents. Signed-off-by: Geert Uytterhoeven Link: https://patch.msgid.link/8281eaf605069aac2211d83233e11285b8e8ca84.1787241693.git.geert+renesas@glider.be Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-pcf85063.c | 2 +- drivers/rtc/rtc-pcf8563.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/rtc/rtc-pcf85063.c b/drivers/rtc/rtc-pcf85063.c index 01e209d88f5f..8cb9ffc73f6d 100644 --- a/drivers/rtc/rtc-pcf85063.c +++ b/drivers/rtc/rtc-pcf85063.c @@ -498,7 +498,7 @@ static const struct clk_ops pcf85063_clkout_ops = { static struct clk *pcf85063_clkout_register_clk(struct pcf85063 *pcf85063) { struct clk *clk; - struct clk_init_data init; + struct clk_init_data init = {}; struct device_node *node = pcf85063->rtc->dev.parent->of_node; struct device_node *fixed_clock; diff --git a/drivers/rtc/rtc-pcf8563.c b/drivers/rtc/rtc-pcf8563.c index a90dc940474b..7eebdb058067 100644 --- a/drivers/rtc/rtc-pcf8563.c +++ b/drivers/rtc/rtc-pcf8563.c @@ -425,7 +425,7 @@ static const struct clk_ops pcf8563_clkout_ops = { static struct clk *pcf8563_clkout_register_clk(struct pcf8563 *pcf8563) { struct device_node *node = pcf8563->rtc->dev.parent->of_node; - struct clk_init_data init; + struct clk_init_data init = {}; struct clk *clk; int ret; From a12eac458814f70ed9b0b2e3293b7478c7032eaa Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 21 Aug 2026 12:09:06 +0200 Subject: [PATCH 322/641] rtc: microcrystal: Make sure clk_init_data is fully initialized The clk_init_data structure contains several mutually-exclusive members for different methods to specify the possible parents of a clock, prompting drivers to initialize only the members they need. However, not initializing all members may cause subtle issues, which are only exposed when CONFIG_INIT_STACK_ALL_PATTERN or CONFIG_INIT_STACK_NONE is enabled. Make sure all members are fully initialized, to avoid such bugs, and to prevent future breakage when converting drivers to a different method for specifying the parents. Signed-off-by: Geert Uytterhoeven Link: https://patch.msgid.link/6e7b5258ff73e93fe23dd83cf56f806ee60fa7a9.1787241693.git.geert+renesas@glider.be Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rv3028.c | 2 +- drivers/rtc/rtc-rv3032.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/rtc/rtc-rv3028.c b/drivers/rtc/rtc-rv3028.c index d96f6bb68850..d25e33d0835e 100644 --- a/drivers/rtc/rtc-rv3028.c +++ b/drivers/rtc/rtc-rv3028.c @@ -816,7 +816,7 @@ static int rv3028_clkout_register_clk(struct rv3028_data *rv3028, { int ret; struct clk *clk; - struct clk_init_data init; + struct clk_init_data init = {}; struct device_node *node = client->dev.of_node; ret = regmap_update_bits(rv3028->regmap, RV3028_STATUS, diff --git a/drivers/rtc/rtc-rv3032.c b/drivers/rtc/rtc-rv3032.c index 6bafdec637ae..687faaef42b2 100644 --- a/drivers/rtc/rtc-rv3032.c +++ b/drivers/rtc/rtc-rv3032.c @@ -752,7 +752,7 @@ static int rv3032_clkout_register_clk(struct rv3032_data *rv3032, { int ret; struct clk *clk; - struct clk_init_data init; + struct clk_init_data init = {}; struct device_node *node = client->dev.of_node; ret = regmap_update_bits(rv3032->regmap, RV3032_TLSB, RV3032_TLSB_CLKF, 0); From bf2fe566a8cbd3671b816223eec8fadd18ec8d77 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Sat, 25 Jul 2026 20:32:40 +0900 Subject: [PATCH 323/641] coccinelle: double_lock: improve performance when no double lock exists The 'balanced' rule collects the locks that are taken and released under the same condition, to prevent them from being reported as a double lock. It runs on every file that contains a lock call. To avoid this, collect the double-lock candidates first, so that 'balanced' runs only when one exists. The report then excludes what 'balanced' found. Every double lock that can be reported is also a candidate, so the same reports are made as before and the output does not change. A report-mode run over every .c file in the tree produces identical output. Signed-off-by: Sang-Heon Jeon Signed-off-by: Julia Lawall --- scripts/coccinelle/locks/double_lock.cocci | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/scripts/coccinelle/locks/double_lock.cocci b/scripts/coccinelle/locks/double_lock.cocci index 619cfc714409..381060849a7b 100644 --- a/scripts/coccinelle/locks/double_lock.cocci +++ b/scripts/coccinelle/locks/double_lock.cocci @@ -38,7 +38,20 @@ write_lock@p1 write_trylock@p1 ) (E1@p,...); -@balanced@ +@r_candidate exists@ +expression x <= locked.E1; +expression locked.E1; +expression E2; +identifier lock; +position locked.p,p1,p2; +@@ + +lock@p1 (E1@p,...); +... when != E1 + when != \(x = E2\|&x\) +lock@p2 (E1,...); + +@balanced depends on r_candidate@ position p1 != locked.p1; position locked.p; identifier lock,unlock; From b32cf68d78a1f2a05797ec2a969b80a9472f45a7 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Sat, 25 Jul 2026 20:32:41 +0900 Subject: [PATCH 324/641] coccinelle: misc: minmax: improve performance when no candidate exists The rules that report an opencoded min() or max() search every function body, even when the file contains nothing to find. To avoid this, collect the candidates first and run the search only when one exists. A candidate is any conditional expression whose condition is a comparison. Every opencoded min() or max() is also a candidate, so the same opportunities are reported as before and the output does not change. A report-mode run over every .c file in the tree produces identical output. Signed-off-by: Sang-Heon Jeon Signed-off-by: Julia Lawall --- scripts/coccinelle/misc/minmax.cocci | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/scripts/coccinelle/misc/minmax.cocci b/scripts/coccinelle/misc/minmax.cocci index ca4830ae3042..93c074b9439f 100644 --- a/scripts/coccinelle/misc/minmax.cocci +++ b/scripts/coccinelle/misc/minmax.cocci @@ -17,7 +17,21 @@ virtual org virtual context virtual patch -@rmax depends on !patch@ +@max_candidate@ +expression E1, E2, E3, E4; +binary operator cmp = {>, >=}; +@@ + + (E1 cmp E2 ? E3 : E4) + +@min_candidate@ +expression E1, E2, E3, E4; +binary operator cmp = {<, <=}; +@@ + + (E1 cmp E2 ? E3 : E4) + +@rmax depends on !patch && max_candidate@ identifier func; expression x, y; binary operator cmp = {>, >=}; @@ -51,7 +65,7 @@ func(...) } // Ignore errcode returns. -@errcode@ +@errcode depends on min_candidate@ position p; identifier func; expression x; @@ -65,7 +79,7 @@ func(...) ...> } -@rmin depends on !patch@ +@rmin depends on !patch && min_candidate@ identifier func; expression x, y; binary operator cmp = {<, <=}; @@ -98,7 +112,7 @@ func(...) ...> } -@pmax depends on patch@ +@pmax depends on patch && max_candidate@ identifier func; expression x, y; binary operator cmp = {>=, >}; @@ -131,7 +145,7 @@ func(...) ...> } -@pmin depends on patch@ +@pmin depends on patch && min_candidate@ identifier func; expression x, y; binary operator cmp = {<=, <}; From fd0a63f086b3f2f45c49b1378f6c2ac7a542db19 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sun, 26 Jul 2026 16:01:38 +0200 Subject: [PATCH 325/641] coccinelle: misc: minmax: drop unneeded parentheses The outer parentheses don't matter when just matching an returning a line. Eliminating them reduces the complexity of the pattern to match and gives a small performance improvement in the non-patch cases. Signed-off-by: Julia Lawall --- scripts/coccinelle/misc/minmax.cocci | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/coccinelle/misc/minmax.cocci b/scripts/coccinelle/misc/minmax.cocci index 93c074b9439f..905dd2292609 100644 --- a/scripts/coccinelle/misc/minmax.cocci +++ b/scripts/coccinelle/misc/minmax.cocci @@ -22,14 +22,14 @@ expression E1, E2, E3, E4; binary operator cmp = {>, >=}; @@ - (E1 cmp E2 ? E3 : E4) + E1 cmp E2 ? E3 : E4 @min_candidate@ expression E1, E2, E3, E4; binary operator cmp = {<, <=}; @@ - (E1 cmp E2 ? E3 : E4) + E1 cmp E2 ? E3 : E4 @rmax depends on !patch && max_candidate@ identifier func; @@ -41,7 +41,7 @@ position p; func(...) { <... -* ((x) cmp@p (y) ? (x) : (y)) +* (x) cmp@p (y) ? (x) : (y) ...> } @@ -89,7 +89,7 @@ position p != errcode.p; func(...) { <... -* ((x) cmp@p (y) ? (x) : (y)) +* (x) cmp@p (y) ? (x) : (y) ...> } From af8613c863a3160b7fb6fd09cbf5b56e37330d80 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sun, 26 Jul 2026 16:08:56 +0200 Subject: [PATCH 326/641] coccinelle: misc: minmax: check for the presence of if cases As done previously for the ternary command, check that a file contains the min or max if pattern before applying the minif and maxif rules. Signed-off-by: Julia Lawall --- scripts/coccinelle/misc/minmax.cocci | 32 ++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/scripts/coccinelle/misc/minmax.cocci b/scripts/coccinelle/misc/minmax.cocci index 905dd2292609..f14e608240b6 100644 --- a/scripts/coccinelle/misc/minmax.cocci +++ b/scripts/coccinelle/misc/minmax.cocci @@ -45,7 +45,19 @@ func(...) ...> } -@rmaxif depends on !patch@ +@maxif_candidate@ +expression x, y; +expression max_val; +binary operator cmp = {>, >=}; +@@ + +if ((x) cmp (y)) { + max_val = (x); +} else { + max_val = (y); +} + +@rmaxif depends on !patch && maxif_candidate@ identifier func; expression x, y; expression max_val; @@ -93,7 +105,19 @@ func(...) ...> } -@rminif depends on !patch@ +@minif_candidate@ +expression x, y; +expression min_val; +binary operator cmp = {<, <=}; +@@ + +if ((x) cmp (y)) { + min_val = (x); +} else { + min_val = (y); +} + +@rminif depends on !patch && minif_candidate@ identifier func; expression x, y; expression min_val; @@ -126,7 +150,7 @@ func(...) ...> } -@pmaxif depends on patch@ +@pmaxif depends on patch && maxif_candidate@ identifier func; expression x, y; expression max_val; @@ -160,7 +184,7 @@ func(...) ...> } -@pminif depends on patch@ +@pminif depends on patch && maxif_candidate@ identifier func; expression x, y; expression min_val; From 4fc2656db0c8026139f1634d557ef66e4b383765 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sun, 26 Jul 2026 17:07:54 +0200 Subject: [PATCH 327/641] coccinelle: misc: minmax: avoid unhelpful isomorphisms Avoid isomorphisms that introduce comparisons with 0 that do not occur in code. Signed-off-by: Julia Lawall --- scripts/coccinelle/misc/minmax.cocci | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/scripts/coccinelle/misc/minmax.cocci b/scripts/coccinelle/misc/minmax.cocci index f14e608240b6..b83d4a01e47a 100644 --- a/scripts/coccinelle/misc/minmax.cocci +++ b/scripts/coccinelle/misc/minmax.cocci @@ -17,21 +17,21 @@ virtual org virtual context virtual patch -@max_candidate@ +@max_candidate disable not_int1, not_int2, neg_if_exp@ expression E1, E2, E3, E4; binary operator cmp = {>, >=}; @@ E1 cmp E2 ? E3 : E4 -@min_candidate@ +@min_candidate disable not_int1, not_int2, neg_if_exp@ expression E1, E2, E3, E4; binary operator cmp = {<, <=}; @@ E1 cmp E2 ? E3 : E4 -@rmax depends on !patch && max_candidate@ +@rmax depends on !patch && max_candidate disable not_int1, not_int2, neg_if_exp@ identifier func; expression x, y; binary operator cmp = {>, >=}; @@ -45,7 +45,7 @@ func(...) ...> } -@maxif_candidate@ +@maxif_candidate disable not_int1, not_int2, neg_if@ expression x, y; expression max_val; binary operator cmp = {>, >=}; @@ -57,7 +57,7 @@ if ((x) cmp (y)) { max_val = (y); } -@rmaxif depends on !patch && maxif_candidate@ +@rmaxif depends on !patch && maxif_candidate disable not_int1, not_int2, neg_if@ identifier func; expression x, y; expression max_val; @@ -77,7 +77,7 @@ func(...) } // Ignore errcode returns. -@errcode depends on min_candidate@ +@errcode depends on min_candidate disable not_int1, not_int2, neg_if_exp@ position p; identifier func; expression x; @@ -91,7 +91,7 @@ func(...) ...> } -@rmin depends on !patch && min_candidate@ +@rmin depends on !patch && min_candidate disable not_int1, not_int2, neg_if_exp@ identifier func; expression x, y; binary operator cmp = {<, <=}; @@ -105,7 +105,7 @@ func(...) ...> } -@minif_candidate@ +@minif_candidate disable not_int1, not_int2, neg_if@ expression x, y; expression min_val; binary operator cmp = {<, <=}; @@ -117,7 +117,7 @@ if ((x) cmp (y)) { min_val = (y); } -@rminif depends on !patch && minif_candidate@ +@rminif depends on !patch && minif_candidate disable not_int1, not_int2, neg_if@ identifier func; expression x, y; expression min_val; @@ -136,7 +136,7 @@ func(...) ...> } -@pmax depends on patch && max_candidate@ +@pmax depends on patch && max_candidate disable not_int1, not_int2, neg_if_exp@ identifier func; expression x, y; binary operator cmp = {>=, >}; @@ -150,7 +150,7 @@ func(...) ...> } -@pmaxif depends on patch && maxif_candidate@ +@pmaxif depends on patch && maxif_candidate disable not_int1, not_int2, neg_if@ identifier func; expression x, y; expression max_val; @@ -169,7 +169,7 @@ func(...) ...> } -@pmin depends on patch && min_candidate@ +@pmin depends on patch && min_candidate disable not_int1, not_int2, neg_if_exp@ identifier func; expression x, y; binary operator cmp = {<=, <}; @@ -184,7 +184,7 @@ func(...) ...> } -@pminif depends on patch && maxif_candidate@ +@pminif depends on patch && minif_candidate disable not_int1, not_int2, neg_if@ identifier func; expression x, y; expression min_val; From 5adb3698bbe8f2053afbe046dee8eb184ac78478 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=9B=B8=E6=B5=A6=E5=BD=B0=20/=20AIURA=EF=BC=8CAKIRA?= Date: Thu, 30 Jul 2026 02:38:40 +0000 Subject: [PATCH 328/641] coccinelle: update Coccinelle website URL The old Coccinelle project URL is no longer available. Replace it with the current Coccinelle homepage already referenced by Documentation/dev-tools/coccinelle.rst. Signed-off-by: Akira Aiura Signed-off-by: Julia Lawall --- Documentation/process/4.Coding.rst | 3 ++- scripts/coccicheck | 4 ++-- scripts/nsdeps | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Documentation/process/4.Coding.rst b/Documentation/process/4.Coding.rst index c0f57d0c4f73..c23b9e48ce49 100644 --- a/Documentation/process/4.Coding.rst +++ b/Documentation/process/4.Coding.rst @@ -312,7 +312,8 @@ be found at https://sparse.wiki.kernel.org/index.php/Main_Page if your distributor does not package it); it can then be run on the code by adding "C=1" to your make command. -The "Coccinelle" tool (http://coccinelle.lip6.fr/) is able to find a wide +The "Coccinelle" tool +(https://coccinelle.gitlabpages.inria.fr/website) is able to find a wide variety of potential coding problems; it can also propose fixes for those problems. Quite a few "semantic patches" for the kernel have been packaged under the scripts/coccinelle directory; running "make coccicheck" will run diff --git a/scripts/coccicheck b/scripts/coccicheck index 8dd766009de1..a0a0e0f72bc7 100755 --- a/scripts/coccicheck +++ b/scripts/coccicheck @@ -11,7 +11,7 @@ DIR="$(dirname $(readlink -f $0))/.." SPATCH="`which ${SPATCH:=spatch}`" if [ ! -x "$SPATCH" ]; then - echo 'spatch is part of the Coccinelle project and is available at http://coccinelle.lip6.fr/' + echo 'spatch is part of the Coccinelle project and is available at https://coccinelle.gitlabpages.inria.fr/website' exit 1 fi @@ -229,7 +229,7 @@ coccinelle () { echo " in $FILE." echo '' echo ' More information about semantic patching is available at' - echo ' http://coccinelle.lip6.fr/' + echo ' https://coccinelle.gitlabpages.inria.fr/website' echo '' if [ "`sed -ne 's|^//#||p' $COCCI`" ] ; then diff --git a/scripts/nsdeps b/scripts/nsdeps index a3372166ac01..d4b5ebcc2016 100644 --- a/scripts/nsdeps +++ b/scripts/nsdeps @@ -8,7 +8,7 @@ SPATCH_REQ_VERSION="1.0.4" DIR="$(dirname $(readlink -f $0))/.." SPATCH="`which ${SPATCH:=spatch}`" if [ ! -x "$SPATCH" ]; then - echo 'spatch is part of the Coccinelle project and is available at http://coccinelle.lip6.fr/' + echo 'spatch is part of the Coccinelle project and is available at https://coccinelle.gitlabpages.inria.fr/website' exit 1 fi From b64acacd5429ff3fe7c38cb3cf959bbd3a9fb30f Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sun, 2 Aug 2026 21:20:51 +0200 Subject: [PATCH 329/641] coccinelle: api: check for macro context A cast on a call to an allocation function that is the body of a macro can be useful, as it ensures tha the macro is used for allocating objects of the right type. Add two new rules to ignore the macro case. Suggested-by: Steven Rostedt Signed-off-by: Julia Lawall --- scripts/coccinelle/api/alloc/alloc_cast.cocci | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/scripts/coccinelle/api/alloc/alloc_cast.cocci b/scripts/coccinelle/api/alloc/alloc_cast.cocci index f6f0ccdb6409..958c914f8ba6 100644 --- a/scripts/coccinelle/api/alloc/alloc_cast.cocci +++ b/scripts/coccinelle/api/alloc/alloc_cast.cocci @@ -20,6 +20,24 @@ virtual patch virtual org virtual report +@m1@ +identifier i; +expression e; +type T; +position p1; +@@ + +#define i (T@p1 *)e + +@m2@ +identifier i; +expression e; +type T; +position p2; +@@ + +#define i(...) (T@p2 *)e + @initialize:python@ @@ import re @@ -28,9 +46,10 @@ m = re.compile(pattern) @r1 depends on context || patch@ type T; +position p != {m1.p1,m2.p2}; @@ - (T *) + (T@p *) \(kmalloc\|kzalloc\|kcalloc\|kmem_cache_alloc\|kmem_cache_zalloc\| kmem_cache_alloc_node\|kmalloc_node\|kzalloc_node\|vmalloc\|vzalloc\| dma_alloc_coherent\|devm_kmalloc\|devm_kzalloc\| @@ -90,7 +109,7 @@ type r1.T; @r2 depends on org || report@ type T; -position p; +position p != {m1.p1,m2.p2}; @@ (T@p *) From a53cbd149844cbde80f6adfc7f4233b8f230f664 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Mon, 27 Jul 2026 22:52:44 +0900 Subject: [PATCH 330/641] coccinelle: mini_lock: improve performance when searching loops The 'looped' rule collects the returns inside a for loop to prevent 'err' from reporting them. It searches every for loop in the file, and on files with large loop bodies the search explodes. For example, kernel/bpf/verifier.c runs for over 200 seconds, almost entirely in 'looped' according to --profile. Since the kernel .cocciconfig sets a 200 second timeout, coccicheck silently skips the file. To avoid this, collect the candidate returns first, so that 'looped' checks only those positions. 'err' then excludes what 'looped' found. Every return that 'err' can report is also a candidate, so the same returns are excluded as before and the output does not change. A report-mode run over every .c file in the tree produces identical output. So verifier.c now finishes well within the timeout, in a few seconds. Signed-off-by: Sang-Heon Jeon Signed-off-by: Julia Lawall --- scripts/coccinelle/locks/mini_lock.cocci | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/scripts/coccinelle/locks/mini_lock.cocci b/scripts/coccinelle/locks/mini_lock.cocci index 71065d8a5d54..c65241c895ff 100644 --- a/scripts/coccinelle/locks/mini_lock.cocci +++ b/scripts/coccinelle/locks/mini_lock.cocci @@ -53,11 +53,31 @@ spin_lock_irq@p1 spin_lock_irqsave@p1 ) (E1@p,...); -@looped@ +@err_candidate exists@ +expression E1; +position prelocked.p; +position up != prelocked.p1; +position rc; +identifier lock,unlock; +@@ + +lock(E1@p,...); +... when != E1 + when any +if (...) { + ... when != E1 + return@rc ...; +} +... when != E1 + when any +unlock@up(E1,...); + +@looped exists@ +position err_candidate.rc; position r; @@ -for(...;...;...) { <+... return@r ...; ...+> } +for(...;...;...) { <+... return@rc@r ...; ...+> } @err exists@ expression E1; From 707f8e0ddf3f730c15e5c18b346d4258ddb46868 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Sun, 9 Aug 2026 13:53:34 +0900 Subject: [PATCH 331/641] coccinelle: misc: struct_size: drop unneeded parentheses The outer parentheses and the single-branch disjunction don't matter when just matching and reporting a line. Eliminating them reduces the complexity of the pattern to match and gives a performance improvement in the non-patch cases, like the previous minmax change. Signed-off-by: Sang-Heon Jeon Signed-off-by: Julia Lawall --- scripts/coccinelle/misc/struct_size.cocci | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/scripts/coccinelle/misc/struct_size.cocci b/scripts/coccinelle/misc/struct_size.cocci index 9b02c37438e4..406884d68d43 100644 --- a/scripts/coccinelle/misc/struct_size.cocci +++ b/scripts/coccinelle/misc/struct_size.cocci @@ -29,9 +29,7 @@ f expression E1, E2; identifier m; @@ -( -* (sizeof(*E1) + (E2 * sizeof(*E1->m))) -) +* sizeof(*E1) + (E2 * sizeof(*E1->m)) //---------------------------------------------------------- // For patch mode @@ -55,9 +53,7 @@ expression E1, E2; identifier m; position p; @@ -( - (sizeof(*E1)@p + (E2 * sizeof(*E1->m))) -) + sizeof(*E1)@p + (E2 * sizeof(*E1->m)) @script:python depends on org@ p << r.p; From 8c59f5c467aff29d9d8088e07b6ca34926f6151d Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sun, 9 Aug 2026 18:43:36 +0200 Subject: [PATCH 332/641] scripts: coccinelle: devm_free: reduce false positives False positives could be introduced due to allocations using the new _obj functions. Add these to the "safe" rule accordingly. False positives could also be introduced when the same variable name has two possible types. Incorporate type information to avoid reporting this case This does lead to false negatives when no type information is available. Signed-off-by: Julia Lawall Reported-by: Ricardo Ribalda --- scripts/coccinelle/free/devm_free.cocci | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/scripts/coccinelle/free/devm_free.cocci b/scripts/coccinelle/free/devm_free.cocci index 0880729badbc..947d7e685655 100644 --- a/scripts/coccinelle/free/devm_free.cocci +++ b/scripts/coccinelle/free/devm_free.cocci @@ -26,7 +26,8 @@ virtual report virtual context @r depends on context || org || report@ -expression x; +type T; +T x; @@ ( @@ -56,18 +57,26 @@ expression x; ) @safe depends on context || org || report exists@ -expression x; +r.T x; position p; @@ ( x = kmalloc(...) +| + x = kmalloc_obj(...) +| + x = kmalloc_objs(...) | x = kvasprintf(...) | x = kasprintf(...) | x = kzalloc(...) +| + x = kzalloc_obj(...) +| + x = kzalloc_objs(...) | x = kmalloc_array(...) | @@ -105,7 +114,7 @@ position p; ) @pb@ -expression r.x; +r.T r.x; position p != safe.p; @@ From cfeffda899903bfaf5244dab3596f585511af814 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Mon, 24 Aug 2026 01:18:13 +0900 Subject: [PATCH 333/641] coccinelle: remove obsolete pci_free_consistent.cocci pci_alloc_consistent() and pci_free_consistent() were removed by commit 7968778914e5 ("PCI: Remove the deprecated "pci-dma-compat.h" API"). So remove the obsolete script. Signed-off-by: Sang-Heon Jeon Signed-off-by: Julia Lawall --- .../coccinelle/free/pci_free_consistent.cocci | 53 ------------------- 1 file changed, 53 deletions(-) delete mode 100644 scripts/coccinelle/free/pci_free_consistent.cocci diff --git a/scripts/coccinelle/free/pci_free_consistent.cocci b/scripts/coccinelle/free/pci_free_consistent.cocci deleted file mode 100644 index e062b9ba09ff..000000000000 --- a/scripts/coccinelle/free/pci_free_consistent.cocci +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/// Find missing pci_free_consistent for every pci_alloc_consistent. -/// -// Confidence: Moderate -// Copyright: (C) 2013 Petr Strnad. -// URL: https://coccinelle.gitlabpages.inria.fr/website -// Keywords: pci_free_consistent, pci_alloc_consistent -// Options: --no-includes --include-headers - -virtual report -virtual org - -@search@ -local idexpression id; -expression x,y,z,e; -position p1,p2; -type T; -@@ - -id = pci_alloc_consistent@p1(x,y,&z) -... when != e = id -if (id == NULL || ...) { ... return ...; } -... when != pci_free_consistent(x,y,id,z) - when != if (id) { ... pci_free_consistent(x,y,id,z) ... } - when != if (y) { ... pci_free_consistent(x,y,id,z) ... } - when != e = (T)id - when exists -( -return 0; -| -return 1; -| -return id; -| -return@p2 ...; -) - -@script:python depends on report@ -p1 << search.p1; -p2 << search.p2; -@@ - -msg = "ERROR: missing pci_free_consistent; pci_alloc_consistent on line %s and return without freeing on line %s" % (p1[0].line,p2[0].line) -coccilib.report.print_report(p2[0],msg) - -@script:python depends on org@ -p1 << search.p1; -p2 << search.p2; -@@ - -msg = "ERROR: missing pci_free_consistent; pci_alloc_consistent on line %s and return without freeing on line %s" % (p1[0].line,p2[0].line) -cocci.print_main(msg,p1) -cocci.print_secs("",p2) From 2bd30b8dc75b823dd7bbef9d7bc68e1fd7ecee5c Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Mon, 24 Aug 2026 01:18:14 +0900 Subject: [PATCH 334/641] coccinelle: alloc_cast: drop removed allocators - pci_alloc_consistent() and pci_zalloc_consistent() were removed by commit 7968778914e5 ("PCI: Remove the deprecated "pci-dma-compat.h" API") - kmem_alloc() was removed by commit f078d4ea8276 ("xfs: convert kmem_alloc() to kmalloc()") - kmem_zalloc() was removed by commit 10634530f7ba ("xfs: convert kmem_zalloc() to kzalloc()") - kmem_zone_alloc() and kmem_zone_zalloc() were removed by commit bae633a4a283 ("xfs: remove xfs_zone_{alloc,zalloc} helpers") So drop them from the rules. No functional change. Signed-off-by: Sang-Heon Jeon Signed-off-by: Julia Lawall --- scripts/coccinelle/api/alloc/alloc_cast.cocci | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/scripts/coccinelle/api/alloc/alloc_cast.cocci b/scripts/coccinelle/api/alloc/alloc_cast.cocci index 958c914f8ba6..a4b83cc8ea01 100644 --- a/scripts/coccinelle/api/alloc/alloc_cast.cocci +++ b/scripts/coccinelle/api/alloc/alloc_cast.cocci @@ -53,9 +53,8 @@ position p != {m1.p1,m2.p2}; \(kmalloc\|kzalloc\|kcalloc\|kmem_cache_alloc\|kmem_cache_zalloc\| kmem_cache_alloc_node\|kmalloc_node\|kzalloc_node\|vmalloc\|vzalloc\| dma_alloc_coherent\|devm_kmalloc\|devm_kzalloc\| - kvmalloc\|kvzalloc\|kvmalloc_node\|kvzalloc_node\|pci_alloc_consistent\| - pci_zalloc_consistent\|kmem_alloc\|kmem_zalloc\|kmem_zone_alloc\| - kmem_zone_zalloc\|vmalloc_node\|vzalloc_node\)(...) + kvmalloc\|kvzalloc\|kvmalloc_node\|kvzalloc_node\| + vmalloc_node\|vzalloc_node\)(...) //---------------------------------------------------------- // For context mode @@ -76,9 +75,8 @@ type r1.T; \(kmalloc\|kzalloc\|kcalloc\|kmem_cache_alloc\|kmem_cache_zalloc\| kmem_cache_alloc_node\|kmalloc_node\|kzalloc_node\|vmalloc\|vzalloc\| dma_alloc_coherent\|devm_kmalloc\|devm_kzalloc\| - kvmalloc\|kvzalloc\|kvmalloc_node\|kvzalloc_node\|pci_alloc_consistent\| - pci_zalloc_consistent\|kmem_alloc\|kmem_zalloc\|kmem_zone_alloc\| - kmem_zone_zalloc\|vmalloc_node\|vzalloc_node\)(...) + kvmalloc\|kvzalloc\|kvmalloc_node\|kvzalloc_node\| + vmalloc_node\|vzalloc_node\)(...) //---------------------------------------------------------- // For patch mode @@ -99,9 +97,8 @@ type r1.T; \(kmalloc\|kzalloc\|kcalloc\|kmem_cache_alloc\|kmem_cache_zalloc\| kmem_cache_alloc_node\|kmalloc_node\|kzalloc_node\|vmalloc\|vzalloc\| dma_alloc_coherent\|devm_kmalloc\|devm_kzalloc\| - kvmalloc\|kvzalloc\|kvmalloc_node\|kvzalloc_node\|pci_alloc_consistent\| - pci_zalloc_consistent\|kmem_alloc\|kmem_zalloc\|kmem_zone_alloc\| - kmem_zone_zalloc\|vmalloc_node\|vzalloc_node\)(...) + kvmalloc\|kvzalloc\|kvmalloc_node\|kvzalloc_node\| + vmalloc_node\|vzalloc_node\)(...) //---------------------------------------------------------- // For org and report mode @@ -116,9 +113,8 @@ position p != {m1.p1,m2.p2}; \(kmalloc\|kzalloc\|kcalloc\|kmem_cache_alloc\|kmem_cache_zalloc\| kmem_cache_alloc_node\|kmalloc_node\|kzalloc_node\|vmalloc\|vzalloc\| dma_alloc_coherent\|devm_kmalloc\|devm_kzalloc\| - kvmalloc\|kvzalloc\|kvmalloc_node\|kvzalloc_node\|pci_alloc_consistent\| - pci_zalloc_consistent\|kmem_alloc\|kmem_zalloc\|kmem_zone_alloc\| - kmem_zone_zalloc\|vmalloc_node\|vzalloc_node\)(...) + kvmalloc\|kvzalloc\|kvmalloc_node\|kvzalloc_node\| + vmalloc_node\|vzalloc_node\)(...) @script:python depends on org@ p << r2.p; From 0319b42e1e28b845a3092082fa1e1ff9043f833e Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Mon, 24 Aug 2026 01:18:15 +0900 Subject: [PATCH 335/641] coccinelle: zalloc-simple: drop the kmem_alloc rules - kmem_alloc() was removed by commit f078d4ea8276 ("xfs: convert kmem_alloc() to kmalloc()") - kmem_zalloc() was removed by commit 10634530f7ba ("xfs: convert kmem_zalloc() to kzalloc()") So drop the kmem_alloc rules. No functional change. Signed-off-by: Sang-Heon Jeon Signed-off-by: Julia Lawall --- .../coccinelle/api/alloc/zalloc-simple.cocci | 41 +------------------ 1 file changed, 1 insertion(+), 40 deletions(-) diff --git a/scripts/coccinelle/api/alloc/zalloc-simple.cocci b/scripts/coccinelle/api/alloc/zalloc-simple.cocci index d66c45356691..ae1d39e71e31 100644 --- a/scripts/coccinelle/api/alloc/zalloc-simple.cocci +++ b/scripts/coccinelle/api/alloc/zalloc-simple.cocci @@ -35,7 +35,7 @@ statement S; @@ * x = (T)\(kmalloc(E1, ...)\|vmalloc(E1)\|dma_alloc_coherent(...,E1,...)\| - kmalloc_node(E1, ...)\|kmem_cache_alloc(...)\|kmem_alloc(E1, ...)\| + kmalloc_node(E1, ...)\|kmem_cache_alloc(...)\| devm_kmalloc(...,E1,...)\|kvmalloc(E1, ...)\|kvmalloc_node(E1,...)\); if ((x==NULL) || ...) S * memset((T2)x,0,E1); @@ -88,15 +88,6 @@ statement S; - x = (T)kmem_cache_alloc(E3,E4); + x = (T)kmem_cache_zalloc(E3,E4); | -- x = kmem_alloc(E1,E2); -+ x = kmem_zalloc(E1,E2); -| -- x = (T *)kmem_alloc(E1,E2); -+ x = kmem_zalloc(E1,E2); -| -- x = (T)kmem_alloc(E1,E2); -+ x = (T)kmem_zalloc(E1,E2); -| - x = devm_kmalloc(E2,E1,E3); + x = devm_kzalloc(E2,E1,E3); | @@ -290,36 +281,6 @@ x << r4.x; msg="WARNING: kmem_cache_zalloc should be used for %s, instead of kmem_cache_alloc/memset" % (x) coccilib.report.print_report(p[0], msg) -//----------------------------------------------------------------- -@r5 depends on org || report@ -type T, T2; -expression x; -expression E1,E2; -statement S; -position p; -@@ - - x = (T)kmem_alloc@p(E1,E2); - if ((x==NULL) || ...) S - memset((T2)x,0,E1); - -@script:python depends on org@ -p << r5.p; -x << r5.x; -@@ - -msg="%s" % (x) -msg_safe=msg.replace("[","@(").replace("]",")") -coccilib.org.print_todo(p[0], msg_safe) - -@script:python depends on report@ -p << r5.p; -x << r5.x; -@@ - -msg="WARNING: kmem_zalloc should be used for %s, instead of kmem_alloc/memset" % (x) -coccilib.report.print_report(p[0], msg) - //----------------------------------------------------------------- @r6 depends on org || report@ type T, T2; From d34a88f53a7ab0a4d84fbff137c9077701e95b06 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 14 Aug 2026 16:20:08 +0000 Subject: [PATCH 336/641] scsi: core: Enable context analysis for hosts.o Enable compiler-based context analysis for drivers/scsi/hosts.c by setting CONTEXT_ANALYSIS_hosts.o := y in drivers/scsi/Makefile. The SCSI host management code in hosts.c now has the necessary lock context annotations (such as __must_hold(shost->host_lock) on scsi_host_set_state) and conforms to compile-time lock checking rules. It builds cleanly without triggering any context analysis warnings. Enable context analysis for hosts.o so that lock correctness and context safety invariants for SCSI host operations are verified at compile time when CONFIG_WARN_CONTEXT_ANALYSIS is enabled. Fixes: fb0fc67db962 ("scsi: core: Enable context analysis") Reported-by: John Garry Signed-off-by: Bart Van Assche Reviewed-by: John Garry Link: https://patch.msgid.link/3e1c3c0ca9307e2581cf4b96cf3fcdae35202255.1786724393.git.bvanassche@acm.org Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile index 72eb395ccf1d..533623382eca 100644 --- a/drivers/scsi/Makefile +++ b/drivers/scsi/Makefile @@ -15,6 +15,7 @@ # *!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*! CONTEXT_ANALYSIS_constants.o := y +CONTEXT_ANALYSIS_hosts.o := y CONTEXT_ANALYSIS_scsi.o := y CONTEXT_ANALYSIS_scsi_common.o := y CONTEXT_ANALYSIS_scsi_devinfo.o := y From b79b88b655a84187aed12d773566dabf5ab72ed6 Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Mon, 17 Aug 2026 12:04:55 -0700 Subject: [PATCH 337/641] scsi: ibmvfc: Fix use of uninitialized rport in ibmvfc_do_work() After commit 696d1cc2aaa2 ("scsi: ibmvfc: process NVMe/FC rports in work thread"), clang warns (or errors with CONFIG_WERROR=y / W=e): drivers/scsi/ibmvscsi/ibmvfc-core.c:6154:15: error: variable 'rport' is uninitialized when used here [-Werror,-Wuninitialized] 6154 | } else if (rport && tgt->action == IBMVFC_TGT_ACTION_DEL_AND_LOGOUT_RPORT) { | ^~~~~ The check for rport is unnecessary in this block, it was accidentally included from copying and pasting. Remove it to clear up the warning. Fixes: 696d1cc2aaa2 ("scsi: ibmvfc: process NVMe/FC rports in work thread") Suggested-by: Tyrel Datwyler Link: https://lore.kernel.org/6ccbe8c5-beb6-483f-bfa4-c2d3819ad5f2@linux.ibm.com/ Signed-off-by: Nathan Chancellor Acked-by: Tyrel Datwyler Link: https://patch.msgid.link/20260817-ibmvscsi-rport-wuninitialized-v1-1-0fdfb27a5f01@kernel.org Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/ibmvscsi/ibmvfc-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c index 93b9f699c2e5..b3bc3ce872d6 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-core.c +++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c @@ -6151,7 +6151,7 @@ static void ibmvfc_do_work(struct ibmvfc_host *vhost) timer_delete_sync(&tgt->timer); kref_put(&tgt->kref, ibmvfc_release_tgt); return; - } else if (rport && tgt->action == IBMVFC_TGT_ACTION_DEL_AND_LOGOUT_RPORT) { + } else if (tgt->action == IBMVFC_TGT_ACTION_DEL_AND_LOGOUT_RPORT) { tgt_dbg(tgt, "Deleting NVMe rport with outstanding I/O\n"); nvme_rport = tgt->nvme_remote_port; ibmvfc_set_tgt_action(tgt, IBMVFC_TGT_ACTION_LOGOUT_DELETED_RPORT); From ff9365a4c9991cca68fd48c7729808f2fdae2036 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 13 Aug 2026 10:08:27 +0300 Subject: [PATCH 338/641] scsi: qla2xxx: Fix an error code in qla_get_tmf() Negative -EIO was intended instead of positive EIO. The caller, doesn't care so this doesn't affect runtime. It's just a cleanup. Signed-off-by: Dan Carpenter Link: https://patch.msgid.link/an1taxANE_4_vzJT@stanley.mountain Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/qla2xxx/qla_init.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/scsi/qla2xxx/qla_init.c b/drivers/scsi/qla2xxx/qla_init.c index 900cd141928e..bb0cc71de37b 100644 --- a/drivers/scsi/qla2xxx/qla_init.c +++ b/drivers/scsi/qla2xxx/qla_init.c @@ -2302,7 +2302,7 @@ int qla_get_tmf(struct tmf_arg *arg) if (TMF_NOT_READY(fcport)) { ql_log(ql_log_warn, vha, 0x802c, "Unable to acquire TM resource due to disruption.\n"); - rc = EIO; + rc = -EIO; break; } if (ha->active_tmf < MAX_ACTIVE_TMF && From 11e48f5201fd86ffa038809dd9c41144d43ee2e4 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 13 Aug 2026 10:09:53 +0300 Subject: [PATCH 339/641] scsi: qla2xxx: Fix an loop timeout test This loop timeout with "retries" set to -1, not 0. Fix the test for failure. Fixes: 7ec0effd30bb ("[SCSI] qla2xxx: Add support for ISP8044.") Signed-off-by: Dan Carpenter Link: https://patch.msgid.link/an1twcxTYSFkkUTA@stanley.mountain Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/qla2xxx/qla_nx2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/scsi/qla2xxx/qla_nx2.c b/drivers/scsi/qla2xxx/qla_nx2.c index 41ff6fbdb933..04d7ab6ad037 100644 --- a/drivers/scsi/qla2xxx/qla_nx2.c +++ b/drivers/scsi/qla2xxx/qla_nx2.c @@ -3507,7 +3507,7 @@ qla8044_poll_flash_status_reg(struct scsi_qla_host *vha) msleep(QLA8044_FLASH_STATUS_REG_POLL_DELAY); } - if (!retries) + if (retries == -1) ret_val = QLA_FUNCTION_FAILED; return ret_val; From 0ec418204f23f0a1dfff79d5f6721080ec006042 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 13 Aug 2026 10:09:32 +0300 Subject: [PATCH 340/641] scsi: lpfc: Remove unnnecessary NULL check The "evt_dat" variale is non-NULL at this point so there is no need to check. Delete the check and pull the code in a tab. Signed-off-by: Dan Carpenter Reviewed-by: Paul Ely Link: https://patch.msgid.link/an1trOAUeQmYEus_@stanley.mountain Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/lpfc/lpfc_bsg.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/scsi/lpfc/lpfc_bsg.c b/drivers/scsi/lpfc/lpfc_bsg.c index c95165905483..7354ae9ba8e5 100644 --- a/drivers/scsi/lpfc/lpfc_bsg.c +++ b/drivers/scsi/lpfc/lpfc_bsg.c @@ -1329,10 +1329,8 @@ lpfc_bsg_hba_get_event(struct bsg_job *job) else bsg_reply->reply_payload_rcv_len = 0; - if (evt_dat) { - kfree(evt_dat->data); - kfree(evt_dat); - } + kfree(evt_dat->data); + kfree(evt_dat); spin_lock_irqsave(&phba->ct_ev_lock, flags); lpfc_bsg_event_unref(evt); From 970f69b6bf71562df5afcefb89c77b4e971d68de Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Fri, 14 Aug 2026 11:38:44 +0800 Subject: [PATCH 341/641] scsi: leapraid: Balance host references for firmware log VMAs leapraid_fw_mmap() keeps the Scsi_Host reference obtained while looking up the adapter for the lifetime of the initial VMA. The VMA close callback drops that reference. The open callback is also invoked when a VMA is duplicated or split, but it only increments mmap_refcnt. Since every corresponding close callback drops a host reference, cloning the mapping can release the host while another VMA still refers to the adapter. Take a host device reference for every VMA open and release the lookup reference once the initial mapping has acquired its own reference. Use get_device() because a VMA can be cloned after the host enters SHOST_DEL; an existing VMA still pins the host at that point and open cannot fail. Fixes: 5597088c9e79 ("scsi: leapraid: Add new SCSI driver") Signed-off-by: Linmao Li Reviewed-by: Dongdong Hao Link: https://patch.msgid.link/20260814033845.2971706-2-lilinmao@kylinos.cn Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/leapraid/leapraid_app.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/scsi/leapraid/leapraid_app.c b/drivers/scsi/leapraid/leapraid_app.c index 742f19c07fcb..841027ce2501 100644 --- a/drivers/scsi/leapraid/leapraid_app.c +++ b/drivers/scsi/leapraid/leapraid_app.c @@ -697,6 +697,7 @@ static void leapraid_fw_mmap_open(struct vm_area_struct *vma) if (!adapter) return; + get_device(&adapter->shost->shost_gendev); atomic_inc(&adapter->fw_log_desc.mmap_refcnt); } @@ -767,7 +768,6 @@ static int leapraid_fw_mmap(struct file *filp, struct vm_area_struct *vma) vma->vm_private_data = adapter; vma->vm_ops = &leapraid_fw_mmap_vm_ops; leapraid_fw_mmap_open(vma); - adapter = NULL; rc = 0; out_put: From 00b7c8d4ce441aa9ac704840332ba4738e1c6d51 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Fri, 14 Aug 2026 11:38:45 +0800 Subject: [PATCH 342/641] scsi: leapraid: Serialize firmware log mmap with teardown leapraid_fw_log_exit() waits for mmap_refcnt to reach zero before it frees the firmware log buffer. leapraid_fw_mmap() checks host_removing, but it does not increment mmap_refcnt until after dma_mmap_coherent() succeeds and the VMA open callback runs. Removal can set host_removing and observe a zero mmap_refcnt between the check and the VMA open. It can then free the coherent buffer while the mmap path is still establishing a userspace mapping of it. Claim a temporary mmap reference while looking up the adapter under leapraid_adapter_lock. Removal deletes the adapter from the same locked list after setting host_removing, so a mapping is either rejected or included in the count that removal waits for. Drop the temporary reference on the common exit path, after a successful VMA open has acquired the reference covering the VMA lifetime. Fixes: 5597088c9e79 ("scsi: leapraid: Add new SCSI driver") Signed-off-by: Linmao Li Reviewed-by: Dongdong Hao Link: https://patch.msgid.link/20260814033845.2971706-3-lilinmao@kylinos.cn Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/leapraid/leapraid_app.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/scsi/leapraid/leapraid_app.c b/drivers/scsi/leapraid/leapraid_app.c index 841027ce2501..84eef50947ae 100644 --- a/drivers/scsi/leapraid/leapraid_app.c +++ b/drivers/scsi/leapraid/leapraid_app.c @@ -171,7 +171,8 @@ static int leapraid_ctl_validate_sge_offset(struct leapraid_adapter *adapter, return 0; } -static struct leapraid_adapter *leapraid_ctl_lookup_adapter(int adapter_id) +static struct leapraid_adapter *leapraid_ctl_lookup_adapter(int adapter_id, + bool track_mmap) { struct leapraid_adapter *adapter; struct Scsi_Host *shost; @@ -184,6 +185,8 @@ static struct leapraid_adapter *leapraid_ctl_lookup_adapter(int adapter_id) shost = adapter->shost; if (!shost || !scsi_host_get(shost)) break; + if (track_mmap) + atomic_inc(&adapter->fw_log_desc.mmap_refcnt); spin_unlock(&leapraid_adapter_lock); return adapter; } @@ -589,7 +592,7 @@ static int leapraid_ctl_ioctl_main(struct file *file, unsigned int cmd, return -EFAULT; } - adapter = leapraid_ctl_lookup_adapter(ioctl_header.adapter_id); + adapter = leapraid_ctl_lookup_adapter(ioctl_header.adapter_id, false); if (!adapter) return -EFAULT; @@ -728,7 +731,7 @@ static int leapraid_fw_mmap(struct file *filp, struct vm_area_struct *vma) length = vma->vm_end - vma->vm_start; - adapter = leapraid_ctl_lookup_adapter(adapter_id); + adapter = leapraid_ctl_lookup_adapter(adapter_id, true); if (!adapter) { pr_err("%s: No adapter found!\n", __func__); return -EINVAL; @@ -771,6 +774,9 @@ static int leapraid_fw_mmap(struct file *filp, struct vm_area_struct *vma) rc = 0; out_put: + if (adapter && + atomic_dec_and_test(&adapter->fw_log_desc.mmap_refcnt)) + wake_up(&adapter->fw_log_desc.mmap_waitq); leapraid_ctl_put_adapter(adapter); return rc; } From 46f861d300e87283f81faf9ee377d1e73682903d Mon Sep 17 00:00:00 2001 From: Dongdong Hao Date: Fri, 14 Aug 2026 17:05:26 +0800 Subject: [PATCH 343/641] scsi: leapraid: Standardize NCQ priority sysfs attributes Replace the earlier LeapRAID ncq_cmd_prio_enable attribute with the standard sas_ncq_prio_supported and sas_ncq_prio_enable names documented in Documentation/ABI/testing/sysfs-block-device, and rename the per-device NCQ priority state to match. The earlier ncq_cmd_prio_enable name has not yet been established as part of a released userspace ABI, so no compatibility alias is needed. For LeapRAID, sas_ncq_prio_enable is backed by the driver's per-device NCQ priority state and controls whether RT-class I/O requests are issued with command priority on supported SATA devices. Update leapraid.rst to describe the standard attribute names and paths, and clean up the surrounding RST text for consistency with kernel documentation style. Also switch the capability check from open-coded VPD page 0x89 parsing to sas_ata_ncq_prio_supported(), use kstrtobool() for the enable path, and expose the NCQ priority attributes only for SATA devices using LeapRAID's target-private SAS device state. Reviewed-by: Damien Le Moal Signed-off-by: Dongdong Hao Reviewed-by: Hannes Reinecke Link: https://patch.msgid.link/20260814090526.395704-1-doubled@leap-io-kernel.com Signed-off-by: Martin K. Petersen (Oracle) --- Documentation/scsi/leapraid.rst | 14 +++-- drivers/scsi/leapraid/leapraid_func.h | 4 +- drivers/scsi/leapraid/leapraid_os.c | 87 +++++++++++++++++---------- 3 files changed, 68 insertions(+), 37 deletions(-) diff --git a/Documentation/scsi/leapraid.rst b/Documentation/scsi/leapraid.rst index 99930ce2b8d0..d36ff627d9f0 100644 --- a/Documentation/scsi/leapraid.rst +++ b/Documentation/scsi/leapraid.rst @@ -22,6 +22,7 @@ Supported devices Features ======== + - PCIe Gen4 x8 host interface - Support for SAS and SATA devices - RAID levels: 0, 1, 10, 5, 50, 6, 60 @@ -50,16 +51,20 @@ LeapRAID specific disk attributes :: - /sys/class/scsi_disk/host:bus:target:lun/device/sas_device_handle - /sys/class/scsi_disk/host:bus:target:lun/device/ncq_cmd_prio_enable + /sys/block//device/sas_device_handle + /sys/block//device/sas_ncq_prio_supported + /sys/block//device/sas_ncq_prio_enable The read-only attribute "sas_device_handle" represents the disk's device handle, which is a unique identifier maintained by the firmware. -This attribute "ncq_cmd_prio_enable" controls NCQ command priority. A value +The read-only attribute "sas_ncq_prio_supported" reports whether a SATA +device supports NCQ command priority. + +The attribute "sas_ncq_prio_enable" controls NCQ command priority. A value of 0 disables NCQ priority handling for RT-priority I/O. Writing 1 enables NCQ priority handling when the device reports support for the feature through -VPD page 0x89. Unsupported devices keep the effective state at 0. +VPD page 0x89. Writes to unsupported devices fail with an error. LeapRAID module parameters ========================== @@ -100,6 +105,7 @@ in io_uring poll mode. The default value is 0. File Location ============= + The driver source is located at: ``drivers/scsi/leapraid/`` diff --git a/drivers/scsi/leapraid/leapraid_func.h b/drivers/scsi/leapraid/leapraid_func.h index 923596211aa1..4c0b9ca728d8 100644 --- a/drivers/scsi/leapraid/leapraid_func.h +++ b/drivers/scsi/leapraid/leapraid_func.h @@ -1064,7 +1064,7 @@ struct leapraid_starget_priv { * @starget_priv: Associated target private data. * @lun: Logical Unit Number. * @flg: Flags. - * @ncq_cmd_prio_enable: Enables NCQ command priority for RT I/O. + * @ncq_prio_enable: Enables NCQ command priority for RT I/O. * @block: Block flag. * @deleted: Deletion flag. * @sep: SEP flag. @@ -1073,7 +1073,7 @@ struct leapraid_sdev_priv { struct leapraid_starget_priv *starget_priv; unsigned int lun; u32 flg; - u8 ncq_cmd_prio_enable; + u8 ncq_prio_enable; u8 block; u8 deleted; u8 sep; diff --git a/drivers/scsi/leapraid/leapraid_os.c b/drivers/scsi/leapraid/leapraid_os.c index a8e1c9f33896..ee3242779dfd 100644 --- a/drivers/scsi/leapraid/leapraid_os.c +++ b/drivers/scsi/leapraid/leapraid_os.c @@ -859,7 +859,7 @@ static u32 build_scsiio_req_control(struct scsi_cmnd *scmd, control |= LEAPRAID_SCSIIO_CTRL_SIMPLEQ; - if (sdev_priv->ncq_cmd_prio_enable && + if (sdev_priv->ncq_prio_enable && (IOPRIO_PRIO_CLASS(req_get_ioprio(scsi_cmd_to_rq(scmd))) == IOPRIO_CLASS_RT)) control |= LEAPRAID_SCSIIO_CTRL_CMDPRI; @@ -1854,7 +1854,16 @@ static ssize_t sas_device_handle_show(struct device *dev, sas_device_priv_data->starget_priv->hdl); } -static ssize_t ncq_cmd_prio_enable_show(struct device *dev, +static ssize_t sas_ncq_prio_supported_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct scsi_device *sdev = to_scsi_device(dev); + + return sysfs_emit(buf, "%d\n", sas_ata_ncq_prio_supported(sdev)); +} + +static ssize_t sas_ncq_prio_enable_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -1867,19 +1876,16 @@ static ssize_t ncq_cmd_prio_enable_show(struct device *dev, return -EINVAL; } - return sysfs_emit(buf, "%d\n", - sas_device_priv_data->ncq_cmd_prio_enable); + return sysfs_emit(buf, "%d\n", sas_device_priv_data->ncq_prio_enable); } -static ssize_t ncq_cmd_prio_enable_store(struct device *dev, +static ssize_t sas_ncq_prio_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct scsi_device *sdev = to_scsi_device(dev); struct leapraid_sdev_priv *sas_device_priv_data = sdev->hostdata; - struct scsi_vpd *vpd_pg89; - int ncq_cmd_prio_enable; - bool ncq_supported; + bool enable; if (!sas_device_priv_data) { dev_err(&sdev->sdev_gendev, @@ -1887,44 +1893,63 @@ static ssize_t ncq_cmd_prio_enable_store(struct device *dev, return -EINVAL; } - if (kstrtoint(buf, 0, &ncq_cmd_prio_enable)) + if (kstrtobool(buf, &enable)) return -EINVAL; - if (ncq_cmd_prio_enable != 0 && ncq_cmd_prio_enable != 1) { - dev_err(&sdev->sdev_gendev, - "%s: Invalid NCQ cmd prio %d (0/1 only)\n", - __func__, ncq_cmd_prio_enable); + if (!sas_ata_ncq_prio_supported(sdev)) return -EINVAL; - } - rcu_read_lock(); - vpd_pg89 = rcu_dereference(sdev->vpd_pg89); - if (!vpd_pg89 || vpd_pg89->len < LEAPRAID_VPD_PG89_MIN_LEN) { - rcu_read_unlock(); - return -EINVAL; - } - - ncq_supported = (vpd_pg89->data[LEAPRAID_VPD_PG89_NCQ_BYTE_IDX] >> - LEAPRAID_VPD_PG89_NCQ_BIT_SHIFT) & - LEAPRAID_VPD_PG89_NCQ_BIT_MASK; - rcu_read_unlock(); - if (ncq_supported) - sas_device_priv_data->ncq_cmd_prio_enable = - ncq_cmd_prio_enable; + sas_device_priv_data->ncq_prio_enable = enable; return count; } static DEVICE_ATTR_RO(sas_device_handle); +static DEVICE_ATTR_RO(sas_ncq_prio_supported); +static DEVICE_ATTR_RW(sas_ncq_prio_enable); -static DEVICE_ATTR_RW(ncq_cmd_prio_enable); +static bool leapraid_sdev_is_sata(struct scsi_device *sdev) +{ + struct scsi_target *starget = sdev->sdev_target; + struct leapraid_starget_priv *starget_priv = starget->hostdata; + struct leapraid_sas_dev *sas_dev; + + if (!starget_priv) + return false; + + sas_dev = starget_priv->sas_dev; + return sas_dev && (sas_dev->dev_info & LEAPRAID_DEVTYP_SATA_DEV); +} static struct attribute *leapraid_sdev_attrs[] = { &dev_attr_sas_device_handle.attr, - &dev_attr_ncq_cmd_prio_enable.attr, + &dev_attr_sas_ncq_prio_supported.attr, + &dev_attr_sas_ncq_prio_enable.attr, NULL, }; -ATTRIBUTE_GROUPS(leapraid_sdev); +static umode_t leapraid_sdev_attr_is_visible(struct kobject *kobj, + struct attribute *attr, int i) +{ + struct device *dev = kobj_to_dev(kobj); + struct scsi_device *sdev = to_scsi_device(dev); + + if (attr == &dev_attr_sas_ncq_prio_supported.attr || + attr == &dev_attr_sas_ncq_prio_enable.attr) + if (!leapraid_sdev_is_sata(sdev)) + return 0; + + return attr->mode; +} + +static const struct attribute_group leapraid_sdev_attr_group = { + .attrs = leapraid_sdev_attrs, + .is_visible = leapraid_sdev_attr_is_visible, +}; + +static const struct attribute_group *leapraid_sdev_groups[] = { + &leapraid_sdev_attr_group, + NULL, +}; static struct scsi_host_template leapraid_driver_template = { .module = THIS_MODULE, From 764587d7d76846716a568dea7ae48be1c50ecc18 Mon Sep 17 00:00:00 2001 From: Finn Thain Date: Tue, 18 Aug 2026 11:00:24 +1000 Subject: [PATCH 344/641] scsi: MAINTAINERS: Leave the cumana_1 and oak drivers to the RISCPC maintainers The NCR5380 entry in MAINTAINERS includes drivers/scsi/arm/cumana_1.c and drivers/scsi/arm/oak.c. However, those two files are also covered by the drivers/scsi/arm/ pathname in the ARM/RISCPC entry. The latter entry is more effective than the former because, AIUI, neither Michael nor I have access to the necessary hardware. IMHO, such access is a pre-requisite for the 'maintainer' role for device drivers. To work on these particular drivers would require an old GCC compiler, having support for -march=armv3m, which is a problem for contributors. Cc: Michael Schmitz Cc: Russell King Cc: Arnd Bergmann Signed-off-by: Finn Thain Acked-by: Michael Schmitz Link: https://patch.msgid.link/935b08c0fb292888c06c2233570331f2ccadcd53.1787014824.git.fthain@linux-m68k.org Signed-off-by: Martin K. Petersen (Oracle) --- MAINTAINERS | 2 -- 1 file changed, 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index a8a58f059c49..95fd1a961cd5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18576,8 +18576,6 @@ L: linux-scsi@vger.kernel.org S: Maintained F: Documentation/scsi/g_NCR5380.rst F: drivers/scsi/NCR5380.* -F: drivers/scsi/arm/cumana_1.c -F: drivers/scsi/arm/oak.c F: drivers/scsi/atari_scsi.* F: drivers/scsi/dmx3191d.c F: drivers/scsi/g_NCR5380.* From df125bd16280b19835bfa2eef6504b505790f1f6 Mon Sep 17 00:00:00 2001 From: "Martin K. Petersen (Oracle)" Date: Sun, 23 Aug 2026 22:08:18 -0400 Subject: [PATCH 345/641] scsi: MAINTAINERS: Update my email address Use my kernel.org address for Linux development. Signed-off-by: Martin K. Petersen (Oracle) --- .mailmap | 3 +++ MAINTAINERS | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.mailmap b/.mailmap index 23eb9a4b04f4..e484232b32a1 100644 --- a/.mailmap +++ b/.mailmap @@ -553,6 +553,9 @@ Mark Brown Mark Starovoytov Markus Schneider-Pargmann Mark Yao +Martin K. Petersen +Martin K. Petersen +Martin K. Petersen Martin Kepplinger-Novakovic Martyna Szapar-Mudlaw Mathieu Othacehe diff --git a/MAINTAINERS b/MAINTAINERS index 95fd1a961cd5..9c768040658d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -24291,7 +24291,7 @@ F: include/scsi/sg.h SCSI SUBSYSTEM M: "James E.J. Bottomley" -M: "Martin K. Petersen" +M: "Martin K. Petersen" L: linux-scsi@vger.kernel.org S: Maintained Q: https://patchwork.kernel.org/project/linux-scsi/list/ @@ -24321,7 +24321,7 @@ F: drivers/target/target_core_user.c F: include/uapi/linux/target_core_user.h SCSI TARGET SUBSYSTEM -M: "Martin K. Petersen" +M: "Martin K. Petersen" L: linux-scsi@vger.kernel.org L: target-devel@vger.kernel.org S: Supported From 12e67eb89eb2b9516685c744d3f7de0a2d1bd701 Mon Sep 17 00:00:00 2001 From: Chen Changcheng Date: Mon, 27 Jul 2026 15:34:38 +0800 Subject: [PATCH 346/641] scsi: snic: Fix SCSI host leak on workqueue allocation failure In snic_add_host(), if scsi_add_host() succeeds but alloc_ordered_workqueue() fails, the function returns -ENOMEM with shost->work_q left as NULL. The caller's error path then calls snic_del_host(), which returns early when !shost->work_q without calling scsi_remove_host(). The Scsi_Host remains registered in sysfs as a zombie device even after the probe has failed. This causes: - The leaked host remains visible in /sys/class/scsi_host/ after probe failure, with state "running". - Subsequent SCSI host numbering is permanently shifted (the leaked host ID from ida_alloc() is never reclaimed). - Memory leak: the Scsi_Host allocation can never be freed because device_add() took a reference that can only be released by device_del() inside scsi_remove_host(). Fix by adding scsi_remove_host() in the workqueue allocation failure path inside snic_add_host(), undoing the successful scsi_add_host() before returning the error. This is cleaner than modifying snic_del_host() because snic_del_host() is called from a shared error label that also serves paths where snic_add_host() was never invoked. Reproducer (requires no real SNIC hardware): - Build CONFIG_SCSI_SNIC=y (built-in) - Add snic.test_mode=1 snic.inject_wq_fail=1 to kernel cmdline - Boot with a PCI device matching the snic driver (e.g. QEMU edu device, PCI ID 0x1234:0x11e8, temporarily added to the driver's PCI ID table) Before the fix: # /sys/class/scsi_host/ contains a zombie host0: $ cat /sys/class/scsi_host/host0/proc_name snic_scsi $ cat /sys/class/scsi_host/host0/state running # ata_piix gets host1, host2 (host0 stuck): scsi host1: ata_piix scsi host2: ata_piix After the fix: # host0 is properly freed and reused by ata_piix: scsi host0: ata_piix scsi host1: ata_piix # No zombie host in /sys/class/scsi_host/ Signed-off-by: Chen Changcheng Acked-by: Narsimhulu Musini Link: https://patch.msgid.link/20260727073438.209673-1-chenchangcheng@kylinos.cn Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/snic/snic_main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/scsi/snic/snic_main.c b/drivers/scsi/snic/snic_main.c index 82953e6a0915..cd638b4a4d7b 100644 --- a/drivers/scsi/snic/snic_main.c +++ b/drivers/scsi/snic/snic_main.c @@ -305,6 +305,7 @@ snic_add_host(struct Scsi_Host *shost, struct pci_dev *pdev) if (!shost->work_q) { SNIC_HOST_ERR(shost, "Failed to Create ScsiHost wq.\n"); + scsi_remove_host(shost); ret = -ENOMEM; } From 1035a8f63bae28e498b0e7b5ac91d749844a7158 Mon Sep 17 00:00:00 2001 From: Marouane El Moufid Date: Sun, 23 Aug 2026 13:55:48 +0000 Subject: [PATCH 347/641] ALSA: usb-audio: fix OOB write in snd_usbmidi_novation_output() snd_usbmidi_novation_output() lays out a two-byte header at transfer_buffer[0..1] and passes &transfer_buffer[2] together with a length of ep->max_transfer - 2 to snd_rawmidi_transmit(): count = snd_rawmidi_transmit(ep->ports[0].substream, &transfer_buffer[2], ep->max_transfer - 2); ep->max_transfer comes from the output endpoint's wMaxPacketSize via usb_maxpacket(). A malformed or malicious device can advertise a bulk OUT endpoint with a wMaxPacketSize of 1 - the USB core only clamps this value downwards - so ep->max_transfer becomes 1 and the count argument becomes -1. snd_rawmidi_transmit() passes the negative count on to __snd_rawmidi_transmit_peek(), where "if (count1 > count) count1 = count" leaves count1 negative; get_aligned_size() keeps it negative for a byte-stream substream, so the following memcpy(buffer, ..., count1) runs with a (size_t)-1 length and writes far past the transfer buffer, which was allocated with usb_alloc_coherent(ep->max_transfer). This is the same class of bug that was fixed for snd_usbmidi_akai_output() in commit 0970274613fb ("ALSA: usb-audio: fix OOB write in snd_usbmidi_akai_output()"); the novation output routine was left unguarded. Bail out when the endpoint cannot hold the two-byte header plus at least one payload byte. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Marouane El Moufid Link: https://patch.msgid.link/178749334830.543645.13722252148340572274@espilon.net Signed-off-by: Takashi Iwai --- sound/usb/midi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/usb/midi.c b/sound/usb/midi.c index f8996416c3be..8a9bc37f0b6e 100644 --- a/sound/usb/midi.c +++ b/sound/usb/midi.c @@ -875,6 +875,8 @@ static void snd_usbmidi_novation_output(struct snd_usb_midi_out_endpoint *ep, if (!ep->ports[0].active) return; + if (ep->max_transfer < 3) + return; transfer_buffer = urb->transfer_buffer; count = snd_rawmidi_transmit(ep->ports[0].substream, &transfer_buffer[2], From e72d5659a2606056a0c34af212b46a3275a55bbf Mon Sep 17 00:00:00 2001 From: Eckhart Mohr Date: Fri, 21 Aug 2026 16:44:34 +0200 Subject: [PATCH 348/641] ALSA: hda/realtek: Add quirk for TongFang XxAF5xxx Fix microphone detection on built in headphone jack for some devices Signed-off-by: Eckhart Mohr Cc: stable@vger.kernel.org Signed-off-by: Werner Sembach Link: https://patch.msgid.link/20260821144437.27233-1-wse@tuxedocomputers.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 5c4ea24e9651..b08d25fb0e17 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -8239,6 +8239,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1d05, 0x3031, "TongFang X6AR55xU", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1d05, 0x3033, "TongFang X6SP45xU", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1d05, 0x3034, "TongFang X6KK45xU", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1d05, 0x30ba, "TongFang XxAF5xxx", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1d17, 0x3288, "Haier Boyue G42", ALC269VC_FIXUP_ACER_VCOPPERBOX_PINS), SND_PCI_QUIRK(0x1d72, 0x1602, "RedmiBook", ALC255_FIXUP_XIAOMI_HEADSET_MIC), SND_PCI_QUIRK(0x1d72, 0x1701, "XiaomiNotebook Pro", ALC298_FIXUP_DELL1_MIC_NO_PRESENCE), From 46094a7708b7945cb7eba9eb887e3ea9757440a7 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Mon, 24 Aug 2026 12:49:10 +0200 Subject: [PATCH 349/641] locking: Revert switching guards to _irq_{disable,enable}() Revert commit 1b0866874833 ("locking: Switch to _irq_{disable,enable}() variants in cleanup guards"). While the guards are properly nested, not all wrapped code is nice, as already highlighted by that fair.c hunk. Syzbot found another instance of this pattern in posix_timer_delete(), which does spin_unlock_irq()+spin_lock_irq() inside scoped_guard(spinlock_irq). Combined with this patch, that goes sideways most spectacular. Undo this until we've developed stronger tools / debug for such issues. Fixes: 1b0866874833 ("locking: Switch to _irq_{disable,enable}() variants in cleanup guards") Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260824105523.GA4121620%40noisy.programming.kicks-ass.net --- include/linux/spinlock.h | 26 ++++++++++++++------------ kernel/sched/fair.c | 12 ++++++------ 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/include/linux/spinlock.h b/include/linux/spinlock.h index 799a8f7d2741..3d405cc4c121 100644 --- a/include/linux/spinlock.h +++ b/include/linux/spinlock.h @@ -572,12 +572,12 @@ DECLARE_LOCK_GUARD_1_ATTRS(raw_spinlock_nested, __acquires(_T), __releases(*(raw #define class_raw_spinlock_nested_constructor(_T) WITH_LOCK_GUARD_1_ATTRS(raw_spinlock_nested, _T) DEFINE_LOCK_GUARD_1(raw_spinlock_irq, raw_spinlock_t, - raw_spin_lock_irq_disable(_T->lock), - raw_spin_unlock_irq_enable(_T->lock)) + raw_spin_lock_irq(_T->lock), + raw_spin_unlock_irq(_T->lock)) DECLARE_LOCK_GUARD_1_ATTRS(raw_spinlock_irq, __acquires(_T), __releases(*(raw_spinlock_t **)_T)) #define class_raw_spinlock_irq_constructor(_T) WITH_LOCK_GUARD_1_ATTRS(raw_spinlock_irq, _T) -DEFINE_LOCK_GUARD_1_COND(raw_spinlock_irq, _try, raw_spin_trylock_irq_disable(_T->lock)) +DEFINE_LOCK_GUARD_1_COND(raw_spinlock_irq, _try, raw_spin_trylock_irq(_T->lock)) DECLARE_LOCK_GUARD_1_ATTRS(raw_spinlock_irq_try, __acquires(_T), __releases(*(raw_spinlock_t **)_T)) #define class_raw_spinlock_irq_try_constructor(_T) WITH_LOCK_GUARD_1_ATTRS(raw_spinlock_irq_try, _T) @@ -592,13 +592,14 @@ DECLARE_LOCK_GUARD_1_ATTRS(raw_spinlock_bh_try, __acquires(_T), __releases(*(raw #define class_raw_spinlock_bh_try_constructor(_T) WITH_LOCK_GUARD_1_ATTRS(raw_spinlock_bh_try, _T) DEFINE_LOCK_GUARD_1(raw_spinlock_irqsave, raw_spinlock_t, - raw_spin_lock_irq_disable(_T->lock), - raw_spin_unlock_irq_enable(_T->lock)) + raw_spin_lock_irqsave(_T->lock, _T->flags), + raw_spin_unlock_irqrestore(_T->lock, _T->flags), + unsigned long flags) DECLARE_LOCK_GUARD_1_ATTRS(raw_spinlock_irqsave, __acquires(_T), __releases(*(raw_spinlock_t **)_T)) #define class_raw_spinlock_irqsave_constructor(_T) WITH_LOCK_GUARD_1_ATTRS(raw_spinlock_irqsave, _T) DEFINE_LOCK_GUARD_1_COND(raw_spinlock_irqsave, _try, - raw_spin_trylock_irq_disable(_T->lock)) + raw_spin_trylock_irqsave(_T->lock, _T->flags)) DECLARE_LOCK_GUARD_1_ATTRS(raw_spinlock_irqsave_try, __acquires(_T), __releases(*(raw_spinlock_t **)_T)) #define class_raw_spinlock_irqsave_try_constructor(_T) WITH_LOCK_GUARD_1_ATTRS(raw_spinlock_irqsave_try, _T) @@ -617,13 +618,13 @@ DECLARE_LOCK_GUARD_1_ATTRS(spinlock_try, __acquires(_T), __releases(*(spinlock_t #define class_spinlock_try_constructor(_T) WITH_LOCK_GUARD_1_ATTRS(spinlock_try, _T) DEFINE_LOCK_GUARD_1(spinlock_irq, spinlock_t, - spin_lock_irq_disable(_T->lock), - spin_unlock_irq_enable(_T->lock)) + spin_lock_irq(_T->lock), + spin_unlock_irq(_T->lock)) DECLARE_LOCK_GUARD_1_ATTRS(spinlock_irq, __acquires(_T), __releases(*(spinlock_t **)_T)) #define class_spinlock_irq_constructor(_T) WITH_LOCK_GUARD_1_ATTRS(spinlock_irq, _T) DEFINE_LOCK_GUARD_1_COND(spinlock_irq, _try, - spin_trylock_irq_disable(_T->lock)) + spin_trylock_irq(_T->lock)) DECLARE_LOCK_GUARD_1_ATTRS(spinlock_irq_try, __acquires(_T), __releases(*(spinlock_t **)_T)) #define class_spinlock_irq_try_constructor(_T) WITH_LOCK_GUARD_1_ATTRS(spinlock_irq_try, _T) @@ -639,13 +640,14 @@ DECLARE_LOCK_GUARD_1_ATTRS(spinlock_bh_try, __acquires(_T), __releases(*(spinloc #define class_spinlock_bh_try_constructor(_T) WITH_LOCK_GUARD_1_ATTRS(spinlock_bh_try, _T) DEFINE_LOCK_GUARD_1(spinlock_irqsave, spinlock_t, - spin_lock_irq_disable(_T->lock), - spin_unlock_irq_enable(_T->lock)) + spin_lock_irqsave(_T->lock, _T->flags), + spin_unlock_irqrestore(_T->lock, _T->flags), + unsigned long flags) DECLARE_LOCK_GUARD_1_ATTRS(spinlock_irqsave, __acquires(_T), __releases(*(spinlock_t **)_T)) #define class_spinlock_irqsave_constructor(_T) WITH_LOCK_GUARD_1_ATTRS(spinlock_irqsave, _T) DEFINE_LOCK_GUARD_1_COND(spinlock_irqsave, _try, - spin_trylock_irq_disable(_T->lock)) + spin_trylock_irqsave(_T->lock, _T->flags)) DECLARE_LOCK_GUARD_1_ATTRS(spinlock_irqsave_try, __acquires(_T), __releases(*(spinlock_t **)_T)) #define class_spinlock_irqsave_try_constructor(_T) WITH_LOCK_GUARD_1_ATTRS(spinlock_irqsave_try, _T) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index 6d881e530f89..8dff37059faf 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -7253,7 +7253,7 @@ static bool distribute_cfs_runtime(struct cfs_bandwidth *cfs_b) * period the timer is deactivated until scheduling resumes; cfs_b->idle is * used to track this state. */ -static int do_sched_cfs_period_timer(struct cfs_bandwidth *cfs_b, int overrun) +static int do_sched_cfs_period_timer(struct cfs_bandwidth *cfs_b, int overrun, unsigned long flags) __must_hold(&cfs_b->lock) { int throttled; @@ -7288,10 +7288,10 @@ static int do_sched_cfs_period_timer(struct cfs_bandwidth *cfs_b, int overrun) * This check is repeated as we release cfs_b->lock while we unthrottle. */ while (throttled && cfs_b->runtime > 0) { - raw_spin_unlock_irq_enable(&cfs_b->lock); + raw_spin_unlock_irqrestore(&cfs_b->lock, flags); /* we can't nest cfs_b->lock while distributing bandwidth */ throttled = distribute_cfs_runtime(cfs_b); - raw_spin_lock_irq_disable(&cfs_b->lock); + raw_spin_lock_irqsave(&cfs_b->lock, flags); } /* @@ -7399,7 +7399,7 @@ static __always_inline void return_cfs_rq_runtime(struct cfs_rq *cfs_rq) static void do_sched_cfs_slack_timer(struct cfs_bandwidth *cfs_b) { /* confirm we're still not at a refresh boundary */ - scoped_guard(raw_spinlock_irq, &cfs_b->lock) { + scoped_guard(raw_spinlock_irqsave, &cfs_b->lock) { u64 runtime = 0, slice = sched_cfs_bandwidth_slice(); cfs_b->slack_started = false; @@ -7484,14 +7484,14 @@ static enum hrtimer_restart sched_cfs_period_timer(struct hrtimer *timer) int idle = 0; int count = 0; - guard(raw_spinlock_irq)(&cfs_b->lock); + CLASS(raw_spinlock_irqsave, cfsb_guard)(&cfs_b->lock); for (;;) { overrun = hrtimer_forward_now(timer, cfs_b->period); if (!overrun) break; - idle = do_sched_cfs_period_timer(cfs_b, overrun); + idle = do_sched_cfs_period_timer(cfs_b, overrun, cfsb_guard.flags); if (++count > 3) { u64 new, old = ktime_to_ns(cfs_b->period); From 7dd6343fc127935425a81a8f1989907996c2868d Mon Sep 17 00:00:00 2001 From: Younes Akhouayri Date: Sat, 22 Aug 2026 10:44:47 +0200 Subject: [PATCH 350/641] rust: num: restrict bool conversion to unsigned Bounded From turns true into 1. A signed Bounded with N = 1 can hold only -1 and 0. The current implementation can therefore create a value that breaks Bounded's invariant. Deref relies on that invariant and calls unreachable_unchecked() when it is broken, so safe Rust can reach undefined behavior. The other primitive conversions require the source and destination to have the same signedness. Treat bool as an unsigned one-bit value and allow conversions between bool and Bounded only when the backing integer type is unsigned. Fixes: 01e345e82ec3 ("rust: num: add Bounded integer wrapping type") Closes: https://lore.kernel.org/rust-for-linux/OzuVxu0--J-9@younes.io/ Cc: stable@vger.kernel.org Suggested-by: Alexandre Courbot Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Younes Akhouayri Reviewed-by: Alexandre Courbot Link: https://patch.msgid.link/20260822-fix-rust-bounded-from-bool-submit-v4-1-aa780bfe7f30@younes.io Signed-off-by: Miguel Ojeda --- rust/kernel/num/bounded.rs | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/rust/kernel/num/bounded.rs b/rust/kernel/num/bounded.rs index d192610a687d..2a2b0a4bca5e 100644 --- a/rust/kernel/num/bounded.rs +++ b/rust/kernel/num/bounded.rs @@ -13,7 +13,10 @@ use core::{ }; use kernel::{ - num::Integer, + num::{ + Integer, + Unsigned, // + }, prelude::*, // }; @@ -174,13 +177,16 @@ fn fits_within(value: T, num_bits: u32) -> bool { /// // `u8` (regardless of the passed value). /// // let _ = Bounded::::from(10u8); /// -/// // Booleans can be converted into single-bit `Bounded`s. +/// // Booleans can be converted into unsigned `Bounded`s. /// /// let v = Bounded::::from(false); /// assert_eq!(v.get(), 0); /// /// let v = Bounded::::from(true); /// assert_eq!(v.get(), 1); +/// +/// // This does not build because `i8` is signed. +/// // let _ = Bounded::::from(true); /// ``` /// /// Infallible conversions from a [`Bounded`] to a primitive integer are also supported, and @@ -203,12 +209,16 @@ fn fits_within(value: T, num_bits: u32) -> bool { /// let _v = Bounded::::new::<10>(); /// // assert_eq!(u8::from(_v), 10); /// -/// // Single-bit `Bounded`s can be converted into a boolean. +/// // Unsigned single-bit `Bounded`s can be converted into a boolean. /// let v = Bounded::::new::<1>(); /// assert_eq!(bool::from(v), true); /// /// let v = Bounded::::new::<0>(); /// assert_eq!(bool::from(v), false); +/// +/// // This does not build because `i8` is signed. +/// // let v = Bounded::::new::<-1>(); +/// // let _ = bool::from(v); /// ``` /// /// Fallible conversions from any primitive integer to any [`Bounded`] are also supported using the @@ -1109,31 +1119,33 @@ impl_into_primitive!( i8 i16 i32 i64 isize ); -// Single-bit `Bounded`s can be converted from/to a boolean. +// Unsigned single-bit `Bounded`s can be converted to a boolean. impl From> for bool where - T: Integer + Zeroable, + T: Integer + Zeroable, { fn from(value: Bounded) -> Self { value.get() != Zeroable::zeroed() } } +// Booleans can be converted to unsigned `Bounded`s. + impl From for Bounded where - T: Integer + From, + T: Integer + From, { fn from(value: bool) -> Self { - // SAFETY: A boolean can be represented using a single bit, and thus fits within any - // integer type for any `N` > 0. + // SAFETY: A boolean is represented by `0` or `1`, so it fits within any valid unsigned + // `Bounded` width. unsafe { Self::__new(T::from(value)) } } } impl Bounded where - T: Integer + Zeroable, + T: Integer + Zeroable, { /// Converts this [`Bounded`] into a [`bool`]. /// From eb049bdbf2b98d103d93daef44a5e1a0164d01eb Mon Sep 17 00:00:00 2001 From: Anthony Iliopoulos Date: Thu, 20 Aug 2026 14:36:51 +0200 Subject: [PATCH 351/641] xtensa: remove unused setup_profiling_timer function setup_profiling_timer() is not used by any code at this point. Since a default weak implementation exists, there is no need to keep this arch-specific definition around. Remove it along with the now-redundant profile header includes. Signed-off-by: Anthony Iliopoulos Message-ID: <20260820123711.142005-1-ailiop@suse.com> Signed-off-by: Max Filippov --- arch/xtensa/kernel/smp.c | 7 ------- arch/xtensa/kernel/time.c | 1 - 2 files changed, 8 deletions(-) diff --git a/arch/xtensa/kernel/smp.c b/arch/xtensa/kernel/smp.c index 94a23f100726..e4c25d0c2158 100644 --- a/arch/xtensa/kernel/smp.c +++ b/arch/xtensa/kernel/smp.c @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include @@ -459,12 +458,6 @@ void show_ipi_list(struct seq_file *p, int prec) } } -int setup_profiling_timer(unsigned int multiplier) -{ - pr_debug("setup_profiling_timer %d\n", multiplier); - return 0; -} - /* TLB flush functions */ struct flush_data { diff --git a/arch/xtensa/kernel/time.c b/arch/xtensa/kernel/time.c index 1c3dfea843ec..09fda6a3fe3b 100644 --- a/arch/xtensa/kernel/time.c +++ b/arch/xtensa/kernel/time.c @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include From c9e17e381e021536a9f0fe36f4c9c693d6c0f27c Mon Sep 17 00:00:00 2001 From: Daniel Palmer Date: Mon, 13 Jul 2026 22:39:19 +0900 Subject: [PATCH 352/641] rtc: msc313: Select by default on MSTARV7 All of the ARCH_MSTARV7 chips have this RTC so it's more likely ARCH_MSTARV7 wants it than doesn't. At the same time fix up the indent on the depends on line that looks like it got converted from a tab to spaces at some point. Signed-off-by: Daniel Palmer Link: https://patch.msgid.link/20260713133919.1356748-1-daniel@thingy.jp Signed-off-by: Alexandre Belloni --- drivers/rtc/Kconfig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index d23a0fbe8d89..05b9233b9418 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -2107,7 +2107,8 @@ config RTC_DRV_MACSMC config RTC_DRV_MSC313 tristate "MStar MSC313 RTC" - depends on ARCH_MSTARV7 || COMPILE_TEST + depends on ARCH_MSTARV7 || COMPILE_TEST + default ARCH_MSTARV7 help If you say yes here you get support for the Mstar MSC313e On-Chip Real Time Clock. From 60e73c07b3fc12958a63de77b482d18bda413a6a Mon Sep 17 00:00:00 2001 From: Shuming Fan Date: Mon, 24 Aug 2026 18:39:36 +0800 Subject: [PATCH 353/641] ASoC: rt766: add RT766/RT767 VA1 device IDs This patch adds the RT766/RT767 VA1 device IDs, which support SoundWire v1.3. Signed-off-by: Shuming Fan Link: https://patch.msgid.link/20260824103936.3524080-1-shumingf@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt766-sdca-sdw.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/codecs/rt766-sdca-sdw.c b/sound/soc/codecs/rt766-sdca-sdw.c index 5d60c3bcc6cd..8e76bf725ccc 100644 --- a/sound/soc/codecs/rt766-sdca-sdw.c +++ b/sound/soc/codecs/rt766-sdca-sdw.c @@ -233,6 +233,8 @@ static void rt766_sdca_sdw_remove(struct sdw_slave *slave) static const struct sdw_device_id rt766_sdca_id[] = { SDW_SLAVE_ENTRY_EXT(0x025d, 0x766, 0x3, 0x1, 0), SDW_SLAVE_ENTRY_EXT(0x025d, 0x767, 0x3, 0x1, 0), + SDW_SLAVE_ENTRY_EXT(0x025d, 0x766, 0x4, 0x1, 0), + SDW_SLAVE_ENTRY_EXT(0x025d, 0x767, 0x4, 0x1, 0), {}, }; MODULE_DEVICE_TABLE(sdw, rt766_sdca_id); From 902caade3cfd60f99bf71b355e7c86344bd831e5 Mon Sep 17 00:00:00 2001 From: Wei-Lin Chang Date: Fri, 14 Aug 2026 23:24:57 +0100 Subject: [PATCH 354/641] arm64: ptdump: Make note_page_flush() range aware note_page_flush() calls note_page() with addr == 0 and level == -1 to dump the last row of a ptdump. addr == 0 (1 << 64 wrapped around) renders a huge region with enormous size for address spaces with IA bits < 64. For example the stage-2 page tables and the EFI runtime page table. More importantly, the last region of the address space and everything after the address space up to 1 << 64 are merged into one row of output. If the last region within the address space is valid, it will appear to remain valid up to 1 << 64 with the same attributes. Currently only the EFI runtime ptdump is affected by this, but KVM will soon fix its stage-2 ptdump by using note_page_flush(). Here is an example of an EFI runtime ptdump (last row): 0x0000008000000000-0x0000000000000000 17179868672G PGD With this patch: 0x0000008000000000-0x0001000000000000 261632G PGD To fix this, cache the end address of a ptdump in ptdump_pg_state so note_page_flush() can call the final note_page() with the correct end address. Fixes: 9d80448ac92b ("efi/arm64: Add debugfs node to dump UEFI runtime page tables") Signed-off-by: Wei-Lin Chang Reviewed-by: Marc Zyngier Signed-off-by: Will Deacon --- arch/arm64/include/asm/ptdump.h | 2 ++ arch/arm64/mm/ptdump.c | 14 +++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/arch/arm64/include/asm/ptdump.h b/arch/arm64/include/asm/ptdump.h index 5b374a6ab34a..1b743de7d89e 100644 --- a/arch/arm64/include/asm/ptdump.h +++ b/arch/arm64/include/asm/ptdump.h @@ -52,6 +52,8 @@ struct ptdump_pg_state { const struct addr_marker *marker; const struct mm_struct *mm; unsigned long start_address; + /* exclusive end, ULONG_MAX represents an end at 1 << 64 */ + unsigned long end_address; int level; ptval_t current_prot; bool check_wx; diff --git a/arch/arm64/mm/ptdump.c b/arch/arm64/mm/ptdump.c index 1c20144700d7..eab400e744d9 100644 --- a/arch/arm64/mm/ptdump.c +++ b/arch/arm64/mm/ptdump.c @@ -278,9 +278,19 @@ void note_page_pgd(struct ptdump_state *pt_st, unsigned long addr, pgd_t pgd) void note_page_flush(struct ptdump_state *pt_st) { + struct ptdump_pg_state *st = container_of(pt_st, struct ptdump_pg_state, ptdump); + unsigned long end = st->end_address; pte_t pte_zero = {0}; - note_page(pt_st, 0, -1, pte_val(pte_zero)); + /* + * Address spaces that end at 1 << 64 have end_address == ULONG_MAX, + * but note_page() expects the exclusive end. In this case adjust end + * to the wraparound value 0. + */ + if (end == ULONG_MAX) + end = 0; + + note_page(pt_st, end, -1, pte_val(pte_zero)); } static void arm64_ptdump_walk_pgd(struct ptdump_state *st, struct mm_struct *mm) @@ -303,6 +313,7 @@ void ptdump_walk(struct seq_file *s, struct ptdump_info *info) .marker = info->markers, .mm = info->mm, .pg_level = &kernel_pg_levels[0], + .end_address = end, .level = -1, .ptdump = { .note_page_pte = note_page_pte, @@ -344,6 +355,7 @@ bool ptdump_check_wx(void) { -1, NULL}, }, .pg_level = &kernel_pg_levels[0], + .end_address = ~0UL, .level = -1, .check_wx = true, .ptdump = { From beca1b97a21d81eae9cbd267ecc44b5d2193e60a Mon Sep 17 00:00:00 2001 From: Wei-Lin Chang Date: Fri, 14 Aug 2026 23:24:58 +0100 Subject: [PATCH 355/641] KVM: arm64: ptdump: Flush the last region Currently the stage-2 ptdump calls note_page() at each leaf entry visit. This simply misses the output of the last region, because note_page() only dumps output when it detects a change in level/prot, or when the walk enters a next marker section. The last region in the guest IPA space with the same level/prot is not dumped since there is no change after it. Call note_page_flush() to dump the final region. note_page_flush() uses ptdump_pg_state.end_address to call the final note_page(), so also provide the end address. Also change the second marker's start address to ULONG_MAX so we never cross it. This avoids dumping redundant marker names (which are NULL), and advancing beyond the end of the marker array. Fixes: 7c4f73548ed1 ("KVM: arm64: Register ptdump with debugfs on guest creation") Reported-by: Sashiko AI Closes: https://lore.kernel.org/kvmarm/20260630122758.891011F00A3A@smtp.kernel.org/ Reviewed-by: Dev Jain Tested-by: Dev Jain Signed-off-by: Wei-Lin Chang Reviewed-by: Marc Zyngier Signed-off-by: Will Deacon --- arch/arm64/kvm/ptdump.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/arch/arm64/kvm/ptdump.c b/arch/arm64/kvm/ptdump.c index c9140e22abcf..69899797dbad 100644 --- a/arch/arm64/kvm/ptdump.c +++ b/arch/arm64/kvm/ptdump.c @@ -130,7 +130,7 @@ static struct kvm_ptdump_guest_state *kvm_ptdump_parser_create(struct kvm_s2_mmu } st->ipa_marker[0].name = "Guest IPA"; - st->ipa_marker[1].start_address = BIT(pgtable->ia_bits); + st->ipa_marker[1].start_address = ULONG_MAX; st->mmu = mmu; return st; @@ -148,18 +148,21 @@ static int kvm_ptdump_guest_show(struct seq_file *m, void *unused) .flags = KVM_PGTABLE_WALK_LEAF, }; + guard(write_lock)(&kvm->mmu_lock); st->parser_state = (struct ptdump_pg_state) { .marker = &st->ipa_marker[0], + .end_address = BIT(mmu->pgt->ia_bits), .level = -1, .pg_level = &st->level[0], .seq = m, }; - write_lock(&kvm->mmu_lock); ret = kvm_pgtable_walk(mmu->pgt, 0, BIT(mmu->pgt->ia_bits), &walker); - write_unlock(&kvm->mmu_lock); + if (ret) + return ret; + note_page_flush(&st->parser_state.ptdump); - return ret; + return 0; } static int kvm_ptdump_guest_open(struct inode *m, struct file *file) From b8f070ac3167595069feb1f794c127b805115645 Mon Sep 17 00:00:00 2001 From: Vladimir Murzin Date: Wed, 19 Aug 2026 15:48:59 +0100 Subject: [PATCH 356/641] arm64: process: Fix context switching MTE store-only tag check SCTLR_EL1.TCSO0 is set when user opt-in for MTE store-only tag check mode. However, it is not part of SCTLR_USER_MASK which imply that on context switch we never clear SCTLR_EL1.TCSO0, so we are leaking that setting into another task. Fix that by including SCTLR_EL1_TCSO0_MASK into SCTLR_USER_MASK Fixes: 4d51ff5bba00 ("arm64/kernel: Support store-only mte tag check") Signed-off-by: Vladimir Murzin Reviewed-by: Catalin Marinas Reviewed-by: Yeoreum Yun Signed-off-by: Will Deacon --- arch/arm64/include/asm/processor.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/include/asm/processor.h b/arch/arm64/include/asm/processor.h index c2a627f39314..6dfbcacd9ba0 100644 --- a/arch/arm64/include/asm/processor.h +++ b/arch/arm64/include/asm/processor.h @@ -264,7 +264,7 @@ static inline void task_set_sve_vl_onexec(struct task_struct *task, #define SCTLR_USER_MASK \ (SCTLR_ELx_ENIA | SCTLR_ELx_ENIB | SCTLR_ELx_ENDA | SCTLR_ELx_ENDB | \ - SCTLR_EL1_TCF0_MASK) + SCTLR_EL1_TCF0_MASK | SCTLR_EL1_TCSO0_MASK) static inline void arch_thread_struct_whitelist(unsigned long *offset, unsigned long *size) From 3b2c5d35cf4398edf47c4a3ad076838654a30015 Mon Sep 17 00:00:00 2001 From: Seongjae Jeong Date: Mon, 24 Aug 2026 02:32:31 +0000 Subject: [PATCH 357/641] f2fs: use adjusted write range after f2fs_write_checks() generic_write_checks() in f2fs_write_checks() can adjust iocb->ki_pos for append writes and truncate the iterator to limit the number of bytes to write. In f2fs_file_write_iter(), the pinned-file overwrite check currently uses the position and count saved before f2fs_write_checks(), so it can check a range different from the actual write range. The forced buffered I/O cleanup also uses orig_pos saved before f2fs_write_checks(). For O_APPEND writes, this can make the cleanup flush and invalidate the wrong page cache range. Move the pinned-file overwrite check after f2fs_write_checks() and use the adjusted iocb->ki_pos and iov_iter_count(from). Also save the adjusted write position and use it for the forced buffered I/O cleanup. Fixes: 3fdd89b452c2 ("f2fs: prevent writing without fallocate() for pinned files") Fixes: 92318f20d703 ("f2fs: preserve direct write semantics when buffering is forced") Signed-off-by: Seongjae Jeong Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/file.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index d82be8c1502a..d440231b8cb9 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -5641,9 +5641,8 @@ static ssize_t f2fs_file_write_iter(struct kiocb *iocb, struct iov_iter *from) bool dio; bool may_need_sync = true; int preallocated; - const loff_t pos = iocb->ki_pos; - const ssize_t count = iov_iter_count(from); ssize_t ret; + loff_t bufio_start_pos; if (unlikely(f2fs_cp_error(F2FS_I_SB(inode)))) { ret = -EIO; @@ -5664,15 +5663,17 @@ static ssize_t f2fs_file_write_iter(struct kiocb *iocb, struct iov_iter *from) inode_lock(inode); } + ret = f2fs_write_checks(iocb, from); + if (ret <= 0) + goto out_unlock; + if (f2fs_is_pinned_file(inode) && - !f2fs_overwrite_io(inode, pos, count)) { + !f2fs_overwrite_io(inode, iocb->ki_pos, iov_iter_count(from))) { ret = -EIO; goto out_unlock; } - ret = f2fs_write_checks(iocb, from); - if (ret <= 0) - goto out_unlock; + bufio_start_pos = iocb->ki_pos; /* Determine whether we will do a direct write or a buffered write. */ dio = f2fs_should_use_dio(inode, iocb, from); @@ -5727,8 +5728,8 @@ out: */ if (ret > 0 && !dio && (iocb->ki_flags & IOCB_DIRECT)) f2fs_flush_buffered_write(iocb->ki_filp->f_mapping, - orig_pos, - orig_pos + ret - 1); + bufio_start_pos, + bufio_start_pos + ret - 1); return ret; } From 909a3f0e9d8b0d8001cf6e99808f88eca8e44328 Mon Sep 17 00:00:00 2001 From: Cheng Lingfei Date: Mon, 24 Aug 2026 20:45:46 +0800 Subject: [PATCH 358/641] docs: cgroup-v2: fix misc.events key format description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In misc cgroup, misc.events does not output a simple "max" key. Instead, each registered misc resource outputs a separate key suffixed with ".max" (i.e., ".max"). Update the documentation to clarify that the entry key is ".max". Suggested-by: Michal Koutný Signed-off-by: Cheng Lingfei Signed-off-by: Tejun Heo --- Documentation/admin-guide/cgroup-v2.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst index 0a4f4eb15626..2ec582985b5b 100644 --- a/Documentation/admin-guide/cgroup-v2.rst +++ b/Documentation/admin-guide/cgroup-v2.rst @@ -3019,7 +3019,7 @@ Miscellaneous controller provides 3 interface files. If two misc resources (res_ change in this file generates a file modified event. All fields in this file are hierarchical. - max + .max The number of times the cgroup's resource usage was about to go over the max boundary. From 2bf404b1bd94f50747443234c0a4a5e18e2569bf Mon Sep 17 00:00:00 2001 From: Guopeng Zhang Date: Mon, 24 Aug 2026 10:01:38 +0800 Subject: [PATCH 359/641] selftests/cgroup: Drop invalid boot isolation comparison check_isolcpus() clears ISOLCPUS before rebuilding it from sched domain data. Comparing that empty value with /sys/devices/system/cpu/isolated makes the test fail whenever isolcpus=domain is present. That sysfs file is generated from HK_TYPE_DOMAIN_BOOT and does not change when cpuset updates HK_TYPE_DOMAIN. Re-reading it cannot validate dynamic housekeeping updates. The cpuset.cpus.isolated and sched domain checks already cover the two dynamic interfaces, so remove the invalid comparison. This can be reproduced on a kernel booted with isolcpus=domain,15: # tools/testing/selftests/cgroup/test_cpuset_prs.sh The test fails its first state-matrix isolation check before the change and continues past that check afterward. Fixes: 6df415aa46ec ("cgroup/cpuset: Defer housekeeping_update() calls from CPU hotplug to workqueue") Signed-off-by: Guopeng Zhang Reviewed-by: Waiman Long Signed-off-by: Tejun Heo --- tools/testing/selftests/cgroup/test_cpuset_prs.sh | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tools/testing/selftests/cgroup/test_cpuset_prs.sh b/tools/testing/selftests/cgroup/test_cpuset_prs.sh index da8f7b920178..fdb3185570d4 100755 --- a/tools/testing/selftests/cgroup/test_cpuset_prs.sh +++ b/tools/testing/selftests/cgroup/test_cpuset_prs.sh @@ -797,7 +797,6 @@ check_isolcpus() EXPECTED_ISOLCPUS=$1 ISCPUS=${CGROUP2}/cpuset.cpus.isolated ISOLCPUS=$(cat $ISCPUS) - HKICPUS=$(cat /sys/devices/system/cpu/isolated) LASTISOLCPU= SCHED_DOMAINS=/sys/kernel/debug/sched/domains if [[ $EXPECTED_ISOLCPUS = . ]] @@ -835,11 +834,6 @@ check_isolcpus() ISOLCPUS= EXPECTED_ISOLCPUS=$EXPECTED_SDOMAIN - # - # The inverse of HK_TYPE_DOMAIN cpumask in $HKICPUS should match $ISOLCPUS - # - [[ "$ISOLCPUS" != "$HKICPUS" ]] && return 1 - # # Use the sched domain in debugfs to check isolated CPUs, if available # From 6c37d7e074a4be1ba8da59f4ed5df8977b3daa43 Mon Sep 17 00:00:00 2001 From: Guopeng Zhang Date: Mon, 24 Aug 2026 10:01:39 +0800 Subject: [PATCH 360/641] cgroup/cpuset: Preserve boot-isolated CPUs on partition release isolated_cpus tracks CPUs isolated with isolcpus= as well as CPUs in isolated cpuset partitions. When an isolated partition is released, isolated_cpus_update() removes its whole CPU mask. This also clears CPUs which were already isolated at boot. This can be reproduced on a cgroup v2 system booted with isolcpus=domain,15: cd /sys/fs/cgroup echo +cpuset > cgroup.subtree_control mkdir cpuset-repro echo 15 > cpuset-repro/cpuset.cpus echo isolated > cpuset-repro/cpuset.cpus.partition echo member > cpuset-repro/cpuset.cpus.partition cat cpuset.cpus.isolated CPU 15 is absent before the change. It must remain in cpuset.cpus.isolated after the partition is released. Update isolated_cpus one CPU at a time and keep CPUs outside the boot-time domain housekeeping mask isolated. Fixes: c188f33c864e ("cgroup/cpuset: Account for boot time isolated CPUs") Signed-off-by: Guopeng Zhang Acked-by: Waiman Long Signed-off-by: Tejun Heo --- kernel/cgroup/cpuset.c | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c index 0c34013eda8e..8f24171b6055 100644 --- a/kernel/cgroup/cpuset.c +++ b/kernel/cgroup/cpuset.c @@ -1259,6 +1259,28 @@ static void reset_partition_data(struct cpuset *cs) cpumask_copy(cs->effective_cpus, parent->effective_cpus); } +/* Return true if isolated_cpus changes. */ +static bool isolated_cpu_update(int new_prs, int cpu) +{ + lockdep_assert_held(&callback_lock); + lockdep_assert_held(&cpuset_mutex); + + if (new_prs == PRS_ISOLATED) { + if (cpumask_test_cpu(cpu, isolated_cpus)) + return false; + cpumask_set_cpu(cpu, isolated_cpus); + return true; + } + + /* CPUs isolated at boot must remain isolated. */ + if (!cpumask_test_cpu(cpu, + housekeeping_cpumask(HK_TYPE_DOMAIN_BOOT)) || + !cpumask_test_cpu(cpu, isolated_cpus)) + return false; + cpumask_clear_cpu(cpu, isolated_cpus); + return true; +} + /* * isolated_cpus_update - Update the isolated_cpus mask * @old_prs: old partition_root_state @@ -1267,19 +1289,16 @@ static void reset_partition_data(struct cpuset *cs) */ static void isolated_cpus_update(int old_prs, int new_prs, struct cpumask *xcpus) { + bool updated = false; + int cpu; + WARN_ON_ONCE(old_prs == new_prs); lockdep_assert_held(&callback_lock); lockdep_assert_held(&cpuset_mutex); - if (new_prs == PRS_ISOLATED) { - if (cpumask_subset(xcpus, isolated_cpus)) - return; - cpumask_or(isolated_cpus, isolated_cpus, xcpus); - } else { - if (!cpumask_intersects(xcpus, isolated_cpus)) - return; - cpumask_andnot(isolated_cpus, isolated_cpus, xcpus); - } - update_housekeeping = true; + for_each_cpu(cpu, xcpus) + updated |= isolated_cpu_update(new_prs, cpu); + if (updated) + update_housekeeping = true; } /* From 87d347a8c8545a9234d1dd215023064413284c34 Mon Sep 17 00:00:00 2001 From: Guopeng Zhang Date: Mon, 24 Aug 2026 10:01:40 +0800 Subject: [PATCH 361/641] selftests/cgroup: Add test for preserving boot-isolated CPUs Put a CPU isolated at boot into an isolated partition, change the partition back to member and check that the CPU remains isolated. Signed-off-by: Guopeng Zhang Reviewed-by: Waiman Long Signed-off-by: Tejun Heo --- .../selftests/cgroup/test_cpuset_prs.sh | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/tools/testing/selftests/cgroup/test_cpuset_prs.sh b/tools/testing/selftests/cgroup/test_cpuset_prs.sh index fdb3185570d4..131d8b4551ef 100755 --- a/tools/testing/selftests/cgroup/test_cpuset_prs.sh +++ b/tools/testing/selftests/cgroup/test_cpuset_prs.sh @@ -1155,6 +1155,63 @@ test_isolated() pause 0.05 } +# +# Select an online CPU isolated from scheduler domains at boot. +# $1: test name used in the skip message +# +get_boot_isolated_cpu() +{ + TEST_NAME=$1 + BOOT_ISOLATED_FILE=/sys/devices/system/cpu/isolated + + [[ -r $BOOT_ISOLATED_FILE ]] || { + echo "$TEST_NAME test SKIPPED: boot isolation state unavailable" + return 1 + } + BOOT_CPUS=$(cat $BOOT_ISOLATED_FILE) + [[ -n "$BOOT_CPUS" ]] || { + echo "$TEST_NAME test SKIPPED: no boot-isolated CPU" + return 1 + } + + BOOT_CPU=$(echo "$BOOT_CPUS" | sed -e 's/[,-].*//') + CPU_ONLINE=/sys/devices/system/cpu/cpu${BOOT_CPU}/online + [[ ! -e $CPU_ONLINE || $(cat $CPU_ONLINE) -eq 1 ]] || { + echo "$TEST_NAME test SKIPPED: CPU $BOOT_CPU is offline" + return 1 + } +} + +# +# A CPU isolated at boot must stay isolated after it is released by a dynamic +# isolated partition. +# +test_boot_isolated() +{ + TEST_NAME="Boot-isolated CPU partition release" + get_boot_isolated_cpu "$TEST_NAME" || return 0 + echo "Running $TEST_NAME test ..." + + cd $CGROUP2/test + echo member > cpuset.cpus.partition + echo $BOOT_CPU > cpuset.cpus + [[ $(cat cpuset.cpus.effective) = "$BOOT_CPU" ]] || { + echo "$TEST_NAME test SKIPPED: CPU $BOOT_CPU is unavailable" + echo "" > cpuset.cpus + cd $CGROUP2 + return 0 + } + test_partition isolated + test_partition member + check_isolcpus "." || { + echo "Boot-isolated CPU $BOOT_CPU was lost after partition release" + exit 1 + } + echo "" > cpuset.cpus + cd $CGROUP2 + echo "$TEST_NAME test PASSED." +} + # # Wait for inotify event for the given file and read it # $1: cgroup file to wait for @@ -1226,5 +1283,6 @@ trap cleanup 0 2 3 6 run_state_test TEST_MATRIX run_remote_state_test REMOTE_TEST_MATRIX test_isolated +test_boot_isolated test_inotify echo "All tests PASSED." From 6586705bc2dc06908309bf65d79efa53a347c9f0 Mon Sep 17 00:00:00 2001 From: Tao Cui Date: Mon, 24 Aug 2026 21:21:16 +0800 Subject: [PATCH 362/641] docs/sched_ext: document that cgroup CPU knobs are scheduler-dependent The scheduler core communicates the initial cpu controller settings to the BPF scheduler through ops.cgroup_init() and reports subsequent changes through the corresponding ops.cgroup_set_*() callbacks. Whether and how a knob takes effect is up to the loaded scheduler: it may implement the corresponding callback partially or not at all, so cpu.max, cpu.weight and friends can silently have no effect. Document this in the basics section of sched-ext.rst. Signed-off-by: Tao Cui Reviewed-by: Andrea Righi Signed-off-by: Tejun Heo --- Documentation/scheduler/sched-ext.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Documentation/scheduler/sched-ext.rst b/Documentation/scheduler/sched-ext.rst index 35b550671ca7..794ae80b3ba3 100644 --- a/Documentation/scheduler/sched-ext.rst +++ b/Documentation/scheduler/sched-ext.rst @@ -242,6 +242,21 @@ optional. The following modified excerpt is from .name = "simple", }; +Scheduler-Dependent Knobs +------------------------- + +The fair-class scheduler enforces CPU controller settings such as +``cpu.max``, ``cpu.weight`` and ``cpu.idle``. For sched_ext tasks, the +scheduler core communicates these settings to the BPF scheduler +through ``ops.cgroup_init()`` and reports subsequent changes through +the corresponding ``ops.cgroup_set_*()`` callbacks. Similarly, per-task +nice changes are converted to weights and reported through +``ops.set_weight()``. + +Each BPF scheduler is responsible for implementing the scheduling +semantics of these settings and may choose to ignore them. Consult the +loaded scheduler's documentation before relying on these controls. + Dispatch Queues --------------- From cf9c8aaea0d47410df8708bec195889f3a85cd3c Mon Sep 17 00:00:00 2001 From: Yao Kai Date: Mon, 24 Aug 2026 11:58:52 +0800 Subject: [PATCH 363/641] workqueue: Fix unbound pool lifetime for pending pwqs KASAN reports a use-after-free of an unbound worker_pool in node_activate_pending_pwq(): BUG: KASAN: slab-use-after-free in _raw_spin_trylock+0x6d/0x120 Read of size 4 at addr ffff8880089ce000 by task kworker/u22:0/318 CPU: 1 UID: 0 PID: 318 Comm: kworker/u22:0 Not tainted 7.2.0 #1 PREEMPT(lazy) Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014 Workqueue: 0x0 (flush-8:0) Call Trace: dump_stack_lvl+0x53/0x70 print_report+0xce/0x610 kasan_report+0xce/0x100 _raw_spin_trylock+0x6d/0x120 pwq_dec_nr_in_flight+0x4b4/0xcb0 process_one_work+0x921/0x11a0 worker_thread+0x4d0/0xd20 kthread+0x2de/0x3c0 ret_from_fork+0x3aa/0x620 ret_from_fork_asm+0x1a/0x30 Allocated by task 311: alloc_pwq+0x439/0xca0 apply_wqattrs_prepare+0x75e/0xd10 apply_workqueue_attrs_locked+0x44/0xa0 wq_nice_store+0x350/0x450 Freed by task 0: kfree+0x127/0x3b0 rcu_core+0x523/0x1780 handle_softirqs+0x1b3/0x610 Last potentially related work creation: put_unbound_pool+0x3f3/0x7d0 pwq_release_workfn+0x494/0x8e0 kthread_worker_fn+0x1ff/0x790 Canceling the last inactive work skips pwq_dec_nr_active(), so an empty pwq can remain on pending_pwqs when its refcnt reaches zero. pwq_release_workfn() currently puts the pool before removing that pwq. If this drops the last pool reference, the pool can be RCU-freed while the pwq remains reachable, and node_activate_pending_pwq() may trylock the freed pool->lock. Remove the pwq from pending_pwqs before putting the pool. Fixes: 5797b1c18919 ("workqueue: Implement system-wide nr_active enforcement for unbound workqueues") Cc: stable@vger.kernel.org Signed-off-by: Yao Kai Signed-off-by: Tejun Heo --- kernel/workqueue.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/kernel/workqueue.c b/kernel/workqueue.c index f2aed36cf7c0..0ee73dcd4a14 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -5294,12 +5294,6 @@ static void pwq_release_workfn(struct kthread_work *work) mutex_unlock(&wq->mutex); } - if (!is_percpu_pool(pool)) { - mutex_lock(&wq_pool_mutex); - put_unbound_pool(pool); - mutex_unlock(&wq_pool_mutex); - } - if (!list_empty(&pwq->pending_node)) { struct wq_node_nr_active *nna = wq_node_nr_active(pwq->wq, pwq->pool->node); @@ -5309,6 +5303,12 @@ static void pwq_release_workfn(struct kthread_work *work) raw_spin_unlock_irq(&nna->lock); } + if (!is_percpu_pool(pool)) { + mutex_lock(&wq_pool_mutex); + put_unbound_pool(pool); + mutex_unlock(&wq_pool_mutex); + } + kfree_rcu(pwq, rcu); /* From 1704aaaf5d22bc765c168402350d191e24e245bc Mon Sep 17 00:00:00 2001 From: Deepanshu Kartikey Date: Mon, 24 Aug 2026 20:16:53 +0530 Subject: [PATCH 364/641] eventfs: Initialize ei->children and ei->list in init_ei() eventfs_create_dir() allocates the eventfs_inode and initializes it with init_ei(). But this does not initialize the eventfs_inode list_heads. If the eventfs_create_dir() fails due to memory pressure, it will call free_ei() before it initialized the lists, and that checks to make sure the eventfs_inode has no children. But because the list wasn't initialized, it will give a false warning. Fix it by moving the list initialization into init_ei(). Cc: stable@vger.kernel.org Fixes: 5790b1fb3d67 ("eventfs: Remove eventfs_file and just use eventfs_inode") Reported-by: syzbot+3ef80b4ed02226d04a06@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=3ef80b4ed02226d04a06 Link: https://patch.msgid.link/20260824144653.54044-1-kartikey406@gmail.com Signed-off-by: Deepanshu Kartikey [ Rewrote change log ] Signed-off-by: Steven Rostedt --- fs/tracefs/event_inode.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/fs/tracefs/event_inode.c b/fs/tracefs/event_inode.c index 604ba3e841d2..6e3513b13cfa 100644 --- a/fs/tracefs/event_inode.c +++ b/fs/tracefs/event_inode.c @@ -438,6 +438,8 @@ static inline struct eventfs_inode *init_ei(struct eventfs_inode *ei, const char if (!ei->name) return NULL; kref_init(&ei->kref); + INIT_LIST_HEAD(&ei->children); + INIT_LIST_HEAD(&ei->list); return ei; } @@ -729,8 +731,6 @@ struct eventfs_inode *eventfs_create_dir(const char *name, struct eventfs_inode ei->entries = entries; ei->nr_entries = size; ei->data = data; - INIT_LIST_HEAD(&ei->children); - INIT_LIST_HEAD(&ei->list); scoped_guard(mutex, &eventfs_mutex) { if (!parent->is_freed) @@ -802,9 +802,6 @@ struct eventfs_inode *eventfs_create_events_dir(const char *name, struct dentry ei->attr.uid = uid; ei->attr.gid = gid; - INIT_LIST_HEAD(&ei->children); - INIT_LIST_HEAD(&ei->list); - ti = get_tracefs(inode); ti->flags |= TRACEFS_EVENT_INODE; ti->private = ei; From 64f74d8f728877858372c52067e5c0c091f8db24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 19 Aug 2026 09:24:21 +0200 Subject: [PATCH 365/641] um: Use asm-generic/timex.h over the host architecture one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The removal of the architecture-specific asm/timex.h was meant to fall back to asm-generic/timex.h. However on UML, the host architecture headers are earlier on the include path and therefore asm/timex.h from the host architecture is used. On x86 that header in turn requires the generation of cpufeaturemasks.h, which is not set up for UML and the build fails. In file included from ../arch/x86/include/asm/tsc.h:9, from ../arch/x86/include/asm/timex.h:6, from ../include/linux/timex.h:67, from ../include/linux/time32.h:13, from ../include/linux/time.h:60, from ../include/linux/jiffies.h:10, from ../include/linux/ktime.h:25, from ../include/linux/timer.h:6, from ../include/linux/uprobes.h:18, from ../include/linux/mm_types.h:16, from ../include/linux/mmzone.h:22, from ../include/linux/gfp.h:7, from ../include/linux/slab.h:17, from ../include/linux/crypto.h:18, from ../arch/um/kernel/asm-offsets.c:6: arch/x86/include/asm/tsc.h: In function ‘get_cycles’: arch/um/include/asm/cpufeature.h:52:39: error: implicit declaration of function ‘DISABLED_MASK_BIT_SET’ [-Wimplicit-function-declaration] 52 | (__builtin_constant_p(bit) && DISABLED_MASK_BIT_SET(bit) ? 0 : _static_cpu_has(bit)) | ^~~~~~~~~~~~~~~~~~~~~ arch/x86/include/asm/tsc.h:79:14: note: in expansion of macro ‘cpu_feature_enabled’ 79 | if (!cpu_feature_enabled(X86_FEATURE_TSC)) | ^~~~~~~~~~~~~~~~~~~ arch/um/include/asm/cpufeature.h:115:17: error: implicit declaration of function ‘_static_cpu_has’ [-Wimplicit-function-declaration] 115 | _static_cpu_has(bit) \ | ^~~~~~~~~~~~~~~ ... The x86-specific timex.h also doesn't make sense on UML in general. Reintroduce the UML-specific asm/timex.h and add a comment to document its necessity. [ bp: Improve that comment to actually state why stuff is there. ] Fixes: 3ed403bbc967 ("treewide: Remove CLOCK_TICK_RATE") Signed-off-by: Thomas Weißschuh Signed-off-by: Borislav Petkov (AMD) Reviewed-by: David Gow Link: https://patch.msgid.link/20260819-uml-timex-fix-v1-1-f690b514fc9f@linutronix.de --- arch/um/include/asm/timex.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 arch/um/include/asm/timex.h diff --git a/arch/um/include/asm/timex.h b/arch/um/include/asm/timex.h new file mode 100644 index 000000000000..39e392ebc8c2 --- /dev/null +++ b/arch/um/include/asm/timex.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __UM_TIMEX_H +#define __UM_TIMEX_H + +/* + * Do not fall back to the host architecture header because latter likely + * includes facilities like cpu_feature_enabled() which are present only + * there. That would result in build breakages and/or efforts to "emulate" + * those facilities in UML. + */ +#include + +#endif From ce366bfa821ec81dd45bde547ee31e659306cc61 Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Mon, 24 Aug 2026 21:17:29 +0800 Subject: [PATCH 366/641] f2fs: fix to avoid potential deadloop in f2fs_fsync_node_pages() There is potential deadloop in race condition: Thread A Thread B - fsync - f2fs_do_sync_file - f2fs_fsync_node_pages - last_fsync_dnode - folio_get(last_folio) - f2fs_setattr - f2fs_truncate - f2fs_truncate_blocks - f2fs_do_truncate_blocks - f2fs_truncate_inode_blocks - truncate_dnode - truncate_node - invalidate_mapping_pages - folio->mapping = NULL - is_node_folio alwasy return false - atomic && !marked is always true, then goto retry Cc: stable@kernel.org Fixes: 608514deba38 ("f2fs: set fsync mark only for the last dnode") Signed-off-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/node.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/f2fs/node.c b/fs/f2fs/node.c index 968e5ed38816..86c2e67e43b6 100644 --- a/fs/f2fs/node.c +++ b/fs/f2fs/node.c @@ -2016,6 +2016,11 @@ continue_unlock: f2fs_debug(sbi, "Retry to write fsync mark: ino=%u, idx=%lx", ino, last_folio->index); folio_lock(last_folio); + if (unlikely(!is_node_folio(last_folio))) { + f2fs_folio_put(last_folio, true); + ret = -EAGAIN; + goto out; + } f2fs_folio_wait_writeback(last_folio, NODE, true, true); folio_mark_dirty(last_folio); folio_unlock(last_folio); From b3be0e5bed8ab9adbf127a3eee3806e61190ec14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Federico=20Valent=C3=ADn=20Andrade?= Date: Mon, 24 Aug 2026 11:02:10 -0300 Subject: [PATCH 367/641] ALSA: usb-audio: Skip reading sample rate on M-Audio Venom MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The M-Audio Venom (0763:2084) is an USB Audio Class 1 compliant synth with an integrated audio interface, it does not implement GET_CUR on SAMPLING_FREQ_CONTROL, hanging up if requested on endpoint 0. The first class request issued by the driver after enumeration is a GET_CUR on endpoint 0x02, freezing the device's USB microcontroller. Timing out on every transfer afterwards with -ETIMEDOUT, such as SET_INTERFACE, so neither the mixer nor any streaming interface is set up. Analyzing a USBPcap capture of the Windows driver I found it never requests the sampling frequency, as the synth exposes a single discrete of 44100Hz on both streaming interfaces, thus asking for it is unnecessary. So I applied get_sample_rate to avoid this check, and disable_autosuspend because the synth doesn't come back from being suspended. Signed-off-by: Federico Valentín Andrade Link: https://patch.msgid.link/20260824140211.17003-2-fandrade@frba.utn.edu.ar Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 5c544bd88728..91938172912a 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2333,6 +2333,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_GENERIC_IMPLICIT_FB), DEVICE_FLG(0x0763, 0x2081, /* M-Audio Fast Track Ultra */ QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x0763, 0x2084, /* M-Audio Venom */ + QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_DISABLE_AUTOSUSPEND), DEVICE_FLG(0x07fd, 0x000b, /* MOTU M Series 2nd hardware revision */ QUIRK_FLAG_CTL_MSG_DELAY_1M), DEVICE_FLG(0x08bb, 0x2702, /* LineX FM Transmitter */ From 4c0ec35e011309bd7091053ad5bbc01420967a66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Federico=20Valent=C3=ADn=20Andrade?= Date: Mon, 24 Aug 2026 11:02:11 -0300 Subject: [PATCH 368/641] ALSA: usb-audio: Skip mixer creation on M-Audio Venom MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The M-Audio Venom (0763:2084) does not answer any GET_CUR request of its feature units, hanging up the USB microcontroller and "responding" with timeouts. So the mixer building lasts around 47 seconds, and the device does not stream at all. The same GET_CUR requests issued through libusb (with no kernel driver bound) complete correctly and blazingly fast. So it seems to only happen during the initial probe. I defined an explicit composite quirk to bypass the mixer creation, as it is not needed (the synth already comes with volume controls). With both this and the device flag applied, the synth works flawlessly. Signed-off-by: Federico Valentín Andrade Link: https://patch.msgid.link/20260824140211.17003-3-fandrade@frba.utn.edu.ar Signed-off-by: Takashi Iwai --- sound/usb/quirks-table.h | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index 0a3d39b8385b..dd02c6294edf 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -1814,6 +1814,28 @@ YAMAHA_DEVICE(0x7010, "UB99"), } } }, +{ + /* + * M-Audio Venom + * + * The AudioControl interface times out on every GET_CUR request, + * which adds around 47 seconds to the card registration and + * freezes the device, blocking streaming. + * Using an explicit composite quirk to skip the mixer entirely. + */ + USB_DEVICE_VENDOR_SPEC(0x0763, 0x2084), + QUIRK_DRIVER_INFO { + .vendor_name = "M-Audio", + .product_name = "Venom", + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_IGNORE(0) }, + { QUIRK_DATA_STANDARD_AUDIO(1) }, + { QUIRK_DATA_STANDARD_AUDIO(2) }, + { QUIRK_DATA_STANDARD_MIDI(3) }, + QUIRK_COMPOSITE_END + } + } +}, /* Casio devices */ { From 15fd93cbbff7d9d92cd19f38a94355462fa91930 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Mon, 24 Aug 2026 17:07:30 +0700 Subject: [PATCH 369/641] ALSA: aoa: i2sbus: Check IRQ before requesting it irq_of_parse_and_map() returns 0 when parsing or mapping an IRQ fails. The return value was passed to request_irq() without checking for failure. Check the return value before passing it to request_irq(). Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260824100730.31601-1-phucduc.bui@gmail.com Signed-off-by: Takashi Iwai --- sound/aoa/soundbus/i2sbus/core.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/aoa/soundbus/i2sbus/core.c b/sound/aoa/soundbus/i2sbus/core.c index 833c44c0a950..adf123dd79a2 100644 --- a/sound/aoa/soundbus/i2sbus/core.c +++ b/sound/aoa/soundbus/i2sbus/core.c @@ -240,6 +240,8 @@ static int i2sbus_add_dev(struct macio_dev *macio, } for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++) { int irq = irq_of_parse_and_map(np, i); + if (!irq) + goto err; if (request_irq(irq, ints[i], 0, dev->rnames[i], dev)) goto err; dev->interrupts[i] = irq; From be79a45e2ec289eb9fad519e8f5a3d4fc3789dd0 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Mon, 24 Aug 2026 20:37:52 +0800 Subject: [PATCH 370/641] ALSA: hda/realtek: Add quirk for Lenovo IdeaPad Slim 3 15ABR8 The internal microphone on Lenovo IdeaPad Slim 3 15ABR8 (subsystem 17aa:3862) exhibits severe distortion at default gain levels. The Capture Volume is fixed at 63 (max) and cannot be lowered without explicit userspace intervention. The existing fixup ALC269_FIXUP_LIMIT_INT_MIC_BOOST limits the gain range and resolves the issue. Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221891 Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260824123752.525173-1-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index b08d25fb0e17..f230a2be3914 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -8086,6 +8086,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x3852, "Lenovo Yoga 7 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x3853, "Lenovo Yoga 7 15ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x3855, "Legion 7 16ITHG6", ALC287_FIXUP_LEGION_16ITHG6), + SND_PCI_QUIRK(0x17aa, 0x3862, "Lenovo IdeaPad Slim 3 15ABR8", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), SND_PCI_QUIRK(0x17aa, 0x3865, "Lenovo 13X", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x3866, "Lenovo 13X", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x3869, "Lenovo Yoga7 14IAL7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), From 1739a976312e110c93a8dee66a1cdf893a1b187e Mon Sep 17 00:00:00 2001 From: Will Porter Date: Mon, 24 Aug 2026 17:57:57 -0500 Subject: [PATCH 371/641] ALSA: usb-audio: Complete cleanup after system-resume errors A failed system resume can leave the card unusable until reboot. usb_audio_resume() jumps to err_out when snd_usb_pcm_resume() or snd_usb_mixer_resume() fails. The error path skips the out: block, which restores D0 and decrements chip->num_suspended_intf. The card stays in SNDRV_CTL_POWER_D3hot, so later control access blocks in snd_power_ref_and_wait(). USB core logs an interface resume callback error. It does not retry that callback, so a later callback cannot complete the skipped cleanup. usb_audio_suspend() increments num_suspended_intf before returning success. A system-resume callback must consume the system-suspend count even if a component resume fails. Otherwise, the stranded count skews later suspend and resume cycles. Do not apply this cleanup to runtime-resume errors. Runtime PM can retry -EAGAIN or -EBUSY without another suspend callback. The count must continue to describe that suspended interface. Other runtime-resume errors latch runtime_error in the PM core and do not cause an immediate callback retry. Both parts of the system-resume error path are longstanding. Commit 88a8516a2128a ("ALSA: usbaudio: implement USB autosuspend") introduced err_out past the D0 restore. Commit 862b2509d157c ("ALSA: usb-audio: Fix inconsistent card PM state after resume") later moved num_suspended_intf-- into the out: block. The error path now skips both operations. No third-party code is needed to reach the error path. snd_usb_mixer_resume() ends in snd_usb_mixer_activate(), which returns the result of usb_submit_urb() for devices that have a mixer status URB. Its mixer->private_resume hook can also fail through scarlett2_init_notify(). snd_usb_pcm_resume() issues a SET_CUR request to a UAC3 power domain. It can return -EPIPE or -EIO when the device stalls the request. Route a component error through out: only when system_suspend is nonzero. Continue to return runtime-resume errors through err_out. Later component resume stages remain skipped. The original error still reaches USB core. A later transfer can fail if the device did not recover. I reproduced the system-resume failure on an Audient iD14 MkI with an out-of-tree diagnostic mixer resume hook. An injected -EIO on the unpatched core left control readers in uninterruptible sleep in snd_power_ref_and_wait() until a reboot. With this patch, the same failure restored control access. A second system suspend and resume also succeeded after I disabled fault injection. Assisted-by: Claude:claude-opus-5 Assisted-by: Antigravity:gemini-3.1-pro-high Assisted-by: Codex:gpt-5.6-sol Fixes: 88a8516a2128a ("ALSA: usbaudio: implement USB autosuspend") Fixes: 862b2509d157c ("ALSA: usb-audio: Fix inconsistent card PM state after resume") Cc: Signed-off-by: Will Porter Link: https://patch.msgid.link/20260824225757.26749-1-mrwillporter@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/card.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/sound/usb/card.c b/sound/usb/card.c index 24112e491779..9307da95efbe 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -1282,8 +1282,11 @@ static int usb_audio_resume(struct usb_interface *intf) list_for_each_entry(as, &chip->pcm_list, list) { err = snd_usb_pcm_resume(as); - if (err < 0) - goto err_out; + if (err < 0) { + if (!chip->system_suspend) + goto err_out; + goto out; + } } /* @@ -1292,8 +1295,11 @@ static int usb_audio_resume(struct usb_interface *intf) */ list_for_each_entry(mixer, &chip->mixer_list, list) { err = snd_usb_mixer_resume(mixer); - if (err < 0) - goto err_out; + if (err < 0) { + if (!chip->system_suspend) + goto err_out; + goto out; + } } list_for_each(p, &chip->midi_list) { From adb176c1407a7f802b21627a7ec912ff649ab6a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Habil=20Eren=20T=C3=BCrker?= Date: Tue, 25 Aug 2026 11:40:14 +0300 Subject: [PATCH 372/641] ALSA: hda/realtek: Fix speaker mute LED for HP Laptop 15-fd0039nt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HP Laptop 15-fd0039nt (SSID 103c:8bb6) needs a quirk to control the speaker mute LED via VREF100 on NID 0x1a (active-high). This patch replaces the previous ALC236_FIXUP_HP_MICMUTE_LED_ONLY with ALC236_FIXUP_HP_15_FD0XXX, which covers both mic mute (GPIO0) and speaker mute (NID 0x1a) LEDs. Use spec->no_shutup_pins instead of a custom shutup hook, as suggested by Takashi Iwai. Fixes: e711ebfadc9a ("ALSA: hda/realtek: Add quirk for HP Laptop 15-fd0039nt") Tested-by: Habil Eren Türker Signed-off-by: Habil Eren Türker Link: https://patch.msgid.link/20260825084125.4103-1-habilerenturker@hotmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 61 +++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 8 deletions(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index f230a2be3914..e4349743a251 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -1370,14 +1370,59 @@ static void alc269_fixup_hp_gpio_led(struct hda_codec *codec, alc_fixup_hp_gpio_led(codec, action, 0x08, 0x10); } -static void alc236_fixup_hp_micmute_led_only(struct hda_codec *codec, - const struct hda_fixup *fix, int action) +/* + * HP Laptop 15-fd0xxx (SSID 103c:8bb6) Speaker Mute LED fix + * + * The speaker mute LED is controlled via VREF100 on NID 0x1a. + * This pin must remain powered (D0) even during suspend, otherwise + * the LED state is lost and the pin defaults to Hi-Z on resume, + * causing the LED to stop responding to mute toggles. + * Windows keeps this pin powered unconditionally, so matching that + * behavior ensures consistent LED operation across suspend/resume. + * The mic-mute LED is controlled via GPIO 0 with active-low polarity. + */ +static unsigned int hp_8bb6_power_filter(struct hda_codec *codec, + hda_nid_t nid, + unsigned int power_state) +{ + if (nid == 0x1a) + return AC_PWRST_D0; + return snd_hda_gen_path_power_filter(codec, nid, power_state); +} + +static int hp_8bb6_speaker_mute_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent); + unsigned int val = (brightness == LED_OFF) ? PIN_IN : PIN_VREF100; + + snd_hda_set_pin_ctl_cache(codec, 0x1a, val); + return 0; +} + +static void alc236_fixup_hp_15_fd0xxx(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) { struct alc_spec *spec = codec->spec; - if (action == HDA_FIXUP_ACT_PRE_PROBE) + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: spec->micmute_led_polarity = 1; - alc_fixup_hp_gpio_led(codec, action, 0x00, 0x01); + alc_fixup_hp_gpio_led(codec, action, 0x00, 0x01); + + spec->mute_led_polarity = 0; + snd_hda_gen_add_mute_led_cdev(codec, hp_8bb6_speaker_mute_led_set); + + codec->power_filter = hp_8bb6_power_filter; + spec->no_shutup_pins = 1; + break; + + case HDA_FIXUP_ACT_INIT: + if (spec->gen.vmaster_mute.hook) + snd_hda_sync_vmaster_hook(&spec->gen.vmaster_mute); + break; + } } static void alc285_fixup_hp_gpio_led(struct hda_codec *codec, @@ -4163,7 +4208,7 @@ enum { ALC236_FIXUP_HP_GPIO_LED, ALC236_FIXUP_HP_MUTE_LED, ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF, - ALC236_FIXUP_HP_MICMUTE_LED_ONLY, + ALC236_FIXUP_HP_15_FD0XXX, ALC236_FIXUP_LENOVO_INV_DMIC, ALC298_FIXUP_SAMSUNG_AMP, ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS, @@ -5943,9 +5988,9 @@ static const struct hda_fixup alc269_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc236_fixup_hp_mute_led_micmute_gpio, }, - [ALC236_FIXUP_HP_MICMUTE_LED_ONLY] = { + [ALC236_FIXUP_HP_15_FD0XXX] = { .type = HDA_FIXUP_FUNC, - .v.func = alc236_fixup_hp_micmute_led_only, + .v.func = alc236_fixup_hp_15_fd0xxx, }, [ALC236_FIXUP_LENOVO_INV_DMIC] = { .type = HDA_FIXUP_FUNC, @@ -7488,7 +7533,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8b97, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), SND_PCI_QUIRK(0x103c, 0x8bb3, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8bb4, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), - SND_PCI_QUIRK(0x103c, 0x8bb6, "HP Laptop 15-fd0039nt", ALC236_FIXUP_HP_MICMUTE_LED_ONLY), + SND_PCI_QUIRK(0x103c, 0x8bb6, "HP Laptop 15-fd0039nt", ALC236_FIXUP_HP_15_FD0XXX), SND_PCI_QUIRK(0x103c, 0x8bbe, "HP Victus 16-r0xxx (MB 8BBE)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8bc8, "HP Victus 15-fa1xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8bcd, "HP Omen 16-xd0xxx", ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT), From e2aa5ad3be41accfcdcccc62348f21af7baa3a38 Mon Sep 17 00:00:00 2001 From: Christopher Tolang Date: Sun, 23 Aug 2026 19:32:21 +0800 Subject: [PATCH 373/641] ASoC: amd: yc: Add DMI entry for MSI Thin A15 B7UC This model requires an additional detection quirk to enable the internal microphone. Fixes: fa991481b8b2 ("ASoC: amd: add YC machine driver using dmic") Cc: stable@vger.kernel.org Assisted-by: OpenAI Codex Signed-off-by: Christopher Tolang Link: https://patch.msgid.link/20260823113221.19744-1-christophertolang@gmail.com Signed-off-by: Mark Brown --- sound/soc/amd/yc/acp6x-mach.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c index 2dde300a9220..4f2ccf9e1fa6 100644 --- a/sound/soc/amd/yc/acp6x-mach.c +++ b/sound/soc/amd/yc/acp6x-mach.c @@ -836,6 +836,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Thin A15 B7VE"), } }, + { + .driver_data = &acp6x_card, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Micro-Star International Co., Ltd."), + DMI_MATCH(DMI_PRODUCT_NAME, "Thin A15 B7UC"), + } + }, { .driver_data = &acp6x_card, .matches = { From fd8ed52ecea6b93e23d52cb2758fb4fb4029067f Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Mon, 24 Aug 2026 21:03:02 +0800 Subject: [PATCH 374/641] ASoC: amd: yc: Add DMI entry for Alienware m18 R1 AMD Add DMI entry so the YC machine driver probes on this model and the internal DMIC works. Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221485 Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260824130302.553419-1-zhangheng@kylinos.cn Signed-off-by: Mark Brown --- sound/soc/amd/yc/acp6x-mach.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c index 4f2ccf9e1fa6..d1d2ab1bfefa 100644 --- a/sound/soc/amd/yc/acp6x-mach.c +++ b/sound/soc/amd/yc/acp6x-mach.c @@ -570,6 +570,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m17 R5 AMD"), } }, + { + .driver_data = &acp6x_card, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m18 R1 AMD"), + } + }, { .driver_data = &acp6x_card, .matches = { From 58c1c30074a8d1179e17a0188cd695b2f416bf75 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 25 Aug 2026 15:49:31 +0200 Subject: [PATCH 375/641] ALSA: rawmidi: Another workaround for false-positive mutex lockdep warning While we attempted to work around the false-positive lockdep warning due to the nested mutex lock in rawmidi at the open path for a UMP legacy rawmidi, it didn't cover the similar locking at its close path, and this still caused another false-positive reports by syzkaller. Add a similar workaround to snd_rawmidi_kernel_release() as done in the former commit 9c04742e73b3 ("ALSA: rawmidi: Work around false-positive mutex lockdep warning") to cover completely. Reported-by: syzbot+7d1edf0ff6a05961020c@syzkaller.appspotmail.com Closes: https://lore.kernel.org/6a8c7e4d.4d75e56a.c9a88.0052.GAE@google.com Link: https://patch.msgid.link/20260825134942.1289272-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/rawmidi.h | 8 +++++++- sound/core/rawmidi.c | 11 +++++++---- sound/core/ump.c | 3 ++- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/include/sound/rawmidi.h b/include/sound/rawmidi.h index 88a6159364d0..4154035af414 100644 --- a/include/sound/rawmidi.h +++ b/include/sound/rawmidi.h @@ -179,7 +179,8 @@ int snd_rawmidi_info_select(struct snd_card *card, struct snd_rawmidi_info *info int snd_rawmidi_kernel_open_nested(struct snd_rawmidi *rmidi, int subdevice, int mode, struct snd_rawmidi_file *rfile, int depth); -int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile); +int snd_rawmidi_kernel_release_nested(struct snd_rawmidi_file *rfile, + int depth); int snd_rawmidi_output_params(struct snd_rawmidi_substream *substream, struct snd_rawmidi_params *params); int snd_rawmidi_input_params(struct snd_rawmidi_substream *substream, @@ -201,6 +202,11 @@ static inline int snd_rawmidi_kernel_open(struct snd_rawmidi *rmidi, return snd_rawmidi_kernel_open_nested(rmidi, subdevice, mode, rfile, 0); } +static inline int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile) +{ + return snd_rawmidi_kernel_release_nested(rfile, 0); +} + /* set up the tied devices */ static inline void snd_rawmidi_tie_devices(struct snd_rawmidi *r1, struct snd_rawmidi *r2) diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c index bf504e27f73e..34b4c7d6dbe6 100644 --- a/sound/core/rawmidi.c +++ b/sound/core/rawmidi.c @@ -571,7 +571,6 @@ static void rawmidi_release_priv(struct snd_rawmidi_file *rfile) struct snd_rawmidi *rmidi; rmidi = rfile->rmidi; - guard(mutex)(&rmidi->open_mutex); if (rfile->input) { close_substream(rmidi, rfile->input, 1); rfile->input = NULL; @@ -585,7 +584,8 @@ static void rawmidi_release_priv(struct snd_rawmidi_file *rfile) } /* called from sound/core/seq/seq_midi.c */ -int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile) +int snd_rawmidi_kernel_release_nested(struct snd_rawmidi_file *rfile, + int depth) { struct snd_rawmidi *rmidi; @@ -593,11 +593,13 @@ int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile) return -ENXIO; rmidi = rfile->rmidi; + mutex_lock_nested(&rmidi->open_mutex, depth); rawmidi_release_priv(rfile); + mutex_unlock(&rmidi->open_mutex); module_put(rmidi->card->module); return 0; } -EXPORT_SYMBOL(snd_rawmidi_kernel_release); +EXPORT_SYMBOL(snd_rawmidi_kernel_release_nested); static int snd_rawmidi_release(struct inode *inode, struct file *file) { @@ -607,7 +609,8 @@ static int snd_rawmidi_release(struct inode *inode, struct file *file) rfile = file->private_data; rmidi = rfile->rmidi; - rawmidi_release_priv(rfile); + scoped_guard(mutex, &rmidi->open_mutex) + rawmidi_release_priv(rfile); kfree(rfile); module = rmidi->card->module; snd_card_file_remove(rmidi->card, file); diff --git a/sound/core/ump.c b/sound/core/ump.c index 82ad155c56e6..d183c8a000bd 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -1184,7 +1184,8 @@ static int snd_ump_legacy_close(struct snd_rawmidi_substream *substream) ump->legacy_substreams[dir][group] = NULL; if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) { if (!--ump->legacy_out_opens) - snd_rawmidi_kernel_release(&ump->legacy_out_rfile); + snd_rawmidi_kernel_release_nested(&ump->legacy_out_rfile, + SINGLE_DEPTH_NESTING); } return 0; } From 705c4ed0643366963547b2616d53165f2519c81f Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Fri, 26 Jun 2026 19:35:52 +0300 Subject: [PATCH 376/641] tpm: tpm_i2c_nuvoton: disable IRQ on wait timeout i2c_nuvoton_wait_for_stat() enables the IRQ before waiting for the interrupt handler to report a status change. If the wait times out, or is interrupted before the handler runs, the function returns without balancing the enable_irq() call. Disable the IRQ before leaving the failed wait path. Also preserve an interrupted wait's original error code instead of converting it to -ETIMEDOUT inside the helper. Cc: stable@vger.kernel.org # v5.10+ Fixes: 4c336e4b1556 ("tpm: Add support for the Nuvoton NPCT501 I2C TPM") Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Reviewed-by: Jarkko Sakkinen Link: https://lore.kernel.org/r/20260626091653.54929-1-mhun512@gmail.com Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/tpm_i2c_nuvoton.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/char/tpm/tpm_i2c_nuvoton.c b/drivers/char/tpm/tpm_i2c_nuvoton.c index d44903b29929..aa3673002f3a 100644 --- a/drivers/char/tpm/tpm_i2c_nuvoton.c +++ b/drivers/char/tpm/tpm_i2c_nuvoton.c @@ -182,8 +182,10 @@ static int i2c_nuvoton_wait_for_stat(struct tpm_chip *chip, u8 mask, u8 value, timeout); if (rc > 0) return 0; - /* At this point we know that the SINT pin is asserted, so we - * do not need to do i2c_nuvoton_check_status */ + + disable_irq(priv->irq); + if (rc < 0) + return rc; } else { unsigned long ten_msec, stop; bool status_valid; From cf92237e81130e50337456dc2bedbc7818f75269 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Sat, 11 Jul 2026 20:36:37 +0300 Subject: [PATCH 377/641] tpm: Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() calls. Signed-off-by: Pan Chuang Reviewed-by: Jarkko Sakkinen Link: https://lore.kernel.org/r/20260710105318.376496-3-panchuang@vivo.com Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/st33zp24/st33zp24.c | 5 +---- drivers/char/tpm/tpm_i2c_nuvoton.c | 2 -- drivers/char/tpm/tpm_tis_i2c_cr50.c | 4 +--- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/drivers/char/tpm/st33zp24/st33zp24.c b/drivers/char/tpm/st33zp24/st33zp24.c index e2b7451ea7cc..8d5179367eac 100644 --- a/drivers/char/tpm/st33zp24/st33zp24.c +++ b/drivers/char/tpm/st33zp24/st33zp24.c @@ -506,11 +506,8 @@ int st33zp24_probe(void *phy_id, const struct st33zp24_phy_ops *ops, ret = devm_request_irq(dev, irq, tpm_ioserirq_handler, IRQF_TRIGGER_HIGH, "TPM SERIRQ management", chip); - if (ret < 0) { - dev_err(&chip->dev, "TPM SERIRQ signals %d not available\n", - irq); + if (ret < 0) goto _tpm_clean_answer; - } intmask |= TPM_INTF_CMD_READY_INT | TPM_INTF_STS_VALID_INT diff --git a/drivers/char/tpm/tpm_i2c_nuvoton.c b/drivers/char/tpm/tpm_i2c_nuvoton.c index aa3673002f3a..c84afb29d548 100644 --- a/drivers/char/tpm/tpm_i2c_nuvoton.c +++ b/drivers/char/tpm/tpm_i2c_nuvoton.c @@ -576,8 +576,6 @@ static int i2c_nuvoton_probe(struct i2c_client *client) dev_name(&chip->dev), chip); if (rc) { - dev_err(dev, "%s() Unable to request irq: %d for use\n", - __func__, priv->irq); priv->irq = 0; } else { chip->flags |= TPM_CHIP_FLAG_IRQ; diff --git a/drivers/char/tpm/tpm_tis_i2c_cr50.c b/drivers/char/tpm/tpm_tis_i2c_cr50.c index b48cacacc066..7f828fae70d3 100644 --- a/drivers/char/tpm/tpm_tis_i2c_cr50.c +++ b/drivers/char/tpm/tpm_tis_i2c_cr50.c @@ -751,10 +751,8 @@ static int tpm_cr50_i2c_probe(struct i2c_client *client) rc = devm_request_irq(dev, client->irq, tpm_cr50_i2c_int_handler, IRQF_TRIGGER_FALLING | IRQF_NO_AUTOEN, dev->driver->name, chip); - if (rc < 0) { - dev_err(dev, "Failed to probe IRQ %d\n", client->irq); + if (rc < 0) return rc; - } priv->irq = client->irq; } else { From 4bfacee94f309346bef0b598ea091088b9150af9 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sun, 12 Jul 2026 20:32:34 +0200 Subject: [PATCH 378/641] tpm: atmel: depend on X86 tpm_atmel probes for the chip at fixed x86 Super-I/O ports (0x4e) with inb()/outb(), so it only works on x86. TCG_ATMEL nevertheless depends only on HAS_IOPORT_MAP/HAS_IOPORT, which arm and arm64 also satisfy. There the probe is useless, and on platforms whose unbacked I/O access faults it oopses in init_atmel() at boot (e.g. arm/versatile): Unable to handle kernel paging request at virtual address fee0004e PC is at init_atmel+0x34/0x244 TCG_NSC and TCG_TIS already "depends on X86" (commit 2f592f2a7d74 ("TPM: NSC and TIS drivers X86 dependency fix")); TCG_ATMEL was missed. Add the same dependency. Signed-off-by: Karl Mehltretter Reviewed-by: Jarkko Sakkinen Link: https://lore.kernel.org/r/20260712183234.23125-1-kmehltretter@gmail.com Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/Kconfig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/char/tpm/Kconfig b/drivers/char/tpm/Kconfig index 8a8f692b6088..5f672f2c01b0 100644 --- a/drivers/char/tpm/Kconfig +++ b/drivers/char/tpm/Kconfig @@ -163,8 +163,7 @@ config TCG_NSC config TCG_ATMEL tristate "Atmel TPM Interface" - depends on HAS_IOPORT_MAP - depends on HAS_IOPORT + depends on X86 help If you have a TPM security chip from Atmel say Yes and it will be accessible from within Linux. To compile this driver From b4d1adc70471d0752bba674552d19971ed9fa869 Mon Sep 17 00:00:00 2001 From: Li Jun Date: Wed, 12 Aug 2026 18:09:14 +0800 Subject: [PATCH 379/641] tpm: tpm_tis_spi: fix nodef CR50 tpm_tis_spi_resume is null if no define CONFIG_TCG_TIS_SPI_CR50 in config, the add of tpm_tis_spi_resume is null, this cause the tpm chip to fail to resume. Signed-off-by: Li Jun Link: https://lore.kernel.org/r/20260812100914.3540149-1-lijun01@kylinos.cn Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/tpm_tis_spi.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/char/tpm/tpm_tis_spi.h b/drivers/char/tpm/tpm_tis_spi.h index d0f66f6f1931..ced416ecd92c 100644 --- a/drivers/char/tpm/tpm_tis_spi.h +++ b/drivers/char/tpm/tpm_tis_spi.h @@ -40,8 +40,12 @@ static inline int cr50_spi_probe(struct spi_device *spi) } #endif -#if defined(CONFIG_PM_SLEEP) && defined(CONFIG_TCG_TIS_SPI_CR50) -extern int tpm_tis_spi_resume(struct device *dev); +#if defined(CONFIG_PM_SLEEP) + #if defined(CONFIG_TCG_TIS_SPI_CR50) + extern int tpm_tis_spi_resume(struct device *dev); + #else + #define tpm_tis_spi_resume tpm_tis_resume + #endif #else #define tpm_tis_spi_resume NULL #endif From 8b92687708f5ef980de01c2042dbd76d11f78547 Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Fri, 21 Aug 2026 04:38:05 +0300 Subject: [PATCH 380/641] tpm: st33zp24: Return zero on status read failure st33zp24_status() ignores the result of the transport read and returns data even when no byte was received. The I2C transport, for example, skips i2c_master_recv() when the register-select write is short or fails, leaving data uninitialized. The resulting stack value can be interpreted as TPM_STS flags and let status checks complete spuriously. The status callback cannot propagate a transport error. Return zero unless recv() reports exactly one byte. With no status bits set, callers retry or take their existing timeout or error path instead of acting on an invalid status value. This issue was found by a static analysis checker and confirmed by manual source review. Fixes: 251a7b08213a ("TPM: STMicroelectronics ST33 I2C KERNEL 3.x") Signed-off-by: Ruoyu Wang Link: https://lore.kernel.org/r/20260813153032.3951878-1-ruoyuw560@gmail.com Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/st33zp24/st33zp24.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/char/tpm/st33zp24/st33zp24.c b/drivers/char/tpm/st33zp24/st33zp24.c index 8d5179367eac..168df7c8d13b 100644 --- a/drivers/char/tpm/st33zp24/st33zp24.c +++ b/drivers/char/tpm/st33zp24/st33zp24.c @@ -93,7 +93,9 @@ static u8 st33zp24_status(struct tpm_chip *chip) struct st33zp24_dev *tpm_dev = dev_get_drvdata(&chip->dev); u8 data; - tpm_dev->ops->recv(tpm_dev->phy_id, TPM_STS, &data, 1); + if (tpm_dev->ops->recv(tpm_dev->phy_id, TPM_STS, &data, 1) != 1) + return 0; + return data; } From 264216889d39df509b9c8045df53529480b0b718 Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Thu, 13 Aug 2026 23:30:32 +0800 Subject: [PATCH 381/641] tpm: st33zp24: Validate locality read result check_locality() treats every nonzero transport return as success. SPI errors remain negative, while the I2C path can convert a negative write error through its byte-sized status variable. Either result is nonzero even though the TPM_ACCESS byte can remain unwritten, so indeterminate ACTIVE_LOCALITY and VALID bits can falsely report an active locality. Require recv() to return exactly the requested byte before examining TPM_ACCESS. Transport errors and short reads now report an inactive locality, while successful reads retain the existing behavior. This issue was found by a static analysis checker and confirmed by manual source review. Fixes: 251a7b08213a ("TPM: STMicroelectronics ST33 I2C KERNEL 3.x") Signed-off-by: Ruoyu Wang Link: https://lore.kernel.org/r/20260813153032.3951878-2-ruoyuw560@gmail.com Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/st33zp24/st33zp24.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/char/tpm/st33zp24/st33zp24.c b/drivers/char/tpm/st33zp24/st33zp24.c index 168df7c8d13b..c15a9b7aaff8 100644 --- a/drivers/char/tpm/st33zp24/st33zp24.c +++ b/drivers/char/tpm/st33zp24/st33zp24.c @@ -106,10 +106,10 @@ static bool check_locality(struct tpm_chip *chip) { struct st33zp24_dev *tpm_dev = dev_get_drvdata(&chip->dev); u8 data; - u8 status; + int status; status = tpm_dev->ops->recv(tpm_dev->phy_id, TPM_ACCESS, &data, 1); - if (status && (data & + if (status == 1 && (data & (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) == (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) return true; From e06f28d32f31206c0be32a08b0273e26cfc1fc92 Mon Sep 17 00:00:00 2001 From: Ross Philipson Date: Sat, 11 Jul 2026 09:01:01 -0700 Subject: [PATCH 382/641] tpm: Initial step to reorganize TPM public headers Consolidate TPM1 constants in tpm_command.h and remove duplicate constants from tpm1-cmd.c. Co-developed-by: Daniel P. Smith Signed-off-by: Daniel P. Smith Co-developed-by: Alec Brown Signed-off-by: Alec Brown Signed-off-by: Ross Philipson Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/tpm-buf.c | 1 - drivers/char/tpm/tpm1-cmd.c | 14 +------- include/keys/trusted_tpm.h | 1 - include/linux/tpm.h | 2 ++ include/linux/tpm_command.h | 41 ++++++++++++++++------- security/keys/trusted-keys/trusted_tpm1.c | 1 - security/keys/trusted-keys/trusted_tpm2.c | 1 - 7 files changed, 31 insertions(+), 30 deletions(-) diff --git a/drivers/char/tpm/tpm-buf.c b/drivers/char/tpm/tpm-buf.c index dc882fc9fa9e..4c4f450630df 100644 --- a/drivers/char/tpm/tpm-buf.c +++ b/drivers/char/tpm/tpm-buf.c @@ -3,7 +3,6 @@ * Handling of TPM command and other buffers. */ -#include #include #include diff --git a/drivers/char/tpm/tpm1-cmd.c b/drivers/char/tpm/tpm1-cmd.c index b49a790f1bd5..664ca1fff2e8 100644 --- a/drivers/char/tpm/tpm1-cmd.c +++ b/drivers/char/tpm/tpm1-cmd.c @@ -22,8 +22,6 @@ #include "tpm.h" -#define TPM_MAX_ORDINAL 243 - /* * Array with one entry per ordinal defining the maximum amount * of time the chip could take to return the result. The ordinal @@ -308,9 +306,6 @@ unsigned long tpm1_calc_ordinal_duration(struct tpm_chip *chip, u32 ordinal) return duration; } -#define TPM_ORD_STARTUP 153 -#define TPM_ST_CLEAR 1 - /** * tpm1_startup() - turn on the TPM * @chip: TPM chip to use @@ -459,7 +454,6 @@ int tpm1_get_timeouts(struct tpm_chip *chip) return 0; } -#define TPM_ORD_PCR_EXTEND 20 int tpm1_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, const u8 *hash, const char *log_msg) { @@ -478,7 +472,6 @@ int tpm1_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, const u8 *hash, return rc; } -#define TPM_ORD_GET_CAP 101 ssize_t tpm1_getcap(struct tpm_chip *chip, u32 subcap_id, cap_t *cap, const char *desc, size_t min_cap_length) { @@ -511,7 +504,6 @@ ssize_t tpm1_getcap(struct tpm_chip *chip, u32 subcap_id, cap_t *cap, } EXPORT_SYMBOL_GPL(tpm1_getcap); -#define TPM_ORD_GET_RANDOM 70 struct tpm1_get_random_out { __be32 rng_data_len; u8 rng_data[TPM_MAX_RNG_DATA]; @@ -580,13 +572,12 @@ out: return rc; } -#define TPM_ORD_PCRREAD 21 int tpm1_pcr_read(struct tpm_chip *chip, u32 pcr_idx, u8 *res_buf) { struct tpm_buf buf; int rc; - rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_PCRREAD); + rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_PCR_READ); if (rc) return rc; @@ -609,7 +600,6 @@ out: return rc; } -#define TPM_ORD_CONTINUE_SELFTEST 83 /** * tpm1_continue_selftest() - run TPM's selftest * @chip: TPM chip to use @@ -726,8 +716,6 @@ out: return rc; } -#define TPM_ORD_SAVESTATE 152 - /** * tpm1_pm_suspend() - pm suspend handler * @chip: TPM chip to use. diff --git a/include/keys/trusted_tpm.h b/include/keys/trusted_tpm.h index 0fadc6a4f166..3a0fa3bc8454 100644 --- a/include/keys/trusted_tpm.h +++ b/include/keys/trusted_tpm.h @@ -3,7 +3,6 @@ #define __TRUSTED_TPM_H #include -#include extern struct trusted_key_ops trusted_key_tpm_ops; diff --git a/include/linux/tpm.h b/include/linux/tpm.h index 202da079d500..1846d5485a2c 100644 --- a/include/linux/tpm.h +++ b/include/linux/tpm.h @@ -25,6 +25,8 @@ #include #include +#include + #define TPM_DIGEST_SIZE 20 /* Max TPM v1.2 PCR size */ #define TPM2_MAX_DIGEST_SIZE SHA512_DIGEST_SIZE diff --git a/include/linux/tpm_command.h b/include/linux/tpm_command.h index f5c03e9c3913..174b043d8bbc 100644 --- a/include/linux/tpm_command.h +++ b/include/linux/tpm_command.h @@ -3,27 +3,42 @@ #define __LINUX_TPM_COMMAND_H__ /* - * TPM Command constants from specifications at - * http://www.trustedcomputinggroup.org + * == TPM 1 Family Chips == + * + * TPM 1.2 Main Specification: + * https://trustedcomputinggroup.org/resource/tpm-main-specification/ */ +#define TPM_MAX_ORDINAL 243 + /* Command TAGS */ -#define TPM_TAG_RQU_COMMAND 193 -#define TPM_TAG_RQU_AUTH1_COMMAND 194 -#define TPM_TAG_RQU_AUTH2_COMMAND 195 -#define TPM_TAG_RSP_COMMAND 196 -#define TPM_TAG_RSP_AUTH1_COMMAND 197 -#define TPM_TAG_RSP_AUTH2_COMMAND 198 +enum tpm_command_tags { + TPM_TAG_RQU_COMMAND = 193, + TPM_TAG_RQU_AUTH1_COMMAND = 194, + TPM_TAG_RQU_AUTH2_COMMAND = 195, + TPM_TAG_RSP_COMMAND = 196, + TPM_TAG_RSP_AUTH1_COMMAND = 197, + TPM_TAG_RSP_AUTH2_COMMAND = 198, +}; /* Command Ordinals */ -#define TPM_ORD_GETRANDOM 70 -#define TPM_ORD_OSAP 11 -#define TPM_ORD_OIAP 10 -#define TPM_ORD_SEAL 23 -#define TPM_ORD_UNSEAL 24 +enum tpm_command_ordinals { + TPM_ORD_CONTINUE_SELFTEST = 83, + TPM_ORD_GET_CAP = 101, + TPM_ORD_GET_RANDOM = 70, + TPM_ORD_PCR_EXTEND = 20, + TPM_ORD_PCR_READ = 21, + TPM_ORD_OSAP = 11, + TPM_ORD_OIAP = 10, + TPM_ORD_SAVESTATE = 152, + TPM_ORD_SEAL = 23, + TPM_ORD_STARTUP = 153, + TPM_ORD_UNSEAL = 24, +}; /* Other constants */ #define SRKHANDLE 0x40000000 #define TPM_NONCE_SIZE 20 +#define TPM_ST_CLEAR 1 #endif diff --git a/security/keys/trusted-keys/trusted_tpm1.c b/security/keys/trusted-keys/trusted_tpm1.c index 8f57c6111e7e..ae256c540ca0 100644 --- a/security/keys/trusted-keys/trusted_tpm1.c +++ b/security/keys/trusted-keys/trusted_tpm1.c @@ -18,7 +18,6 @@ #include #include #include -#include #include diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c index 6340823f8b53..29d79c05ed6b 100644 --- a/security/keys/trusted-keys/trusted_tpm2.c +++ b/security/keys/trusted-keys/trusted_tpm2.c @@ -9,7 +9,6 @@ #include #include #include -#include #include #include From 94d48062eb9626244f6ddb5a682cdaae40540030 Mon Sep 17 00:00:00 2001 From: Ross Philipson Date: Sat, 11 Jul 2026 09:01:02 -0700 Subject: [PATCH 383/641] tpm: Move TPM1 specific definitions to the command header Gather all the TPM1 definitions and structures from the internal header file drivers/char/tpm/tpm.h into the command header. In addition, bring in the single RNG structure from tpm1-cmd.c. The definitions moved to these files correspond to the TCG specification for TPM 1 family: TPM 1.2 Main Specification - https://trustedcomputinggroup.org/resource/tpm-main-specification/ Co-developed-by: Daniel P. Smith Signed-off-by: Daniel P. Smith Co-developed-by: Alec Brown Signed-off-by: Alec Brown Signed-off-by: Ross Philipson Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/tpm.h | 102 -------------------------------- drivers/char/tpm/tpm1-cmd.c | 5 -- include/linux/tpm_command.h | 115 ++++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 107 deletions(-) diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h index 87d68ddf270a..043d78a9617a 100644 --- a/drivers/char/tpm/tpm.h +++ b/drivers/char/tpm/tpm.h @@ -52,105 +52,9 @@ enum tpm_addr { TPM_ADDR = 0x4E, }; -#define TPM_WARN_RETRY 0x800 -#define TPM_WARN_DOING_SELFTEST 0x802 -#define TPM_ERR_DEACTIVATED 0x6 -#define TPM_ERR_DISABLED 0x7 -#define TPM_ERR_FAILEDSELFTEST 0x1C -#define TPM_ERR_INVALID_POSTINIT 38 - -#define TPM_TAG_RQU_COMMAND 193 - /* TPM2 specific constants. */ #define TPM2_SPACE_BUFFER_SIZE 16384 /* 16 kB */ -struct stclear_flags_t { - __be16 tag; - u8 deactivated; - u8 disableForceClear; - u8 physicalPresence; - u8 physicalPresenceLock; - u8 bGlobalLock; -} __packed; - -struct tpm1_version { - u8 major; - u8 minor; - u8 rev_major; - u8 rev_minor; -} __packed; - -struct tpm1_version2 { - __be16 tag; - struct tpm1_version version; -} __packed; - -struct timeout_t { - __be32 a; - __be32 b; - __be32 c; - __be32 d; -} __packed; - -struct duration_t { - __be32 tpm_short; - __be32 tpm_medium; - __be32 tpm_long; -} __packed; - -struct permanent_flags_t { - __be16 tag; - u8 disable; - u8 ownership; - u8 deactivated; - u8 readPubek; - u8 disableOwnerClear; - u8 allowMaintenance; - u8 physicalPresenceLifetimeLock; - u8 physicalPresenceHWEnable; - u8 physicalPresenceCMDEnable; - u8 CEKPUsed; - u8 TPMpost; - u8 TPMpostLock; - u8 FIPS; - u8 operator; - u8 enableRevokeEK; - u8 nvLocked; - u8 readSRKPub; - u8 tpmEstablished; - u8 maintenanceDone; - u8 disableFullDALogicInfo; -} __packed; - -typedef union { - struct permanent_flags_t perm_flags; - struct stclear_flags_t stclear_flags; - __u8 owned; - __be32 num_pcrs; - struct tpm1_version version1; - struct tpm1_version2 version2; - __be32 manufacturer_id; - struct timeout_t timeout; - struct duration_t duration; -} cap_t; - -enum tpm_capabilities { - TPM_CAP_FLAG = 4, - TPM_CAP_PROP = 5, - TPM_CAP_VERSION_1_1 = 0x06, - TPM_CAP_VERSION_1_2 = 0x1A, -}; - -enum tpm_sub_capabilities { - TPM_CAP_PROP_PCR = 0x101, - TPM_CAP_PROP_MANUFACTURER = 0x103, - TPM_CAP_FLAG_PERM = 0x108, - TPM_CAP_FLAG_VOL = 0x109, - TPM_CAP_PROP_OWNER = 0x111, - TPM_CAP_PROP_TIS_TIMEOUT = 0x115, - TPM_CAP_PROP_TIS_DURATION = 0x120, -}; - enum tpm2_pt_props { TPM2_PT_NONE = 0x00000000, TPM2_PT_GROUP = 0x00000100, @@ -225,12 +129,6 @@ enum tpm2_pt_props { TPM2_PT_AUDIT_COUNTER_1 = TPM2_PT_VAR + 20, }; -/* 128 bytes is an arbitrary cap. This could be as large as TPM_BUFSIZE - 18 - * bytes, but 128 is still a relatively large number of random bytes and - * anything much bigger causes users of struct tpm_cmd_t to start getting - * compiler warnings about stack frame size. */ -#define TPM_MAX_RNG_DATA 128 - extern const struct class tpm_class; extern const struct class tpmrm_class; extern dev_t tpm_devt; diff --git a/drivers/char/tpm/tpm1-cmd.c b/drivers/char/tpm/tpm1-cmd.c index 664ca1fff2e8..96f189b5fd6f 100644 --- a/drivers/char/tpm/tpm1-cmd.c +++ b/drivers/char/tpm/tpm1-cmd.c @@ -504,11 +504,6 @@ ssize_t tpm1_getcap(struct tpm_chip *chip, u32 subcap_id, cap_t *cap, } EXPORT_SYMBOL_GPL(tpm1_getcap); -struct tpm1_get_random_out { - __be32 rng_data_len; - u8 rng_data[TPM_MAX_RNG_DATA]; -} __packed; - /** * tpm1_get_random() - get random bytes from the TPM's RNG * @chip: a &struct tpm_chip instance diff --git a/include/linux/tpm_command.h b/include/linux/tpm_command.h index 174b043d8bbc..30d01953a6f8 100644 --- a/include/linux/tpm_command.h +++ b/include/linux/tpm_command.h @@ -36,6 +36,121 @@ enum tpm_command_ordinals { TPM_ORD_UNSEAL = 24, }; +enum tpm_capabilities { + TPM_CAP_FLAG = 4, + TPM_CAP_PROP = 5, + TPM_CAP_VERSION_1_1 = 0x06, + TPM_CAP_VERSION_1_2 = 0x1A, +}; + +enum tpm_sub_capabilities { + TPM_CAP_PROP_PCR = 0x101, + TPM_CAP_PROP_MANUFACTURER = 0x103, + TPM_CAP_FLAG_PERM = 0x108, + TPM_CAP_FLAG_VOL = 0x109, + TPM_CAP_PROP_OWNER = 0x111, + TPM_CAP_PROP_TIS_TIMEOUT = 0x115, + TPM_CAP_PROP_TIS_DURATION = 0x120, +}; + +/* Return Codes */ +enum tpm_return_codes { + TPM_BASE_MASK = 0, + TPM_NON_FATAL_MASK = 0x00000800, + TPM_SUCCESS = TPM_BASE_MASK + 0, + TPM_ERR_DEACTIVATED = TPM_BASE_MASK + 6, + TPM_ERR_DISABLED = TPM_BASE_MASK + 7, + TPM_ERR_FAIL = TPM_BASE_MASK + 9, + TPM_ERR_FAILEDSELFTEST = TPM_BASE_MASK + 28, + TPM_ERR_INVALID_POSTINIT = TPM_BASE_MASK + 38, + TPM_ERR_INVALID_FAMILY = TPM_BASE_MASK + 55, + TPM_WARN_RETRY = TPM_BASE_MASK + TPM_NON_FATAL_MASK + 0, + TPM_WARN_DOING_SELFTEST = TPM_BASE_MASK + TPM_NON_FATAL_MASK + 2, +}; + +struct stclear_flags_t { + __be16 tag; + u8 deactivated; + u8 disableForceClear; + u8 physicalPresence; + u8 physicalPresenceLock; + u8 bGlobalLock; +} __packed; + +struct tpm1_version { + u8 major; + u8 minor; + u8 rev_major; + u8 rev_minor; +} __packed; + +struct tpm1_version2 { + __be16 tag; + struct tpm1_version version; +} __packed; + +struct timeout_t { + __be32 a; + __be32 b; + __be32 c; + __be32 d; +} __packed; + +struct duration_t { + __be32 tpm_short; + __be32 tpm_medium; + __be32 tpm_long; +} __packed; + +struct permanent_flags_t { + __be16 tag; + u8 disable; + u8 ownership; + u8 deactivated; + u8 readPubek; + u8 disableOwnerClear; + u8 allowMaintenance; + u8 physicalPresenceLifetimeLock; + u8 physicalPresenceHWEnable; + u8 physicalPresenceCMDEnable; + u8 CEKPUsed; + u8 TPMpost; + u8 TPMpostLock; + u8 FIPS; + u8 operator; + u8 enableRevokeEK; + u8 nvLocked; + u8 readSRKPub; + u8 tpmEstablished; + u8 maintenanceDone; + u8 disableFullDALogicInfo; +} __packed; + +typedef union { + struct permanent_flags_t perm_flags; + struct stclear_flags_t stclear_flags; + __u8 owned; + __be32 num_pcrs; + struct tpm1_version version1; + struct tpm1_version2 version2; + __be32 manufacturer_id; + struct timeout_t timeout; + struct duration_t duration; +} cap_t; + +/* + * 128 bytes is an arbitrary cap. This could be as large as TPM_BUFSIZE - 18 + * bytes, but 128 is still a relatively large number of random bytes and + * anything much bigger causes users of struct tpm_cmd_t to start getting + * compiler warnings about stack frame size. + */ +#define TPM_MAX_RNG_DATA 128 + +struct tpm1_get_random_out { + __be32 rng_data_len; + u8 rng_data[TPM_MAX_RNG_DATA]; +} __packed; + /* Other constants */ #define SRKHANDLE 0x40000000 #define TPM_NONCE_SIZE 20 From 7a5ec92813bc794ab3674aab993fe8f8b5cee9f0 Mon Sep 17 00:00:00 2001 From: Ross Philipson Date: Sat, 11 Jul 2026 09:01:03 -0700 Subject: [PATCH 384/641] tpm: Move TPM2 specific definitions to the command header Gather all the TPM2 definitions and structures in the internal header file drivers/char/tpm/tpm.h into the command header, including: - Command codes, return codes and definitions from the public and internal tpm.h files. - Structures defined in numerous TPM driver C modules. The definitions moved to these files correspond to the TCG specification for TPM 2 family: TPM 2.0 Library - https://trustedcomputinggroup.org/resource/tpm-library-specification/ Co-developed-by: Daniel P. Smith Signed-off-by: Daniel P. Smith Co-developed-by: Alec Brown Signed-off-by: Alec Brown Signed-off-by: Ross Philipson Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/tpm.h | 77 ---------- drivers/char/tpm/tpm2-cmd.c | 30 ---- drivers/char/tpm/tpm2-space.c | 13 -- include/linux/tpm.h | 145 ------------------ include/linux/tpm_command.h | 271 ++++++++++++++++++++++++++++++++++ 5 files changed, 271 insertions(+), 265 deletions(-) diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h index 043d78a9617a..680f89d9c9f9 100644 --- a/drivers/char/tpm/tpm.h +++ b/drivers/char/tpm/tpm.h @@ -52,83 +52,6 @@ enum tpm_addr { TPM_ADDR = 0x4E, }; -/* TPM2 specific constants. */ -#define TPM2_SPACE_BUFFER_SIZE 16384 /* 16 kB */ - -enum tpm2_pt_props { - TPM2_PT_NONE = 0x00000000, - TPM2_PT_GROUP = 0x00000100, - TPM2_PT_FIXED = TPM2_PT_GROUP * 1, - TPM2_PT_FAMILY_INDICATOR = TPM2_PT_FIXED + 0, - TPM2_PT_LEVEL = TPM2_PT_FIXED + 1, - TPM2_PT_REVISION = TPM2_PT_FIXED + 2, - TPM2_PT_DAY_OF_YEAR = TPM2_PT_FIXED + 3, - TPM2_PT_YEAR = TPM2_PT_FIXED + 4, - TPM2_PT_MANUFACTURER = TPM2_PT_FIXED + 5, - TPM2_PT_VENDOR_STRING_1 = TPM2_PT_FIXED + 6, - TPM2_PT_VENDOR_STRING_2 = TPM2_PT_FIXED + 7, - TPM2_PT_VENDOR_STRING_3 = TPM2_PT_FIXED + 8, - TPM2_PT_VENDOR_STRING_4 = TPM2_PT_FIXED + 9, - TPM2_PT_VENDOR_TPM_TYPE = TPM2_PT_FIXED + 10, - TPM2_PT_FIRMWARE_VERSION_1 = TPM2_PT_FIXED + 11, - TPM2_PT_FIRMWARE_VERSION_2 = TPM2_PT_FIXED + 12, - TPM2_PT_INPUT_BUFFER = TPM2_PT_FIXED + 13, - TPM2_PT_HR_TRANSIENT_MIN = TPM2_PT_FIXED + 14, - TPM2_PT_HR_PERSISTENT_MIN = TPM2_PT_FIXED + 15, - TPM2_PT_HR_LOADED_MIN = TPM2_PT_FIXED + 16, - TPM2_PT_ACTIVE_SESSIONS_MAX = TPM2_PT_FIXED + 17, - TPM2_PT_PCR_COUNT = TPM2_PT_FIXED + 18, - TPM2_PT_PCR_SELECT_MIN = TPM2_PT_FIXED + 19, - TPM2_PT_CONTEXT_GAP_MAX = TPM2_PT_FIXED + 20, - TPM2_PT_NV_COUNTERS_MAX = TPM2_PT_FIXED + 22, - TPM2_PT_NV_INDEX_MAX = TPM2_PT_FIXED + 23, - TPM2_PT_MEMORY = TPM2_PT_FIXED + 24, - TPM2_PT_CLOCK_UPDATE = TPM2_PT_FIXED + 25, - TPM2_PT_CONTEXT_HASH = TPM2_PT_FIXED + 26, - TPM2_PT_CONTEXT_SYM = TPM2_PT_FIXED + 27, - TPM2_PT_CONTEXT_SYM_SIZE = TPM2_PT_FIXED + 28, - TPM2_PT_ORDERLY_COUNT = TPM2_PT_FIXED + 29, - TPM2_PT_MAX_COMMAND_SIZE = TPM2_PT_FIXED + 30, - TPM2_PT_MAX_RESPONSE_SIZE = TPM2_PT_FIXED + 31, - TPM2_PT_MAX_DIGEST = TPM2_PT_FIXED + 32, - TPM2_PT_MAX_OBJECT_CONTEXT = TPM2_PT_FIXED + 33, - TPM2_PT_MAX_SESSION_CONTEXT = TPM2_PT_FIXED + 34, - TPM2_PT_PS_FAMILY_INDICATOR = TPM2_PT_FIXED + 35, - TPM2_PT_PS_LEVEL = TPM2_PT_FIXED + 36, - TPM2_PT_PS_REVISION = TPM2_PT_FIXED + 37, - TPM2_PT_PS_DAY_OF_YEAR = TPM2_PT_FIXED + 38, - TPM2_PT_PS_YEAR = TPM2_PT_FIXED + 39, - TPM2_PT_SPLIT_MAX = TPM2_PT_FIXED + 40, - TPM2_PT_TOTAL_COMMANDS = TPM2_PT_FIXED + 41, - TPM2_PT_LIBRARY_COMMANDS = TPM2_PT_FIXED + 42, - TPM2_PT_VENDOR_COMMANDS = TPM2_PT_FIXED + 43, - TPM2_PT_NV_BUFFER_MAX = TPM2_PT_FIXED + 44, - TPM2_PT_MODES = TPM2_PT_FIXED + 45, - TPM2_PT_MAX_CAP_BUFFER = TPM2_PT_FIXED + 46, - TPM2_PT_VAR = TPM2_PT_GROUP * 2, - TPM2_PT_PERMANENT = TPM2_PT_VAR + 0, - TPM2_PT_STARTUP_CLEAR = TPM2_PT_VAR + 1, - TPM2_PT_HR_NV_INDEX = TPM2_PT_VAR + 2, - TPM2_PT_HR_LOADED = TPM2_PT_VAR + 3, - TPM2_PT_HR_LOADED_AVAIL = TPM2_PT_VAR + 4, - TPM2_PT_HR_ACTIVE = TPM2_PT_VAR + 5, - TPM2_PT_HR_ACTIVE_AVAIL = TPM2_PT_VAR + 6, - TPM2_PT_HR_TRANSIENT_AVAIL = TPM2_PT_VAR + 7, - TPM2_PT_HR_PERSISTENT = TPM2_PT_VAR + 8, - TPM2_PT_HR_PERSISTENT_AVAIL = TPM2_PT_VAR + 9, - TPM2_PT_NV_COUNTERS = TPM2_PT_VAR + 10, - TPM2_PT_NV_COUNTERS_AVAIL = TPM2_PT_VAR + 11, - TPM2_PT_ALGORITHM_SET = TPM2_PT_VAR + 12, - TPM2_PT_LOADED_CURVES = TPM2_PT_VAR + 13, - TPM2_PT_LOCKOUT_COUNTER = TPM2_PT_VAR + 14, - TPM2_PT_MAX_AUTH_FAIL = TPM2_PT_VAR + 15, - TPM2_PT_LOCKOUT_INTERVAL = TPM2_PT_VAR + 16, - TPM2_PT_LOCKOUT_RECOVERY = TPM2_PT_VAR + 17, - TPM2_PT_NV_WRITE_RECOVERY = TPM2_PT_VAR + 18, - TPM2_PT_AUDIT_COUNTER_0 = TPM2_PT_VAR + 19, - TPM2_PT_AUDIT_COUNTER_1 = TPM2_PT_VAR + 20, -}; - extern const struct class tpm_class; extern const struct class tpmrm_class; extern dev_t tpm_devt; diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c index 52ee350da867..280f870e6517 100644 --- a/drivers/char/tpm/tpm2-cmd.c +++ b/drivers/char/tpm/tpm2-cmd.c @@ -94,17 +94,6 @@ unsigned long tpm2_calc_ordinal_duration(u32 ordinal) return msecs_to_jiffies(TPM2_DURATION_DEFAULT); } -struct tpm2_pcr_read_out { - __be32 update_cnt; - __be32 pcr_selects_cnt; - __be16 hash_alg; - u8 pcr_select_size; - u8 pcr_select[TPM2_PCR_SELECT_MIN]; - __be32 digests_cnt; - __be16 digest_size; - u8 digest[]; -} __packed; - /** * tpm2_pcr_read() - read a PCR value * @chip: TPM chip to use. @@ -238,11 +227,6 @@ int tpm2_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, return rc; } -struct tpm2_get_random_out { - __be16 size; - u8 buffer[TPM_MAX_RNG_DATA]; -} __packed; - /** * tpm2_get_random() - get random bytes from the TPM RNG * @@ -364,14 +348,6 @@ void tpm2_flush_context(struct tpm_chip *chip, u32 handle) } EXPORT_SYMBOL_GPL(tpm2_flush_context); -struct tpm2_get_cap_out { - u8 more_data; - __be32 subcap_id; - __be32 property_cnt; - __be32 property_id; - __be32 value; -} __packed; - /** * tpm2_get_tpm_pt() - get value of a TPM_CAP_TPM_PROPERTIES type property * @chip: a &tpm_chip instance @@ -539,12 +515,6 @@ static int tpm2_init_bank_info(struct tpm_chip *chip, u32 bank_index) return tpm2_pcr_read(chip, 0, &digest, &bank->digest_size); } -struct tpm2_pcr_selection { - __be16 hash_alg; - u8 size_of_select; - u8 pcr_select[3]; -} __packed; - ssize_t tpm2_get_pcr_allocation(struct tpm_chip *chip) { struct tpm2_pcr_selection pcr_selection; diff --git a/drivers/char/tpm/tpm2-space.c b/drivers/char/tpm/tpm2-space.c index 60354cd53b5c..7c1c0a174a2b 100644 --- a/drivers/char/tpm/tpm2-space.c +++ b/drivers/char/tpm/tpm2-space.c @@ -15,19 +15,6 @@ #include #include "tpm.h" -enum tpm2_handle_types { - TPM2_HT_HMAC_SESSION = 0x02000000, - TPM2_HT_POLICY_SESSION = 0x03000000, - TPM2_HT_TRANSIENT = 0x80000000, -}; - -struct tpm2_context { - __be64 sequence; - __be32 saved_handle; - __be32 hierarchy; - __be16 blob_size; -} __packed; - static void tpm2_flush_sessions(struct tpm_chip *chip, struct tpm_space *space) { int i; diff --git a/include/linux/tpm.h b/include/linux/tpm.h index 1846d5485a2c..8551b24c2bff 100644 --- a/include/linux/tpm.h +++ b/include/linux/tpm.h @@ -38,12 +38,6 @@ struct trusted_key_options; /* opaque structure, holds auth session parameters like the session key */ struct tpm2_auth; -enum tpm2_session_types { - TPM2_SE_HMAC = 0x00, - TPM2_SE_POLICY = 0x01, - TPM2_SE_TRIAL = 0x02, -}; - /* if you add a new hash to this, increment TPM_MAX_HASHES below */ enum tpm_algorithms { TPM_ALG_ERROR = 0x0000, @@ -65,11 +59,6 @@ enum tpm_algorithms { */ #define TPM_MAX_HASHES 5 -enum tpm2_curves { - TPM2_ECC_NONE = 0x0000, - TPM2_ECC_NIST_P256 = 0x0003, -}; - struct tpm_digest { u16 alg_id; u8 digest[TPM2_MAX_DIGEST_SIZE]; @@ -222,122 +211,11 @@ struct tpm_chip { #define TPM_HEADER_SIZE 10 -enum tpm2_const { - TPM2_PLATFORM_PCR = 24, - TPM2_PCR_SELECT_MIN = ((TPM2_PLATFORM_PCR + 7) / 8), -}; - -enum tpm2_timeouts { - TPM2_TIMEOUT_A = 750, - TPM2_TIMEOUT_B = 4000, - TPM2_TIMEOUT_C = 200, - TPM2_TIMEOUT_D = 30, -}; - -enum tpm2_durations { - TPM2_DURATION_SHORT = 20, - TPM2_DURATION_LONG = 2000, - TPM2_DURATION_DEFAULT = 120000, -}; - -enum tpm2_structures { - TPM2_ST_NO_SESSIONS = 0x8001, - TPM2_ST_SESSIONS = 0x8002, - TPM2_ST_CREATION = 0x8021, -}; - -/* Indicates from what layer of the software stack the error comes from */ -#define TSS2_RC_LAYER_SHIFT 16 -#define TSS2_RESMGR_TPM_RC_LAYER (11 << TSS2_RC_LAYER_SHIFT) - -enum tpm2_return_codes { - TPM2_RC_SUCCESS = 0x0000, - TPM2_RC_HASH = 0x0083, /* RC_FMT1 */ - TPM2_RC_HANDLE = 0x008B, - TPM2_RC_INTEGRITY = 0x009F, - TPM2_RC_INITIALIZE = 0x0100, /* RC_VER1 */ - TPM2_RC_FAILURE = 0x0101, - TPM2_RC_DISABLED = 0x0120, - TPM2_RC_UPGRADE = 0x012D, - TPM2_RC_COMMAND_CODE = 0x0143, - TPM2_RC_TESTING = 0x090A, /* RC_WARN */ - TPM2_RC_REFERENCE_H0 = 0x0910, - TPM2_RC_RETRY = 0x0922, - TPM2_RC_SESSION_MEMORY = 0x0903, -}; - -enum tpm2_command_codes { - TPM2_CC_FIRST = 0x011F, - TPM2_CC_HIERARCHY_CONTROL = 0x0121, - TPM2_CC_HIERARCHY_CHANGE_AUTH = 0x0129, - TPM2_CC_CREATE_PRIMARY = 0x0131, - TPM2_CC_SEQUENCE_COMPLETE = 0x013E, - TPM2_CC_SELF_TEST = 0x0143, - TPM2_CC_STARTUP = 0x0144, - TPM2_CC_SHUTDOWN = 0x0145, - TPM2_CC_NV_READ = 0x014E, - TPM2_CC_CREATE = 0x0153, - TPM2_CC_LOAD = 0x0157, - TPM2_CC_SEQUENCE_UPDATE = 0x015C, - TPM2_CC_UNSEAL = 0x015E, - TPM2_CC_CONTEXT_LOAD = 0x0161, - TPM2_CC_CONTEXT_SAVE = 0x0162, - TPM2_CC_FLUSH_CONTEXT = 0x0165, - TPM2_CC_READ_PUBLIC = 0x0173, - TPM2_CC_START_AUTH_SESS = 0x0176, - TPM2_CC_VERIFY_SIGNATURE = 0x0177, - TPM2_CC_GET_CAPABILITY = 0x017A, - TPM2_CC_GET_RANDOM = 0x017B, - TPM2_CC_PCR_READ = 0x017E, - TPM2_CC_PCR_EXTEND = 0x0182, - TPM2_CC_EVENT_SEQUENCE_COMPLETE = 0x0185, - TPM2_CC_HASH_SEQUENCE_START = 0x0186, - TPM2_CC_CREATE_LOADED = 0x0191, - TPM2_CC_LAST = 0x0193, /* Spec 1.36 */ -}; - -enum tpm2_permanent_handles { - TPM2_RH_NULL = 0x40000007, - TPM2_RS_PW = 0x40000009, -}; - -/* Most Significant Octet for key types */ -enum tpm2_mso_type { - TPM2_MSO_NVRAM = 0x01, - TPM2_MSO_SESSION = 0x02, - TPM2_MSO_POLICY = 0x03, - TPM2_MSO_PERMANENT = 0x40, - TPM2_MSO_VOLATILE = 0x80, - TPM2_MSO_PERSISTENT = 0x81, -}; - static inline enum tpm2_mso_type tpm2_handle_mso(u32 handle) { return handle >> 24; } -enum tpm2_capabilities { - TPM2_CAP_HANDLES = 1, - TPM2_CAP_COMMANDS = 2, - TPM2_CAP_PCRS = 5, - TPM2_CAP_TPM_PROPERTIES = 6, -}; - -enum tpm2_properties { - TPM_PT_TOTAL_COMMANDS = 0x0129, -}; - -enum tpm2_startup_types { - TPM2_SU_CLEAR = 0x0000, - TPM2_SU_STATE = 0x0001, -}; - -enum tpm2_cc_attrs { - TPM2_CC_ATTR_CHANDLES = 25, - TPM2_CC_ATTR_RHANDLE = 28, - TPM2_CC_ATTR_VENDOR = 29, -}; - #define TPM_VID_INTEL 0x8086 #define TPM_VID_WINBOND 0x1050 #define TPM_VID_STM 0x104A @@ -389,29 +267,6 @@ struct tpm_buf { u8 handles; }; -enum tpm2_object_attributes { - TPM2_OA_FIXED_TPM = BIT(1), - TPM2_OA_ST_CLEAR = BIT(2), - TPM2_OA_FIXED_PARENT = BIT(4), - TPM2_OA_SENSITIVE_DATA_ORIGIN = BIT(5), - TPM2_OA_USER_WITH_AUTH = BIT(6), - TPM2_OA_ADMIN_WITH_POLICY = BIT(7), - TPM2_OA_NO_DA = BIT(10), - TPM2_OA_ENCRYPTED_DUPLICATION = BIT(11), - TPM2_OA_RESTRICTED = BIT(16), - TPM2_OA_DECRYPT = BIT(17), - TPM2_OA_SIGN = BIT(18), -}; - -enum tpm2_session_attributes { - TPM2_SA_CONTINUE_SESSION = BIT(0), - TPM2_SA_AUDIT_EXCLUSIVE = BIT(1), - TPM2_SA_AUDIT_RESET = BIT(3), - TPM2_SA_DECRYPT = BIT(5), - TPM2_SA_ENCRYPT = BIT(6), - TPM2_SA_AUDIT = BIT(7), -}; - struct tpm2_hash { unsigned int crypto_id; unsigned int tpm_id; diff --git a/include/linux/tpm_command.h b/include/linux/tpm_command.h index 30d01953a6f8..9dd903dd6b5c 100644 --- a/include/linux/tpm_command.h +++ b/include/linux/tpm_command.h @@ -156,4 +156,275 @@ struct tpm1_get_random_out { #define TPM_NONCE_SIZE 20 #define TPM_ST_CLEAR 1 +/* + * == TPM 2 Family Chips == + * + * TPM 2.0 Library + * https://trustedcomputinggroup.org/resource/tpm-library-specification/ + */ + +/* TPM2 specific constants. */ +#define TPM2_SPACE_BUFFER_SIZE 16384 /* 16 kB */ + +enum tpm2_session_types { + TPM2_SE_HMAC = 0x00, + TPM2_SE_POLICY = 0x01, + TPM2_SE_TRIAL = 0x02, +}; + +enum tpm2_timeouts { + TPM2_TIMEOUT_A = 750, + TPM2_TIMEOUT_B = 4000, + TPM2_TIMEOUT_C = 200, + TPM2_TIMEOUT_D = 30, + TPM2_DURATION_SHORT = 20, + TPM2_DURATION_MEDIUM = 750, + TPM2_DURATION_LONG = 2000, + TPM2_DURATION_LONG_LONG = 300000, + TPM2_DURATION_DEFAULT = 120000, +}; + +enum tpm2_structures { + TPM2_ST_NO_SESSIONS = 0x8001, + TPM2_ST_SESSIONS = 0x8002, + TPM2_ST_CREATION = 0x8021, +}; + +/* Indicates from what layer of the software stack the error comes from */ +#define TSS2_RC_LAYER_SHIFT 16 +#define TSS2_RESMGR_TPM_RC_LAYER (11 << TSS2_RC_LAYER_SHIFT) + +enum tpm2_return_codes { + TPM2_RC_SUCCESS = 0x0000, + TPM2_RC_HASH = 0x0083, /* RC_FMT1 */ + TPM2_RC_HANDLE = 0x008B, + TPM2_RC_INTEGRITY = 0x009F, + TPM2_RC_INITIALIZE = 0x0100, /* RC_VER1 */ + TPM2_RC_FAILURE = 0x0101, + TPM2_RC_DISABLED = 0x0120, + TPM2_RC_UPGRADE = 0x012D, + TPM2_RC_COMMAND_CODE = 0x0143, + TPM2_RC_TESTING = 0x090A, /* RC_WARN */ + TPM2_RC_REFERENCE_H0 = 0x0910, + TPM2_RC_RETRY = 0x0922, + TPM2_RC_SESSION_MEMORY = 0x0903, +}; + +enum tpm2_command_codes { + TPM2_CC_FIRST = 0x011F, + TPM2_CC_HIERARCHY_CONTROL = 0x0121, + TPM2_CC_HIERARCHY_CHANGE_AUTH = 0x0129, + TPM2_CC_CREATE_PRIMARY = 0x0131, + TPM2_CC_SEQUENCE_COMPLETE = 0x013E, + TPM2_CC_SELF_TEST = 0x0143, + TPM2_CC_STARTUP = 0x0144, + TPM2_CC_SHUTDOWN = 0x0145, + TPM2_CC_NV_READ = 0x014E, + TPM2_CC_CREATE = 0x0153, + TPM2_CC_LOAD = 0x0157, + TPM2_CC_SEQUENCE_UPDATE = 0x015C, + TPM2_CC_UNSEAL = 0x015E, + TPM2_CC_CONTEXT_LOAD = 0x0161, + TPM2_CC_CONTEXT_SAVE = 0x0162, + TPM2_CC_FLUSH_CONTEXT = 0x0165, + TPM2_CC_READ_PUBLIC = 0x0173, + TPM2_CC_START_AUTH_SESS = 0x0176, + TPM2_CC_VERIFY_SIGNATURE = 0x0177, + TPM2_CC_GET_CAPABILITY = 0x017A, + TPM2_CC_GET_RANDOM = 0x017B, + TPM2_CC_PCR_READ = 0x017E, + TPM2_CC_PCR_EXTEND = 0x0182, + TPM2_CC_EVENT_SEQUENCE_COMPLETE = 0x0185, + TPM2_CC_HASH_SEQUENCE_START = 0x0186, + TPM2_CC_CREATE_LOADED = 0x0191, + TPM2_CC_LAST = 0x0193, /* Spec 1.36 */ +}; + +enum tpm2_capabilities { + TPM2_CAP_HANDLES = 1, + TPM2_CAP_COMMANDS = 2, + TPM2_CAP_PCRS = 5, + TPM2_CAP_TPM_PROPERTIES = 6, +}; + +enum tpm2_properties { + TPM_PT_TOTAL_COMMANDS = 0x0129, +}; + +enum tpm2_startup_types { + TPM2_SU_CLEAR = 0x0000, + TPM2_SU_STATE = 0x0001, +}; + +enum tpm2_cc_attrs { + TPM2_CC_ATTR_CHANDLES = 25, + TPM2_CC_ATTR_RHANDLE = 28, + TPM2_CC_ATTR_VENDOR = 29, +}; + +enum tpm2_permanent_handles { + TPM2_RH_NULL = 0x40000007, + TPM2_RS_PW = 0x40000009, +}; + +/* Most Significant Octet for key types */ +enum tpm2_mso_type { + TPM2_MSO_NVRAM = 0x01, + TPM2_MSO_SESSION = 0x02, + TPM2_MSO_POLICY = 0x03, + TPM2_MSO_PERMANENT = 0x40, + TPM2_MSO_VOLATILE = 0x80, + TPM2_MSO_PERSISTENT = 0x81, +}; + +enum tpm2_curves { + TPM2_ECC_NONE = 0x0000, + TPM2_ECC_NIST_P256 = 0x0003, +}; + +enum tpm2_object_attributes { + TPM2_OA_FIXED_TPM = BIT(1), + TPM2_OA_ST_CLEAR = BIT(2), + TPM2_OA_FIXED_PARENT = BIT(4), + TPM2_OA_SENSITIVE_DATA_ORIGIN = BIT(5), + TPM2_OA_USER_WITH_AUTH = BIT(6), + TPM2_OA_ADMIN_WITH_POLICY = BIT(7), + TPM2_OA_NO_DA = BIT(10), + TPM2_OA_ENCRYPTED_DUPLICATION = BIT(11), + TPM2_OA_RESTRICTED = BIT(16), + TPM2_OA_DECRYPT = BIT(17), + TPM2_OA_SIGN = BIT(18), +}; + +enum tpm2_session_attributes { + TPM2_SA_CONTINUE_SESSION = BIT(0), + TPM2_SA_AUDIT_EXCLUSIVE = BIT(1), + TPM2_SA_AUDIT_RESET = BIT(3), + TPM2_SA_DECRYPT = BIT(5), + TPM2_SA_ENCRYPT = BIT(6), + TPM2_SA_AUDIT = BIT(7), +}; + +enum tpm2_pcr_select { + TPM2_PLATFORM_PCR = 24, + TPM2_PCR_SELECT_MIN = ((TPM2_PLATFORM_PCR + 7) / 8), +}; + +enum tpm2_handle_types { + TPM2_HT_HMAC_SESSION = 0x02000000, + TPM2_HT_POLICY_SESSION = 0x03000000, + TPM2_HT_TRANSIENT = 0x80000000, +}; + +enum tpm2_pt_props { + TPM2_PT_NONE = 0x00000000, + TPM2_PT_GROUP = 0x00000100, + TPM2_PT_FIXED = TPM2_PT_GROUP * 1, + TPM2_PT_FAMILY_INDICATOR = TPM2_PT_FIXED + 0, + TPM2_PT_LEVEL = TPM2_PT_FIXED + 1, + TPM2_PT_REVISION = TPM2_PT_FIXED + 2, + TPM2_PT_DAY_OF_YEAR = TPM2_PT_FIXED + 3, + TPM2_PT_YEAR = TPM2_PT_FIXED + 4, + TPM2_PT_MANUFACTURER = TPM2_PT_FIXED + 5, + TPM2_PT_VENDOR_STRING_1 = TPM2_PT_FIXED + 6, + TPM2_PT_VENDOR_STRING_2 = TPM2_PT_FIXED + 7, + TPM2_PT_VENDOR_STRING_3 = TPM2_PT_FIXED + 8, + TPM2_PT_VENDOR_STRING_4 = TPM2_PT_FIXED + 9, + TPM2_PT_VENDOR_TPM_TYPE = TPM2_PT_FIXED + 10, + TPM2_PT_FIRMWARE_VERSION_1 = TPM2_PT_FIXED + 11, + TPM2_PT_FIRMWARE_VERSION_2 = TPM2_PT_FIXED + 12, + TPM2_PT_INPUT_BUFFER = TPM2_PT_FIXED + 13, + TPM2_PT_HR_TRANSIENT_MIN = TPM2_PT_FIXED + 14, + TPM2_PT_HR_PERSISTENT_MIN = TPM2_PT_FIXED + 15, + TPM2_PT_HR_LOADED_MIN = TPM2_PT_FIXED + 16, + TPM2_PT_ACTIVE_SESSIONS_MAX = TPM2_PT_FIXED + 17, + TPM2_PT_PCR_COUNT = TPM2_PT_FIXED + 18, + TPM2_PT_PCR_SELECT_MIN = TPM2_PT_FIXED + 19, + TPM2_PT_CONTEXT_GAP_MAX = TPM2_PT_FIXED + 20, + TPM2_PT_NV_COUNTERS_MAX = TPM2_PT_FIXED + 22, + TPM2_PT_NV_INDEX_MAX = TPM2_PT_FIXED + 23, + TPM2_PT_MEMORY = TPM2_PT_FIXED + 24, + TPM2_PT_CLOCK_UPDATE = TPM2_PT_FIXED + 25, + TPM2_PT_CONTEXT_HASH = TPM2_PT_FIXED + 26, + TPM2_PT_CONTEXT_SYM = TPM2_PT_FIXED + 27, + TPM2_PT_CONTEXT_SYM_SIZE = TPM2_PT_FIXED + 28, + TPM2_PT_ORDERLY_COUNT = TPM2_PT_FIXED + 29, + TPM2_PT_MAX_COMMAND_SIZE = TPM2_PT_FIXED + 30, + TPM2_PT_MAX_RESPONSE_SIZE = TPM2_PT_FIXED + 31, + TPM2_PT_MAX_DIGEST = TPM2_PT_FIXED + 32, + TPM2_PT_MAX_OBJECT_CONTEXT = TPM2_PT_FIXED + 33, + TPM2_PT_MAX_SESSION_CONTEXT = TPM2_PT_FIXED + 34, + TPM2_PT_PS_FAMILY_INDICATOR = TPM2_PT_FIXED + 35, + TPM2_PT_PS_LEVEL = TPM2_PT_FIXED + 36, + TPM2_PT_PS_REVISION = TPM2_PT_FIXED + 37, + TPM2_PT_PS_DAY_OF_YEAR = TPM2_PT_FIXED + 38, + TPM2_PT_PS_YEAR = TPM2_PT_FIXED + 39, + TPM2_PT_SPLIT_MAX = TPM2_PT_FIXED + 40, + TPM2_PT_TOTAL_COMMANDS = TPM2_PT_FIXED + 41, + TPM2_PT_LIBRARY_COMMANDS = TPM2_PT_FIXED + 42, + TPM2_PT_VENDOR_COMMANDS = TPM2_PT_FIXED + 43, + TPM2_PT_NV_BUFFER_MAX = TPM2_PT_FIXED + 44, + TPM2_PT_MODES = TPM2_PT_FIXED + 45, + TPM2_PT_MAX_CAP_BUFFER = TPM2_PT_FIXED + 46, + TPM2_PT_VAR = TPM2_PT_GROUP * 2, + TPM2_PT_PERMANENT = TPM2_PT_VAR + 0, + TPM2_PT_STARTUP_CLEAR = TPM2_PT_VAR + 1, + TPM2_PT_HR_NV_INDEX = TPM2_PT_VAR + 2, + TPM2_PT_HR_LOADED = TPM2_PT_VAR + 3, + TPM2_PT_HR_LOADED_AVAIL = TPM2_PT_VAR + 4, + TPM2_PT_HR_ACTIVE = TPM2_PT_VAR + 5, + TPM2_PT_HR_ACTIVE_AVAIL = TPM2_PT_VAR + 6, + TPM2_PT_HR_TRANSIENT_AVAIL = TPM2_PT_VAR + 7, + TPM2_PT_HR_PERSISTENT = TPM2_PT_VAR + 8, + TPM2_PT_HR_PERSISTENT_AVAIL = TPM2_PT_VAR + 9, + TPM2_PT_NV_COUNTERS = TPM2_PT_VAR + 10, + TPM2_PT_NV_COUNTERS_AVAIL = TPM2_PT_VAR + 11, + TPM2_PT_ALGORITHM_SET = TPM2_PT_VAR + 12, + TPM2_PT_LOADED_CURVES = TPM2_PT_VAR + 13, + TPM2_PT_LOCKOUT_COUNTER = TPM2_PT_VAR + 14, + TPM2_PT_MAX_AUTH_FAIL = TPM2_PT_VAR + 15, + TPM2_PT_LOCKOUT_INTERVAL = TPM2_PT_VAR + 16, + TPM2_PT_LOCKOUT_RECOVERY = TPM2_PT_VAR + 17, + TPM2_PT_NV_WRITE_RECOVERY = TPM2_PT_VAR + 18, + TPM2_PT_AUDIT_COUNTER_0 = TPM2_PT_VAR + 19, + TPM2_PT_AUDIT_COUNTER_1 = TPM2_PT_VAR + 20, +}; + +struct tpm2_pcr_read_out { + __be32 update_cnt; + __be32 pcr_selects_cnt; + __be16 hash_alg; + u8 pcr_select_size; + u8 pcr_select[TPM2_PCR_SELECT_MIN]; + __be32 digests_cnt; + __be16 digest_size; + u8 digest[]; +} __packed; + +struct tpm2_get_random_out { + __be16 size; + u8 buffer[TPM_MAX_RNG_DATA]; +} __packed; + +struct tpm2_get_cap_out { + u8 more_data; + __be32 subcap_id; + __be32 property_cnt; + __be32 property_id; + __be32 value; +} __packed; + +struct tpm2_pcr_selection { + __be16 hash_alg; + u8 size_of_select; + u8 pcr_select[3]; +} __packed; + +struct tpm2_context { + __be64 sequence; + __be32 saved_handle; + __be32 hierarchy; + __be16 blob_size; +} __packed; + #endif From 88c5a62c3af7ca6f36dc6ffc52bcfb29cd2990cb Mon Sep 17 00:00:00 2001 From: Ross Philipson Date: Sat, 11 Jul 2026 09:01:04 -0700 Subject: [PATCH 385/641] tpm: Move TPM common base definitions to the command header These are top level definitions shared by both TPM 1 and 2 family chips. This includes core definitions like TPM localities, common crypto algorithm IDs, and the base TPM command header. Co-developed-by: Daniel P. Smith Signed-off-by: Daniel P. Smith Co-developed-by: Alec Brown Signed-off-by: Alec Brown Signed-off-by: Ross Philipson Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/tpm.h | 1 - include/linux/tpm.h | 50 +------------------- include/linux/tpm_command.h | 91 +++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 50 deletions(-) diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h index 680f89d9c9f9..fa554c5ad80b 100644 --- a/drivers/char/tpm/tpm.h +++ b/drivers/char/tpm/tpm.h @@ -33,7 +33,6 @@ #endif #define TPM_MINOR 224 /* officially assigned */ -#define TPM_BUFSIZE 4096 #define TPM_NUM_DEVICES 65536 #define TPM_RETRY 50 diff --git a/include/linux/tpm.h b/include/linux/tpm.h index 8551b24c2bff..3630b2ea6aef 100644 --- a/include/linux/tpm.h +++ b/include/linux/tpm.h @@ -27,49 +27,12 @@ #include -#define TPM_DIGEST_SIZE 20 /* Max TPM v1.2 PCR size */ - -#define TPM2_MAX_DIGEST_SIZE SHA512_DIGEST_SIZE -#define TPM2_MAX_PCR_BANKS 8 - struct tpm_chip; struct trusted_key_payload; struct trusted_key_options; /* opaque structure, holds auth session parameters like the session key */ struct tpm2_auth; -/* if you add a new hash to this, increment TPM_MAX_HASHES below */ -enum tpm_algorithms { - TPM_ALG_ERROR = 0x0000, - TPM_ALG_SHA1 = 0x0004, - TPM_ALG_AES = 0x0006, - TPM_ALG_KEYEDHASH = 0x0008, - TPM_ALG_SHA256 = 0x000B, - TPM_ALG_SHA384 = 0x000C, - TPM_ALG_SHA512 = 0x000D, - TPM_ALG_NULL = 0x0010, - TPM_ALG_SM3_256 = 0x0012, - TPM_ALG_ECC = 0x0023, - TPM_ALG_CFB = 0x0043, -}; - -/* - * maximum number of hashing algorithms a TPM can have. This is - * basically a count of every hash in tpm_algorithms above - */ -#define TPM_MAX_HASHES 5 - -struct tpm_digest { - u16 alg_id; - u8 digest[TPM2_MAX_DIGEST_SIZE]; -} __packed; - -struct tpm_bank_info { - u16 alg_id; - u16 digest_size; - u16 crypto_id; -}; - enum TPM_OPS_FLAGS { TPM_OPS_AUTO_STARTUP = BIT(0), }; @@ -127,7 +90,7 @@ struct tpm_chip_seqops { const struct seq_operations *seqops; }; -/* fixed define for the curve we use which is NIST_P256 */ +/* Fixed define for the curve we use which is NIST_P256 */ #define EC_PT_SZ 32 /* @@ -209,8 +172,6 @@ struct tpm_chip { #endif }; -#define TPM_HEADER_SIZE 10 - static inline enum tpm2_mso_type tpm2_handle_mso(u32 handle) { return handle >> 24; @@ -239,15 +200,6 @@ enum tpm_chip_flags { #define to_tpm_chip(d) container_of(d, struct tpm_chip, dev) -struct tpm_header { - __be16 tag; - __be32 length; - union { - __be32 ordinal; - __be32 return_code; - }; -} __packed; - enum tpm_buf_flags { /* the capacity exceeded: */ TPM_BUF_OVERFLOW = BIT(0), diff --git a/include/linux/tpm_command.h b/include/linux/tpm_command.h index 9dd903dd6b5c..fc446a1282e2 100644 --- a/include/linux/tpm_command.h +++ b/include/linux/tpm_command.h @@ -2,6 +2,8 @@ #ifndef __LINUX_TPM_COMMAND_H__ #define __LINUX_TPM_COMMAND_H__ +#include + /* * == TPM 1 Family Chips == * @@ -427,4 +429,93 @@ struct tpm2_context { __be16 blob_size; } __packed; +/* + * == TPM Common Defs == + */ + +#define TPM_DIGEST_SIZE 20 /* Max TPM v1.2 PCR size */ +#define TPM_BUFSIZE 4096 + +/* + * SHA-512 is, as of today, the largest digest in the TCG algorithm repository. + */ +#define TPM2_MAX_DIGEST_SIZE SHA512_DIGEST_SIZE + +/* + * A TPM name digest i.e., TPMT_HA, is a concatenation of TPM_ALG_ID of the + * name algorithm and hash of TPMT_PUBLIC. + */ +#define TPM2_MAX_NAME_SIZE (TPM2_MAX_DIGEST_SIZE + 2) + +/* + * Fixed define for the size of a name. This is actually HASHALG size + * plus 2, so 32 for SHA256 + */ +#define TPM2_NULL_NAME_SIZE 34 + +/* + * The maximum number of PCR banks. + */ +#define TPM2_MAX_PCR_BANKS 8 + +/* If you add a new hash to this, increment TPM_MAX_HASHES below */ +enum tpm_algorithms { + TPM_ALG_ERROR = 0x0000, + TPM_ALG_SHA1 = 0x0004, + TPM_ALG_AES = 0x0006, + TPM_ALG_KEYEDHASH = 0x0008, + TPM_ALG_SHA256 = 0x000B, + TPM_ALG_SHA384 = 0x000C, + TPM_ALG_SHA512 = 0x000D, + TPM_ALG_NULL = 0x0010, + TPM_ALG_SM3_256 = 0x0012, + TPM_ALG_ECC = 0x0023, + TPM_ALG_CFB = 0x0043, +}; + +/* + * The locality (0 - 4) for a TPM, as defined in section 3.2 of the + * Client Platform Profile Specification. + */ +enum tpm_localities { + TPM_LOCALITY_0 = 0, /* Static RTM */ + TPM_LOCALITY_1 = 1, /* Dynamic OS */ + TPM_LOCALITY_2 = 2, /* DRTM Environment */ + TPM_LOCALITY_3 = 3, /* Aux Components */ + TPM_LOCALITY_4 = 4, /* CPU DRTM Establishment */ + TPM_MAX_LOCALITY = TPM_LOCALITY_4 +}; + +/* + * Structure to represent active PCR algorithm banks usable by the + * TPM chip. + */ +struct tpm_bank_info { + u16 alg_id; + u16 digest_size; + u16 crypto_id; +}; + +/* + * Maximum number of hashing algorithms a TPM can have. This is + * basically a count of every hash in tpm_algorithms above + */ +#define TPM_MAX_HASHES 5 + +struct tpm_digest { + u16 alg_id; + u8 digest[TPM2_MAX_DIGEST_SIZE]; +} __packed; + +#define TPM_HEADER_SIZE 10 + +struct tpm_header { + __be16 tag; + __be32 length; + union { + __be32 ordinal; + __be32 return_code; + }; +} __packed; + #endif From 5467ade151973477742af922cc33bb30e816af42 Mon Sep 17 00:00:00 2001 From: Ross Philipson Date: Sat, 11 Jul 2026 09:01:05 -0700 Subject: [PATCH 386/641] tpm: Move platform specific definitions to the new PTP header These are definitions for TPM 2.0 interface and interactions with the platform as defined in the TCG specification: These definitions are located here in a separate file to avoid conflicts with vendor specific TIS/FIFO definition (e.g. STMicroelectronics, Infineon Technologies, etc). This allows the TCG defined TIS/FIFO interface to be in a public header while the former chip specific implementations contain their own definitions. TPM 1.x family chips that adhere to the TCG specifications use the TIS/FIFO interface as defined here. TCG PC Client Platform TPM Profile (PTP) Specification - https://trustedcomputinggroup.org/resource/pc-client-platform-tpm-profile-ptp-specification/ Co-developed-by: Daniel P. Smith Signed-off-by: Daniel P. Smith Co-developed-by: Alec Brown Signed-off-by: Alec Brown Signed-off-by: Ross Philipson Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/tpm_tis_core.h | 64 +-------------- include/linux/tpm_ptp.h | 133 ++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+), 63 deletions(-) create mode 100644 include/linux/tpm_ptp.h diff --git a/drivers/char/tpm/tpm_tis_core.h b/drivers/char/tpm/tpm_tis_core.h index aa6d78898ef3..d737b3d67411 100644 --- a/drivers/char/tpm/tpm_tis_core.h +++ b/drivers/char/tpm/tpm_tis_core.h @@ -19,71 +19,9 @@ #ifndef __TPM_TIS_CORE_H__ #define __TPM_TIS_CORE_H__ +#include #include "tpm.h" -enum tis_access { - TPM_ACCESS_VALID = 0x80, - TPM_ACCESS_ACTIVE_LOCALITY = 0x20, - TPM_ACCESS_REQUEST_PENDING = 0x04, - TPM_ACCESS_REQUEST_USE = 0x02, -}; - -enum tis_status { - TPM_STS_VALID = 0x80, - TPM_STS_COMMAND_READY = 0x40, - TPM_STS_GO = 0x20, - TPM_STS_DATA_AVAIL = 0x10, - TPM_STS_DATA_EXPECT = 0x08, - TPM_STS_RESPONSE_RETRY = 0x02, - TPM_STS_READ_ZERO = 0x23, /* bits that must be zero on read */ -}; - -enum tis_int_flags { - TPM_GLOBAL_INT_ENABLE = 0x80000000, - TPM_INTF_BURST_COUNT_STATIC = 0x100, - TPM_INTF_CMD_READY_INT = 0x080, - TPM_INTF_INT_EDGE_FALLING = 0x040, - TPM_INTF_INT_EDGE_RISING = 0x020, - TPM_INTF_INT_LEVEL_LOW = 0x010, - TPM_INTF_INT_LEVEL_HIGH = 0x008, - TPM_INTF_LOCALITY_CHANGE_INT = 0x004, - TPM_INTF_STS_VALID_INT = 0x002, - TPM_INTF_DATA_AVAIL_INT = 0x001, -}; - -enum tis_defaults { - TIS_MEM_LEN = 0x5000, - TIS_SHORT_TIMEOUT = 750, /* ms */ - TIS_LONG_TIMEOUT = 4000, /* 4 secs */ - TIS_TIMEOUT_MIN_ATML = 14700, /* usecs */ - TIS_TIMEOUT_MAX_ATML = 15000, /* usecs */ -}; - -/* Some timeout values are needed before it is known whether the chip is - * TPM 1.0 or TPM 2.0. - */ -#define TIS_TIMEOUT_A_MAX max_t(int, TIS_SHORT_TIMEOUT, TPM2_TIMEOUT_A) -#define TIS_TIMEOUT_B_MAX max_t(int, TIS_LONG_TIMEOUT, TPM2_TIMEOUT_B) -#define TIS_TIMEOUT_C_MAX max_t(int, TIS_SHORT_TIMEOUT, TPM2_TIMEOUT_C) -#define TIS_TIMEOUT_D_MAX max_t(int, TIS_SHORT_TIMEOUT, TPM2_TIMEOUT_D) - -#define TPM_ACCESS(l) (0x0000 | ((l) << 12)) -#define TPM_INT_ENABLE(l) (0x0008 | ((l) << 12)) -#define TPM_INT_VECTOR(l) (0x000C | ((l) << 12)) -#define TPM_INT_STATUS(l) (0x0010 | ((l) << 12)) -#define TPM_INTF_CAPS(l) (0x0014 | ((l) << 12)) -#define TPM_STS(l) (0x0018 | ((l) << 12)) -#define TPM_STS3(l) (0x001b | ((l) << 12)) -#define TPM_DATA_FIFO(l) (0x0024 | ((l) << 12)) - -#define TPM_DID_VID(l) (0x0F00 | ((l) << 12)) -#define TPM_RID(l) (0x0F04 | ((l) << 12)) - -#define LPC_CNTRL_OFFSET 0x84 -#define LPC_CLKRUN_EN (1 << 2) -#define INTEL_LEGACY_BLK_BASE_ADDR 0xFED08000 -#define ILB_REMAP_SIZE 0x100 - enum tpm_tis_flags { TPM_TIS_ITPM_WORKAROUND = 0, TPM_TIS_INVALID_STATUS = 1, diff --git a/include/linux/tpm_ptp.h b/include/linux/tpm_ptp.h new file mode 100644 index 000000000000..e80ed810a269 --- /dev/null +++ b/include/linux/tpm_ptp.h @@ -0,0 +1,133 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Following copyright information was take from the original file + * where the definitions were moved + * from: + * + * Copyright (C) 2005, 2006 IBM Corporation + * Copyright (C) 2014, 2015 Intel Corporation + * + * Authors: + * Leendert van Doorn + * Kylene Hall + * + * Maintained by: + * + * Device driver for TCG/TCPA TPM (trusted platform module). + * Specifications at www.trustedcomputinggroup.org + * + * This device driver implements the TPM interface as defined in + * the TCG TPM Interface Spec version 1.2, revision 1.0. + */ + +#ifndef __LINUX_TPM_PTP_H__ +#define __LINUX_TPM_PTP_H__ + +/* + * TCG PC Client Platform TPM Profile (PTP) Specification + * https://trustedcomputinggroup.org/resource/pc-client-platform-tpm-profile-ptp-specification/ + */ + +/* TIS/FIFO macros and definitions */ + +enum tis_access { + TPM_ACCESS_VALID = 0x80, + TPM_ACCESS_ACTIVE_LOCALITY = 0x20, /* (R) */ + TPM_ACCESS_RELINQUISH_LOCALITY = 0x20, /* (W) */ + TPM_ACCESS_REQUEST_PENDING = 0x04, /* (W) */ + TPM_ACCESS_REQUEST_USE = 0x02, /* (W) */ +}; + +enum tis_status { + TPM_STS_VALID = 0x80, /* (R) */ + TPM_STS_COMMAND_READY = 0x40, /* (R) */ + TPM_STS_DATA_AVAIL = 0x10, /* (R) */ + TPM_STS_DATA_EXPECT = 0x08, /* (R) */ + TPM_STS_GO = 0x20, /* (W) */ + TPM_STS_RESPONSE_RETRY = 0x02, /* (R) */ + TPM_STS_READ_ZERO = 0x23, /* bits that must be zero on read */ +}; + +enum tis_int_flags { + TPM_GLOBAL_INT_ENABLE = 0x80000000, + TPM_INTF_BURST_COUNT_STATIC = 0x100, + TPM_INTF_CMD_READY_INT = 0x080, + TPM_INTF_INT_EDGE_FALLING = 0x040, + TPM_INTF_INT_EDGE_RISING = 0x020, + TPM_INTF_INT_LEVEL_LOW = 0x010, + TPM_INTF_INT_LEVEL_HIGH = 0x008, + TPM_INTF_LOCALITY_CHANGE_INT = 0x004, + TPM_INTF_STS_VALID_INT = 0x002, + TPM_INTF_DATA_AVAIL_INT = 0x001, +}; + +enum tis_defaults { + TIS_MEM_LEN = 0x5000, + TIS_SHORT_TIMEOUT = 750, /* ms */ + TIS_LONG_TIMEOUT = 4000, /* 4 secs */ + TIS_TIMEOUT_MIN_ATML = 14700, /* usecs */ + TIS_TIMEOUT_MAX_ATML = 15000, /* usecs */ +}; + +#define TIS_MEM_X86_LPC_BASE 0xFED40000 +#define INTEL_LEGACY_BLK_BASE_ADDR 0xFED08000 + +enum tis_x86_defaults { + TIS_MEM_X86_LEN = 0x5000, + ILB_REMAP_SIZE = 0x100, + LPC_CNTRL_OFFSET = 0x84, + LPC_CLKRUN_EN = (1 << 2), +}; + +/* + * Some timeout values are needed before it is known whether the chip is + * TPM 1.0 or TPM 2.0. + */ +#define TIS_TIMEOUT_A_MAX max_t(int, TIS_SHORT_TIMEOUT, TPM2_TIMEOUT_A) +#define TIS_TIMEOUT_B_MAX max_t(int, TIS_LONG_TIMEOUT, TPM2_TIMEOUT_B) +#define TIS_TIMEOUT_C_MAX max_t(int, TIS_SHORT_TIMEOUT, TPM2_TIMEOUT_C) +#define TIS_TIMEOUT_D_MAX max_t(int, TIS_SHORT_TIMEOUT, TPM2_TIMEOUT_D) + +#define TPM_ACCESS(l) (0x0000 | ((l) << 12)) +#define TPM_INT_ENABLE(l) (0x0008 | ((l) << 12)) +#define TPM_INT_VECTOR(l) (0x000C | ((l) << 12)) +#define TPM_INT_STATUS(l) (0x0010 | ((l) << 12)) +#define TPM_INTF_CAPS(l) (0x0014 | ((l) << 12)) +#define TPM_STS(l) (0x0018 | ((l) << 12)) +#define TPM_STS3(l) (0x001b | ((l) << 12)) +#define TPM_DATA_FIFO(l) (0x0024 | ((l) << 12)) +#define TPM_INTF_ID(l) (0x0030 | ((l) << 12)) + +#define TPM_DID_VID(l) (0x0F00 | ((l) << 12)) +#define TPM_RID(l) (0x0F04 | ((l) << 12)) + +/* TPM HW Interface and Capabilities */ +#define TPM_TIS_INTF_ACTIVE 0x00 +#define TPM_CRB_INTF_ACTIVE 0x01 + +#define TPM_INTID_INTERFACE_TYPE GENMASK(3, 0) +#define TPM_INTID_INTERFACE_VERSION GENMASK(7, 4) +#define TPM_INTID_CAP_LOCALITY BIT(8) +#define TPM_INTID_CAP_TIS BIT(13) +#define TPM_INTID_CAP_CRB BIT(14) +#define TPM_INTID_CAP_IF_RES GENMASK(16, 15) +#define TPM_INTID_INTERFACE_SELECTOR GENMASK(18, 17) +#define TPM_INTID_INTF_SEL_LOCK BIT(19) + +#define TPM_TIS_INTF_12 0x00 +#define TPM_TIS_INTF_13 0x02 +#define TPM2_TIS_INTF_13 0x03 + +#define TPM_INTF_DATA_AVAIL_INT_SUPPORT BIT(0) +#define TPM_INTF_STS_VALID_INT_SUPPORT BIT(1) +#define TPM_INTF_LOCALITY_CHANGE_INT_SUPPORT BIT(2) +#define TPM_INTF_INTERRUPT_LEVEL_HIGH BIT(3) +#define TPM_INTF_INTERRUPT_LEVEL_LOW BIT(4) +#define TPM_INTF_INTERRUPT_EDGE_RISING BIT(5) +#define TPM_INTF_INTERRUPT_EDGE_FALLING BIT(6) +#define TPM_INTF_COMMAND_READY_INT_SUPPORT BIT(7) +#define TPM_INTF_BURST_COUNT_STATIC BIT(8) +#define TPM_INTF_DATA_TRANSFER_SIZE_SUPPORT GENMASK(10, 9) +#define TPM_INTF_INTERFACE_VERSION GENMASK(30, 28) + +#endif From 71ab22dbcfb5cf4c99d167a7e0452a40a74a192e Mon Sep 17 00:00:00 2001 From: Alec Brown Date: Sat, 11 Jul 2026 09:01:06 -0700 Subject: [PATCH 387/641] tpm: Remove main TPM header from TPM event log header Allow the TPM event log functionality to be used without including the main TPM driver definitions. Signed-off-by: Alec Brown Signed-off-by: Ross Philipson Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- include/linux/tpm_eventlog.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/tpm_eventlog.h b/include/linux/tpm_eventlog.h index aff8ea2fa98e..40fe92eb7deb 100644 --- a/include/linux/tpm_eventlog.h +++ b/include/linux/tpm_eventlog.h @@ -3,7 +3,7 @@ #ifndef __LINUX_TPM_EVENTLOG_H__ #define __LINUX_TPM_EVENTLOG_H__ -#include +#include #define TCG_EVENT_NAME_LEN_MAX 255 #define MAX_TEXT_EVENT 1000 /* Max event string length */ From 0b9551c189518544b65000a234277298e931c8f3 Mon Sep 17 00:00:00 2001 From: Jarkko Sakkinen Date: Sat, 11 Jul 2026 09:01:07 -0700 Subject: [PATCH 388/641] tpm-buf: Merge TPM_BUF_BOUNDARY_ERROR and TPM_BUF_OVERFLOW Merge TPM_BUF_BOUNDARY_ERROR and TPM_BUF_OVERFLOW flags into the TPM_BUF_INVALID flag, as their behavior is identical (the only difference being the associated log messages). Message-ID: <20260125192526.782202-11-jarkko@kernel.org> Signed-off-by: Jarkko Sakkinen Reviewed-by: Jonathan McDowell Signed-off-by: Ross Philipson --- drivers/char/tpm/tpm-buf.c | 10 ++++------ include/linux/tpm.h | 8 +++----- security/keys/trusted-keys/trusted_tpm2.c | 6 +++--- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/drivers/char/tpm/tpm-buf.c b/drivers/char/tpm/tpm-buf.c index 4c4f450630df..61833b4d81f0 100644 --- a/drivers/char/tpm/tpm-buf.c +++ b/drivers/char/tpm/tpm-buf.c @@ -103,13 +103,12 @@ EXPORT_SYMBOL_GPL(tpm_buf_length); */ void tpm_buf_append(struct tpm_buf *buf, const u8 *new_data, u16 new_length) { - /* Return silently if overflow has already happened. */ - if (buf->flags & TPM_BUF_OVERFLOW) + if (buf->flags & TPM_BUF_INVALID) return; if ((buf->length + new_length) > PAGE_SIZE) { WARN(1, "tpm_buf: write overflow\n"); - buf->flags |= TPM_BUF_OVERFLOW; + buf->flags |= TPM_BUF_INVALID; return; } @@ -176,14 +175,13 @@ static void tpm_buf_read(struct tpm_buf *buf, off_t *offset, size_t count, void { off_t next_offset; - /* Return silently if overflow has already happened. */ - if (buf->flags & TPM_BUF_BOUNDARY_ERROR) + if (buf->flags & TPM_BUF_INVALID) return; next_offset = *offset + count; if (next_offset > buf->length) { WARN(1, "tpm_buf: read out of boundary\n"); - buf->flags |= TPM_BUF_BOUNDARY_ERROR; + buf->flags |= TPM_BUF_INVALID; return; } diff --git a/include/linux/tpm.h b/include/linux/tpm.h index 3630b2ea6aef..3c6a5bcc138a 100644 --- a/include/linux/tpm.h +++ b/include/linux/tpm.h @@ -201,12 +201,10 @@ enum tpm_chip_flags { #define to_tpm_chip(d) container_of(d, struct tpm_chip, dev) enum tpm_buf_flags { - /* the capacity exceeded: */ - TPM_BUF_OVERFLOW = BIT(0), /* TPM2B format: */ - TPM_BUF_TPM2B = BIT(1), - /* read out of boundary: */ - TPM_BUF_BOUNDARY_ERROR = BIT(2), + TPM_BUF_TPM2B = BIT(0), + /* The buffer is in invalid and unusable state: */ + TPM_BUF_INVALID = BIT(1), }; /* diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c index 29d79c05ed6b..779a2e66ac20 100644 --- a/security/keys/trusted-keys/trusted_tpm2.c +++ b/security/keys/trusted-keys/trusted_tpm2.c @@ -312,7 +312,7 @@ int tpm2_seal_trusted(struct tpm_chip *chip, /* creation PCR */ tpm_buf_append_u32(&buf, 0); - if (buf.flags & TPM_BUF_OVERFLOW) { + if (buf.flags & TPM_BUF_INVALID) { rc = -E2BIG; tpm2_end_auth_session(chip); goto out; @@ -328,7 +328,7 @@ int tpm2_seal_trusted(struct tpm_chip *chip, goto out; blob_len = tpm_buf_read_u32(&buf, &offset); - if (blob_len > MAX_BLOB_SIZE || buf.flags & TPM_BUF_BOUNDARY_ERROR) { + if (blob_len > MAX_BLOB_SIZE || buf.flags & TPM_BUF_INVALID) { rc = -E2BIG; goto out; } @@ -441,7 +441,7 @@ static int tpm2_load_cmd(struct tpm_chip *chip, tpm_buf_append(&buf, blob, blob_len); - if (buf.flags & TPM_BUF_OVERFLOW) { + if (buf.flags & TPM_BUF_INVALID) { rc = -E2BIG; tpm2_end_auth_session(chip); goto out; From 065cfba1f4545e5948a2ddc5888a27c7641d5bfc Mon Sep 17 00:00:00 2001 From: Jarkko Sakkinen Date: Sat, 11 Jul 2026 09:01:08 -0700 Subject: [PATCH 389/641] tpm-buf: Remove chip parameter from tpm_buf_append_handle() Remove the TPM driver chip parameter from the function tpm_buf_append_handle(). The chip parameter is only for error logging which can be done with other facilities like WARN(). Message-ID: <20260125192526.782202-11-jarkko@kernel.org> Signed-off-by: Jarkko Sakkinen Signed-off-by: Ross Philipson --- drivers/char/tpm/tpm-buf.c | 9 ++++++--- drivers/char/tpm/tpm2-cmd.c | 2 +- drivers/char/tpm/tpm2-sessions.c | 2 +- include/linux/tpm.h | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/char/tpm/tpm-buf.c b/drivers/char/tpm/tpm-buf.c index 61833b4d81f0..99811809a72a 100644 --- a/drivers/char/tpm/tpm-buf.c +++ b/drivers/char/tpm/tpm-buf.c @@ -146,17 +146,20 @@ EXPORT_SYMBOL_GPL(tpm_buf_append_u32); /** * tpm_buf_append_handle() - Add a handle - * @chip: &tpm_chip instance * @buf: &tpm_buf instance * @handle: a TPM object handle * * Add a handle to the buffer, and increase the count tracking the number of * handles in the command buffer. Works only for command buffers. */ -void tpm_buf_append_handle(struct tpm_chip *chip, struct tpm_buf *buf, u32 handle) +void tpm_buf_append_handle(struct tpm_buf *buf, u32 handle) { + if (buf->flags & TPM_BUF_INVALID) + return; + if (buf->flags & TPM_BUF_TPM2B) { - dev_err(&chip->dev, "Invalid buffer type (TPM2B)\n"); + WARN(1, "tpm-buf: invalid type: TPM2B\n"); + buf->flags |= TPM_BUF_INVALID; return; } diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c index 280f870e6517..c7952319384e 100644 --- a/drivers/char/tpm/tpm2-cmd.c +++ b/drivers/char/tpm/tpm2-cmd.c @@ -198,7 +198,7 @@ int tpm2_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, } tpm_buf_append_hmac_session(chip, &buf, 0, NULL, 0); } else { - tpm_buf_append_handle(chip, &buf, pcr_idx); + tpm_buf_append_handle(&buf, pcr_idx); tpm_buf_append_auth(chip, &buf, NULL, 0); } diff --git a/drivers/char/tpm/tpm2-sessions.c b/drivers/char/tpm/tpm2-sessions.c index a1ae5e1829cb..0d89643e6880 100644 --- a/drivers/char/tpm/tpm2-sessions.c +++ b/drivers/char/tpm/tpm2-sessions.c @@ -260,7 +260,7 @@ int tpm_buf_append_name(struct tpm_chip *chip, struct tpm_buf *buf, #endif if (!tpm2_chip_auth(chip)) { - tpm_buf_append_handle(chip, buf, handle); + tpm_buf_append_handle(buf, handle); return 0; } diff --git a/include/linux/tpm.h b/include/linux/tpm.h index 3c6a5bcc138a..b357f8971d03 100644 --- a/include/linux/tpm.h +++ b/include/linux/tpm.h @@ -235,7 +235,7 @@ void tpm_buf_append_u32(struct tpm_buf *buf, const u32 value); u8 tpm_buf_read_u8(struct tpm_buf *buf, off_t *offset); u16 tpm_buf_read_u16(struct tpm_buf *buf, off_t *offset); u32 tpm_buf_read_u32(struct tpm_buf *buf, off_t *offset); -void tpm_buf_append_handle(struct tpm_chip *chip, struct tpm_buf *buf, u32 handle); +void tpm_buf_append_handle(struct tpm_buf *buf, u32 handle); /* * Check if TPM device is in the firmware upgrade mode. From 3d9e043dab0a038a53a43570f60bbf4b1e27d63f Mon Sep 17 00:00:00 2001 From: Jarkko Sakkinen Date: Sat, 11 Jul 2026 09:01:09 -0700 Subject: [PATCH 390/641] tpm-buf: Memory-safe allocations Decouple kzalloc from buffer creation, so that a managed allocation can be used: struct tpm_buf *buf __free(kfree) buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); if (!buf) return -ENOMEM; tpm_buf_init(buf, TPM_BUFSIZE); Alternatively, other allocations are also possible (static data, stack, etc) for example: u8 buf_data[512]; struct tpm_buf *buf = (struct tpm_buf *)buf_data; tpm_buf_init(buf, sizeof(buf_data)); This is achieved by embedding buffer's header inside the allocated blob, instead of having an outer wrapper. Reviewed-by: Stefan Berger Signed-off-by: Jarkko Sakkinen Tested-by: Srish Srinivasan Message-ID: <20260522013555.1063716-1-jarkko@kernel.org> Signed-off-by: Ross Philipson --- drivers/char/tpm/tpm-buf.c | 124 +++++---- drivers/char/tpm/tpm-sysfs.c | 26 +- drivers/char/tpm/tpm1-cmd.c | 167 ++++++------ drivers/char/tpm/tpm2-cmd.c | 297 +++++++++++----------- drivers/char/tpm/tpm2-sessions.c | 164 ++++++------ drivers/char/tpm/tpm2-space.c | 44 ++-- drivers/char/tpm/tpm_vtpm_proxy.c | 30 +-- include/linux/tpm.h | 17 +- security/keys/trusted-keys/trusted_tpm1.c | 44 ++-- security/keys/trusted-keys/trusted_tpm2.c | 167 ++++++------ 10 files changed, 538 insertions(+), 542 deletions(-) diff --git a/drivers/char/tpm/tpm-buf.c b/drivers/char/tpm/tpm-buf.c index 99811809a72a..1e5c11c312a8 100644 --- a/drivers/char/tpm/tpm-buf.c +++ b/drivers/char/tpm/tpm-buf.c @@ -6,90 +6,118 @@ #include #include -/** - * tpm_buf_init() - Allocate and initialize a TPM command - * @buf: A &tpm_buf - * @tag: TPM_TAG_RQU_COMMAND, TPM2_ST_NO_SESSIONS or TPM2_ST_SESSIONS - * @ordinal: A command ordinal - * - * Return: 0 or -ENOMEM - */ -int tpm_buf_init(struct tpm_buf *buf, u16 tag, u32 ordinal) +static void __tpm_buf_size_invariant(struct tpm_buf *buf, u16 buf_size) { - buf->data = (u8 *)__get_free_page(GFP_KERNEL); - if (!buf->data) - return -ENOMEM; + u32 buf_size_2 = (u32)buf->capacity + (u32)sizeof(*buf); - tpm_buf_reset(buf, tag, ordinal); - return 0; + if (!buf->capacity) { + if (buf_size > TPM_BUFSIZE) { + WARN(1, "%s: size overflow: %u\n", __func__, buf_size); + buf->flags |= TPM_BUF_INVALID; + } + } else { + if (buf_size != buf_size_2) { + WARN(1, "%s: size mismatch: %u != %u\n", __func__, + buf_size, buf_size_2); + buf->flags |= TPM_BUF_INVALID; + } + } } -EXPORT_SYMBOL_GPL(tpm_buf_init); -/** - * tpm_buf_reset() - Initialize a TPM command - * @buf: A &tpm_buf - * @tag: TPM_TAG_RQU_COMMAND, TPM2_ST_NO_SESSIONS or TPM2_ST_SESSIONS - * @ordinal: A command ordinal - */ -void tpm_buf_reset(struct tpm_buf *buf, u16 tag, u32 ordinal) +static void __tpm_buf_reset(struct tpm_buf *buf, u16 buf_size, u16 tag, + u32 ordinal) { struct tpm_header *head = (struct tpm_header *)buf->data; + __tpm_buf_size_invariant(buf, buf_size); + + if (buf->flags & TPM_BUF_INVALID) + return; + WARN_ON(tag != TPM_TAG_RQU_COMMAND && tag != TPM2_ST_NO_SESSIONS && tag != TPM2_ST_SESSIONS && tag != 0); buf->flags = 0; buf->length = sizeof(*head); + buf->capacity = buf_size - sizeof(*buf); + buf->handles = 0; head->tag = cpu_to_be16(tag); head->length = cpu_to_be32(sizeof(*head)); head->ordinal = cpu_to_be32(ordinal); - buf->handles = 0; } -EXPORT_SYMBOL_GPL(tpm_buf_reset); + +static void __tpm_buf_reset_sized(struct tpm_buf *buf, u16 buf_size) +{ + __tpm_buf_size_invariant(buf, buf_size); + + if (buf->flags & TPM_BUF_INVALID) + return; + + buf->flags = TPM_BUF_TPM2B; + buf->length = 2; + buf->capacity = buf_size - sizeof(*buf); + buf->handles = 0; + buf->data[0] = 0; + buf->data[1] = 0; +} /** - * tpm_buf_init_sized() - Allocate and initialize a sized (TPM2B) buffer - * @buf: A @tpm_buf - * - * Return: 0 or -ENOMEM + * tpm_buf_init() - Initialize a TPM command + * @buf: A &tpm_buf + * @buf_size: Size of the buffer. */ -int tpm_buf_init_sized(struct tpm_buf *buf) +void tpm_buf_init(struct tpm_buf *buf, u16 buf_size) { - buf->data = (u8 *)__get_free_page(GFP_KERNEL); - if (!buf->data) - return -ENOMEM; + memset(buf, 0, buf_size); + __tpm_buf_reset(buf, buf_size, TPM_TAG_RQU_COMMAND, 0); +} +EXPORT_SYMBOL_GPL(tpm_buf_init); - tpm_buf_reset_sized(buf); - return 0; +/** + * tpm_buf_init_sized() - Initialize a sized buffer + * @buf: A &tpm_buf + * @buf_size: Size of the buffer. + */ +void tpm_buf_init_sized(struct tpm_buf *buf, u16 buf_size) +{ + memset(buf, 0, buf_size); + __tpm_buf_reset_sized(buf, buf_size); } EXPORT_SYMBOL_GPL(tpm_buf_init_sized); /** - * tpm_buf_reset_sized() - Initialize a sized buffer + * tpm_buf_reset() - Re-initialize a TPM command + * @buf: A &tpm_buf + * @tag: TPM_TAG_RQU_COMMAND, TPM2_ST_NO_SESSIONS or TPM2_ST_SESSIONS + * @ordinal: A command ordinal + */ +void tpm_buf_reset(struct tpm_buf *buf, u16 tag, u32 ordinal) +{ + u16 buf_size = buf->capacity + sizeof(*buf); + + __tpm_buf_reset(buf, buf_size, tag, ordinal); +} +EXPORT_SYMBOL_GPL(tpm_buf_reset); + +/** + * tpm_buf_reset_sized() - Re-initialize a sized buffer * @buf: A &tpm_buf */ void tpm_buf_reset_sized(struct tpm_buf *buf) { - buf->flags = TPM_BUF_TPM2B; - buf->length = 2; - buf->data[0] = 0; - buf->data[1] = 0; + u16 buf_size = buf->capacity + sizeof(*buf); + + __tpm_buf_reset_sized(buf, buf_size); } EXPORT_SYMBOL_GPL(tpm_buf_reset_sized); -void tpm_buf_destroy(struct tpm_buf *buf) -{ - free_page((unsigned long)buf->data); -} -EXPORT_SYMBOL_GPL(tpm_buf_destroy); - /** * tpm_buf_length() - Return the number of bytes consumed by the data * @buf: A &tpm_buf * * Return: The number of bytes consumed by the buffer */ -u32 tpm_buf_length(struct tpm_buf *buf) +u16 tpm_buf_length(struct tpm_buf *buf) { return buf->length; } @@ -103,10 +131,12 @@ EXPORT_SYMBOL_GPL(tpm_buf_length); */ void tpm_buf_append(struct tpm_buf *buf, const u8 *new_data, u16 new_length) { + u32 total_length = (u32)buf->length + (u32)new_length; + if (buf->flags & TPM_BUF_INVALID) return; - if ((buf->length + new_length) > PAGE_SIZE) { + if (total_length > (u32)buf->capacity) { WARN(1, "tpm_buf: write overflow\n"); buf->flags |= TPM_BUF_INVALID; return; diff --git a/drivers/char/tpm/tpm-sysfs.c b/drivers/char/tpm/tpm-sysfs.c index 94231f052ea7..d915307ae833 100644 --- a/drivers/char/tpm/tpm-sysfs.c +++ b/drivers/char/tpm/tpm-sysfs.c @@ -32,28 +32,35 @@ struct tpm_readpubek_out { static ssize_t pubek_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct tpm_buf tpm_buf; struct tpm_readpubek_out *out; int i; char *str = buf; struct tpm_chip *chip = to_tpm_chip(dev); char anti_replay[20]; + struct tpm_buf *tpm_buf __free(kfree) = NULL; memset(&anti_replay, 0, sizeof(anti_replay)); if (tpm_try_get_ops(chip)) return 0; - if (tpm_buf_init(&tpm_buf, TPM_TAG_RQU_COMMAND, TPM_ORD_READPUBEK)) - goto out_ops; + tpm_buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!tpm_buf) { + tpm_put_ops(chip); + return 0; + } - tpm_buf_append(&tpm_buf, anti_replay, sizeof(anti_replay)); + tpm_buf_init(tpm_buf, TPM_BUFSIZE); + tpm_buf_reset(tpm_buf, TPM_TAG_RQU_COMMAND, TPM_ORD_READPUBEK); + tpm_buf_append(tpm_buf, anti_replay, sizeof(anti_replay)); - if (tpm_transmit_cmd(chip, &tpm_buf, READ_PUBEK_RESULT_MIN_BODY_SIZE, - "attempting to read the PUBEK")) - goto out_buf; + if (tpm_transmit_cmd(chip, tpm_buf, READ_PUBEK_RESULT_MIN_BODY_SIZE, + "attempting to read the PUBEK")) { + tpm_put_ops(chip); + return 0; + } - out = (struct tpm_readpubek_out *)&tpm_buf.data[10]; + out = (struct tpm_readpubek_out *)&tpm_buf->data[10]; str += sprintf(str, "Algorithm: %4ph\n" @@ -71,9 +78,6 @@ static ssize_t pubek_show(struct device *dev, struct device_attribute *attr, for (i = 0; i < 256; i += 16) str += sprintf(str, "%16ph\n", &out->modulus[i]); -out_buf: - tpm_buf_destroy(&tpm_buf); -out_ops: tpm_put_ops(chip); return str - buf; } diff --git a/drivers/char/tpm/tpm1-cmd.c b/drivers/char/tpm/tpm1-cmd.c index 96f189b5fd6f..7a8606e8c711 100644 --- a/drivers/char/tpm/tpm1-cmd.c +++ b/drivers/char/tpm/tpm1-cmd.c @@ -318,20 +318,18 @@ unsigned long tpm1_calc_ordinal_duration(struct tpm_chip *chip, u32 ordinal) */ static int tpm1_startup(struct tpm_chip *chip) { - struct tpm_buf buf; - int rc; + struct tpm_buf *buf __free(kfree) = NULL; dev_info(&chip->dev, "starting up the TPM manually\n"); - rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_STARTUP); - if (rc < 0) - return rc; + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; - tpm_buf_append_u16(&buf, TPM_ST_CLEAR); - - rc = tpm_transmit_cmd(chip, &buf, 0, "attempting to start the TPM"); - tpm_buf_destroy(&buf); - return rc; + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_STARTUP); + tpm_buf_append_u16(buf, TPM_ST_CLEAR); + return tpm_transmit_cmd(chip, buf, 0, "attempting to start the TPM"); } int tpm1_get_timeouts(struct tpm_chip *chip) @@ -457,49 +455,49 @@ int tpm1_get_timeouts(struct tpm_chip *chip) int tpm1_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, const u8 *hash, const char *log_msg) { - struct tpm_buf buf; - int rc; + struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; - rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_PCR_EXTEND); - if (rc) - return rc; + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_PCR_EXTEND); + tpm_buf_append_u32(buf, pcr_idx); + tpm_buf_append(buf, hash, TPM_DIGEST_SIZE); - tpm_buf_append_u32(&buf, pcr_idx); - tpm_buf_append(&buf, hash, TPM_DIGEST_SIZE); - - rc = tpm_transmit_cmd(chip, &buf, TPM_DIGEST_SIZE, log_msg); - tpm_buf_destroy(&buf); - return rc; + return tpm_transmit_cmd(chip, buf, TPM_DIGEST_SIZE, log_msg); } ssize_t tpm1_getcap(struct tpm_chip *chip, u32 subcap_id, cap_t *cap, const char *desc, size_t min_cap_length) { - struct tpm_buf buf; int rc; - rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_GET_CAP); - if (rc) - return rc; + struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_GET_CAP); if (subcap_id == TPM_CAP_VERSION_1_1 || subcap_id == TPM_CAP_VERSION_1_2) { - tpm_buf_append_u32(&buf, subcap_id); - tpm_buf_append_u32(&buf, 0); + tpm_buf_append_u32(buf, subcap_id); + tpm_buf_append_u32(buf, 0); } else { if (subcap_id == TPM_CAP_FLAG_PERM || subcap_id == TPM_CAP_FLAG_VOL) - tpm_buf_append_u32(&buf, TPM_CAP_FLAG); + tpm_buf_append_u32(buf, TPM_CAP_FLAG); else - tpm_buf_append_u32(&buf, TPM_CAP_PROP); + tpm_buf_append_u32(buf, TPM_CAP_PROP); - tpm_buf_append_u32(&buf, 4); - tpm_buf_append_u32(&buf, subcap_id); + tpm_buf_append_u32(buf, 4); + tpm_buf_append_u32(buf, subcap_id); } - rc = tpm_transmit_cmd(chip, &buf, min_cap_length, desc); + + rc = tpm_transmit_cmd(chip, buf, min_cap_length, desc); if (!rc) - *cap = *(cap_t *)&buf.data[TPM_HEADER_SIZE + 4]; - tpm_buf_destroy(&buf); + *cap = *(cap_t *)&buf->data[TPM_HEADER_SIZE + 4]; + return rc; } EXPORT_SYMBOL_GPL(tpm1_getcap); @@ -517,81 +515,77 @@ EXPORT_SYMBOL_GPL(tpm1_getcap); int tpm1_get_random(struct tpm_chip *chip, u8 *dest, size_t max) { struct tpm1_get_random_out *out; + struct tpm_buf *buf __free(kfree) = NULL; u32 num_bytes = min_t(u32, max, TPM_MAX_RNG_DATA); - struct tpm_buf buf; u32 total = 0; int retries = 5; u32 recd; int rc; - rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_GET_RANDOM); - if (rc) - return rc; + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_GET_RANDOM); do { - tpm_buf_append_u32(&buf, num_bytes); + tpm_buf_append_u32(buf, num_bytes); - rc = tpm_transmit_cmd(chip, &buf, sizeof(out->rng_data_len), + rc = tpm_transmit_cmd(chip, buf, sizeof(out->rng_data_len), "attempting get random"); if (rc) { if (rc > 0) rc = -EIO; - goto out; + return rc; } - out = (struct tpm1_get_random_out *)&buf.data[TPM_HEADER_SIZE]; + out = (struct tpm1_get_random_out *)&buf->data[TPM_HEADER_SIZE]; recd = be32_to_cpu(out->rng_data_len); - if (recd > num_bytes) { - rc = -EFAULT; - goto out; - } + if (recd > num_bytes) + return -EFAULT; + + if (tpm_buf_length(buf) < TPM_HEADER_SIZE + + sizeof(out->rng_data_len) + recd) + return -EFAULT; - if (tpm_buf_length(&buf) < TPM_HEADER_SIZE + - sizeof(out->rng_data_len) + recd) { - rc = -EFAULT; - goto out; - } memcpy(dest, out->rng_data, recd); dest += recd; total += recd; num_bytes -= recd; - tpm_buf_reset(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_GET_RANDOM); + tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_GET_RANDOM); } while (retries-- && total < max); rc = total ? (int)total : -EIO; -out: - tpm_buf_destroy(&buf); + return rc; } int tpm1_pcr_read(struct tpm_chip *chip, u32 pcr_idx, u8 *res_buf) { - struct tpm_buf buf; int rc; - rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_PCR_READ); + struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_PCR_READ); + tpm_buf_append_u32(buf, pcr_idx); + + rc = tpm_transmit_cmd(chip, buf, TPM_DIGEST_SIZE, + "attempting to read a pcr value"); if (rc) return rc; - tpm_buf_append_u32(&buf, pcr_idx); + if (tpm_buf_length(buf) < TPM_DIGEST_SIZE) + return -EFAULT; - rc = tpm_transmit_cmd(chip, &buf, TPM_DIGEST_SIZE, - "attempting to read a pcr value"); - if (rc) - goto out; + memcpy(res_buf, &buf->data[TPM_HEADER_SIZE], TPM_DIGEST_SIZE); - if (tpm_buf_length(&buf) < TPM_DIGEST_SIZE) { - rc = -EFAULT; - goto out; - } - - memcpy(res_buf, &buf.data[TPM_HEADER_SIZE], TPM_DIGEST_SIZE); - -out: - tpm_buf_destroy(&buf); return rc; } @@ -604,16 +598,13 @@ out: */ static int tpm1_continue_selftest(struct tpm_chip *chip) { - struct tpm_buf buf; - int rc; + struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; - rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_CONTINUE_SELFTEST); - if (rc) - return rc; - - rc = tpm_transmit_cmd(chip, &buf, 0, "continue selftest"); - tpm_buf_destroy(&buf); - return rc; + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_CONTINUE_SELFTEST); + return tpm_transmit_cmd(chip, buf, 0, "continue selftest"); } /** @@ -725,22 +716,24 @@ out: int tpm1_pm_suspend(struct tpm_chip *chip, u32 tpm_suspend_pcr) { u8 dummy_hash[TPM_DIGEST_SIZE] = { 0 }; - struct tpm_buf buf; unsigned int try; + struct tpm_buf *buf __free(kfree) = NULL; int rc; - /* for buggy tpm, flush pcrs with extend to selected dummy */ if (tpm_suspend_pcr) rc = tpm1_pcr_extend(chip, tpm_suspend_pcr, dummy_hash, "extending dummy pcr before suspend"); + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_SAVESTATE); - rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_SAVESTATE); - if (rc) - return rc; /* now do the actual savestate */ for (try = 0; try < TPM_RETRY; try++) { - rc = tpm_transmit_cmd(chip, &buf, 0, NULL); + rc = tpm_transmit_cmd(chip, buf, 0, NULL); /* * If the TPM indicates that it is too busy to respond to * this command then retry before giving up. It can take @@ -755,7 +748,7 @@ int tpm1_pm_suspend(struct tpm_chip *chip, u32 tpm_suspend_pcr) break; tpm_msleep(TPM_TIMEOUT_RETRY); - tpm_buf_reset(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_SAVESTATE); + tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_SAVESTATE); } if (rc) @@ -765,8 +758,6 @@ int tpm1_pm_suspend(struct tpm_chip *chip, u32 tpm_suspend_pcr) dev_warn(&chip->dev, "TPM savestate took %dms\n", try * TPM_TIMEOUT_RETRY); - tpm_buf_destroy(&buf); - return rc; } diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c index c7952319384e..48cec39995fe 100644 --- a/drivers/char/tpm/tpm2-cmd.c +++ b/drivers/char/tpm/tpm2-cmd.c @@ -108,12 +108,13 @@ int tpm2_pcr_read(struct tpm_chip *chip, u32 pcr_idx, { int i; int rc; - struct tpm_buf buf; struct tpm2_pcr_read_out *out; u8 pcr_select[TPM2_PCR_SELECT_MIN] = {0}; u16 digest_size; u16 expected_digest_size = 0; + struct tpm_buf *buf __free(kfree) = NULL; + if (pcr_idx >= TPM2_PLATFORM_PCR) return -EINVAL; @@ -128,36 +129,35 @@ int tpm2_pcr_read(struct tpm_chip *chip, u32 pcr_idx, expected_digest_size = chip->allocated_banks[i].digest_size; } - rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_PCR_READ); - if (rc) - return rc; + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_PCR_READ); pcr_select[pcr_idx >> 3] = 1 << (pcr_idx & 0x7); - tpm_buf_append_u32(&buf, 1); - tpm_buf_append_u16(&buf, digest->alg_id); - tpm_buf_append_u8(&buf, TPM2_PCR_SELECT_MIN); - tpm_buf_append(&buf, (const unsigned char *)pcr_select, + tpm_buf_append_u32(buf, 1); + tpm_buf_append_u16(buf, digest->alg_id); + tpm_buf_append_u8(buf, TPM2_PCR_SELECT_MIN); + tpm_buf_append(buf, (const unsigned char *)pcr_select, sizeof(pcr_select)); - rc = tpm_transmit_cmd(chip, &buf, 0, "attempting to read a pcr value"); + rc = tpm_transmit_cmd(chip, buf, 0, "attempting to read a pcr value"); if (rc) - goto out; + return rc; - out = (struct tpm2_pcr_read_out *)&buf.data[TPM_HEADER_SIZE]; + out = (struct tpm2_pcr_read_out *)&buf->data[TPM_HEADER_SIZE]; digest_size = be16_to_cpu(out->digest_size); if (digest_size > sizeof(digest->digest) || - (!digest_size_ptr && digest_size != expected_digest_size)) { - rc = -EINVAL; - goto out; - } + (!digest_size_ptr && digest_size != expected_digest_size)) + return -EINVAL; if (digest_size_ptr) *digest_size_ptr = digest_size; memcpy(digest->digest, out->digest, digest_size); -out: - tpm_buf_destroy(&buf); return rc; } @@ -173,7 +173,7 @@ out: int tpm2_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, struct tpm_digest *digests) { - struct tpm_buf buf; + struct tpm_buf *buf __free(kfree) = NULL; int rc; int i; @@ -183,46 +183,45 @@ int tpm2_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, return rc; } - rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_PCR_EXTEND); - if (rc) { + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) { if (!disable_pcr_integrity) tpm2_end_auth_session(chip); - return rc; + return -ENOMEM; } + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_PCR_EXTEND); + if (!disable_pcr_integrity) { - rc = tpm_buf_append_name(chip, &buf, pcr_idx, NULL); + rc = tpm_buf_append_name(chip, buf, pcr_idx, NULL); if (rc) { - tpm_buf_destroy(&buf); + tpm2_end_auth_session(chip); return rc; } - tpm_buf_append_hmac_session(chip, &buf, 0, NULL, 0); + tpm_buf_append_hmac_session(chip, buf, 0, NULL, 0); } else { - tpm_buf_append_handle(&buf, pcr_idx); - tpm_buf_append_auth(chip, &buf, NULL, 0); + tpm_buf_append_handle(buf, pcr_idx); + tpm_buf_append_auth(chip, buf, NULL, 0); } - tpm_buf_append_u32(&buf, chip->nr_allocated_banks); + tpm_buf_append_u32(buf, chip->nr_allocated_banks); for (i = 0; i < chip->nr_allocated_banks; i++) { - tpm_buf_append_u16(&buf, digests[i].alg_id); - tpm_buf_append(&buf, (const unsigned char *)&digests[i].digest, + tpm_buf_append_u16(buf, digests[i].alg_id); + tpm_buf_append(buf, (const unsigned char *)&digests[i].digest, chip->allocated_banks[i].digest_size); } if (!disable_pcr_integrity) { - rc = tpm_buf_fill_hmac_session(chip, &buf); - if (rc) { - tpm_buf_destroy(&buf); + rc = tpm_buf_fill_hmac_session(chip, buf); + if (rc) return rc; - } } - rc = tpm_transmit_cmd(chip, &buf, 0, "attempting extend a PCR value"); + rc = tpm_transmit_cmd(chip, buf, 0, "attempting extend a PCR value"); if (!disable_pcr_integrity) - rc = tpm_buf_check_hmac_response(chip, &buf, rc); - - tpm_buf_destroy(&buf); + rc = tpm_buf_check_hmac_response(chip, buf, rc); return rc; } @@ -242,7 +241,6 @@ int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max) { struct tpm2_get_random_out *out; struct tpm_header *head; - struct tpm_buf buf; u32 recd; u32 num_bytes = max; int err; @@ -251,6 +249,8 @@ int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max) u8 *dest_ptr = dest; off_t offset; + struct tpm_buf *buf __free(kfree) = NULL; + if (!num_bytes || max > TPM_MAX_RNG_DATA) return -EINVAL; @@ -258,55 +258,58 @@ int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max) if (err) return err; - err = tpm_buf_init(&buf, 0, 0); - if (err) { + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) { tpm2_end_auth_session(chip); - return err; + return -ENOMEM; } + tpm_buf_init(buf, TPM_BUFSIZE); + do { - tpm_buf_reset(&buf, TPM2_ST_SESSIONS, TPM2_CC_GET_RANDOM); + tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_GET_RANDOM); if (tpm2_chip_auth(chip)) { - tpm_buf_append_hmac_session(chip, &buf, + tpm_buf_append_hmac_session(chip, buf, TPM2_SA_ENCRYPT | TPM2_SA_CONTINUE_SESSION, NULL, 0); } else { - offset = buf.handles * 4 + TPM_HEADER_SIZE; - head = (struct tpm_header *)buf.data; - if (tpm_buf_length(&buf) == offset) + offset = buf->handles * 4 + TPM_HEADER_SIZE; + head = (struct tpm_header *)buf->data; + if (tpm_buf_length(buf) == offset) head->tag = cpu_to_be16(TPM2_ST_NO_SESSIONS); } - tpm_buf_append_u16(&buf, num_bytes); - err = tpm_buf_fill_hmac_session(chip, &buf); + tpm_buf_append_u16(buf, num_bytes); + err = tpm_buf_fill_hmac_session(chip, buf); if (err) - goto out; + return err; - err = tpm_transmit_cmd(chip, &buf, + err = tpm_transmit_cmd(chip, buf, offsetof(struct tpm2_get_random_out, buffer), "attempting get random"); - err = tpm_buf_check_hmac_response(chip, &buf, err); + err = tpm_buf_check_hmac_response(chip, buf, err); if (err) { if (err > 0) err = -EIO; - goto out; + tpm2_end_auth_session(chip); + return err; } - head = (struct tpm_header *)buf.data; + head = (struct tpm_header *)buf->data; offset = TPM_HEADER_SIZE; /* Skip the parameter size field: */ if (be16_to_cpu(head->tag) == TPM2_ST_SESSIONS) offset += 4; - out = (struct tpm2_get_random_out *)&buf.data[offset]; + out = (struct tpm2_get_random_out *)&buf->data[offset]; recd = min_t(u32, be16_to_cpu(out->size), num_bytes); - if (tpm_buf_length(&buf) < + if (tpm_buf_length(buf) < TPM_HEADER_SIZE + offsetof(struct tpm2_get_random_out, buffer) + recd) { - err = -EFAULT; - goto out; + tpm2_end_auth_session(chip); + return -EFAULT; } memcpy(dest_ptr, out->buffer, recd); @@ -315,13 +318,7 @@ int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max) num_bytes -= recd; } while (retries-- && total < max); - tpm_buf_destroy(&buf); - return total ? total : -EIO; -out: - tpm_buf_destroy(&buf); - tpm2_end_auth_session(chip); - return err; } /** @@ -331,20 +328,18 @@ out: */ void tpm2_flush_context(struct tpm_chip *chip, u32 handle) { - struct tpm_buf buf; - int rc; - - rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_FLUSH_CONTEXT); - if (rc) { + struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) { dev_warn(&chip->dev, "0x%08x was not flushed, out of memory\n", handle); return; } - tpm_buf_append_u32(&buf, handle); + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_FLUSH_CONTEXT); + tpm_buf_append_u32(buf, handle); - tpm_transmit_cmd(chip, &buf, 0, "flushing context"); - tpm_buf_destroy(&buf); + tpm_transmit_cmd(chip, buf, 0, "flushing context"); } EXPORT_SYMBOL_GPL(tpm2_flush_context); @@ -363,19 +358,21 @@ ssize_t tpm2_get_tpm_pt(struct tpm_chip *chip, u32 property_id, u32 *value, const char *desc) { struct tpm2_get_cap_out *out; - struct tpm_buf buf; int rc; - rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_GET_CAPABILITY); - if (rc) - return rc; - tpm_buf_append_u32(&buf, TPM2_CAP_TPM_PROPERTIES); - tpm_buf_append_u32(&buf, property_id); - tpm_buf_append_u32(&buf, 1); - rc = tpm_transmit_cmd(chip, &buf, 0, NULL); + struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_GET_CAPABILITY); + tpm_buf_append_u32(buf, TPM2_CAP_TPM_PROPERTIES); + tpm_buf_append_u32(buf, property_id); + tpm_buf_append_u32(buf, 1); + rc = tpm_transmit_cmd(chip, buf, 0, NULL); if (!rc) { out = (struct tpm2_get_cap_out *) - &buf.data[TPM_HEADER_SIZE]; + &buf->data[TPM_HEADER_SIZE]; /* * To prevent failing boot up of some systems, Infineon TPM2.0 * returns SUCCESS on TPM2_Startup in field upgrade mode. Also @@ -387,7 +384,6 @@ ssize_t tpm2_get_tpm_pt(struct tpm_chip *chip, u32 property_id, u32 *value, else rc = -ENODATA; } - tpm_buf_destroy(&buf); return rc; } EXPORT_SYMBOL_GPL(tpm2_get_tpm_pt); @@ -404,15 +400,14 @@ EXPORT_SYMBOL_GPL(tpm2_get_tpm_pt); */ void tpm2_shutdown(struct tpm_chip *chip, u16 shutdown_type) { - struct tpm_buf buf; - int rc; - - rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_SHUTDOWN); - if (rc) + struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) return; - tpm_buf_append_u16(&buf, shutdown_type); - tpm_transmit_cmd(chip, &buf, 0, "stopping the TPM"); - tpm_buf_destroy(&buf); + + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_SHUTDOWN); + tpm_buf_append_u16(buf, shutdown_type); + tpm_transmit_cmd(chip, buf, 0, "stopping the TPM"); } /** @@ -430,20 +425,21 @@ void tpm2_shutdown(struct tpm_chip *chip, u16 shutdown_type) */ static int tpm2_do_selftest(struct tpm_chip *chip) { - struct tpm_buf buf; int full; int rc; for (full = 0; full < 2; full++) { - rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_SELF_TEST); - if (rc) - return rc; + struct tpm_buf *buf __free(kfree) = NULL; - tpm_buf_append_u8(&buf, full); - rc = tpm_transmit_cmd(chip, &buf, 0, + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_SELF_TEST); + tpm_buf_append_u8(buf, full); + rc = tpm_transmit_cmd(chip, buf, 0, "attempting the self test"); - tpm_buf_destroy(&buf); - if (rc == TPM2_RC_TESTING) rc = TPM2_RC_SUCCESS; if (rc == TPM2_RC_INITIALIZE || rc == TPM2_RC_SUCCESS) @@ -468,23 +464,24 @@ static int tpm2_do_selftest(struct tpm_chip *chip) int tpm2_probe(struct tpm_chip *chip) { struct tpm_header *out; - struct tpm_buf buf; int rc; - rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_GET_CAPABILITY); - if (rc) - return rc; - tpm_buf_append_u32(&buf, TPM2_CAP_TPM_PROPERTIES); - tpm_buf_append_u32(&buf, TPM_PT_TOTAL_COMMANDS); - tpm_buf_append_u32(&buf, 1); - rc = tpm_transmit_cmd(chip, &buf, 0, NULL); + struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_GET_CAPABILITY); + tpm_buf_append_u32(buf, TPM2_CAP_TPM_PROPERTIES); + tpm_buf_append_u32(buf, TPM_PT_TOTAL_COMMANDS); + tpm_buf_append_u32(buf, 1); + rc = tpm_transmit_cmd(chip, buf, 0, NULL); /* We ignore TPM return codes on purpose. */ if (rc >= 0) { - out = (struct tpm_header *)buf.data; + out = (struct tpm_header *)buf->data; if (be16_to_cpu(out->tag) == TPM2_ST_NO_SESSIONS) chip->flags |= TPM_CHIP_FLAG_TPM2; } - tpm_buf_destroy(&buf); return 0; } EXPORT_SYMBOL_GPL(tpm2_probe); @@ -518,7 +515,6 @@ static int tpm2_init_bank_info(struct tpm_chip *chip, u32 bank_index) ssize_t tpm2_get_pcr_allocation(struct tpm_chip *chip) { struct tpm2_pcr_selection pcr_selection; - struct tpm_buf buf; void *marker; void *end; void *pcr_select_offset; @@ -530,31 +526,32 @@ ssize_t tpm2_get_pcr_allocation(struct tpm_chip *chip) int rc; int i = 0; - rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_GET_CAPABILITY); + struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_GET_CAPABILITY); + tpm_buf_append_u32(buf, TPM2_CAP_PCRS); + tpm_buf_append_u32(buf, 0); + tpm_buf_append_u32(buf, 1); + + rc = tpm_transmit_cmd(chip, buf, 9, "get tpm pcr allocation"); if (rc) return rc; - tpm_buf_append_u32(&buf, TPM2_CAP_PCRS); - tpm_buf_append_u32(&buf, 0); - tpm_buf_append_u32(&buf, 1); - - rc = tpm_transmit_cmd(chip, &buf, 9, "get tpm pcr allocation"); - if (rc) - goto out; - nr_possible_banks = be32_to_cpup( - (__be32 *)&buf.data[TPM_HEADER_SIZE + 5]); + (__be32 *)&buf->data[TPM_HEADER_SIZE + 5]); if (nr_possible_banks > TPM2_MAX_PCR_BANKS) { pr_err("tpm: out of bank capacity: %u > %u\n", nr_possible_banks, TPM2_MAX_PCR_BANKS); - rc = -ENOMEM; - goto out; + return -ENOMEM; } - marker = &buf.data[TPM_HEADER_SIZE + 9]; + marker = &buf->data[TPM_HEADER_SIZE + 9]; - rsp_len = be32_to_cpup((__be32 *)&buf.data[2]); - end = &buf.data[rsp_len]; + rsp_len = be32_to_cpup((__be32 *)&buf->data[2]); + end = &buf->data[rsp_len]; for (i = 0; i < nr_possible_banks; i++) { pcr_select_offset = marker + @@ -586,21 +583,20 @@ ssize_t tpm2_get_pcr_allocation(struct tpm_chip *chip) } chip->nr_allocated_banks = nr_alloc_banks; -out: - tpm_buf_destroy(&buf); return rc; } int tpm2_get_cc_attrs_tbl(struct tpm_chip *chip) { - struct tpm_buf buf; u32 nr_commands; __be32 *attrs; u32 cc; int i; int rc; + struct tpm_buf *buf __free(kfree) = NULL; + rc = tpm2_get_tpm_pt(chip, TPM_PT_TOTAL_COMMANDS, &nr_commands, NULL); if (rc) goto out; @@ -617,30 +613,31 @@ int tpm2_get_cc_attrs_tbl(struct tpm_chip *chip) goto out; } - rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_GET_CAPABILITY); - if (rc) - goto out; - - tpm_buf_append_u32(&buf, TPM2_CAP_COMMANDS); - tpm_buf_append_u32(&buf, TPM2_CC_FIRST); - tpm_buf_append_u32(&buf, nr_commands); - - rc = tpm_transmit_cmd(chip, &buf, 9 + 4 * nr_commands, NULL); - if (rc) { - tpm_buf_destroy(&buf); + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) { + rc = -ENOMEM; goto out; } + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_GET_CAPABILITY); + tpm_buf_append_u32(buf, TPM2_CAP_COMMANDS); + tpm_buf_append_u32(buf, TPM2_CC_FIRST); + tpm_buf_append_u32(buf, nr_commands); + + rc = tpm_transmit_cmd(chip, buf, 9 + 4 * nr_commands, NULL); + if (rc) + goto out; + if (nr_commands != - be32_to_cpup((__be32 *)&buf.data[TPM_HEADER_SIZE + 5])) { + be32_to_cpup((__be32 *)&buf->data[TPM_HEADER_SIZE + 5])) { rc = -EFAULT; - tpm_buf_destroy(&buf); goto out; } chip->nr_commands = nr_commands; - attrs = (__be32 *)&buf.data[TPM_HEADER_SIZE + 9]; + attrs = (__be32 *)&buf->data[TPM_HEADER_SIZE + 9]; for (i = 0; i < nr_commands; i++, attrs++) { chip->cc_attrs_tbl[i] = be32_to_cpup(attrs); cc = chip->cc_attrs_tbl[i] & 0xFFFF; @@ -652,8 +649,6 @@ int tpm2_get_cc_attrs_tbl(struct tpm_chip *chip) } } - tpm_buf_destroy(&buf); - out: if (rc > 0) rc = -ENODEV; @@ -674,20 +669,18 @@ EXPORT_SYMBOL_GPL(tpm2_get_cc_attrs_tbl); static int tpm2_startup(struct tpm_chip *chip) { - struct tpm_buf buf; - int rc; + struct tpm_buf *buf __free(kfree) = NULL; dev_info(&chip->dev, "starting up the TPM manually\n"); - rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_STARTUP); - if (rc < 0) - return rc; + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; - tpm_buf_append_u16(&buf, TPM2_SU_CLEAR); - rc = tpm_transmit_cmd(chip, &buf, 0, "attempting to start the TPM"); - tpm_buf_destroy(&buf); - - return rc; + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_STARTUP); + tpm_buf_append_u16(buf, TPM2_SU_CLEAR); + return tpm_transmit_cmd(chip, buf, 0, "attempting to start the TPM"); } /** diff --git a/drivers/char/tpm/tpm2-sessions.c b/drivers/char/tpm/tpm2-sessions.c index 0d89643e6880..cf8f1fd6790b 100644 --- a/drivers/char/tpm/tpm2-sessions.c +++ b/drivers/char/tpm/tpm2-sessions.c @@ -167,8 +167,8 @@ static int tpm2_read_public(struct tpm_chip *chip, u32 handle, void *name) { u32 mso = tpm2_handle_mso(handle); off_t offset = TPM_HEADER_SIZE; + struct tpm_buf *buf __free(kfree) = NULL; int rc, name_size_alg; - struct tpm_buf buf; if (mso != TPM2_MSO_PERSISTENT && mso != TPM2_MSO_VOLATILE && mso != TPM2_MSO_NVRAM) { @@ -176,50 +176,40 @@ static int tpm2_read_public(struct tpm_chip *chip, u32 handle, void *name) return sizeof(u32); } - rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_READ_PUBLIC); + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_READ_PUBLIC); + tpm_buf_append_u32(buf, handle); + + rc = tpm_transmit_cmd(chip, buf, 0, "TPM2_ReadPublic"); if (rc) - return rc; - - tpm_buf_append_u32(&buf, handle); - - rc = tpm_transmit_cmd(chip, &buf, 0, "TPM2_ReadPublic"); - if (rc) { - tpm_buf_destroy(&buf); return tpm_ret_to_err(rc); - } /* Skip TPMT_PUBLIC: */ - offset += tpm_buf_read_u16(&buf, &offset); + offset += tpm_buf_read_u16(buf, &offset); /* * Ensure space for the length field of TPM2B_NAME and hashAlg field of * TPMT_HA (the extra four bytes). */ - if (offset + 4 > tpm_buf_length(&buf)) { - tpm_buf_destroy(&buf); + if (offset + 4 > tpm_buf_length(buf)) return -EIO; - } - rc = tpm_buf_read_u16(&buf, &offset); - name_size_alg = name_size(&buf.data[offset]); - - if (name_size_alg < 0) { - tpm_buf_destroy(&buf); + rc = tpm_buf_read_u16(buf, &offset); + name_size_alg = name_size(&buf->data[offset]); + if (name_size_alg < 0) return name_size_alg; - } - if (rc != name_size_alg) { - tpm_buf_destroy(&buf); + if (rc != name_size_alg) return -EIO; - } - if (offset + rc > tpm_buf_length(&buf)) { - tpm_buf_destroy(&buf); + if (offset + rc > tpm_buf_length(buf)) return -EIO; - } - memcpy(name, &buf.data[offset], rc); - tpm_buf_destroy(&buf); + memcpy(name, &buf->data[offset], rc); return name_size_alg; } #endif /* CONFIG_TCG_TPM2_HMAC */ @@ -1005,8 +995,8 @@ err: */ int tpm2_start_auth_session(struct tpm_chip *chip) { + struct tpm_buf *buf __free(kfree) = NULL; struct tpm2_auth *auth; - struct tpm_buf buf; u32 null_key; int rc; @@ -1020,58 +1010,61 @@ int tpm2_start_auth_session(struct tpm_chip *chip) return -ENOMEM; rc = tpm2_load_null(chip, &null_key); - if (rc) - goto out; + if (rc) { + kfree_sensitive(auth); + return rc; + } auth->session = TPM_HEADER_SIZE; - rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_START_AUTH_SESS); - if (rc) - goto out; + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) { + kfree_sensitive(auth); + return -ENOMEM; + } + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_START_AUTH_SESS); /* salt key handle */ - tpm_buf_append_u32(&buf, null_key); + tpm_buf_append_u32(buf, null_key); /* bind key handle */ - tpm_buf_append_u32(&buf, TPM2_RH_NULL); + tpm_buf_append_u32(buf, TPM2_RH_NULL); /* nonce caller */ get_random_bytes(auth->our_nonce, sizeof(auth->our_nonce)); - tpm_buf_append_u16(&buf, sizeof(auth->our_nonce)); - tpm_buf_append(&buf, auth->our_nonce, sizeof(auth->our_nonce)); + tpm_buf_append_u16(buf, sizeof(auth->our_nonce)); + tpm_buf_append(buf, auth->our_nonce, sizeof(auth->our_nonce)); /* append encrypted salt and squirrel away unencrypted in auth */ - rc = tpm_buf_append_salt(&buf, chip, auth); + rc = tpm_buf_append_salt(buf, chip, auth); if (rc) { tpm2_flush_context(chip, null_key); - tpm_buf_destroy(&buf); - goto out; + kfree_sensitive(auth); + return rc; } /* session type (HMAC, audit or policy) */ - tpm_buf_append_u8(&buf, TPM2_SE_HMAC); + tpm_buf_append_u8(buf, TPM2_SE_HMAC); /* symmetric encryption parameters */ /* symmetric algorithm */ - tpm_buf_append_u16(&buf, TPM_ALG_AES); + tpm_buf_append_u16(buf, TPM_ALG_AES); /* bits for symmetric algorithm */ - tpm_buf_append_u16(&buf, AES_KEY_BITS); + tpm_buf_append_u16(buf, AES_KEY_BITS); /* symmetric algorithm mode (must be CFB) */ - tpm_buf_append_u16(&buf, TPM_ALG_CFB); + tpm_buf_append_u16(buf, TPM_ALG_CFB); /* hash algorithm for session */ - tpm_buf_append_u16(&buf, TPM_ALG_SHA256); + tpm_buf_append_u16(buf, TPM_ALG_SHA256); - rc = tpm_ret_to_err(tpm_transmit_cmd(chip, &buf, 0, "StartAuthSession")); + rc = tpm_ret_to_err(tpm_transmit_cmd(chip, buf, 0, "StartAuthSession")); tpm2_flush_context(chip, null_key); if (rc == TPM2_RC_SUCCESS) - rc = tpm2_parse_start_auth_session(auth, &buf); - - tpm_buf_destroy(&buf); + rc = tpm2_parse_start_auth_session(auth, buf); if (rc == TPM2_RC_SUCCESS) { chip->auth = auth; return 0; } -out: kfree_sensitive(auth); return rc; } @@ -1285,19 +1278,21 @@ static int tpm2_parse_create_primary(struct tpm_chip *chip, struct tpm_buf *buf, static int tpm2_create_primary(struct tpm_chip *chip, u32 hierarchy, u32 *handle, u8 *name) { + struct tpm_buf *template __free(kfree) = NULL; + struct tpm_buf *buf __free(kfree) = NULL; int rc; - struct tpm_buf buf; - struct tpm_buf template; - rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_CREATE_PRIMARY); - if (rc) - return rc; + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; - rc = tpm_buf_init_sized(&template); - if (rc) { - tpm_buf_destroy(&buf); - return rc; - } + template = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!template) + return -ENOMEM; + + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_CREATE_PRIMARY); + tpm_buf_init_sized(template, TPM_BUFSIZE); /* * create the template. Note: in order for userspace to @@ -1309,75 +1304,72 @@ static int tpm2_create_primary(struct tpm_chip *chip, u32 hierarchy, */ /* key type */ - tpm_buf_append_u16(&template, TPM_ALG_ECC); + tpm_buf_append_u16(template, TPM_ALG_ECC); /* name algorithm */ - tpm_buf_append_u16(&template, TPM_ALG_SHA256); + tpm_buf_append_u16(template, TPM_ALG_SHA256); /* object properties */ - tpm_buf_append_u32(&template, TPM2_OA_NULL_KEY); + tpm_buf_append_u32(template, TPM2_OA_NULL_KEY); /* sauth policy (empty) */ - tpm_buf_append_u16(&template, 0); + tpm_buf_append_u16(template, 0); /* BEGIN parameters: key specific; for ECC*/ /* symmetric algorithm */ - tpm_buf_append_u16(&template, TPM_ALG_AES); + tpm_buf_append_u16(template, TPM_ALG_AES); /* bits for symmetric algorithm */ - tpm_buf_append_u16(&template, AES_KEY_BITS); + tpm_buf_append_u16(template, AES_KEY_BITS); /* algorithm mode (must be CFB) */ - tpm_buf_append_u16(&template, TPM_ALG_CFB); + tpm_buf_append_u16(template, TPM_ALG_CFB); /* scheme (NULL means any scheme) */ - tpm_buf_append_u16(&template, TPM_ALG_NULL); + tpm_buf_append_u16(template, TPM_ALG_NULL); /* ECC Curve ID */ - tpm_buf_append_u16(&template, TPM2_ECC_NIST_P256); + tpm_buf_append_u16(template, TPM2_ECC_NIST_P256); /* KDF Scheme */ - tpm_buf_append_u16(&template, TPM_ALG_NULL); + tpm_buf_append_u16(template, TPM_ALG_NULL); /* unique: key specific; for ECC it is two zero size points */ - tpm_buf_append_u16(&template, 0); - tpm_buf_append_u16(&template, 0); + tpm_buf_append_u16(template, 0); + tpm_buf_append_u16(template, 0); /* END parameters */ /* primary handle */ - tpm_buf_append_u32(&buf, hierarchy); - tpm_buf_append_empty_auth(&buf, TPM2_RS_PW); + tpm_buf_append_u32(buf, hierarchy); + tpm_buf_append_empty_auth(buf, TPM2_RS_PW); /* sensitive create size is 4 for two empty buffers */ - tpm_buf_append_u16(&buf, 4); + tpm_buf_append_u16(buf, 4); /* sensitive create auth data (empty) */ - tpm_buf_append_u16(&buf, 0); + tpm_buf_append_u16(buf, 0); /* sensitive create sensitive data (empty) */ - tpm_buf_append_u16(&buf, 0); + tpm_buf_append_u16(buf, 0); /* the public template */ - tpm_buf_append(&buf, template.data, template.length); - tpm_buf_destroy(&template); + tpm_buf_append(buf, template->data, template->length); /* outside info (empty) */ - tpm_buf_append_u16(&buf, 0); + tpm_buf_append_u16(buf, 0); /* creation PCR (none) */ - tpm_buf_append_u32(&buf, 0); + tpm_buf_append_u32(buf, 0); - rc = tpm_transmit_cmd(chip, &buf, 0, + rc = tpm_transmit_cmd(chip, buf, 0, "attempting to create NULL primary"); if (rc == TPM2_RC_SUCCESS) - rc = tpm2_parse_create_primary(chip, &buf, handle, hierarchy, + rc = tpm2_parse_create_primary(chip, buf, handle, hierarchy, name); - tpm_buf_destroy(&buf); - return rc; } diff --git a/drivers/char/tpm/tpm2-space.c b/drivers/char/tpm/tpm2-space.c index 7c1c0a174a2b..5b0f233db898 100644 --- a/drivers/char/tpm/tpm2-space.c +++ b/drivers/char/tpm/tpm2-space.c @@ -58,24 +58,25 @@ void tpm2_del_space(struct tpm_chip *chip, struct tpm_space *space) int tpm2_load_context(struct tpm_chip *chip, u8 *buf, unsigned int *offset, u32 *handle) { - struct tpm_buf tbuf; struct tpm2_context *ctx; unsigned int body_size; int rc; - rc = tpm_buf_init(&tbuf, TPM2_ST_NO_SESSIONS, TPM2_CC_CONTEXT_LOAD); - if (rc) - return rc; + struct tpm_buf *tbuf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!tbuf) + return -ENOMEM; + + tpm_buf_init(tbuf, TPM_BUFSIZE); + tpm_buf_reset(tbuf, TPM2_ST_NO_SESSIONS, TPM2_CC_CONTEXT_LOAD); ctx = (struct tpm2_context *)&buf[*offset]; body_size = sizeof(*ctx) + be16_to_cpu(ctx->blob_size); - tpm_buf_append(&tbuf, &buf[*offset], body_size); + tpm_buf_append(tbuf, &buf[*offset], body_size); - rc = tpm_transmit_cmd(chip, &tbuf, 4, NULL); + rc = tpm_transmit_cmd(chip, tbuf, 4, NULL); if (rc < 0) { dev_warn(&chip->dev, "%s: failed with a system error %d\n", __func__, rc); - tpm_buf_destroy(&tbuf); return -EFAULT; } else if (tpm2_rc_value(rc) == TPM2_RC_HANDLE || rc == TPM2_RC_REFERENCE_H0) { @@ -90,64 +91,55 @@ int tpm2_load_context(struct tpm_chip *chip, u8 *buf, * flushed outside the space */ *handle = 0; - tpm_buf_destroy(&tbuf); return -ENOENT; } else if (tpm2_rc_value(rc) == TPM2_RC_INTEGRITY) { - tpm_buf_destroy(&tbuf); return -EINVAL; } else if (rc > 0) { dev_warn(&chip->dev, "%s: failed with a TPM error 0x%04X\n", __func__, rc); - tpm_buf_destroy(&tbuf); return -EFAULT; } - *handle = be32_to_cpup((__be32 *)&tbuf.data[TPM_HEADER_SIZE]); + *handle = be32_to_cpup((__be32 *)&tbuf->data[TPM_HEADER_SIZE]); *offset += body_size; - - tpm_buf_destroy(&tbuf); return 0; } int tpm2_save_context(struct tpm_chip *chip, u32 handle, u8 *buf, unsigned int buf_size, unsigned int *offset) { - struct tpm_buf tbuf; unsigned int body_size; int rc; - rc = tpm_buf_init(&tbuf, TPM2_ST_NO_SESSIONS, TPM2_CC_CONTEXT_SAVE); - if (rc) - return rc; + struct tpm_buf *tbuf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!tbuf) + return -ENOMEM; - tpm_buf_append_u32(&tbuf, handle); + tpm_buf_init(tbuf, TPM_BUFSIZE); + tpm_buf_reset(tbuf, TPM2_ST_NO_SESSIONS, TPM2_CC_CONTEXT_SAVE); + tpm_buf_append_u32(tbuf, handle); - rc = tpm_transmit_cmd(chip, &tbuf, 0, NULL); + rc = tpm_transmit_cmd(chip, tbuf, 0, NULL); if (rc < 0) { dev_warn(&chip->dev, "%s: failed with a system error %d\n", __func__, rc); - tpm_buf_destroy(&tbuf); return -EFAULT; } else if (tpm2_rc_value(rc) == TPM2_RC_REFERENCE_H0) { - tpm_buf_destroy(&tbuf); return -ENOENT; } else if (rc) { dev_warn(&chip->dev, "%s: failed with a TPM error 0x%04X\n", __func__, rc); - tpm_buf_destroy(&tbuf); return -EFAULT; } - body_size = tpm_buf_length(&tbuf) - TPM_HEADER_SIZE; + body_size = tpm_buf_length(tbuf) - TPM_HEADER_SIZE; if ((*offset + body_size) > buf_size) { dev_warn(&chip->dev, "%s: out of backing storage\n", __func__); - tpm_buf_destroy(&tbuf); return -ENOMEM; } - memcpy(&buf[*offset], &tbuf.data[TPM_HEADER_SIZE], body_size); + memcpy(&buf[*offset], &tbuf->data[TPM_HEADER_SIZE], body_size); *offset += body_size; - tpm_buf_destroy(&tbuf); return 0; } diff --git a/drivers/char/tpm/tpm_vtpm_proxy.c b/drivers/char/tpm/tpm_vtpm_proxy.c index 7bb0f4d4a2ed..b81fd2a537df 100644 --- a/drivers/char/tpm/tpm_vtpm_proxy.c +++ b/drivers/char/tpm/tpm_vtpm_proxy.c @@ -395,40 +395,36 @@ static bool vtpm_proxy_tpm_req_canceled(struct tpm_chip *chip, u8 status) static int vtpm_proxy_request_locality(struct tpm_chip *chip, int locality) { - struct tpm_buf buf; int rc; const struct tpm_header *header; struct proxy_dev *proxy_dev = dev_get_drvdata(&chip->dev); + struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + tpm_buf_init(buf, TPM_BUFSIZE); if (chip->flags & TPM_CHIP_FLAG_TPM2) - rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, - TPM2_CC_SET_LOCALITY); + tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_SET_LOCALITY); else - rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, - TPM_ORD_SET_LOCALITY); - if (rc) - return rc; - tpm_buf_append_u8(&buf, locality); + tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_SET_LOCALITY); + + tpm_buf_append_u8(buf, locality); proxy_dev->state |= STATE_DRIVER_COMMAND; - rc = tpm_transmit_cmd(chip, &buf, 0, "attempting to set locality"); + rc = tpm_transmit_cmd(chip, buf, 0, "attempting to set locality"); proxy_dev->state &= ~STATE_DRIVER_COMMAND; - if (rc < 0) { - locality = rc; - goto out; - } + if (rc < 0) + return rc; - header = (const struct tpm_header *)buf.data; + header = (const struct tpm_header *)buf->data; rc = be32_to_cpu(header->return_code); if (rc) locality = -1; -out: - tpm_buf_destroy(&buf); - return locality; } diff --git a/include/linux/tpm.h b/include/linux/tpm.h index b357f8971d03..598dd53a10d8 100644 --- a/include/linux/tpm.h +++ b/include/linux/tpm.h @@ -208,13 +208,15 @@ enum tpm_buf_flags { }; /* - * A string buffer type for constructing TPM commands. + * A buffer for constructing and parsing TPM commands, responses and sized + * (TPM2B) buffers. */ struct tpm_buf { - u32 flags; - u32 length; - u8 *data; + u8 flags; + u16 length; + u16 capacity; u8 handles; + u8 data[]; }; struct tpm2_hash { @@ -222,12 +224,11 @@ struct tpm2_hash { unsigned int tpm_id; }; -int tpm_buf_init(struct tpm_buf *buf, u16 tag, u32 ordinal); +void tpm_buf_init(struct tpm_buf *buf, u16 buf_size); +void tpm_buf_init_sized(struct tpm_buf *buf, u16 buf_size); void tpm_buf_reset(struct tpm_buf *buf, u16 tag, u32 ordinal); -int tpm_buf_init_sized(struct tpm_buf *buf); void tpm_buf_reset_sized(struct tpm_buf *buf); -void tpm_buf_destroy(struct tpm_buf *buf); -u32 tpm_buf_length(struct tpm_buf *buf); +u16 tpm_buf_length(struct tpm_buf *buf); void tpm_buf_append(struct tpm_buf *buf, const u8 *new_data, u16 new_length); void tpm_buf_append_u8(struct tpm_buf *buf, const u8 value); void tpm_buf_append_u16(struct tpm_buf *buf, const u16 value); diff --git a/security/keys/trusted-keys/trusted_tpm1.c b/security/keys/trusted-keys/trusted_tpm1.c index ae256c540ca0..bf0bf7f36970 100644 --- a/security/keys/trusted-keys/trusted_tpm1.c +++ b/security/keys/trusted-keys/trusted_tpm1.c @@ -316,9 +316,8 @@ static int TSS_checkhmac2(unsigned char *buffer, * For key specific tpm requests, we will generate and send our * own TPM command packets using the drivers send function. */ -static int trusted_tpm_send(unsigned char *cmd, size_t buflen) +static int trusted_tpm_send(struct tpm_buf *buf) { - struct tpm_buf buf; int rc; if (!chip) @@ -328,12 +327,9 @@ static int trusted_tpm_send(unsigned char *cmd, size_t buflen) if (rc) return rc; - buf.flags = 0; - buf.length = buflen; - buf.data = cmd; - dump_tpm_buf(cmd); - rc = tpm_transmit_cmd(chip, &buf, 4, "sending data"); - dump_tpm_buf(cmd); + dump_tpm_buf(buf->data); + rc = tpm_transmit_cmd(chip, buf, 4, "sending data"); + dump_tpm_buf(buf->data); if (rc > 0) /* TPM error */ @@ -379,7 +375,7 @@ static int osap(struct tpm_buf *tb, struct osapsess *s, tpm_buf_append_u32(tb, handle); tpm_buf_append(tb, ononce, TPM_NONCE_SIZE); - ret = trusted_tpm_send(tb->data, tb->length); + ret = trusted_tpm_send(tb); if (ret < 0) return ret; @@ -403,7 +399,7 @@ static int oiap(struct tpm_buf *tb, uint32_t *handle, unsigned char *nonce) return -ENODEV; tpm_buf_reset(tb, TPM_TAG_RQU_COMMAND, TPM_ORD_OIAP); - ret = trusted_tpm_send(tb->data, tb->length); + ret = trusted_tpm_send(tb); if (ret < 0) return ret; @@ -512,7 +508,7 @@ static int tpm_seal(struct tpm_buf *tb, uint16_t keytype, tpm_buf_append_u8(tb, cont); tpm_buf_append(tb, td->pubauth, SHA1_DIGEST_SIZE); - ret = trusted_tpm_send(tb->data, tb->length); + ret = trusted_tpm_send(tb); if (ret < 0) goto out; @@ -603,7 +599,7 @@ static int tpm_unseal(struct tpm_buf *tb, tpm_buf_append_u8(tb, cont); tpm_buf_append(tb, authdata2, SHA1_DIGEST_SIZE); - ret = trusted_tpm_send(tb->data, tb->length); + ret = trusted_tpm_send(tb); if (ret < 0) { pr_info("authhmac failed (%d)\n", ret); return ret; @@ -630,23 +626,23 @@ static int tpm_unseal(struct tpm_buf *tb, static int key_seal(struct trusted_key_payload *p, struct trusted_key_options *o) { - struct tpm_buf tb; int ret; - ret = tpm_buf_init(&tb, 0, 0); - if (ret) - return ret; + struct tpm_buf *tb __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!tb) + return -ENOMEM; + + tpm_buf_init(tb, TPM_BUFSIZE); /* include migratable flag at end of sealed key */ p->key[p->key_len] = p->migratable; - ret = tpm_seal(&tb, o->keytype, o->keyhandle, o->keyauth, + ret = tpm_seal(tb, o->keytype, o->keyhandle, o->keyauth, p->key, p->key_len + 1, p->blob, &p->blob_len, o->blobauth, o->pcrinfo, o->pcrinfo_len); if (ret < 0) pr_info("srkseal failed (%d)\n", ret); - tpm_buf_destroy(&tb); return ret; } @@ -656,14 +652,15 @@ static int key_seal(struct trusted_key_payload *p, static int key_unseal(struct trusted_key_payload *p, struct trusted_key_options *o) { - struct tpm_buf tb; int ret; - ret = tpm_buf_init(&tb, 0, 0); - if (ret) - return ret; + struct tpm_buf *tb __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!tb) + return -ENOMEM; - ret = tpm_unseal(&tb, o->keyhandle, o->keyauth, p->blob, p->blob_len, + tpm_buf_init(tb, TPM_BUFSIZE); + + ret = tpm_unseal(tb, o->keyhandle, o->keyauth, p->blob, p->blob_len, o->blobauth, p->key, &p->key_len); if (ret < 0) pr_info("srkunseal failed (%d)\n", ret); @@ -671,7 +668,6 @@ static int key_unseal(struct trusted_key_payload *p, /* pull migratable flag out of sealed key */ p->migratable = p->key[--p->key_len]; - tpm_buf_destroy(&tb); return ret; } diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c index 779a2e66ac20..67225dd562a9 100644 --- a/security/keys/trusted-keys/trusted_tpm2.c +++ b/security/keys/trusted-keys/trusted_tpm2.c @@ -233,7 +233,8 @@ int tpm2_seal_trusted(struct tpm_chip *chip, struct trusted_key_options *options) { off_t offset = TPM_HEADER_SIZE; - struct tpm_buf buf, sized; + struct tpm_buf *buf __free(kfree) = NULL; + struct tpm_buf *sized __free(kfree) = NULL; int blob_len = 0; int hash; u32 flags; @@ -254,97 +255,100 @@ int tpm2_seal_trusted(struct tpm_chip *chip, if (rc) goto out_put; - rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_CREATE); - if (rc) { + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) { + rc = -ENOMEM; tpm2_end_auth_session(chip); goto out_put; } - rc = tpm_buf_init_sized(&sized); - if (rc) { - tpm_buf_destroy(&buf); + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_CREATE); + + sized = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!sized) { + rc = -ENOMEM; tpm2_end_auth_session(chip); goto out_put; } - rc = tpm_buf_append_name(chip, &buf, options->keyhandle, NULL); + tpm_buf_init_sized(sized, TPM_BUFSIZE); + + rc = tpm_buf_append_name(chip, buf, options->keyhandle, NULL); if (rc) goto out; - tpm_buf_append_hmac_session(chip, &buf, TPM2_SA_DECRYPT, + tpm_buf_append_hmac_session(chip, buf, TPM2_SA_DECRYPT, options->keyauth, TPM_DIGEST_SIZE); /* sensitive */ - tpm_buf_append_u16(&sized, options->blobauth_len); + tpm_buf_append_u16(sized, options->blobauth_len); if (options->blobauth_len) - tpm_buf_append(&sized, options->blobauth, options->blobauth_len); + tpm_buf_append(sized, options->blobauth, options->blobauth_len); - tpm_buf_append_u16(&sized, payload->key_len); - tpm_buf_append(&sized, payload->key, payload->key_len); - tpm_buf_append(&buf, sized.data, sized.length); + tpm_buf_append_u16(sized, payload->key_len); + tpm_buf_append(sized, payload->key, payload->key_len); + tpm_buf_append(buf, sized->data, sized->length); /* public */ - tpm_buf_reset_sized(&sized); - tpm_buf_append_u16(&sized, TPM_ALG_KEYEDHASH); - tpm_buf_append_u16(&sized, hash); + tpm_buf_reset_sized(sized); + tpm_buf_append_u16(sized, TPM_ALG_KEYEDHASH); + tpm_buf_append_u16(sized, hash); /* key properties */ flags = 0; flags |= options->policydigest_len ? 0 : TPM2_OA_USER_WITH_AUTH; flags |= payload->migratable ? 0 : (TPM2_OA_FIXED_TPM | TPM2_OA_FIXED_PARENT); - tpm_buf_append_u32(&sized, flags); + tpm_buf_append_u32(sized, flags); /* policy */ - tpm_buf_append_u16(&sized, options->policydigest_len); + tpm_buf_append_u16(sized, options->policydigest_len); if (options->policydigest_len) - tpm_buf_append(&sized, options->policydigest, options->policydigest_len); + tpm_buf_append(sized, options->policydigest, options->policydigest_len); /* public parameters */ - tpm_buf_append_u16(&sized, TPM_ALG_NULL); - tpm_buf_append_u16(&sized, 0); + tpm_buf_append_u16(sized, TPM_ALG_NULL); + tpm_buf_append_u16(sized, 0); - tpm_buf_append(&buf, sized.data, sized.length); + tpm_buf_append(buf, sized->data, sized->length); /* outside info */ - tpm_buf_append_u16(&buf, 0); + tpm_buf_append_u16(buf, 0); /* creation PCR */ - tpm_buf_append_u32(&buf, 0); + tpm_buf_append_u32(buf, 0); - if (buf.flags & TPM_BUF_INVALID) { + if (buf->flags & TPM_BUF_INVALID) { rc = -E2BIG; tpm2_end_auth_session(chip); goto out; } - rc = tpm_buf_fill_hmac_session(chip, &buf); + rc = tpm_buf_fill_hmac_session(chip, buf); if (rc) goto out; - rc = tpm_transmit_cmd(chip, &buf, 4, "sealing data"); - rc = tpm_buf_check_hmac_response(chip, &buf, rc); + rc = tpm_transmit_cmd(chip, buf, 4, "sealing data"); + rc = tpm_buf_check_hmac_response(chip, buf, rc); if (rc) goto out; - blob_len = tpm_buf_read_u32(&buf, &offset); - if (blob_len > MAX_BLOB_SIZE || buf.flags & TPM_BUF_INVALID) { + blob_len = tpm_buf_read_u32(buf, &offset); + if (blob_len > MAX_BLOB_SIZE || buf->flags & TPM_BUF_INVALID) { rc = -E2BIG; goto out; } - if (buf.length - offset < blob_len) { + if (buf->length - offset < blob_len) { rc = -EFAULT; goto out; } - blob_len = tpm2_key_encode(payload, options, &buf.data[offset], blob_len); + blob_len = tpm2_key_encode(payload, options, &buf->data[offset], blob_len); if (blob_len < 0) rc = blob_len; out: - tpm_buf_destroy(&sized); - tpm_buf_destroy(&buf); - if (!rc) payload->blob_len = blob_len; @@ -372,7 +376,7 @@ static int tpm2_load_cmd(struct tpm_chip *chip, u32 *blob_handle) { u8 *blob_ref __free(kfree) = NULL; - struct tpm_buf buf; + struct tpm_buf *buf __free(kfree) = NULL; unsigned int private_len; unsigned int public_len; unsigned int blob_len; @@ -426,39 +430,38 @@ static int tpm2_load_cmd(struct tpm_chip *chip, if (rc) return rc; - rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_LOAD); - if (rc) { + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) { tpm2_end_auth_session(chip); - return rc; + return -ENOMEM; } - rc = tpm_buf_append_name(chip, &buf, options->keyhandle, NULL); - if (rc) - goto out; + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_LOAD); - tpm_buf_append_hmac_session(chip, &buf, 0, options->keyauth, + rc = tpm_buf_append_name(chip, buf, options->keyhandle, NULL); + if (rc) + return rc; + + tpm_buf_append_hmac_session(chip, buf, 0, options->keyauth, TPM_DIGEST_SIZE); - tpm_buf_append(&buf, blob, blob_len); + tpm_buf_append(buf, blob, blob_len); - if (buf.flags & TPM_BUF_INVALID) { - rc = -E2BIG; + if (buf->flags & TPM_BUF_INVALID) { tpm2_end_auth_session(chip); - goto out; + return -E2BIG; } - rc = tpm_buf_fill_hmac_session(chip, &buf); + rc = tpm_buf_fill_hmac_session(chip, buf); if (rc) - goto out; + return rc; - rc = tpm_transmit_cmd(chip, &buf, 4, "loading blob"); - rc = tpm_buf_check_hmac_response(chip, &buf, rc); + rc = tpm_transmit_cmd(chip, buf, 4, "loading blob"); + rc = tpm_buf_check_hmac_response(chip, buf, rc); if (!rc) *blob_handle = be32_to_cpup( - (__be32 *) &buf.data[TPM_HEADER_SIZE]); - -out: - tpm_buf_destroy(&buf); + (__be32 *)&buf->data[TPM_HEADER_SIZE]); return tpm_ret_to_err(rc); } @@ -481,7 +484,7 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip, u32 blob_handle) { struct tpm_header *head; - struct tpm_buf buf; + struct tpm_buf *buf __free(kfree) = NULL; u16 data_len; int offset; u8 *data; @@ -491,18 +494,21 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip, if (rc) return rc; - rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_UNSEAL); - if (rc) { + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) { tpm2_end_auth_session(chip); - return rc; + return -ENOMEM; } - rc = tpm_buf_append_name(chip, &buf, blob_handle, NULL); + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_UNSEAL); + + rc = tpm_buf_append_name(chip, buf, blob_handle, NULL); if (rc) - goto out; + return rc; if (!options->policyhandle) { - tpm_buf_append_hmac_session(chip, &buf, TPM2_SA_ENCRYPT, + tpm_buf_append_hmac_session(chip, buf, TPM2_SA_ENCRYPT, options->blobauth, options->blobauth_len); } else { @@ -517,39 +523,36 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip, * could repeat our actions with the exfiltrated * password. */ - tpm2_buf_append_auth(&buf, options->policyhandle, + tpm2_buf_append_auth(buf, options->policyhandle, NULL /* nonce */, 0, 0, options->blobauth, options->blobauth_len); if (tpm2_chip_auth(chip)) { - tpm_buf_append_hmac_session(chip, &buf, TPM2_SA_ENCRYPT, NULL, 0); + tpm_buf_append_hmac_session(chip, buf, TPM2_SA_ENCRYPT, + NULL, 0); } else { - offset = buf.handles * 4 + TPM_HEADER_SIZE; - head = (struct tpm_header *)buf.data; - if (tpm_buf_length(&buf) == offset) + offset = buf->handles * 4 + TPM_HEADER_SIZE; + head = (struct tpm_header *)buf->data; + if (tpm_buf_length(buf) == offset) head->tag = cpu_to_be16(TPM2_ST_NO_SESSIONS); } } - rc = tpm_buf_fill_hmac_session(chip, &buf); + rc = tpm_buf_fill_hmac_session(chip, buf); if (rc) - goto out; + return rc; - rc = tpm_transmit_cmd(chip, &buf, 6, "unsealing"); - rc = tpm_buf_check_hmac_response(chip, &buf, rc); + rc = tpm_transmit_cmd(chip, buf, 6, "unsealing"); + rc = tpm_buf_check_hmac_response(chip, buf, rc); if (!rc) { data_len = be16_to_cpup( - (__be16 *) &buf.data[TPM_HEADER_SIZE + 4]); - if (data_len < MIN_KEY_SIZE || data_len > MAX_KEY_SIZE) { - rc = -EFAULT; - goto out; - } + (__be16 *)&buf->data[TPM_HEADER_SIZE + 4]); + if (data_len < MIN_KEY_SIZE || data_len > MAX_KEY_SIZE) + return -EFAULT; - if (tpm_buf_length(&buf) < TPM_HEADER_SIZE + 6 + data_len) { - rc = -EFAULT; - goto out; - } - data = &buf.data[TPM_HEADER_SIZE + 6]; + if (tpm_buf_length(buf) < TPM_HEADER_SIZE + 6 + data_len) + return -EFAULT; + data = &buf->data[TPM_HEADER_SIZE + 6]; if (payload->old_format) { /* migratable flag is at the end of the key */ @@ -566,8 +569,6 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip, } } -out: - tpm_buf_destroy(&buf); return tpm_ret_to_err(rc); } From 55ba91d4c54ab31ee8387bae40e799de467f8269 Mon Sep 17 00:00:00 2001 From: Ross Philipson Date: Sat, 11 Jul 2026 09:01:10 -0700 Subject: [PATCH 391/641] tpm-buf: Add TPM buffer support header for standalone reuse Extract all the functions and definitions for TPM buffer handling and separate them into their own header. Signed-off-by: Ross Philipson Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/tpm-buf.c | 3 +- include/linux/tpm.h | 34 +-------------------- include/linux/tpm_buf.h | 60 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 34 deletions(-) create mode 100644 include/linux/tpm_buf.h diff --git a/drivers/char/tpm/tpm-buf.c b/drivers/char/tpm/tpm-buf.c index 1e5c11c312a8..233e81d3f149 100644 --- a/drivers/char/tpm/tpm-buf.c +++ b/drivers/char/tpm/tpm-buf.c @@ -4,7 +4,8 @@ */ #include -#include +#include +#include static void __tpm_buf_size_invariant(struct tpm_buf *buf, u16 buf_size) { diff --git a/include/linux/tpm.h b/include/linux/tpm.h index 598dd53a10d8..0db277af45c3 100644 --- a/include/linux/tpm.h +++ b/include/linux/tpm.h @@ -26,6 +26,7 @@ #include #include +#include struct tpm_chip; struct trusted_key_payload; @@ -200,44 +201,11 @@ enum tpm_chip_flags { #define to_tpm_chip(d) container_of(d, struct tpm_chip, dev) -enum tpm_buf_flags { - /* TPM2B format: */ - TPM_BUF_TPM2B = BIT(0), - /* The buffer is in invalid and unusable state: */ - TPM_BUF_INVALID = BIT(1), -}; - -/* - * A buffer for constructing and parsing TPM commands, responses and sized - * (TPM2B) buffers. - */ -struct tpm_buf { - u8 flags; - u16 length; - u16 capacity; - u8 handles; - u8 data[]; -}; - struct tpm2_hash { unsigned int crypto_id; unsigned int tpm_id; }; -void tpm_buf_init(struct tpm_buf *buf, u16 buf_size); -void tpm_buf_init_sized(struct tpm_buf *buf, u16 buf_size); -void tpm_buf_reset(struct tpm_buf *buf, u16 tag, u32 ordinal); -void tpm_buf_reset_sized(struct tpm_buf *buf); -u16 tpm_buf_length(struct tpm_buf *buf); -void tpm_buf_append(struct tpm_buf *buf, const u8 *new_data, u16 new_length); -void tpm_buf_append_u8(struct tpm_buf *buf, const u8 value); -void tpm_buf_append_u16(struct tpm_buf *buf, const u16 value); -void tpm_buf_append_u32(struct tpm_buf *buf, const u32 value); -u8 tpm_buf_read_u8(struct tpm_buf *buf, off_t *offset); -u16 tpm_buf_read_u16(struct tpm_buf *buf, off_t *offset); -u32 tpm_buf_read_u32(struct tpm_buf *buf, off_t *offset); -void tpm_buf_append_handle(struct tpm_buf *buf, u32 handle); - /* * Check if TPM device is in the firmware upgrade mode. */ diff --git a/include/linux/tpm_buf.h b/include/linux/tpm_buf.h new file mode 100644 index 000000000000..f8c105d8b8bf --- /dev/null +++ b/include/linux/tpm_buf.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Following copyright information was take from the original file + * where the definitions were moved from: + * + * Copyright (C) 2004,2007,2008 IBM Corporation + * + * Authors: + * Leendert van Doorn + * Dave Safford + * Reiner Sailer + * Kylene Hall + * Debora Velarde + * + * Maintained by: + * + * Device driver for TCG/TCPA TPM (trusted platform module). + * Specifications at www.trustedcomputinggroup.org + */ + +#ifndef __LINUX_TPM_BUF_H__ +#define __LINUX_TPM_BUF_H__ + +#include +#include + +enum tpm_buf_flags { + /* TPM2B format: */ + TPM_BUF_TPM2B = BIT(0), + /* The buffer is in invalid and unusable state: */ + TPM_BUF_INVALID = BIT(1), +}; + +/* + * A buffer for constructing and parsing TPM commands, responses and sized + * (TPM2B) buffers. + */ +struct tpm_buf { + u8 flags; + u8 handles; + u16 length; + u16 capacity; + u8 data[]; +}; + +void tpm_buf_init(struct tpm_buf *buf, u16 buf_size); +void tpm_buf_init_sized(struct tpm_buf *buf, u16 buf_size); +void tpm_buf_reset(struct tpm_buf *buf, u16 tag, u32 ordinal); +void tpm_buf_reset_sized(struct tpm_buf *buf); +u16 tpm_buf_length(struct tpm_buf *buf); +void tpm_buf_append(struct tpm_buf *buf, const u8 *new_data, u16 new_length); +void tpm_buf_append_u8(struct tpm_buf *buf, const u8 value); +void tpm_buf_append_u16(struct tpm_buf *buf, const u16 value); +void tpm_buf_append_u32(struct tpm_buf *buf, const u32 value); +u8 tpm_buf_read_u8(struct tpm_buf *buf, off_t *offset); +u16 tpm_buf_read_u16(struct tpm_buf *buf, off_t *offset); +u32 tpm_buf_read_u32(struct tpm_buf *buf, off_t *offset); +void tpm_buf_append_handle(struct tpm_buf *buf, u32 handle); + +#endif From 8c963d1738fdca400082ff5f9d99e083de4f4e70 Mon Sep 17 00:00:00 2001 From: Daeho Jeong Date: Mon, 24 Aug 2026 18:53:03 -0700 Subject: [PATCH 392/641] f2fs: accurately adjust free_sections during free_segment_range In free_segment_range(), MAIN_SECS(sbi) is temporarily reduced by `secs` to restrict block allocation to the safe remaining main area while valid blocks in the truncated range are evacuated by GC. However, FREE_I(sbi)->free_sections tracks the total number of free sections across the whole filesystem. If any sections within the truncated range were already free upon entering free_segment_range(), failing to deduct them from free_sections causes the filesystem to overestimate available free sections in the active, reduced main area. This leads to inconsistent free section accounting during GC data migration and can trigger unexpected allocation failures or assertion errors when space is tight. Fix this by calculating the number of already-free sections in the truncated range, deducting them from free_sections upon entering free_segment_range(), and restoring them on exit. Fixes: b4b10061ef98 ("f2fs: refactor resize_fs to avoid meta updates in progress") Cc: stable@vger.kernel.org Signed-off-by: Daeho Jeong Signed-off-by: Sunmin Jeong Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- fs/f2fs/gc.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c index 0c17038fcfd7..0a00180c21dc 100644 --- a/fs/f2fs/gc.c +++ b/fs/f2fs/gc.c @@ -2231,17 +2231,27 @@ void f2fs_reset_gc_victim_resource(struct f2fs_sb_info *sbi, static int free_segment_range(struct f2fs_sb_info *sbi, unsigned int secs, bool dry_run) { - unsigned int next_inuse, start, end; + unsigned int secno, next_inuse, start, end, end_secno; struct cp_control cpc = { CP_RESIZE, 0, 0, 0 }; + unsigned int freed_secs = 0; int err = 0; int type; MAIN_SECS(sbi) -= secs; start = MAIN_SECS(sbi) * SEGS_PER_SEC(sbi); end = MAIN_SEGS(sbi) - 1; + end_secno = GET_SEC_FROM_SEG(sbi, end); f2fs_reset_gc_victim_resource(sbi, start, end); + spin_lock(&FREE_I(sbi)->segmap_lock); + for (secno = MAIN_SECS(sbi); secno <= end_secno; secno++) { + if (!test_bit(secno, FREE_I(sbi)->free_secmap)) + freed_secs++; + } + FREE_I(sbi)->free_sections -= freed_secs; + spin_unlock(&FREE_I(sbi)->segmap_lock); + /* Move out cursegs from the target range */ for (type = CURSEG_HOT_DATA; type < NR_CURSEG_TYPE; type++) { err = f2fs_allocate_segment_for_resize(sbi, type, start, end); @@ -2266,6 +2276,9 @@ static int free_segment_range(struct f2fs_sb_info *sbi, f2fs_bug_on(sbi, 1); } out: + spin_lock(&FREE_I(sbi)->segmap_lock); + FREE_I(sbi)->free_sections += freed_secs; + spin_unlock(&FREE_I(sbi)->segmap_lock); MAIN_SECS(sbi) += secs; return err; } From ba77efee1b95b4ad7559b1cdbe7cd7fa36dca95b Mon Sep 17 00:00:00 2001 From: Laxman Acharya Padhya Date: Fri, 21 Aug 2026 16:18:17 +0545 Subject: [PATCH 393/641] io_uring/query: cap user size passed to copy_struct_to_user io_handle_query_entry() clamps hdr.size for the inbound copy_from_user() but keeps the original user value as usize. copy_struct_to_user() uses that usize and, when it is larger than the kernel result, clear_user()s the trailing bytes. As hdr.size is a __u32, a query can request nearly 4 GiB of zeroing, including on the error path where res_size stays 0. The interface is reachable without a ring via IORING_REGISTER_QUERY. Reject sizes larger than PAGE_SIZE, as recommended for copy_struct_* interfaces. Fixes: c265ae75f900 ("io_uring: introduce io_uring querying") Cc: stable@vger.kernel.org # 6.18+ Signed-off-by: Laxman Acharya Padhya Reviewed-by: Gabriel Krisman Bertazi Link: https://patch.msgid.link/20260821103317.91437-1-acharyalaxman8848@gmail.com Signed-off-by: Jens Axboe --- io_uring/query.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/io_uring/query.c b/io_uring/query.c index 4321f0b94b4b..7bce2ec162c1 100644 --- a/io_uring/query.c +++ b/io_uring/query.c @@ -76,6 +76,9 @@ static int io_handle_query_entry(union io_query_data *data, void __user *uhdr, if (copy_from_user(&hdr, uhdr, sizeof(hdr))) return -EFAULT; + /* copy_struct_to_user() zeros up to usize bytes */ + if (hdr.size > PAGE_SIZE) + return -E2BIG; usize = hdr.size; hdr.size = min(hdr.size, IO_MAX_QUERY_SIZE); udata = u64_to_user_ptr(hdr.query_data); From 14572de82e5022899e5856008bc9cac97004a88c Mon Sep 17 00:00:00 2001 From: Hui Su Date: Tue, 18 Aug 2026 18:33:36 +0800 Subject: [PATCH 394/641] io_uring/waitid: honor task_work cancellation io_waitid_cb() may run through the fallback task_work path when task_work_add() can no longer queue work to the originating task. The fallback runs from a kworker and io_uring marks such task work as canceled through tw.cancel. io_waitid_cb() currently ignores tw.cancel and calls __do_wait(). waitid is task-context dependent: __do_wait() performs child lookup relative to current, and the retry path also uses current->signal->wait_chldexit. If the callback runs from the fallback kworker, current is therefore not the task that submitted the request. Honor tw.cancel before entering __do_wait(). Complete the request with -ECANCELED and skip the siginfo copy, since canceled task work may run without the submitting task's userspace execution context. Keep the existing siginfo handling for normal waitid completion and explicit cancellation. Fixes: f31ecf671ddc ("io_uring: add IORING_OP_WAITID support") Cc: stable@vger.kernel.org Signed-off-by: Hui Su Link: https://patch.msgid.link/20260818103336.1922818-2-sh_def@163.com Signed-off-by: Jens Axboe --- io_uring/waitid.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/io_uring/waitid.c b/io_uring/waitid.c index 76af129ba8ca..596f2da40260 100644 --- a/io_uring/waitid.c +++ b/io_uring/waitid.c @@ -125,7 +125,7 @@ static void io_waitid_remove_wq(struct io_kiocb *req) } } -static void io_waitid_complete(struct io_kiocb *req, int ret) +static void io_waitid_complete(struct io_kiocb *req, int ret, bool copy_si) { struct io_waitid *iw = io_kiocb_to_cmd(req, struct io_waitid); @@ -137,7 +137,10 @@ static void io_waitid_complete(struct io_kiocb *req, int ret) hlist_del_init(&req->hash_node); io_waitid_remove_wq(req); - ret = io_waitid_finish(req, ret); + if (copy_si) + ret = io_waitid_finish(req, ret); + else + io_waitid_free(req); if (ret < 0) req_set_fail(req); io_req_set_res(req, ret, 0); @@ -159,7 +162,7 @@ static bool __io_waitid_cancel(struct io_kiocb *req) if (atomic_fetch_inc(&iw->refs) & IO_WAITID_REF_MASK) return false; - io_waitid_complete(req, -ECANCELED); + io_waitid_complete(req, -ECANCELED, true); io_req_queue_tw_complete(req, -ECANCELED); return true; } @@ -202,6 +205,11 @@ static void io_waitid_cb(struct io_tw_req tw_req, io_tw_token_t tw) int ret; io_tw_lock(ctx, tw); + if (unlikely(tw.cancel)) { + io_waitid_complete(req, -ECANCELED, false); + io_req_task_complete(tw_req, tw); + return; + } ret = __do_wait(&iwa->wo); @@ -229,7 +237,7 @@ static void io_waitid_cb(struct io_tw_req tw_req, io_tw_token_t tw) } } - io_waitid_complete(req, ret); + io_waitid_complete(req, ret, true); io_req_task_complete(tw_req, tw); } From 2cf20c4e0f72d523b8673053e7120d092ff1f074 Mon Sep 17 00:00:00 2001 From: Hui Su Date: Tue, 18 Aug 2026 18:33:37 +0800 Subject: [PATCH 395/641] io_uring/waitid: avoid siginfo copy during ring teardown During ring teardown, io_ring_exit_work() cancels outstanding requests from a kworker with a NULL tctx. The waitid cancellation path eventually reaches io_waitid_finish(), which copies the stored siginfo to the userspace pointer supplied with the request. Ring-wide teardown does not run in the task context that submitted the request, so it must not access that task's userspace pointer. Depending on the address and mm state, the copy may fail with -EFAULT, but the uaccess itself is inappropriate from the teardown kworker. Use a no-copy cancellation callback when io_waitid_remove_all() is called without an owning task context. Complete the request with -ECANCELED while releasing the waitid state without touching siginfo. Keep the existing siginfo handling for explicit async cancellation and task-scoped cancellation. Fixes: f31ecf671ddc ("io_uring: add IORING_OP_WAITID support") Cc: stable@vger.kernel.org Signed-off-by: Hui Su Link: https://patch.msgid.link/20260818103336.1922818-3-sh_def@163.com Signed-off-by: Jens Axboe --- io_uring/waitid.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/io_uring/waitid.c b/io_uring/waitid.c index 596f2da40260..2b756017423f 100644 --- a/io_uring/waitid.c +++ b/io_uring/waitid.c @@ -146,7 +146,7 @@ static void io_waitid_complete(struct io_kiocb *req, int ret, bool copy_si) io_req_set_res(req, ret, 0); } -static bool __io_waitid_cancel(struct io_kiocb *req) +static bool __io_waitid_cancel(struct io_kiocb *req, bool copy_si) { struct io_waitid *iw = io_kiocb_to_cmd(req, struct io_waitid); @@ -162,21 +162,32 @@ static bool __io_waitid_cancel(struct io_kiocb *req) if (atomic_fetch_inc(&iw->refs) & IO_WAITID_REF_MASK) return false; - io_waitid_complete(req, -ECANCELED, true); + io_waitid_complete(req, -ECANCELED, copy_si); io_req_queue_tw_complete(req, -ECANCELED); return true; } +static bool io_waitid_cancel_cb(struct io_kiocb *req) +{ + return __io_waitid_cancel(req, true); +} + +static bool io_waitid_cancel_nocopy_cb(struct io_kiocb *req) +{ + return __io_waitid_cancel(req, false); +} + int io_waitid_cancel(struct io_ring_ctx *ctx, struct io_cancel_data *cd, unsigned int issue_flags) { - return io_cancel_remove(ctx, cd, issue_flags, &ctx->waitid_list, __io_waitid_cancel); + return io_cancel_remove(ctx, cd, issue_flags, &ctx->waitid_list, io_waitid_cancel_cb); } bool io_waitid_remove_all(struct io_ring_ctx *ctx, struct io_uring_task *tctx, bool cancel_all) { - return io_cancel_remove_all(ctx, tctx, &ctx->waitid_list, cancel_all, __io_waitid_cancel); + return io_cancel_remove_all(ctx, tctx, &ctx->waitid_list, cancel_all, + tctx ? io_waitid_cancel_cb : io_waitid_cancel_nocopy_cb); } static inline bool io_waitid_drop_issue_ref(struct io_kiocb *req) From 1b0bab4a873f1034c27573cfc613394cff7e0a5b Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Sun, 23 Aug 2026 21:35:29 +0200 Subject: [PATCH 396/641] rust: kbuild: disambiguate `zerocopy_derive` for `rusttest` The `rustc-dev` components for Rust 1.82.0 through 1.87.0 include a precompiled `zerocopy_derive` procedural macro in the sysroot. This range includes Rust 1.85.0, our minimum supported version. This makes `rusttest` fail because the compiler finds both the sysroot copy and the copy built in `rust/test`: error[E0464]: multiple candidates for `dylib` dependency `zerocopy_derive` found --> rust/kernel/prelude.rs:70:9 | 70 | pub use zerocopy_derive::{ | ^^^^^^^^^^^^^^^ | = note: candidate #1: .../lib/rustlib/x86_64-unknown-linux-gnu/lib/libzerocopy_derive-54d2b38896fa6bc5.so = note: candidate #2: .../rust/test/libzerocopy_derive.so Commit fe39a233ea52 ("rust: kbuild: disambiguate `zerocopy` for `rusttest`") fixed the equivalent ambiguity for `zerocopy`. Thus point to the dependency explicitly in this case too. Cc: Antoni Boucher Cc: stable@vger.kernel.org Fixes: 506054980429 ("rust: zerocopy-derive: enable support in kbuild") Link: https://patch.msgid.link/20260823193529.156066-1-ojeda@kernel.org Signed-off-by: Miguel Ojeda --- rust/Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rust/Makefile b/rust/Makefile index 3afaad4a4a3a..9211d82ac96d 100644 --- a/rust/Makefile +++ b/rust/Makefile @@ -350,7 +350,8 @@ rusttestlib-pin_init: $(src)/pin-init/src/lib.rs rusttestlib-macros \ rusttestlib-kernel: private rustc_target_flags = --extern ffi \ --extern build_error --extern macros --extern pin_init \ --extern bindings --extern uapi \ - --extern zerocopy=$(objtree)/$(obj)/test/libzerocopy.rlib --extern zerocopy_derive + --extern zerocopy=$(objtree)/$(obj)/test/libzerocopy.rlib \ + --extern zerocopy_derive=$(objtree)/$(obj)/test/$(libzerocopy_derive_name) rusttestlib-kernel: $(src)/kernel/lib.rs rusttestlib-bindings rusttestlib-uapi \ rusttestlib-build_error rusttestlib-pin_init $(obj)/$(libmacros_name) \ $(obj)/bindings.o rusttestlib-zerocopy rusttestlib-zerocopy_derive FORCE From aef2ca9353c2f26dbacfb3b8e6f33fecfbf2e67d Mon Sep 17 00:00:00 2001 From: Prike Liang Date: Wed, 19 Aug 2026 15:23:56 +0800 Subject: [PATCH 397/641] drm/amdgpu/mes: fix the inconsistent indenting for mes_userq_map() Fix the inconsistent indenting warning for mes_userq_map(). Fixes: d0827dda8fa7 ("drm/amdgpu/mes: refactor the amdgpu_mes_alloc/free_proc|gang()") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202608190252.8XCa0HqR-lkp@intel.com/ Signed-off-by: Prike Liang Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/mes_userqueue.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c index fae709f134bb..14a5abe42d1f 100644 --- a/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c +++ b/drivers/gpu/drm/amd/amdgpu/mes_userqueue.c @@ -146,7 +146,7 @@ static int mes_userq_map(struct amdgpu_usermode_queue *queue) queue_input.wptr_mc_addr = queue->wptr_obj.gpu_addr; if (mes->use_rs64mem) { - if (!uq_mgr->proc_ctx_allocated) { + if (!uq_mgr->proc_ctx_allocated) { r = amdgpu_mes_alloc_proc_ctx_index(mes, &uq_mgr->proc_ctx_array_index); if (r) { DRM_ERROR("Failed to allocate userq process index err:%d\n", r); From d36fbf82189319e9af564c93900d30e55e87e7e0 Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Fri, 14 Aug 2026 15:01:44 +0800 Subject: [PATCH 398/641] drm/amdgpu/userq: lock and validate wptr BOs before reading their GPU offset on restore On resume, amdgpu_userq_vm_validate_and_restore_queue() updates each queue's wptr GPU address via amdgpu_bo_gpu_offset(). WPTR BOs are VM-mapped, but each BO has its own reservation object and is not implicitly covered by the VM validation path here. This can leave offset reads without proper BO locking/placement state and trigger WARN_ONs. ------------[ cut here ]------------ WARNING: amdgpu_object.c:1486 at amdgpu_bo_gpu_offset+0x75/0xa0 [amdgpu], CPU#3: kworker/3:1/116 Workqueue: events amdgpu_userq_restore_worker [amdgpu] RIP: 0010:amdgpu_bo_gpu_offset+0x75/0xa0 [amdgpu] Call Trace: amdgpu_userq_vm_validate_and_restore_queue+0x629/0x960 [amdgpu] amdgpu_userq_restore_worker+0xa6/0x180 [amdgpu] process_scheduled_works+0xa6/0x460 worker_thread+0x13c/0x290 kthread+0xfb/0x140 ret_from_fork+0x1b6/0x2b0 ret_from_fork_asm+0x1a/0x30 ---[ end trace 0000000000000000 ]--- ------------[ cut here ]------------ WARNING: amdgpu_object.c:1485 at amdgpu_bo_gpu_offset+0x9a/0xa0 [amdgpu], CPU#2: kworker/2:1/127 Workqueue: events amdgpu_userq_restore_worker [amdgpu] RIP: 0010:amdgpu_bo_gpu_offset+0x9a/0xa0 [amdgpu] Add each queue's WPTR BO to the drm_exec ww context and validate it to its allowed placement before the later offset update. v2: - Clarify that WPTR BOs are VM-mapped (fix incorrect "not part of VM" wording). (Christian) - Describe both parts of the fix: lock BO reservations in drm_exec and validate BO placement before offset reads. Acked-by: Alex Deucher Signed-off-by: Jesse Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index 24adad7be251..0a816b3c5ff9 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -1047,6 +1047,30 @@ retry_lock: drm_exec_retry_on_contention(&exec); if (unlikely(ret)) goto unlock_all; + + /* + * WPTR BOs are VM-mapped, but each BO has its own reservation + * object. Lock them into this drm_exec ww context so the later + * amdgpu_bo_gpu_offset() reads are done with the BO resv locked. + */ + xa_for_each(&uq_mgr->userq_xa, tmp_key, queue) { + struct ttm_operation_ctx wptr_ctx = { false, false }; + + bo = queue->wptr_obj.obj; + if (!bo) + continue; + + ret = drm_exec_prepare_obj(&exec, &bo->tbo.base, + TTM_NUM_MOVE_FENCES + 1); + drm_exec_retry_on_contention(&exec); + if (unlikely(ret)) + goto unlock_all; + + amdgpu_bo_placement_from_domain(bo, bo->allowed_domains); + ret = ttm_bo_validate(&bo->tbo, &bo->placement, &wptr_ctx); + if (unlikely(ret)) + goto unlock_all; + } } if (invalidated) { From 6760f5cb12d2366ddd58a2d8637f7583d73f596b Mon Sep 17 00:00:00 2001 From: Bob Zhou Date: Wed, 19 Aug 2026 14:23:49 +0800 Subject: [PATCH 399/641] drm/amdgpu: avoid force-completing uninitialized UVD rings uvd_v7_0_sw_init() does not initialize the UVD decode ring for an SR-IOV VF. However, amdgpu_uvd_resume() unconditionally force-completes the decode ring when restoring its fence sequence. Skip fence completion when the fence driver is not initialized. Fixes: 0a33b11d26c6 ("drm/amdgpu: mark force completed fences with -ECANCELED") Cc: stable@vger.kernel.org Signed-off-by: Bob Zhou Acked-by: Leo Liu Acked-by: Frank Min Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c index 228a405a94c4..ecd7caa95d4b 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c @@ -517,7 +517,8 @@ int amdgpu_uvd_resume(struct amdgpu_device *adev) } memset_io(ptr, 0, size); /* to restore uvd fence seq */ - amdgpu_fence_driver_force_completion(&adev->uvd.inst[i].ring, NULL); + if (adev->uvd.inst[i].ring.fence_drv.initialized) + amdgpu_fence_driver_force_completion(&adev->uvd.inst[i].ring, NULL); } } return 0; From 290e0be2abb7d9f1473d12ef2ed3220b071c42c3 Mon Sep 17 00:00:00 2001 From: David Belanger Date: Fri, 14 Aug 2026 12:57:18 -0400 Subject: [PATCH 400/641] drm/kfd: Add CU occupancy support to GFX11 Port changes from GFX9 to GFX11 mostly as-is. Minor changes to register access code. Assisted-by: Claude:Sonnet-4-6 Signed-off-by: David Belanger Acked-by: Alex Deucher Reviewed-by: Sreekant Somasekharan Signed-off-by: Alex Deucher --- .../drm/amd/amdgpu/amdgpu_amdkfd_gfx_v11.c | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gfx_v11.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gfx_v11.c index 724beb96ed1a..04fab30ab5dd 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gfx_v11.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gfx_v11.c @@ -807,6 +807,153 @@ static uint32_t kgd_gfx_v11_hqd_sdma_get_doorbell(struct amdgpu_device *adev, return 0; } +static void lock_spi_csq_mutexes(struct amdgpu_device *adev) +{ + mutex_lock(&adev->srbm_mutex); + mutex_lock(&adev->grbm_idx_mutex); + +} + +static void unlock_spi_csq_mutexes(struct amdgpu_device *adev) +{ + mutex_unlock(&adev->grbm_idx_mutex); + mutex_unlock(&adev->srbm_mutex); +} + +/** + * get_wave_count: Read device registers to get number of waves in flight for + * a particular queue. The method also returns the doorbell offset associated + * with the queue. + * + * @adev: Handle of device whose registers are to be read + * @queue_idx: Index of queue in the queue-map bit-field + * @queue_cnt: Stores the wave count and doorbell offset for an active queue + * @inst: xcc's instance number on a multi-XCC setup + */ +static void get_wave_count(struct amdgpu_device *adev, int queue_idx, + struct kfd_cu_occupancy *queue_cnt, uint32_t inst) +{ + int pipe_idx; + int queue_slot; + unsigned int reg_val; + unsigned int wave_cnt; + /* + * Program GRBM with appropriate MEID, PIPEID, QUEUEID and VMID + * parameters to read out waves in flight. Get doorbell offset if there are + * non-zero waves in flight. + */ + pipe_idx = queue_idx / adev->gfx.mec.num_queue_per_pipe; + queue_slot = queue_idx % adev->gfx.mec.num_queue_per_pipe; + soc21_grbm_select(adev, 1, pipe_idx, queue_slot, 0); + reg_val = RREG32_SOC15_IP(GC, SOC15_REG_OFFSET(GC, 0, + regSPI_CSQ_WF_ACTIVE_COUNT_0) + queue_slot); + wave_cnt = reg_val & SPI_CSQ_WF_ACTIVE_COUNT_0__COUNT_MASK; + if (wave_cnt != 0) { + queue_cnt->wave_cnt += wave_cnt; + queue_cnt->doorbell_off = + (RREG32_SOC15(GC, 0, regCP_HQD_PQ_DOORBELL_CONTROL) & + CP_HQD_PQ_DOORBELL_CONTROL__DOORBELL_OFFSET_MASK) >> + CP_HQD_PQ_DOORBELL_CONTROL__DOORBELL_OFFSET__SHIFT; + } +} + +/** + * kgd_gfx_v11_get_cu_occupancy: Reads relevant registers associated with each + * shader engine and aggregates the number of waves that are in flight for the + * process whose pasid is provided as a parameter. The process could have ZERO + * or more queues running and submitting waves to compute units. + * + * @adev: Handle of device from which to get number of waves in flight + * @cu_occupancy: Array that gets filled with wave_cnt and doorbell offset + * for comparison later. + * @max_waves_per_cu: Output parameter updated with maximum number of waves + * possible per Compute Unit + * @inst: xcc's instance number on a multi-XCC setup + * + * Note: It's possible that the device has too many queues (oversubscription) + * in which case a VMID could be remapped to a different PASID. This could lead + * to an inaccurate wave count. Following is a high-level sequence: + * Time T1: vmid = getVmid(); vmid is associated with Pasid P1 + * Time T2: passId = getPasId(vmid); vmid is associated with Pasid P2 + * In the sequence above wave count obtained from time T1 will be incorrectly + * lost or added to total wave count. + * + * The registers that provide the waves in flight are: + * + * SPI_CSQ_WF_ACTIVE_STATUS - bit-map of queues per pipe. The bit is ON if a + * queue is slotted, OFF if there is no queue. A process could have ZERO or + * more queues slotted and submitting waves to be run on compute units. Even + * when there is a queue it is possible there could be zero wave fronts, this + * can happen when queue is waiting on top-of-pipe events - e.g. waitRegMem + * command + * + * For each bit that is ON from above: + * + * Read (SPI_CSQ_WF_ACTIVE_COUNT_0 + queue_idx) register. It provides the + * number of waves that are in flight for the queue at specified index. The + * index ranges from 0 to 7. + * + * If non-zero waves are in flight, store the corresponding doorbell offset + * of the queue, along with the wave count. + * + * Determine if the queue belongs to the process by comparing the doorbell + * offset against the process's queues. If it matches, aggregate the wave + * count for the process. + * + * Reading registers referenced above involves programming GRBM appropriately + */ +static void kgd_gfx_v11_get_cu_occupancy(struct amdgpu_device *adev, + struct kfd_cu_occupancy *cu_occupancy, + int *max_waves_per_cu, uint32_t inst) +{ + int qidx; + int se_idx; + int se_cnt; + int queue_map; + int max_queue_cnt; + DECLARE_BITMAP(cp_queue_bitmap, AMDGPU_MAX_QUEUES); + + lock_spi_csq_mutexes(adev); + soc21_grbm_select(adev, 1, 0, 0, 0); + + /* + * Iterate through the shader engines and arrays of the device + * to get number of waves in flight + */ + bitmap_complement(cp_queue_bitmap, adev->gfx.mec_bitmap[0].queue_bitmap, + AMDGPU_MAX_QUEUES); + max_queue_cnt = adev->gfx.mec.num_pipe_per_mec * + adev->gfx.mec.num_queue_per_pipe; + se_cnt = adev->gfx.config.max_shader_engines; + for (se_idx = 0; se_idx < se_cnt; se_idx++) { + amdgpu_gfx_select_se_sh(adev, se_idx, 0, 0xffffffff, inst); + queue_map = RREG32_SOC15(GC, 0, + regSPI_CSQ_WF_ACTIVE_STATUS); + + for (qidx = 0; qidx < max_queue_cnt; qidx++) { + /* Skip queues that are not associated with + * compute functions + */ + if (!test_bit(qidx, cp_queue_bitmap)) + continue; + + if (!(queue_map & (1 << qidx))) + continue; + + /* Get number of waves in flight and aggregate them */ + get_wave_count(adev, qidx, &cu_occupancy[qidx], inst); + } + } + + amdgpu_gfx_select_se_sh(adev, 0xffffffff, 0xffffffff, 0xffffffff, inst); + soc21_grbm_select(adev, 0, 0, 0, 0); + unlock_spi_csq_mutexes(adev); + + /* Update the output parameters and return */ + *max_waves_per_cu = adev->gfx.cu_info.simd_per_cu * + adev->gfx.cu_info.max_waves_per_simd; +} + const struct kfd2kgd_calls gfx_v11_kfd2kgd = { .program_sh_mem_settings = program_sh_mem_settings_v11, .set_pasid_vmid_mapping = set_pasid_vmid_mapping_v11, @@ -832,5 +979,6 @@ const struct kfd2kgd_calls gfx_v11_kfd2kgd = { .clear_address_watch = kgd_gfx_v11_clear_address_watch, .hqd_get_pq_addr = kgd_gfx_v11_hqd_get_pq_addr, .hqd_reset = kgd_gfx_v11_hqd_reset, + .get_cu_occupancy = kgd_gfx_v11_get_cu_occupancy, .hqd_sdma_get_doorbell = kgd_gfx_v11_hqd_sdma_get_doorbell }; From fb1e65a80dd9167e8dfdfdfef178d1d012d8bb58 Mon Sep 17 00:00:00 2001 From: David Belanger Date: Fri, 14 Aug 2026 13:25:56 -0400 Subject: [PATCH 401/641] drm/kfd: Add CU occupancy support to GFX12 Port changes from GFX9 to GFX12 mostly as-is. Minor changes to register access code. Assisted-by: Claude:Sonnet-4-6 Signed-off-by: David Belanger Acked-by: Alex Deucher Reviewed-by: Sreekant Somasekharan Signed-off-by: Alex Deucher --- .../drm/amd/amdgpu/amdgpu_amdkfd_gfx_v12.c | 150 +++++++++++++++++- 1 file changed, 149 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gfx_v12.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gfx_v12.c index e11ba3e91841..62b9db64368e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gfx_v12.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gfx_v12.c @@ -368,6 +368,153 @@ static uint32_t kgd_gfx_v12_hqd_sdma_get_doorbell(struct amdgpu_device *adev, return 0; } +static void lock_spi_csq_mutexes(struct amdgpu_device *adev) +{ + mutex_lock(&adev->srbm_mutex); + mutex_lock(&adev->grbm_idx_mutex); + +} + +static void unlock_spi_csq_mutexes(struct amdgpu_device *adev) +{ + mutex_unlock(&adev->grbm_idx_mutex); + mutex_unlock(&adev->srbm_mutex); +} + +/** + * get_wave_count: Read device registers to get number of waves in flight for + * a particular queue. The method also returns the doorbell offset associated + * with the queue. + * + * @adev: Handle of device whose registers are to be read + * @queue_idx: Index of queue in the queue-map bit-field + * @queue_cnt: Stores the wave count and doorbell offset for an active queue + * @inst: xcc's instance number on a multi-XCC setup + */ +static void get_wave_count(struct amdgpu_device *adev, int queue_idx, + struct kfd_cu_occupancy *queue_cnt, uint32_t inst) +{ + int pipe_idx; + int queue_slot; + unsigned int reg_val; + unsigned int wave_cnt; + /* + * Program GRBM with appropriate MEID, PIPEID, QUEUEID and VMID + * parameters to read out waves in flight. Get doorbell offset if there are + * non-zero waves in flight. + */ + pipe_idx = queue_idx / adev->gfx.mec.num_queue_per_pipe; + queue_slot = queue_idx % adev->gfx.mec.num_queue_per_pipe; + soc24_grbm_select(adev, 1, pipe_idx, queue_slot, 0); + reg_val = RREG32_SOC15_IP(GC, SOC15_REG_OFFSET(GC, 0, + regSPI_CSQ_WF_ACTIVE_COUNT_0) + queue_slot); + wave_cnt = reg_val & SPI_CSQ_WF_ACTIVE_COUNT_0__COUNT_MASK; + if (wave_cnt != 0) { + queue_cnt->wave_cnt += wave_cnt; + queue_cnt->doorbell_off = + (RREG32_SOC15(GC, 0, regCP_HQD_PQ_DOORBELL_CONTROL) & + CP_HQD_PQ_DOORBELL_CONTROL__DOORBELL_OFFSET_MASK) >> + CP_HQD_PQ_DOORBELL_CONTROL__DOORBELL_OFFSET__SHIFT; + } +} + +/** + * kgd_gfx_v12_get_cu_occupancy: Reads relevant registers associated with each + * shader engine and aggregates the number of waves that are in flight for the + * process whose pasid is provided as a parameter. The process could have ZERO + * or more queues running and submitting waves to compute units. + * + * @adev: Handle of device from which to get number of waves in flight + * @cu_occupancy: Array that gets filled with wave_cnt and doorbell offset + * for comparison later. + * @max_waves_per_cu: Output parameter updated with maximum number of waves + * possible per Compute Unit + * @inst: xcc's instance number on a multi-XCC setup + * + * Note: It's possible that the device has too many queues (oversubscription) + * in which case a VMID could be remapped to a different PASID. This could lead + * to an inaccurate wave count. Following is a high-level sequence: + * Time T1: vmid = getVmid(); vmid is associated with Pasid P1 + * Time T2: passId = getPasId(vmid); vmid is associated with Pasid P2 + * In the sequence above wave count obtained from time T1 will be incorrectly + * lost or added to total wave count. + * + * The registers that provide the waves in flight are: + * + * SPI_CSQ_WF_ACTIVE_STATUS - bit-map of queues per pipe. The bit is ON if a + * queue is slotted, OFF if there is no queue. A process could have ZERO or + * more queues slotted and submitting waves to be run on compute units. Even + * when there is a queue it is possible there could be zero wave fronts, this + * can happen when queue is waiting on top-of-pipe events - e.g. waitRegMem + * command + * + * For each bit that is ON from above: + * + * Read (SPI_CSQ_WF_ACTIVE_COUNT_0 + queue_idx) register. It provides the + * number of waves that are in flight for the queue at specified index. The + * index ranges from 0 to 7. + * + * If non-zero waves are in flight, store the corresponding doorbell offset + * of the queue, along with the wave count. + * + * Determine if the queue belongs to the process by comparing the doorbell + * offset against the process's queues. If it matches, aggregate the wave + * count for the process. + * + * Reading registers referenced above involves programming GRBM appropriately + */ +static void kgd_gfx_v12_get_cu_occupancy(struct amdgpu_device *adev, + struct kfd_cu_occupancy *cu_occupancy, + int *max_waves_per_cu, uint32_t inst) +{ + int qidx; + int se_idx; + int se_cnt; + int queue_map; + int max_queue_cnt; + DECLARE_BITMAP(cp_queue_bitmap, AMDGPU_MAX_QUEUES); + + lock_spi_csq_mutexes(adev); + soc24_grbm_select(adev, 1, 0, 0, 0); + + /* + * Iterate through the shader engines and arrays of the device + * to get number of waves in flight + */ + bitmap_complement(cp_queue_bitmap, adev->gfx.mec_bitmap[0].queue_bitmap, + AMDGPU_MAX_QUEUES); + max_queue_cnt = adev->gfx.mec.num_pipe_per_mec * + adev->gfx.mec.num_queue_per_pipe; + se_cnt = adev->gfx.config.max_shader_engines; + for (se_idx = 0; se_idx < se_cnt; se_idx++) { + amdgpu_gfx_select_se_sh(adev, se_idx, 0, 0xffffffff, inst); + queue_map = RREG32_SOC15(GC, 0, + regSPI_CSQ_WF_ACTIVE_STATUS); + + for (qidx = 0; qidx < max_queue_cnt; qidx++) { + /* Skip queues that are not associated with + * compute functions + */ + if (!test_bit(qidx, cp_queue_bitmap)) + continue; + + if (!(queue_map & (1 << qidx))) + continue; + + /* Get number of waves in flight and aggregate them */ + get_wave_count(adev, qidx, &cu_occupancy[qidx], inst); + } + } + + amdgpu_gfx_select_se_sh(adev, 0xffffffff, 0xffffffff, 0xffffffff, inst); + soc24_grbm_select(adev, 0, 0, 0, 0); + unlock_spi_csq_mutexes(adev); + + /* Update the output parameters and return */ + *max_waves_per_cu = adev->gfx.cu_info.simd_per_cu * + adev->gfx.cu_info.max_waves_per_simd; +} + const struct kfd2kgd_calls gfx_v12_kfd2kgd = { .init_interrupts = init_interrupts_v12, .hqd_dump = hqd_dump_v12, @@ -381,5 +528,6 @@ const struct kfd2kgd_calls gfx_v12_kfd2kgd = { .set_wave_launch_mode = kgd_gfx_v12_set_wave_launch_mode, .set_address_watch = kgd_gfx_v12_set_address_watch, .clear_address_watch = kgd_gfx_v12_clear_address_watch, - .hqd_sdma_get_doorbell = kgd_gfx_v12_hqd_sdma_get_doorbell + .hqd_sdma_get_doorbell = kgd_gfx_v12_hqd_sdma_get_doorbell, + .get_cu_occupancy = kgd_gfx_v12_get_cu_occupancy, }; From fb62f7f031155fe6c1095d2fc921654ad51f87c2 Mon Sep 17 00:00:00 2001 From: David Belanger Date: Fri, 14 Aug 2026 13:26:17 -0400 Subject: [PATCH 402/641] drm/kfd: Add CU occupancy support to GFX12.1 Port changes from GFX9 to GFX12.1 mostly as-is. Minor changes to register access code. Assisted-by: Claude:Sonnet 4.6 Signed-off-by: David Belanger Reviewed-by: Sreekant Somasekharan Signed-off-by: Alex Deucher --- .../drm/amd/amdgpu/amdgpu_amdkfd_gfx_v12_1.c | 150 +++++++++++++++++- 1 file changed, 149 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gfx_v12_1.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gfx_v12_1.c index 38ca1aea33b2..070001fd34b0 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gfx_v12_1.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gfx_v12_1.c @@ -371,6 +371,153 @@ static uint32_t kgd_gfx_v12_1_hqd_sdma_get_doorbell(struct amdgpu_device *adev, return 0; } +static void lock_spi_csq_mutexes(struct amdgpu_device *adev) +{ + mutex_lock(&adev->srbm_mutex); + mutex_lock(&adev->grbm_idx_mutex); + +} + +static void unlock_spi_csq_mutexes(struct amdgpu_device *adev) +{ + mutex_unlock(&adev->grbm_idx_mutex); + mutex_unlock(&adev->srbm_mutex); +} + +/** + * get_wave_count: Read device registers to get number of waves in flight for + * a particular queue. The method also returns the doorbell offset associated + * with the queue. + * + * @adev: Handle of device whose registers are to be read + * @queue_idx: Index of queue in the queue-map bit-field + * @queue_cnt: Stores the wave count and doorbell offset for an active queue + * @inst: xcc's instance number on a multi-XCC setup + */ +static void get_wave_count(struct amdgpu_device *adev, int queue_idx, + struct kfd_cu_occupancy *queue_cnt, uint32_t inst) +{ + int pipe_idx; + int queue_slot; + unsigned int reg_val; + unsigned int wave_cnt; + /* + * Program GRBM with appropriate MEID, PIPEID, QUEUEID and VMID + * parameters to read out waves in flight. Get doorbell offset if there are + * non-zero waves in flight. + */ + pipe_idx = queue_idx / adev->gfx.mec.num_queue_per_pipe; + queue_slot = queue_idx % adev->gfx.mec.num_queue_per_pipe; + amdgpu_gfx_select_me_pipe_q(adev, 1, pipe_idx, queue_slot, 0, inst); + reg_val = RREG32_SOC15_IP(GC, SOC15_REG_OFFSET(GC, GET_INST(GC, inst), + regSPI_CSQ_WF_ACTIVE_COUNT_0) + queue_slot); + wave_cnt = reg_val & SPI_CSQ_WF_ACTIVE_COUNT_0__COUNT_MASK; + if (wave_cnt != 0) { + queue_cnt->wave_cnt += wave_cnt; + queue_cnt->doorbell_off = + (RREG32_SOC15(GC, GET_INST(GC, inst), regCP_HQD_PQ_DOORBELL_CONTROL) & + CP_HQD_PQ_DOORBELL_CONTROL__DOORBELL_OFFSET_MASK) >> + CP_HQD_PQ_DOORBELL_CONTROL__DOORBELL_OFFSET__SHIFT; + } +} + +/** + * kgd_gfx_v12_1_get_cu_occupancy: Reads relevant registers associated with + * each shader engine and aggregates the number of waves that are in flight + * for the process whose pasid is provided as a parameter. The process could + * have ZERO or more queues running and submitting waves to compute units. + * + * @adev: Handle of device from which to get number of waves in flight + * @cu_occupancy: Array that gets filled with wave_cnt and doorbell offset + * for comparison later. + * @max_waves_per_cu: Output parameter updated with maximum number of waves + * possible per Compute Unit + * @inst: xcc's instance number on a multi-XCC setup + * + * Note: It's possible that the device has too many queues (oversubscription) + * in which case a VMID could be remapped to a different PASID. This could lead + * to an inaccurate wave count. Following is a high-level sequence: + * Time T1: vmid = getVmid(); vmid is associated with Pasid P1 + * Time T2: passId = getPasId(vmid); vmid is associated with Pasid P2 + * In the sequence above wave count obtained from time T1 will be incorrectly + * lost or added to total wave count. + * + * The registers that provide the waves in flight are: + * + * SPI_CSQ_WF_ACTIVE_STATUS - bit-map of queues per pipe. The bit is ON if a + * queue is slotted, OFF if there is no queue. A process could have ZERO or + * more queues slotted and submitting waves to be run on compute units. Even + * when there is a queue it is possible there could be zero wave fronts, this + * can happen when queue is waiting on top-of-pipe events - e.g. waitRegMem + * command + * + * For each bit that is ON from above: + * + * Read (SPI_CSQ_WF_ACTIVE_COUNT_0 + queue_idx) register. It provides the + * number of waves that are in flight for the queue at specified index. The + * index ranges from 0 to 7. + * + * If non-zero waves are in flight, store the corresponding doorbell offset + * of the queue, along with the wave count. + * + * Determine if the queue belongs to the process by comparing the doorbell + * offset against the process's queues. If it matches, aggregate the wave + * count for the process. + * + * Reading registers referenced above involves programming GRBM appropriately + */ +static void kgd_gfx_v12_1_get_cu_occupancy(struct amdgpu_device *adev, + struct kfd_cu_occupancy *cu_occupancy, + int *max_waves_per_cu, uint32_t inst) +{ + int qidx; + int se_idx; + int se_cnt; + int queue_map; + int max_queue_cnt; + DECLARE_BITMAP(cp_queue_bitmap, AMDGPU_MAX_QUEUES); + + lock_spi_csq_mutexes(adev); + amdgpu_gfx_select_me_pipe_q(adev, 1, 0, 0, 0, inst); + + /* + * Iterate through the shader engines and arrays of the device + * to get number of waves in flight + */ + bitmap_complement(cp_queue_bitmap, adev->gfx.mec_bitmap[0].queue_bitmap, + AMDGPU_MAX_QUEUES); + max_queue_cnt = adev->gfx.mec.num_pipe_per_mec * + adev->gfx.mec.num_queue_per_pipe; + se_cnt = adev->gfx.config.max_shader_engines; + for (se_idx = 0; se_idx < se_cnt; se_idx++) { + amdgpu_gfx_select_se_sh(adev, se_idx, 0, 0xffffffff, inst); + queue_map = RREG32_SOC15(GC, GET_INST(GC, inst), + regSPI_CSQ_WF_ACTIVE_STATUS); + + for (qidx = 0; qidx < max_queue_cnt; qidx++) { + /* Skip queues that are not associated with + * compute functions + */ + if (!test_bit(qidx, cp_queue_bitmap)) + continue; + + if (!(queue_map & (1 << qidx))) + continue; + + /* Get number of waves in flight and aggregate them */ + get_wave_count(adev, qidx, &cu_occupancy[qidx], inst); + } + } + + amdgpu_gfx_select_se_sh(adev, 0xffffffff, 0xffffffff, 0xffffffff, inst); + amdgpu_gfx_select_me_pipe_q(adev, 0, 0, 0, 0, inst); + unlock_spi_csq_mutexes(adev); + + /* Update the output parameters and return */ + *max_waves_per_cu = adev->gfx.cu_info.simd_per_cu * + adev->gfx.cu_info.max_waves_per_simd; +} + const struct kfd2kgd_calls gfx_v12_1_kfd2kgd = { .init_interrupts = init_interrupts_v12_1, .hqd_dump = hqd_dump_v12_1, @@ -384,5 +531,6 @@ const struct kfd2kgd_calls gfx_v12_1_kfd2kgd = { .set_wave_launch_mode = kgd_gfx_v12_1_set_wave_launch_mode, .set_address_watch = kgd_gfx_v12_1_set_address_watch, .clear_address_watch = kgd_gfx_v12_1_clear_address_watch, - .hqd_sdma_get_doorbell = kgd_gfx_v12_1_hqd_sdma_get_doorbell + .hqd_sdma_get_doorbell = kgd_gfx_v12_1_hqd_sdma_get_doorbell, + .get_cu_occupancy = kgd_gfx_v12_1_get_cu_occupancy }; From 2ee9836545e690c9e66ec203445d7705959fd7a8 Mon Sep 17 00:00:00 2001 From: David Rosca Date: Thu, 20 Aug 2026 16:32:51 +0200 Subject: [PATCH 403/641] drm/amdgpu: Fix VCE 3 ring align_mask The largest frame is 20 dwords, so 0xf mask is too small. This was always wrong, but we were lucky with the VCE_CMD_END commands inserted after fence and vm_flush. Fixes: 8897ea8c761b ("drm/amdgpu: Implement insert_end for VCE 3") Cc: stable@vger.kernel.org Acked-by: Alex Deucher Signed-off-by: David Rosca Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/vce_v3_0.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/vce_v3_0.c b/drivers/gpu/drm/amd/amdgpu/vce_v3_0.c index a9497e2e07f7..4dbbeaf97ad1 100644 --- a/drivers/gpu/drm/amd/amdgpu/vce_v3_0.c +++ b/drivers/gpu/drm/amd/amdgpu/vce_v3_0.c @@ -891,7 +891,7 @@ static const struct amdgpu_ring_funcs vce_v3_0_ring_phys_funcs = { static const struct amdgpu_ring_funcs vce_v3_0_ring_vm_funcs = { .type = AMDGPU_RING_TYPE_VCE, - .align_mask = 0xf, + .align_mask = 0x1f, .nop = VCE_CMD_NO_OP, .support_64bit_ptrs = false, .no_user_fence = true, From 40ba09e11188d1b7f79d51fc28aca5ea45e0c138 Mon Sep 17 00:00:00 2001 From: Sunday Clement Date: Thu, 6 Aug 2026 10:59:34 -0400 Subject: [PATCH 404/641] drm/amdkfd: Reject zero-sized AQL queue allocations after size halving KFD_IOC_ALLOC_MEMORY_OF_GPU with flag KFD_IOC_ALLOC_MEM_FLAGS_AQL_QUEUE_MEM and size=1 triggers the AQL wraparound workaround (size >>= 1), reducing size to 0. The resulting zero passes through PAGE_ALIGN(0) = 0 without validation, bypassing the per-process VRAM quota check in reserve_mem_limit() (vram_used + 0 > vram_available is always false). The fix adds post-halving zero-size validation in the primary allocation path (amdgpu_amdkfd_gpuvm.c). The check happens after size halving but before reserve_mem_limit(), and uses err_alignment_size error path to properly clean up the allocated kgd_mem structure and mutex. Cc: stable@vger.kernel.org Signed-off-by: Sunday Clement Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c | 7 +++++++ drivers/gpu/drm/amd/amdkfd/kfd_chardev.c | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c index 34481ee7065a..d66881684ee5 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c @@ -1795,6 +1795,12 @@ int amdgpu_amdkfd_gpuvm_alloc_memory_of_gpu( size >>= 1; aligned_size = PAGE_ALIGN(size); + /* reject AQL queue with size < 2 */ + if (!aligned_size) { + ret = -EINVAL; + goto err_alignment_size; + } + (*mem)->alloc_flags = flags; amdgpu_sync_create(&(*mem)->sync); @@ -1886,6 +1892,7 @@ err_bo_create: amdgpu_amdkfd_unreserve_mem_limit(adev, aligned_size, flags, xcp_id); err_reserve_limit: amdgpu_sync_free(&(*mem)->sync); +err_alignment_size: mutex_destroy(&(*mem)->lock); if (gobj) drm_gem_object_put(gobj); diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c index 6fd18488d5cf..7fcfc150a7fc 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c @@ -1200,7 +1200,8 @@ static int kfd_ioctl_alloc_memory_of_gpu(struct file *filep, if (flags & KFD_IOC_ALLOC_MEM_FLAGS_AQL_QUEUE_MEM) size >>= 1; - atomic64_add(PAGE_ALIGN(size), &pdd->vram_usage); + size = PAGE_ALIGN(size); + atomic64_add(size, &pdd->vram_usage); } mutex_unlock(&p->mutex); From b30900566642ceb2c9e12b56c2afec28d0fd91a0 Mon Sep 17 00:00:00 2001 From: Xiang Liu Date: Fri, 21 Aug 2026 17:41:57 +0800 Subject: [PATCH 405/641] drm/amdgpu: clamp the isolation index for rings outside a partition adev->isolation[] has one slot per partition, but a ring that is not assigned to one keeps AMDGPU_XCP_NO_PARTITION, which is ~0, so indexing the array with it is out of bounds. SDMA submissions hit this on both the isolation enforcement and the VM flush path and trip UBSAN. Fall back to the first slot the way the cleaner shader path already does, and stop taking the address before the ring type check that makes it relevant. Cc: stable@vger.kernel.org Signed-off-by: Xiang Liu Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 5 ++++- drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 019581577603..44bed0ba64a3 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -6687,8 +6687,8 @@ struct dma_fence *amdgpu_device_enforce_isolation(struct amdgpu_device *adev, struct amdgpu_ring *ring, struct amdgpu_job *job) { - struct amdgpu_isolation *isolation = &adev->isolation[ring->xcp_id]; struct drm_sched_fence *f = job->base.s_fence; + struct amdgpu_isolation *isolation; struct dma_fence *dep; void *owner; int r; @@ -6701,6 +6701,9 @@ struct dma_fence *amdgpu_device_enforce_isolation(struct amdgpu_device *adev, ring->funcs->type != AMDGPU_RING_TYPE_COMPUTE) return NULL; + isolation = &adev->isolation[ring->xcp_id == AMDGPU_XCP_NO_PARTITION ? + 0 : ring->xcp_id]; + /* * All submissions where enforce isolation is false are handled as if * they come from a single client. Use ~0l as the owner to distinct it diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c index a3758c654dd4..aedf72c2333e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c @@ -776,7 +776,9 @@ void amdgpu_vm_flush(struct amdgpu_ring *ring, struct amdgpu_job *job, bool *emit_gds_needed) { struct amdgpu_device *adev = ring->adev; - struct amdgpu_isolation *isolation = &adev->isolation[ring->xcp_id]; + struct amdgpu_isolation *isolation = + &adev->isolation[ring->xcp_id == AMDGPU_XCP_NO_PARTITION ? + 0 : ring->xcp_id]; unsigned vmhub = ring->vm_hub; struct amdgpu_vmid_mgr *id_mgr = &adev->vm_manager.id_mgr[vmhub]; struct amdgpu_vmid *id = &id_mgr->ids[job->vmid]; From 52536ce677a3470c0e5323b940791efb33975450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Fri, 21 Aug 2026 23:50:58 +0200 Subject: [PATCH 406/641] drm/amd/display: Fix HPD consideration for VGA/LVDS connectors on DCE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After a refactor that landed in Linux 7.0, DC now crashes when it is initialized on GPUs that have a VGA or LVDS connector. This is because these connectors have no HPD so the hpd_gpio is NULL and therefore DC takes the code path meant for DCN 4.2+ which sets irq_source_hpd = 255 that causes the subsequent code to try to register the HPD interrupt, which fails, and causes a crash. This commit should be backported to Linux 7.0 and newer. Cc: stable@vger.kernel.org Cc: Dmytro Laktyushkin Cc: Roman Li Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5490 Fixes: def3488eb0fd ("drm/amd/display: refactor HPD to increase flexibility") Signed-off-by: Timur Kristóf Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260821215059.312868-1-timur.kristof@gmail.com Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/link/link_factory.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/link/link_factory.c b/drivers/gpu/drm/amd/display/dc/link/link_factory.c index 89265b083935..7146f8356233 100644 --- a/drivers/gpu/drm/amd/display/dc/link/link_factory.c +++ b/drivers/gpu/drm/amd/display/dc/link/link_factory.c @@ -639,7 +639,7 @@ static bool construct_phy(struct dc_link *link, DC_LOG_DC("BIOS object table - hpd_gpio id: %d", enc_init_data.hpd_gpio->id); DC_LOG_DC("BIOS object table - hpd_gpio en: %d", enc_init_data.hpd_gpio->en); - } else { + } else if (link->ctx->dce_version > DCN_VERSION_4_01) { struct graphics_object_hpd_info hpd_info; if (link->ctx->dc_bios->funcs->get_hpd_info(link->ctx->dc_bios, link->link_id, &hpd_info) == BP_RESULT_OK) { From 9e8bcfde0039238e904b4e720e66b7f4fbf82c0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Fri, 21 Aug 2026 23:50:59 +0200 Subject: [PATCH 407/641] drm/amd/display: Log details when failing to register HPD IRQ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This should help diagnose HPD IRQ related issues in the future. Signed-off-by: Timur Kristóf Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260821215059.312868-2-timur.kristof@gmail.com Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c index 9be63996b062..d0239a3de2e1 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c @@ -1735,7 +1735,9 @@ int amdgpu_dm_register_hpd_handlers(struct amdgpu_device *adev) if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || int_params.irq_source < DC_IRQ_SOURCE_HPD1 || int_params.irq_source > DC_IRQ_SOURCE_HPD6) { - drm_err(adev_to_drm(adev), "Failed to register hpd irq!\n"); + drm_err(adev_to_drm(adev), + "Failed to register hpd irq %u for %s!\n", + int_params.irq_source, connector->name); return -EINVAL; } @@ -1753,7 +1755,9 @@ int amdgpu_dm_register_hpd_handlers(struct amdgpu_device *adev) if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || int_params.irq_source < DC_IRQ_SOURCE_HPD1RX || int_params.irq_source > DC_IRQ_SOURCE_HPD6RX) { - drm_err(adev_to_drm(adev), "Failed to register hpd rx irq!\n"); + drm_err(adev_to_drm(adev), + "Failed to register hpd rx irq %u for %s!\n", + int_params.irq_source, connector->name); return -EINVAL; } From 960c4a8069bfd352c48cc88592618f1ebe24c69e Mon Sep 17 00:00:00 2001 From: Xiaogang Chen Date: Sun, 23 Aug 2026 15:22:54 -0500 Subject: [PATCH 408/641] drm/amdkfd: Fix error path at svm_migrate_copy_to_ram If page migration from device to sys ram fails for some reasons driver needs release and unlock allocated system pages. To do that driver should use page physical address, or pfn, then get struct page*. Current driver uses dma address(for adev) that is not correct with IOMMU enabled, or even in general. The patch releases and unlocks allocated system pages based on where migration failed by struct page* of sys ram pages. Also dma_unmap correspodent system ram pages at error path. Cc: stable@vger.kernel.org Signed-off-by: Xiaogang Chen Reviewed-by: Felix Kuehling Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_migrate.c | 45 ++++++++++++++++-------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c b/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c index f5af1dd3b70e..a6bb41fdc8c1 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c @@ -258,15 +258,6 @@ svm_migrate_get_sys_page(struct vm_area_struct *vma, unsigned long addr) return page; } -static void svm_migrate_put_sys_page(unsigned long addr) -{ - struct page *page; - - page = pfn_to_page(addr >> PAGE_SHIFT); - unlock_page(page); - put_page(page); -} - static unsigned long svm_migrate_successful_pages(struct migrate_vma *migrate) { unsigned long mpages = 0; @@ -591,9 +582,10 @@ svm_migrate_copy_to_ram(struct amdgpu_device *adev, struct svm_range *prange, dma_addr_t *scratch, u64 npages) { struct device *dev = adev->dev; - u64 *src; + struct page *dpage = NULL; dma_addr_t *dst; - struct page *dpage; + u64 *src; + u64 i = 0, j; u64 addr; int r = 0; @@ -647,6 +639,7 @@ svm_migrate_copy_to_ram(struct amdgpu_device *adev, struct svm_range *prange, r = dma_mapping_error(dev, dst[i]); if (r) { dev_err(adev->dev, "%s: fail %d dma_map_page\n", __func__, r); + dst[i] = 0; goto out_oom; } @@ -654,17 +647,39 @@ svm_migrate_copy_to_ram(struct amdgpu_device *adev, struct svm_range *prange, dst[i] >> PAGE_SHIFT, page_to_pfn(dpage)); migrate->dst[i] = migrate_pfn(page_to_pfn(dpage)); + + dpage = NULL; j++; } - r = svm_migrate_copy_memory_gart(adev, dst + i - j, src + i - j, j, - FROM_VRAM_TO_RAM, mfence); - + if (j > 0) + r = svm_migrate_copy_memory_gart(adev, dst + i - j, src + i - j, j, + FROM_VRAM_TO_RAM, mfence); out_oom: if (r) { pr_debug("failed %d copy to ram\n", r); + + /* first release current dpage when dma_map_page fail */ + if (dpage) { + unlock_page(dpage); + put_page(dpage); + } + + /* release previous allocated sys pages and unmap dma address */ while (i--) { - svm_migrate_put_sys_page(dst[i]); + + if (dst[i]) { + dma_unmap_page(dev, dst[i], PAGE_SIZE, + DMA_BIDIRECTIONAL); + dst[i] = 0; + } + + dpage = migrate_pfn_to_page(migrate->dst[i]); + if (!dpage) + continue; + + unlock_page(dpage); + put_page(dpage); migrate->dst[i] = 0; } } From 520e345ffe05aabef1db82beda4288afb1757ff2 Mon Sep 17 00:00:00 2001 From: Xiaogang Chen Date: Sun, 23 Aug 2026 15:47:15 -0500 Subject: [PATCH 409/641] drm/amdkfd: Fix the case that vm range is hole at svm_migrate_copy_to_vram When migration vm range is hole at cpu side(MIGRATE_PFN_MIGRATE set + MIGRATE_PFN_VALID unset) driver still allocates device pages. There is no dma map of src pages and migration. j is 0 and svm_migrate_copy_memory_gart() will return an uninitialized r. That can trigger out_free_vram_pages to drop all VRAM just set up. Initialize r and only call the last svm_migrate_copy_memory_gart if j > 0. Current code postponed the last page to the final copy. This patch flushes on the last page when reach to the end of current drm_buddy_block; avoids another svm_migrate_copy_memory_gart. Cc: stable@vger.kernel.org Signed-off-by: Xiaogang Chen Reviewed-by: Felix Kuehling Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_migrate.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c b/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c index a6bb41fdc8c1..253365a8257e 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c @@ -134,7 +134,7 @@ svm_migrate_copy_memory_gart(struct amdgpu_device *adev, dma_addr_t *sys, u64 gart_s, gart_d; struct dma_fence *next; u64 size; - int r; + int r = 0; ring = to_amdgpu_ring(adev->mman.buffer_funcs_scheds[0]); entity = &adev->mman.move_entities[0]; @@ -284,7 +284,7 @@ svm_migrate_copy_to_vram(struct kfd_node *node, struct svm_range *prange, dma_addr_t *src; u64 *dst; u64 i, j; - int r; + int r = 0; pr_debug("svms 0x%p [0x%lx 0x%lx 0x%llx]\n", prange->svms, prange->start, prange->last, ttm_res_offset); @@ -310,6 +310,7 @@ svm_migrate_copy_to_vram(struct kfd_node *node, struct svm_range *prange, DMA_BIDIRECTIONAL); r = dma_mapping_error(dev, src[i]); if (r) { + src[i] = 0; dev_err(dev, "%s: fail %d dma_map_page\n", __func__, r); goto out_free_vram_pages; @@ -334,7 +335,8 @@ svm_migrate_copy_to_vram(struct kfd_node *node, struct svm_range *prange, pr_debug_ratelimited("dma mapping src to 0x%llx, pfn 0x%lx\n", src[i] >> PAGE_SHIFT, page_to_pfn(spage)); - if (j >= (cursor.size >> PAGE_SHIFT) - 1 && i < npages - 1) { + /* accumulated j + 1 pages reach end of current drm_buddy_block */ + if (j + 1 >= (cursor.size >> PAGE_SHIFT)) { r = svm_migrate_copy_memory_gart(adev, src + i - j, dst + i - j, j + 1, FROM_RAM_TO_VRAM, @@ -348,7 +350,8 @@ svm_migrate_copy_to_vram(struct kfd_node *node, struct svm_range *prange, } } - r = svm_migrate_copy_memory_gart(adev, src + i - j, dst + i - j, j, + if (j > 0) + r = svm_migrate_copy_memory_gart(adev, src + i - j, dst + i - j, j, FROM_RAM_TO_VRAM, mfence); out_free_vram_pages: From a04ea08ddb516f9f21f17574b6a2e7b540dfadf8 Mon Sep 17 00:00:00 2001 From: Prike Liang Date: Fri, 7 Aug 2026 09:46:22 +0800 Subject: [PATCH 410/641] drm/amdgpu/userq: fix lock missing for userq fence error set amdgpu_userq_fence_driver() and amdgpu_userq_fence_driver_destroy() don't acquire the dma_fence spinlock, so locking the dma_fence lock before test the signaled state and set error state to avoid missing lock assert error. Signed-off-by: Prike Liang Acked-by: Alex Deucher Signed-off-by: Alex Deucher --- .../gpu/drm/amd/amdgpu/amdgpu_userq_fence.c | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c index a33dbe978798..4b023e024d9f 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c @@ -191,14 +191,15 @@ void amdgpu_userq_fence_driver_destroy(struct kref *ref) struct dma_fence *f; spin_lock_irqsave(&fence_drv->fence_list_lock, flags); + lockdep_assert_held(&fence_drv->fence_list_lock); list_for_each_entry_safe(fence, tmp, &fence_drv->fences, link) { f = &fence->base; - - if (!dma_fence_is_signaled(f)) { + spin_lock(dma_fence_spinlock(f)); + if (!dma_fence_is_signaled_locked(f)) { dma_fence_set_error(f, -ECANCELED); - dma_fence_signal(f); + dma_fence_signal_locked(f); } - + spin_unlock(dma_fence_spinlock(f)); list_del(&fence->link); dma_fence_put(f); } @@ -423,11 +424,16 @@ amdgpu_userq_fence_driver_set_error(struct amdgpu_userq_fence *fence, struct dma_fence *f; spin_lock_irqsave(&fence_drv->fence_list_lock, flags); - + lockdep_assert_held(&fence_drv->fence_list_lock); f = rcu_dereference_protected(&fence->base, lockdep_is_held(&fence_drv->fence_list_lock)); - if (f && !dma_fence_is_signaled_locked(f)) - dma_fence_set_error(f, error); + if (f) { + /* nest f->lock inside fence_list_lock */ + spin_lock(dma_fence_spinlock(f)); + if (!dma_fence_is_signaled_locked(f)) + dma_fence_set_error(f, error); + spin_unlock(dma_fence_spinlock(f)); + } spin_unlock_irqrestore(&fence_drv->fence_list_lock, flags); } From 6aa530642f95d5c48aa336416f94a35e7949b647 Mon Sep 17 00:00:00 2001 From: Vladimir Marioukhine Date: Wed, 12 Aug 2026 12:58:12 -0400 Subject: [PATCH 411/641] drm/amdkfd: guard against NULL restore_mqd in CRIU queue restore MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both create_queue_cpsch() and create_queue_nocpsch() unconditionally call mqd_mgr->restore_mqd() when a CRIU restore is in progress (qd != NULL), with no NULL guard. On any system where restore_mqd is not implemented for the given queue type, a user holding CAP_CHECKPOINT_RESTORE can trigger a kernel NULL pointer dereference and panic the machine by issuing KFD_IOC_CRIU_OP_RESTORE with a crafted queue restore object. Note that checkpoint_mqd is likewise unimplemented on GFX12, so no legitimate CRIU image can reach this path — only a hand-crafted restore payload. Add a NULL guard for restore_mqd immediately after mqd_mgr is resolved, unwinding via the existing error labels and returning -EOPNOTSUPP if the callback is not implemented. This mirrors the existing checkpoint_mqd guard in checkpoint_mqd(). Fixes: 48f0bdf4e38e ("drm/amdkfd: Added MQD manager files for GFX12.") Cc: stable@vger.kernel.org Signed-off-by: Vladimir Marioukhine Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c | 10 ++++++++++ 1 file changed, 10 insertions(+) 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 a23384571193..4bc947c3bd0d 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -769,6 +769,11 @@ static int create_queue_nocpsch(struct device_queue_manager *dqm, mqd_mgr = dqm->mqd_mgrs[get_mqd_type_from_queue_type( q->properties.type)]; + if (qd && !mqd_mgr->restore_mqd) { + pr_debug("restore_mqd not implemented for this GPU\n"); + retval = -EOPNOTSUPP; + goto deallocate_vmid; + } if (q->properties.type == KFD_QUEUE_TYPE_COMPUTE) { retval = allocate_hqd(dqm, q); if (retval) @@ -2236,6 +2241,11 @@ static int create_queue_cpsch(struct device_queue_manager *dqm, struct queue *q, mqd_mgr = dqm->mqd_mgrs[get_mqd_type_from_queue_type( q->properties.type)]; + if (qd && !mqd_mgr->restore_mqd) { + pr_debug("restore_mqd not implemented for this GPU\n"); + retval = -EOPNOTSUPP; + goto out_deallocate_doorbell; + } if (q->properties.type == KFD_QUEUE_TYPE_SDMA || q->properties.type == KFD_QUEUE_TYPE_SDMA_XGMI) From fdc290ff4ab19c7e0dde36c4cd1e2771b61f6bf5 Mon Sep 17 00:00:00 2001 From: Srinivasan Shanmugam Date: Thu, 20 Aug 2026 15:29:43 +0530 Subject: [PATCH 412/641] drm/amd/display: Fix wrong bytes-per-pixel value for dml2_422_packed_10 The pixel format dml2_422_packed_10 needs BytePerPixelDETY set to 8.0/3. But it was accidentally placed in the wrong group that sets it to 4, so the correct value was never used. This caused wrong DET buffer size and bandwidth calculations whenever this format was used. Fix it by moving dml2_422_packed_10 out of the wrong group so it gets the correct value of 8.0/3. Fixes: 7f7d7ea1fa51 ("drm/amd/display: Add new sources for DCN6") Reported-by: Dan Carpenter Cc: Roman Li Cc: Alex Hung Cc: Tom Chung Cc: Aurabindo Pillai Signed-off-by: Srinivasan Shanmugam Reviewed-by: George Zhang Signed-off-by: Alex Deucher --- .../dml2_0/dml21/src/dml2_core/dml2_core_dcn5_calcs_dchub.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn5_calcs_dchub.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn5_calcs_dchub.c index 05a99c4f761b..38ccf9dab31f 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn5_calcs_dchub.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn5_calcs_dchub.c @@ -74,7 +74,9 @@ void dcn5_calculate_byte_per_pixel_and_block_sizes( *BytePerPixelDETC = 0; *BytePerPixelY = 8; *BytePerPixelC = 0; - } else if (SourcePixelFormat == dml2_444_32 || SourcePixelFormat == dml2_rgbe || SourcePixelFormat == dml2_422_packed_10 || SourcePixelFormat == dml2_422_packed_12) { + } else if (SourcePixelFormat == dml2_444_32 || + SourcePixelFormat == dml2_rgbe || + SourcePixelFormat == dml2_422_packed_12) { *BytePerPixelDETY = 4; *BytePerPixelDETC = 0; *BytePerPixelY = 4; From 84298acf1c8be2b1b03c0339bf1eb63102c51728 Mon Sep 17 00:00:00 2001 From: Srinivasan Shanmugam Date: Thu, 20 Aug 2026 15:46:01 +0530 Subject: [PATCH 413/641] drm/amd/display: Fix redundant GPUVMEnable checks in dcn6 flip schedule Inside dcn6_calculate_flip_schedule(), GPUVMEnable is already checked in the outer if block. But the same GPUVMEnable is checked again in two inner if blocks inside it. Since GPUVMEnable is always true at that point, the inner else branches that assign meta_row_height are never reached. Remove the redundant inner GPUVMEnable checks and directly assign dpte_row_height, which is always the correct value here. Fixes: 7f7d7ea1fa51 ("drm/amd/display: Add new sources for DCN6") Reported-by: Dan Carpenter Cc: Roman Li Cc: Alex Hung Cc: Tom Chung Cc: Aurabindo Pillai Signed-off-by: Srinivasan Shanmugam Reviewed-by: George Zhang Signed-off-by: Alex Deucher --- .../src/dml2_core/dml2_core_dcn6_calcs_dchub.c | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn6_calcs_dchub.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn6_calcs_dchub.c index cae6bee93fe3..a4e2f8604650 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn6_calcs_dchub.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn6_calcs_dchub.c @@ -569,20 +569,11 @@ void dcn6_calculate_flip_schedule( if (GPUVMEnable) { if (l->dual_plane) { - if (GPUVMEnable) { - l->min_row_height = dpte_row_height; - l->min_row_height_chroma = dpte_row_height_chroma; - } else { - l->min_row_height = meta_row_height; - l->min_row_height_chroma = meta_row_height_chroma; - } + l->min_row_height = dpte_row_height; + l->min_row_height_chroma = dpte_row_height_chroma; l->min_row_time = math_min2(l->min_row_height * LineTime / VRatio, l->min_row_height_chroma * LineTime / VRatioChroma); } else { - if (GPUVMEnable) - l->min_row_height = dpte_row_height; - else - l->min_row_height = meta_row_height; - + l->min_row_height = dpte_row_height; l->min_row_time = l->min_row_height * LineTime / VRatio; } DML_LOG_VERBOSE("DML::%s: min_row_time = %f\n", __func__, l->min_row_time); From 92a9eebd2a1f892fe482154d83f9f1626bc73d3b Mon Sep 17 00:00:00 2001 From: Linkai Gong Date: Wed, 19 Aug 2026 13:47:42 +0800 Subject: [PATCH 414/641] drm/amd/display: fix dc_lock leak on GPU reset error paths On GPU reset, dm_suspend() takes dc_lock and leaves it for dm_resume() to drop. If amdgpu_dm_commit_zero_streams() or dm_dmub_hw_init() fails, the function returns with the lock still held. The matching resume path is then skipped, so every later dc_lock take hangs. Release the cached DC state and unlock before returning the error. Fixes: 3cf7a0bc87f0 ("drm/amd/display: Catch failures for amdgpu_dm_commit_zero_streams()") Fixes: 2b6943df5413 ("drm/amd/display: Pass up errors for reset GPU that fails to init HW") Cc: stable@vger.kernel.org Signed-off-by: Linkai Gong Reviewed-by: Mario Limonciello Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 284aac4d96bc..ec483276d753 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -1589,6 +1589,9 @@ static int dm_suspend(struct amdgpu_ip_block *ip_block) res = amdgpu_dm_commit_zero_streams(dm->dc); if (res != DC_OK) { drm_err(adev_to_drm(adev), "Failed to commit zero streams: %d\n", res); + dc_state_release(dm->cached_dc_state); + dm->cached_dc_state = NULL; + mutex_unlock(&dm->dc_lock); return -EINVAL; } @@ -1884,6 +1887,9 @@ static int dm_resume(struct amdgpu_ip_block *ip_block) r = dm_dmub_hw_init(adev); if (r) { drm_err(adev_to_drm(adev), "DMUB interface failed to initialize: status=%d\n", r); + dc_state_release(dm->cached_dc_state); + dm->cached_dc_state = NULL; + mutex_unlock(&dm->dc_lock); return r; } From 05ec76cfbce653e07cec19b9b8b20e33449d5d87 Mon Sep 17 00:00:00 2001 From: Yifei Gao Date: Tue, 25 Aug 2026 23:46:29 +0000 Subject: [PATCH 415/641] ipmi: Fix use-after-free of cmd_rcvr in _ipmi_destroy_user() Commit 9e91f8a6c868 ("ipmi:msghandler: Remove srcu for the ipmi_interfaces list") dropped the synchronize_rcu() between unlinking the command receivers from intf->cmd_rcvrs and freeing them, updating only the comment that explains why the barrier is needed. The cmd_rcvrs list is still traversed under plain RCU: find_cmd_rcvr() walks it inside rcu_read_lock(), and handle_ipmb_get_msg_cmd() borrows rcvr->user from that lookup within the same read-side section. Without the grace period, _ipmi_destroy_user() can kfree() a cmd_rcvr while a reader still holds a pointer to it, causing a use-after-free. The rework only made srcu unnecessary for the interfaces list; the cmd_rcvrs list still relies on plain RCU. Restore the synchronize_rcu() before freeing the receivers. Fixes: 9e91f8a6c868 ("ipmi:msghandler: Remove srcu for the ipmi_interfaces list") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Yifei Gao Message-ID: <20260825234630.1196170-1-gyf161023@gmail.com> Signed-off-by: Corey Minyard --- drivers/char/ipmi/ipmi_msghandler.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/char/ipmi/ipmi_msghandler.c b/drivers/char/ipmi/ipmi_msghandler.c index c73e9def59a6..152dd6b5bb27 100644 --- a/drivers/char/ipmi/ipmi_msghandler.c +++ b/drivers/char/ipmi/ipmi_msghandler.c @@ -1391,6 +1391,7 @@ static void _ipmi_destroy_user(struct ipmi_user *user) } } mutex_unlock(&intf->cmd_rcvrs_mutex); + synchronize_rcu(); while (rcvrs) { rcvr = rcvrs; rcvrs = rcvr->next; From ee440d4fc0d2f15894ab1f64c474a3adbc858880 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Jean?= Date: Sat, 15 Aug 2026 10:09:18 +0000 Subject: [PATCH 416/641] crypto: acomp - allocate async request context when cloning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ACOMP_REQUEST_ON_STACK() reserves only enough storage for the synchronous fallback. When an async implementation is selected, callers clone that stack request before retrying, but acomp_request_clone() currently copies only the stack-sized object. The clone therefore has no storage for the async provider request context, and providers such as QAT write past the allocation through acomp_request_ctx(). KASAN does report a slab OOB write. Allocate a zeroed clone large enough for the runtime acomp request size, copy only the bytes present in the source object, and preserve the existing fallback-on-allocation-failure behavior. Use the runtime reqsize because an implementation may adjust it during tfm initialization. Fixes: 097c432caaa6 ("crypto: acomp - Add ACOMP_REQUEST_CLONE") Assisted-by: Codex:gpt-5 Signed-off-by: Jérémy Jean Signed-off-by: Herbert Xu --- crypto/acompress.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/crypto/acompress.c b/crypto/acompress.c index 032de704eb2c..4de1a2ad577f 100644 --- a/crypto/acompress.c +++ b/crypto/acompress.c @@ -559,12 +559,22 @@ EXPORT_SYMBOL_GPL(acomp_walk_virt); struct acomp_req *acomp_request_clone(struct acomp_req *req, size_t total, gfp_t gfp) { + struct crypto_tfm *tfm = req->base.tfm; struct acomp_req *nreq; + size_t len; - nreq = container_of(crypto_request_clone(&req->base, total, gfp), - struct acomp_req, base); - if (nreq == req) + len = sizeof(*req) + + crypto_acomp_reqsize(crypto_acomp_reqtfm(req)); + len = ALIGN(len, CRYPTO_MINALIGN); + + nreq = kzalloc(len, gfp); + if (!nreq) { + req->base.tfm = tfm->fb; return req; + } + + memcpy(nreq, req, sizeof(*req)); + nreq->base.flags &= ~CRYPTO_TFM_REQ_ON_STACK; if (req->src == &req->chain.ssg) nreq->src = &nreq->chain.ssg; From 24c1a47f1ed28f8b31db9a36eb26d1ffbd089d7a Mon Sep 17 00:00:00 2001 From: Jaegeuk Kim Date: Tue, 18 Aug 2026 19:37:23 +0000 Subject: [PATCH 417/641] f2fs: don't leave the hashed inode while it's unlinked f2fs_symlink() 1. f2fs_new_inode 2. f2fs_add_link 3. write_being|end to fill the symlink path 4. flush dirty pages and or checkpoint Step 4 is nice to succeed, which doesn't become a reason to roll back the created symlink. OTOH, if we get an error till step 3, don't leave its dentry and its inode. Reviewed-by: Chao Yu Reviewed-by: Wenjie Qi Signed-off-by: Jaegeuk Kim --- fs/f2fs/f2fs.h | 3 +- fs/f2fs/inode.c | 6 ++- fs/f2fs/namei.c | 118 +++++++++++++++++++++++++----------------------- 3 files changed, 69 insertions(+), 58 deletions(-) diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index a1f5f375045a..b0a9c14de595 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -3878,7 +3878,8 @@ void f2fs_update_inode_page(struct inode *inode); int f2fs_write_inode(struct inode *inode, struct writeback_control *wbc); void f2fs_remove_donate_inode(struct inode *inode); void f2fs_evict_inode(struct inode *inode); -void f2fs_handle_failed_inode(struct inode *inode, struct f2fs_lock_context *lc); +void f2fs_handle_failed_inode(struct inode *inode, + struct f2fs_lock_context *lc, bool add_orphan); int f2fs_init_evict_inode_work(void); void f2fs_destroy_evict_inode_work(void); diff --git a/fs/f2fs/inode.c b/fs/f2fs/inode.c index bac1e360d966..96cc0e777567 100644 --- a/fs/f2fs/inode.c +++ b/fs/f2fs/inode.c @@ -1107,7 +1107,8 @@ clear_out: } /* caller should call f2fs_lock_op() */ -void f2fs_handle_failed_inode(struct inode *inode, struct f2fs_lock_context *lc) +void f2fs_handle_failed_inode(struct inode *inode, + struct f2fs_lock_context *lc, bool orphan_free) { struct f2fs_sb_info *sbi = F2FS_I_SB(inode); struct node_info ni; @@ -1129,6 +1130,9 @@ void f2fs_handle_failed_inode(struct inode *inode, struct f2fs_lock_context *lc) /* don't make bad inode, since it becomes a regular file. */ unlock_new_inode(inode); + if (!orphan_free) + goto out; + /* * Note: we should add inode to orphan list before f2fs_unlock_op() * so we can prevent losing this orphan when encoutering checkpoint diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c index 7d9f8e92de06..afaab7739283 100644 --- a/fs/f2fs/namei.c +++ b/fs/f2fs/namei.c @@ -411,7 +411,7 @@ static int f2fs_create(struct mnt_idmap *idmap, struct inode *dir, f2fs_balance_fs(sbi, true); return 0; out: - f2fs_handle_failed_inode(inode, &lc); + f2fs_handle_failed_inode(inode, &lc, true); return err; } @@ -566,40 +566,31 @@ out: return ERR_PTR(err); } -static int f2fs_unlink(struct inode *dir, struct dentry *dentry) +static int __do_unlink(struct inode *dir, struct inode *inode, + const struct qstr *name) { struct f2fs_sb_info *sbi = F2FS_I_SB(dir); - struct inode *inode = d_inode(dentry); struct f2fs_dir_entry *de; struct f2fs_lock_context lc; struct folio *folio; int err; - trace_f2fs_unlink_enter(dir, dentry); + if (IS_DEVICE_ALIASING(inode)) + return -EPERM; - if (IS_DEVICE_ALIASING(inode)) { - err = -EPERM; - goto out; - } - - if (unlikely(f2fs_cp_error(sbi))) { - err = -EIO; - goto out; - } + if (unlikely(f2fs_cp_error(sbi))) + return -EIO; err = f2fs_dquot_initialize(dir); if (err) - goto out; + return err; err = f2fs_dquot_initialize(inode); if (err) - goto out; + return err; - de = f2fs_find_entry(dir, &dentry->d_name, &folio); - if (!de) { - if (IS_ERR(folio)) - err = PTR_ERR(folio); - goto out; - } + de = f2fs_find_entry(dir, name, &folio); + if (!de) + return IS_ERR(folio) ? PTR_ERR(folio) : 0; if (unlikely(inode->i_nlink == 0)) { f2fs_warn(sbi, "%s: inode (ino=%llx) has zero i_nlink", @@ -617,11 +608,28 @@ static int f2fs_unlink(struct inode *dir, struct dentry *dentry) err = f2fs_acquire_orphan_inode(sbi); if (err) { f2fs_unlock_op(sbi, &lc); - f2fs_folio_put(folio, false); - goto out; + goto err_out; } f2fs_delete_entry(de, folio, dir, inode); f2fs_unlock_op(sbi, &lc); + return 0; + +corrupted: + err = -EFSCORRUPTED; + set_sbi_flag(sbi, SBI_NEED_FSCK); +err_out: + f2fs_folio_put(folio, false); + return err; +} + +static int f2fs_unlink(struct inode *dir, struct dentry *dentry) +{ + int err; + + trace_f2fs_unlink_enter(dir, dentry); + err = __do_unlink(dir, d_inode(dentry), &dentry->d_name); + if (err) + goto out; /* VFS negative dentries are incompatible with Encoding and * Case-insensitiveness. Eventually we'll want avoid @@ -632,19 +640,10 @@ static int f2fs_unlink(struct inode *dir, struct dentry *dentry) if (IS_ENABLED(CONFIG_UNICODE) && IS_CASEFOLDED(dir)) d_invalidate(dentry); - if (IS_DIRSYNC(dir)) { - err = f2fs_sync_fs(sbi->sb, 1); - if (err) - goto out; - } - - goto out; -corrupted: - err = -EFSCORRUPTED; - set_sbi_flag(sbi, SBI_NEED_FSCK); - f2fs_folio_put(folio, false); + if (IS_DIRSYNC(dir)) + err = f2fs_sync_fs(F2FS_I_SB(dir)->sb, 1); out: - trace_f2fs_unlink_exit(inode, err); + trace_f2fs_unlink_exit(d_inode(dentry), err); return err; } @@ -671,6 +670,8 @@ static int f2fs_symlink(struct mnt_idmap *idmap, struct inode *dir, struct inode *inode; size_t len = strlen(symname); struct fscrypt_str disk_link; + bool orphan_free = true; + int ret = -EAGAIN; int err; if (unlikely(f2fs_cp_error(sbi))) @@ -701,17 +702,19 @@ static int f2fs_symlink(struct mnt_idmap *idmap, struct inode *dir, f2fs_lock_op(sbi, &lc); err = f2fs_add_link(dentry, inode); if (err) - goto out_f2fs_handle_failed_inode; + goto free_inode; f2fs_unlock_op(sbi, &lc); f2fs_alloc_nid_done(sbi, inode->i_ino); + /* Write the symlink path to the new inode. */ err = fscrypt_encrypt_symlink(inode, symname, len, &disk_link); if (err) - goto err_out; + goto unlink_free_inode; err = page_symlink(inode, disk_link.name, disk_link.len); + if (err) + goto unlink_free_inode; -err_out: d_instantiate_new(dentry, inode); /* @@ -723,26 +726,29 @@ err_out: * If the symlink path is stored into inline_data, there is no * performance regression. */ - if (!err) { - err = filemap_write_and_wait_range(inode->i_mapping, 0, - disk_link.len - 1); - - if (!err && IS_DIRSYNC(dir)) - err = f2fs_sync_fs(sbi->sb, 1); - } - - if (err) - f2fs_unlink(dir, dentry); + ret = filemap_write_and_wait_range(inode->i_mapping, 0, + disk_link.len - 1); + if (!ret && IS_DIRSYNC(dir)) + err = f2fs_sync_fs(sbi->sb, 1); f2fs_balance_fs(sbi, true); - goto out_free_encrypted_link; - -out_f2fs_handle_failed_inode: - f2fs_handle_failed_inode(inode, &lc); -out_free_encrypted_link: +out: if (disk_link.name != (unsigned char *)symname) kfree(disk_link.name); return err; + +unlink_free_inode: + ret = __do_unlink(dir, inode, &dentry->d_name); + if (ret) { + /* Give up and leave a broken symlink. */ + d_instantiate_new(dentry, inode); + goto out; + } + orphan_free = false; + f2fs_lock_op(sbi, &lc); +free_inode: + f2fs_handle_failed_inode(inode, &lc, orphan_free); + goto out; } static struct dentry *f2fs_mkdir(struct mnt_idmap *idmap, struct inode *dir, @@ -791,7 +797,7 @@ static struct dentry *f2fs_mkdir(struct mnt_idmap *idmap, struct inode *dir, out_fail: clear_inode_flag(inode, FI_INC_LINK); - f2fs_handle_failed_inode(inode, &lc); + f2fs_handle_failed_inode(inode, &lc, true); return ERR_PTR(err); } @@ -847,7 +853,7 @@ static int f2fs_mknod(struct mnt_idmap *idmap, struct inode *dir, f2fs_balance_fs(sbi, true); return 0; out: - f2fs_handle_failed_inode(inode, &lc); + f2fs_handle_failed_inode(inode, &lc, true); return err; } @@ -918,7 +924,7 @@ static int __f2fs_tmpfile(struct mnt_idmap *idmap, struct inode *dir, release_out: f2fs_release_orphan_inode(sbi); out: - f2fs_handle_failed_inode(inode, &lc); + f2fs_handle_failed_inode(inode, &lc, true); return err; } From 3d3de2aee17d1431694aa085039479b5679e5ad4 Mon Sep 17 00:00:00 2001 From: Baolin Liu Date: Fri, 21 Aug 2026 13:32:30 +0800 Subject: [PATCH 418/641] ntfs: return DT_UNKNOWN on inode lookup failure in readdir ntfs_reparse_tag_dt_types() returns PTR_ERR(vi) when ntfs_iget() fails, but its return type is unsigned int and the caller passes the value straight to dir_emit() as d_type. A stale or corrupt MFT reference in a directory index thus makes readdir report a garbage d_type value to userspace. Return DT_UNKNOWN on lookup failure instead. Fixes: fc053f05ca28 ("ntfs: add reparse and ea operations") Signed-off-by: Baolin Liu Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/reparse.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ntfs/reparse.c b/fs/ntfs/reparse.c index 5e483a2f9060..1cc6dfe19fed 100644 --- a/fs/ntfs/reparse.c +++ b/fs/ntfs/reparse.c @@ -405,7 +405,7 @@ unsigned int ntfs_reparse_tag_dt_types(struct ntfs_volume *vol, unsigned long mr vi = ntfs_iget(vol->sb, mref); if (IS_ERR(vi)) - return PTR_ERR(vi); + return DT_UNKNOWN; reparse_attr = (struct reparse_point *)ntfs_attr_readall(NTFS_I(vi), AT_REPARSE_POINT, NULL, 0, &attr_size); From 9692b1b4fc00cf89628bc43f71729ab21f14f8d3 Mon Sep 17 00:00:00 2001 From: Baolin Liu Date: Fri, 21 Aug 2026 13:32:32 +0800 Subject: [PATCH 419/641] ntfs: propagate reparse index insertion failure update_reparse_data() ignores the return value of set_reparse_index(). When index insertion fails, the code removes the just-written reparse data as cleanup but still returns 0, so symlink(2) (and WSL special file creation) reports success while no reparse data exists on disk. When there was no previous reparse data (oldsize == 0), the failure was likewise silently ignored. Propagate the error to the caller. Fixes: fc053f05ca28 ("ntfs: add reparse and ea operations") Signed-off-by: Baolin Liu Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/reparse.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fs/ntfs/reparse.c b/fs/ntfs/reparse.c index 1cc6dfe19fed..1a6073e22677 100644 --- a/fs/ntfs/reparse.c +++ b/fs/ntfs/reparse.c @@ -694,8 +694,9 @@ static int update_reparse_data(struct ntfs_inode *ni, struct ntfs_index_context goto put_rp_inode; } - if (set_reparse_index(ni, xr, ((const struct reparse_point *)value)->reparse_tag) && - oldsize > 0) { + err = set_reparse_index(ni, xr, + ((const struct reparse_point *)value)->reparse_tag); + if (err && oldsize > 0) { /* * If cannot index, try to remove the reparse * data and log the error. There will be an From ada728801999e25e610091362447372f1b25dd25 Mon Sep 17 00:00:00 2001 From: Baolin Liu Date: Fri, 21 Aug 2026 13:32:33 +0800 Subject: [PATCH 420/641] ntfs: return -ERANGE for undersized xattr buffer When the value buffer passed to getxattr(2) for system.dos_attrib, system.ntfs_attrib or system.ntfs_attrib_be is smaller than the attribute value, ntfs_getxattr() returns -ENODATA, which tells userspace the attribute does not exist. The xattr API expects -ERANGE in this case, and ntfs_get_ea() in the same file already returns -ERANGE for regular EAs. Fixes: fc053f05ca28 ("ntfs: add reparse and ea operations") Signed-off-by: Baolin Liu Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/ea.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c index cdd306933d73..f6dbdfe6ff15 100644 --- a/fs/ntfs/ea.c +++ b/fs/ntfs/ea.c @@ -615,7 +615,7 @@ static int ntfs_getxattr(const struct xattr_handler *handler, if (!buffer) { err = sizeof(u8); } else if (size < sizeof(u8)) { - err = -ENODATA; + err = -ERANGE; } else { err = sizeof(u8); *(u8 *)buffer = (u8)(le32_to_cpu(ni->flags) & 0x3F); @@ -628,7 +628,7 @@ static int ntfs_getxattr(const struct xattr_handler *handler, if (!buffer) { err = sizeof(u32); } else if (size < sizeof(u32)) { - err = -ENODATA; + err = -ERANGE; } else { err = sizeof(u32); *(u32 *)buffer = le32_to_cpu(ni->flags); From 8efe00b098b5b3618c885d2a35a5edccfbfbec7d Mon Sep 17 00:00:00 2001 From: Baolin Liu Date: Fri, 21 Aug 2026 13:32:34 +0800 Subject: [PATCH 421/641] ntfs: preserve error code in ntfs_resident_attr_record_add() ntfs_resident_attr_record_add() collapses every failure to -EIO at its put_err_out label. This defeats the resident-to-non-resident fallback in ntfs_attr_add(), which relies on seeing -ENOSPC to convert the attribute when the MFT record has no room, and also hides -EEXIST and -ENOMEM from callers. Return the actual error code. Every path reaching the label has err set to a negative errno. Fixes: 495e90fa3348 ("ntfs: update attrib operations") Signed-off-by: Baolin Liu Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/attrib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index 60264833bb63..3663259f0b7d 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -2500,7 +2500,7 @@ int ntfs_resident_attr_record_add(struct ntfs_inode *ni, __le32 type, return offset; put_err_out: ntfs_attr_put_search_ctx(ctx); - return -EIO; + return err; } /* From ba1b61ddaa764f31b14abe1d547049682cc5824e Mon Sep 17 00:00:00 2001 From: Baolin Liu Date: Fri, 21 Aug 2026 13:32:35 +0800 Subject: [PATCH 422/641] ntfs: return real error from ntfs_non_resident_attr_record_add() ntfs_non_resident_attr_record_add() returns -1 at its put_err_out label, which callers propagate as -EPERM to userspace. Return the actual error code. Every path reaching the label has err set to a negative errno. Fixes: 495e90fa3348 ("ntfs: update attrib operations") Signed-off-by: Baolin Liu Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/attrib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index 3663259f0b7d..4dff5c3f779b 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -2639,7 +2639,7 @@ static int ntfs_non_resident_attr_record_add(struct ntfs_inode *ni, __le32 type, return offset; put_err_out: ntfs_attr_put_search_ctx(ctx); - return -1; + return err; } /* From cf06dcd572845723821b54a608fc2da995c3c8e2 Mon Sep 17 00:00:00 2001 From: Baolin Liu Date: Fri, 21 Aug 2026 13:32:36 +0800 Subject: [PATCH 423/641] ntfs: fix kmap_local leak in write_mft_record_nolock() error paths write_mft_record_nolock() maps the MFT record folio with kmap_local_folio(), but the pre_write_mst_fixup() and bio_add_folio() failure paths jump to the error label without unmapping it. kmap_local mappings are stack-ordered per task, so leaking one corrupts the nesting for any outer mapping. Unmap the folio on those error paths too. Fixes: 115380f9a2f9 ("ntfs: update mft operations") Signed-off-by: Baolin Liu Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/mft.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index 984a0827f9ac..69b007e574fc 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -580,7 +580,7 @@ int write_mft_record_nolock(struct ntfs_inode *ni, struct mft_record *m, int syn err = pre_write_mst_fixup((struct ntfs_record *)fixup_m, vol->mft_record_size); if (err) { ntfs_error(vol->sb, "Failed to apply mst fixups!"); - goto err_out; + goto unmap_err_out; } folio_size = vol->mft_record_size / ni->mft_lcn_count; @@ -645,6 +645,8 @@ done: return 0; put_bio_out: bio_put(bio); +unmap_err_out: + kunmap_local(kaddr); err_out: /* * The caller should mark the base inode as bad so no more I/O From be9e89ccb8e52a3e4b67feeb03ebd8133091dc7e Mon Sep 17 00:00:00 2001 From: Baolin Liu Date: Fri, 21 Aug 2026 13:32:37 +0800 Subject: [PATCH 424/641] ntfs: only count successfully cleared runs when freeing clusters ntfs_cluster_free_from_rl_nolock() adds a run's length to nr_freed whenever the error bookkeeping condition is false, which includes cases where ntfs_bitmap_clear_run() actually failed - e.g. a second run failing with the same errno as an earlier one, or any failure after a non-ENOMEM error was already recorded. Since a failed ntfs_bitmap_clear_run() rolls back its partial modifications, no bits were cleared for that run, yet its length still inflates vol->free_clusters, corrupting statfs output and the allocator's free space gate. Only count runs whose bitmap clear succeeded. Fixes: 11ccc9107dc4 ("ntfs: update runlist handling and cluster allocator") Signed-off-by: Baolin Liu Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/lcnalloc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/ntfs/lcnalloc.c b/fs/ntfs/lcnalloc.c index aa2e017a4384..795f71d26895 100644 --- a/fs/ntfs/lcnalloc.c +++ b/fs/ntfs/lcnalloc.c @@ -53,10 +53,10 @@ int ntfs_cluster_free_from_rl_nolock(struct ntfs_volume *vol, if (rl->lcn < 0) continue; err = ntfs_bitmap_clear_run(lcnbmp_vi, rl->lcn, rl->length); - if (unlikely(err && (!ret || ret == -ENOMEM) && ret != err)) - ret = err; - else + if (likely(!err)) nr_freed += rl->length; + else if (!ret || ret == -ENOMEM) + ret = err; } ntfs_inc_free_clusters(vol, nr_freed); ntfs_debug("Done."); From 5f2a22b36fe34c98f6d5e35ddb759ee53d684145 Mon Sep 17 00:00:00 2001 From: Baolin Liu Date: Fri, 21 Aug 2026 13:32:38 +0800 Subject: [PATCH 425/641] ntfs: skip free cluster decrement when rollback fails When the rollback in __ntfs_cluster_free() fails, the recursive call returns a negative errno and the subsequent ntfs_dec_free_clusters(vol, delta) subtracts that negative value, adding bogus clusters to the counter on an already-failing volume. Skip the decrement when the rollback failed. Fixes: 11ccc9107dc4 ("ntfs: update runlist handling and cluster allocator") Signed-off-by: Baolin Liu Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/lcnalloc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/ntfs/lcnalloc.c b/fs/ntfs/lcnalloc.c index 795f71d26895..0d6cd08ee2e7 100644 --- a/fs/ntfs/lcnalloc.c +++ b/fs/ntfs/lcnalloc.c @@ -1045,8 +1045,9 @@ err_out: "Failed to rollback (error %i). Leaving inconsistent metadata! Unmount and run chkdsk.", (int)delta); NVolSetErrors(vol); + } else { + ntfs_dec_free_clusters(vol, delta); } - ntfs_dec_free_clusters(vol, delta); up_write(&vol->lcnbmp_lock); memalloc_nofs_restore(memalloc_flags); ntfs_error(vol->sb, "Aborting (error %i).", err); From 0e4c839905418d55bafe571a92533a1d1ac7b0a8 Mon Sep 17 00:00:00 2001 From: Dennis Tighe Date: Sun, 23 Aug 2026 22:08:40 -0700 Subject: [PATCH 426/641] ntfs: do not mark the volume clean in sync_fs when errors were recorded ntfs_put_super() and the remount-read-only path both clear the dirty bit only when NVolErrors(vol) is false. ntfs_sync_fs() clears it unconditionally, so any sync() on a volume that recorded an error marks that volume clean. A volume without this set is then seen as not needing recovery and it does not run one, so whatever went wrong is never repaired. This change skips resetting the dirty bit when there are volume errors. Reproduced on a volume whose $MFTMirr does not match $MFT, which sets the error flag while leaving the mount read-write: after a write and a sync, the on-disk volume flags read 0x0000 with this driver and 0x0001 with the guard in place. Fixes: 6251f0b0de7d ("ntfs: update super block operations") Assisted-by: claude:claude-opus-5 Signed-off-by: Dennis Tighe Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/super.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/ntfs/super.c b/fs/ntfs/super.c index 30481e5d5dd4..a4dc64fb89ed 100644 --- a/fs/ntfs/super.c +++ b/fs/ntfs/super.c @@ -1862,7 +1862,8 @@ static int ntfs_sync_fs(struct super_block *sb, int wait) return 0; /* If there are some dirty buffers in the bdev inode */ - if (ntfs_clear_volume_flags(vol, VOLUME_IS_DIRTY)) { + if (!NVolErrors(vol) && + ntfs_clear_volume_flags(vol, VOLUME_IS_DIRTY)) { ntfs_warning(sb, "Failed to clear dirty bit in volume information flags. Run chkdsk."); err = -EIO; } From 8d139e3635c86e2c97c78d55538ce0b00b6d9986 Mon Sep 17 00:00:00 2001 From: Hongling Zeng Date: Mon, 24 Aug 2026 13:41:15 +0800 Subject: [PATCH 427/641] ntfs: fix incorrect MFT record pointer passed to ntfs_attr_record_resize ntfs_new_attr_flags() passes the wrong MFT record to ntfs_attr_record_resize(). When the attribute is in an extent record, ctx->mrec points to the extent but the function receives the base record pointer m, causing incorrect size calculations in memmove. Fix by passing ctx->mrec (the actual MFT record containing the attribute) instead of m (the base MFT record) to ntfs_attr_record_resize(). Fixes: fc053f05ca28 ("ntfs: add reparse and ea operations") Cc: stable@vger.kernel.org Signed-off-by: Hongling Zeng Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/ea.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c index f6dbdfe6ff15..9f0222c172d9 100644 --- a/fs/ntfs/ea.c +++ b/fs/ntfs/ea.c @@ -764,7 +764,7 @@ static int ntfs_new_attr_flags(struct ntfs_inode *ni, __le32 fattr) memmove((u8 *)a + mp_ofs, (u8 *)a + old_mp_ofs, mp_size); } - err = ntfs_attr_record_resize(m, a, arec_size); + err = ntfs_attr_record_resize(ctx->mrec, a, arec_size); if (unlikely(err)) goto err_out; From 607a9478833db656e7ceac8e9e382fa4acfde545 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Tue, 25 Aug 2026 13:46:59 +0800 Subject: [PATCH 428/641] ntfs: treat any nonzero dio zero-range return as an error ntfs_dio_zero_range() returns either 0 or a negative errno from blkdev_issue_zeroout(); it never returns a positive value. The zeroing failure check in ntfs_attr_fallocate() therefore never fired, so a failed zeroing operation was silently ignored: the loop kept going, the newly allocated clusters were folded into initialized_size and the write could succeed leaving stale on-disk data. Treat any nonzero return as an error and abort the allocation. Fixes: 495e90fa33482 ("ntfs: update attrib operations") Assisted-by: atomcode:deepseek-v4-flash Signed-off-by: Wentao Guan Signed-off-by: Namjae Jeon --- fs/ntfs/attrib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index 4dff5c3f779b..c55dc47d2261 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -5704,7 +5704,7 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo lcn << vol->cluster_size_bits, alloc_cnt << vol->cluster_size_bits); - if (err > 0) + if (err) goto out; } From a79899ca38af4ce5518a59a22f0eb1f59f7e6089 Mon Sep 17 00:00:00 2001 From: Hongling Zeng Date: Tue, 25 Aug 2026 17:54:05 +0800 Subject: [PATCH 429/641] ntfs: fix undefined behavior in mft/index record size calculation The boot sector validation allows clusters_per_mft_record and clusters_per_index_record to range from 0xE1 (-31) to 0xF7 (-9) when interpreted as signed values. When these are used as negative shift counts in expressions like `1 << -clusters_per_mft_record`, values like 0xE1 cause `1 << 31`, which shifts into the sign bit of a 32-bit signed integer, resulting in undefined behavior. Fix by using unsigned shift (1U << ...) instead of signed shift. This prevents undefined behavior while preserving the full valid range of negative values (-31 to -9) that may appear in NTFS boot sectors. The encoding scheme uses negative values to represent record sizes smaller than cluster_size: -log2(record_size). Common values include -10 (1024 bytes) for mft_record_size and -12 (4096 bytes) for index_record_size. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Hongling Zeng Reviewed-by: Baolin Liu Signed-off-by: Namjae Jeon --- fs/ntfs/super.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/ntfs/super.c b/fs/ntfs/super.c index a4dc64fb89ed..2df64712335a 100644 --- a/fs/ntfs/super.c +++ b/fs/ntfs/super.c @@ -695,7 +695,7 @@ static bool parse_ntfs_boot_sector(struct ntfs_volume *vol, * = -log2(mft_record_size) bytes. mft_record_size normaly is * 1024 bytes, which is encoded as 0xF6 (-10 in decimal). */ - vol->mft_record_size = 1 << -clusters_per_mft_record; + vol->mft_record_size = 1U << -clusters_per_mft_record; vol->mft_record_size_mask = vol->mft_record_size - 1; vol->mft_record_size_bits = ffs(vol->mft_record_size) - 1; ntfs_debug("vol->mft_record_size = %i (0x%x)", vol->mft_record_size, @@ -732,7 +732,7 @@ static bool parse_ntfs_boot_sector(struct ntfs_volume *vol, * index_record_size normaly equals 4096 bytes, which is * encoded as 0xF4 (-12 in decimal). */ - vol->index_record_size = 1 << -clusters_per_index_record; + vol->index_record_size = 1U << -clusters_per_index_record; vol->index_record_size_mask = vol->index_record_size - 1; vol->index_record_size_bits = ffs(vol->index_record_size) - 1; ntfs_debug("vol->index_record_size = %i (0x%x)", From c8504fc1245f5322af5fa5c325ab05f9cf792b87 Mon Sep 17 00:00:00 2001 From: Dennis Tighe Date: Tue, 25 Aug 2026 21:44:24 -0700 Subject: [PATCH 430/641] ntfs: bound $AttrDef table walk to the loaded table size ntfs_attr_find_in_attrdef() walks the in-memory $AttrDef table, but the loop condition bounds only the start of each entry, not the whole entry: for (ad = vol->attrdef; (u8 *)ad - (u8 *)vol->attrdef < vol->attrdef_size && ad->type; ++ad) struct attr_def is 160 bytes; the guard reads ad->type at offset 128 and the loop body reads further fields. vol->attrdef is kvzalloc(i_size), where i_size is the on-disk $AttrDef data size, checked in load_and_init_attrdef() only as 0 < i_size <= 0x7fffffff. A volume whose $AttrDef data size is smaller than one entry (e.g. 120 bytes) makes the read of ad->type run past the allocation. Creating a file reaches this through ntfs_attr_size_bounds_check() and reads out of bounds: BUG: KASAN: slab-out-of-bounds in ntfs_attr_find_in_attrdef+0x66/0xa0 Read of size 4 at addr ffff888005833280 by task init/1 ntfs_attr_find_in_attrdef ntfs_attr_size_bounds_check ntfs_attr_can_be_non_resident ntfs_attr_add Require the whole entry to lie within attrdef_size in the loop guard, and reject at mount a $AttrDef too small to hold one attr_def entry. Fixes: 1e9ea7e04472 ("Revert "fs: Remove NTFS classic"") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Dennis Tighe Signed-off-by: Namjae Jeon --- fs/ntfs/attrib.c | 4 ++-- fs/ntfs/super.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index c55dc47d2261..b3e941423a3f 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -1737,8 +1737,8 @@ static struct attr_def *ntfs_attr_find_in_attrdef(const struct ntfs_volume *vol, struct attr_def *ad; WARN_ON(!type); - for (ad = vol->attrdef; (u8 *)ad - (u8 *)vol->attrdef < - vol->attrdef_size && ad->type; ++ad) { + for (ad = vol->attrdef; (u8 *)ad - (u8 *)vol->attrdef <= + vol->attrdef_size - (s32)sizeof(*ad) && ad->type; ++ad) { /* We have not found it yet, carry on searching. */ if (likely(le32_to_cpu(ad->type) < le32_to_cpu(type))) continue; diff --git a/fs/ntfs/super.c b/fs/ntfs/super.c index 2df64712335a..155dca5db7a9 100644 --- a/fs/ntfs/super.c +++ b/fs/ntfs/super.c @@ -1241,9 +1241,9 @@ static bool load_and_init_attrdef(struct ntfs_volume *vol) goto failed; } NInoSetSparseDisabled(NTFS_I(ino)); - /* The size of FILE_AttrDef must be above 0 and fit inside 31 bits. */ + /* FILE_AttrDef must hold at least one entry and fit inside 31 bits. */ i_size = i_size_read(ino); - if (i_size <= 0 || i_size > 0x7fffffff) + if (i_size < (s64)sizeof(struct attr_def) || i_size > 0x7fffffff) goto iput_failed; vol->attrdef = kvzalloc(i_size, GFP_NOFS); if (!vol->attrdef) From 323751a604e7533fa473874d999371592a614207 Mon Sep 17 00:00:00 2001 From: Dennis Tighe Date: Tue, 25 Aug 2026 22:09:34 -0700 Subject: [PATCH 431/641] ntfs: reject invalid sectors_per_cluster in the boot sector is_boot_sector_ntfs() checks the boot sector's sectors_per_cluster field with a range test that rejects 0x81..0xf3 but accepts 0 and other non-power-of-two counts. A zero value reaches parse_ntfs_boot_sector(): sectors_per_cluster_bits = ffs(sectors_per_cluster) - 1; ... vol->cluster_size = vol->sector_size << sectors_per_cluster_bits; ffs(0) is 0, so sectors_per_cluster_bits becomes (unsigned)-1 and the shift is undefined: UBSAN: shift-out-of-bounds in fs/ntfs/super.c:673:39 shift exponent 4294967295 is too large for 32-bit type 'int' This change rejects any non-power-of-two value, since it feeds the aforementioned shift via ffs() - 1, which only yields the correct shift for a power of two. Fixes: 6251f0b0de7d ("ntfs: update super block operations") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Dennis Tighe Signed-off-by: Namjae Jeon --- fs/ntfs/super.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/ntfs/super.c b/fs/ntfs/super.c index 155dca5db7a9..60d43339c590 100644 --- a/fs/ntfs/super.c +++ b/fs/ntfs/super.c @@ -557,8 +557,8 @@ static bool is_boot_sector_ntfs(const struct super_block *sb, * Check sectors per cluster value is valid and the cluster size * is not above the maximum (2MB). */ - if (b->bpb.sectors_per_cluster > 0x80 && - b->bpb.sectors_per_cluster < 0xf4) + if (b->bpb.sectors_per_cluster < 0xf4 && + !is_power_of_2(b->bpb.sectors_per_cluster)) goto not_ntfs; /* Check reserved/unused fields are really zero. */ From 84d2d96ac3f1bb467aca65adf2c19431e6eeb151 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Tue, 25 Aug 2026 15:19:49 +0700 Subject: [PATCH 432/641] ASoC: soc-generic-dmaengine: Fix DMA channel request warning The DMA channel request code currently warns about legacy DMA failures when the channel name is not present in dma-names. This can report a firmware lookup failure as a legacy DMA failure. Furthermore, failures from the legacy DMA path are already reported by find_candidate(), making these warnings redundant. Only warn when the channel name is present in dma-names but the request fails, avoiding misleading and duplicate error messages. Fixes: 9167f260477b ("ASoC: soc-generic-dmaengine: Handle DMA channel request failures correctly") Reported-by: Sebastian Reichel Link: https://lore.kernel.org/all/aoyBuho270dTWYBL@jupiter.universe/ Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260825081949.55537-1-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/soc-generic-dmaengine-pcm.c | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/sound/soc/soc-generic-dmaengine-pcm.c b/sound/soc/soc-generic-dmaengine-pcm.c index 3b18d90e81c3..65b7ec52a054 100644 --- a/sound/soc/soc-generic-dmaengine-pcm.c +++ b/sound/soc/soc-generic-dmaengine-pcm.c @@ -397,24 +397,8 @@ static int dmaengine_pcm_request_chan_of(struct dmaengine_pcm *pcm, if (PTR_ERR(chan) == -EPROBE_DEFER) return -EPROBE_DEFER; - bool has_fw_node = dev->of_node || is_acpi_device_node(dev->fwnode); - bool name_exists_in_fw = false; - - if (has_fw_node) - name_exists_in_fw = device_property_match_string(dev, - "dma-names", - name) >= 0; - - if (has_fw_node && name_exists_in_fw) - dev_warn(dev, "DTS/ACPI DMA channel '%s' request failed (%ld)\n", - name, PTR_ERR(chan)); - - if (has_fw_node && !name_exists_in_fw) - dev_warn(dev, "DTS/ACPI name '%s' not found, legacy failed (%ld)\n", - name, PTR_ERR(chan)); - - if (!has_fw_node) - dev_warn(dev, "Legacy DMA channel '%s' request failed (%ld)\n", + if (device_property_match_string(dev, "dma-names", name) >= 0) + dev_warn(dev, "dma-names has '%s' but request failed (%ld)\n", name, PTR_ERR(chan)); pcm->chan[i] = NULL; From 385c7af4e3b95d0769fd211831674e83b16a2ebf Mon Sep 17 00:00:00 2001 From: Ahmad Byagowi Date: Sun, 23 Aug 2026 09:34:36 -0700 Subject: [PATCH 433/641] i2c: mux: Fix channel node leak on adapter add failure i2c_mux_add_adapter() takes a reference to the Device Tree channel node before registering the new adapter. If adapter registration fails, the error path frees the private data without dropping that reference. Release the channel node before freeing the private data. Fixes: bc45449b1444 ("i2c/of: Automatically populate i2c mux busses from device tree data.") Signed-off-by: Ahmad Byagowi Cc: # v3.5+ Acked-by: Peter Rosin Signed-off-by: Andi Shyti Link: https://patch.msgid.link/b3e46bbee781b3cb4029aca9a71316cc5e36dc17.1787502619.git.ahmadexp@gmail.com --- drivers/i2c/i2c-mux.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/i2c/i2c-mux.c b/drivers/i2c/i2c-mux.c index 681a201c239b..68a4c34b5987 100644 --- a/drivers/i2c/i2c-mux.c +++ b/drivers/i2c/i2c-mux.c @@ -408,6 +408,7 @@ int i2c_mux_add_adapter(struct i2c_mux_core *muxc, return 0; err_free_priv: + of_node_put(priv->adap.dev.of_node); kfree(priv); return ret; } From 032bb633e0c34a88b70a4bfc65ebd4e2b510f706 Mon Sep 17 00:00:00 2001 From: Mehmet Aysel Date: Tue, 25 Aug 2026 12:24:31 +0300 Subject: [PATCH 434/641] ASoC: amd: acp-config: Add HVY-WXX9/M1060 DMI quirk Add DMI match table entry for HUAWEI HVY-WXX9 board, product version M1060, a MateBook D16 2021 (Ryzen 5 4600H) revision not covered by the existing M1010/M1020/M1040 entries. This board uses the same FLAG_AMD_LEGACY / ACP_PCI_DEV_ID configuration as the other HVY-WXX9 variants. Signed-off-by: Mehmet Aysel Link: https://patch.msgid.link/20260825092432.56292-1-mehmet4ysel@gmail.com Signed-off-by: Mark Brown --- sound/soc/amd/acp-config.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c index 3021dc498c85..ef6fa3cca848 100644 --- a/sound/soc/amd/acp-config.c +++ b/sound/soc/amd/acp-config.c @@ -209,6 +209,20 @@ static const struct config_entry config_table[] = { {} }, }, + { + .flags = FLAG_AMD_LEGACY, + .device = ACP_PCI_DEV_ID, + .dmi_table = (const struct dmi_system_id []) { + { + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HVY-WXX9"), + DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1060"), + }, + }, + {} + }, + }, }; static int snd_amd_acp_acpi_find_config(struct pci_dev *pci) From c1a39c228aceebc0ede83781ea906d257e324009 Mon Sep 17 00:00:00 2001 From: Mehmet Aysel Date: Tue, 25 Aug 2026 12:24:32 +0300 Subject: [PATCH 435/641] ASoC: amd: acp3x-es83xx: Add HVY-WXX9/M1060 DMI quirk Add matching DMI table entry for the ES83xx machine driver, so the HUAWEI HVY-WXX9 / M1060 board (MateBook D16 2021, Ryzen 5 4600H) can successfully probe its ES8316 codec via the acp3x-es83xx machine driver, consistent with the existing M1010/M1020/M1040 entries for the same board name. Signed-off-by: Mehmet Aysel Link: https://patch.msgid.link/20260825092432.56292-2-mehmet4ysel@gmail.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c b/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c index 46fff565d732..3a640e652314 100644 --- a/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c +++ b/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c @@ -382,6 +382,14 @@ static const struct dmi_system_id acp3x_es83xx_dmi_table[] = { }, .driver_data = (void *)(ES83XX_ENABLE_DMIC), }, + { + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HVY-WXX9"), + DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1060"), + }, + .driver_data = (void *)(ES83XX_ENABLE_DMIC), + }, {} }; From 4d6642d64cd00f276549ec2c0b0278aea2f8c1aa Mon Sep 17 00:00:00 2001 From: Sehat Mahde Date: Tue, 25 Aug 2026 17:46:40 -0500 Subject: [PATCH 436/641] ASoC: amd: acp-config: force SoundWire probe on HP OmniBook X Flip 16 The BIOS on the HP OmniBook X Flip 16-cc0xxx (board 8EA2) reports acp-audio-config-flag = FLAG_AMD_LEGACY_ONLY_DMIC. This binds the legacy ACP driver and registers a PDM-only card, so the SoundWire links are never scanned and the two TAS2783 speaker amplifiers and RT712-VB codec do not enumerate. Add a DMI entry for board 8EA2 to the ACP70 ACPI flag override table so the firmware-provided flag is overridden and snd_pci_ps probes instead. On the affected system, an otherwise identical upstream kernel without this entry binds snd_acp_pci, enumerates no SoundWire slave devices and exposes no internal speaker PCM. With the entry added, snd_pci_ps binds, both TAS2783 amplifiers and the RT712-VB enumerate over SoundWire, and the amd-soundwire card exposes the internal speaker playback PCM. Developed with AI assistance. ChatGPT helped analyze the ACP and SoundWire behavior, structure the controlled A/B testing, and draft the patch changelog. All hardware measurements, kernel builds, reboots and playback tests were performed by the submitter. The submitter has reviewed the change, understands it and takes responsibility for it. Assisted-by: ChatGPT:GPT-5.6 Sol Signed-off-by: Sehat Mahde Link: https://patch.msgid.link/20260825224640.13662-1-hskmahde@gmail.com Signed-off-by: Mark Brown --- sound/soc/amd/acp-config.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c index ef6fa3cca848..a27336b132f2 100644 --- a/sound/soc/amd/acp-config.c +++ b/sound/soc/amd/acp-config.c @@ -70,6 +70,13 @@ static const struct dmi_system_id acp70_acpi_flag_override_table[] = { DMI_MATCH(DMI_BOARD_NAME, "8EA1"), }, }, + { + /* HP OmniBook X Flip 16-cc0xxx */ + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "HP"), + DMI_MATCH(DMI_BOARD_NAME, "8EA2"), + }, + }, {} }; From 14511c9b54ceeeef487409d73947c89ee8563590 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Tue, 25 Aug 2026 21:57:45 +0900 Subject: [PATCH 437/641] ASoC: dapm: Fix off-by-one check on the second enum channel The snd_soc_dapm_put_enum_double() rejects item[0] once it reaches e->items, but it lets item[1] be equal to it. Both go on to snd_soc_enum_item_to_val(), which indexes e->values with no bound of its own, so an enum with a value table reads one element past the end. The indexing arrived with the MUX consolidation, which relaxed the item[1] check in the same hunk. The value MUX handler it deleted used >= there, and the snd_soc_put_enum_double() in soc-ops.c still does. Only adav80x pairs a value table with two shifts, and its second channel looks accidental, but the control does report two values. Writing three into it reads off the end of adav80x_mux_values. The core catches that only under CONFIG_SND_CTL_INPUT_VALIDATION, which defaults off. Fixes: 3727b4968453 ("ASoC: dapm: Consolidate MUXs and value MUXs") Assisted-by: Claude:claude-opus-5 Signed-off-by: HyeongJun An Link: https://patch.msgid.link/20260825125745.932832-1-sammiee5311@gmail.com Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index c98e917cc911..06b85313af12 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -3614,7 +3614,7 @@ int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol, val = snd_soc_enum_item_to_val(e, item[0]) << e->shift_l; mask = e->mask << e->shift_l; if (e->shift_l != e->shift_r) { - if (item[1] > e->items) + if (item[1] >= e->items) return -EINVAL; val |= snd_soc_enum_item_to_val(e, item[1]) << e->shift_r; mask |= e->mask << e->shift_r; From 9642a5e843758686e02a7f0d1df9eb0f7f96603c Mon Sep 17 00:00:00 2001 From: Bob Song Date: Wed, 26 Aug 2026 19:53:44 +0800 Subject: [PATCH 438/641] ALSA: hda/conexant: Always enable the headset-mic pin on plugin Rename the function to cx_process_headset_detect_plug_type() to reflect that it only reports the detected plug type, and move the pin control write into cx_update_headset_mic_vref() so that node 0x19 is always set to enable the headset mic with the 80% VREF whenever the mic is present, regardless of the type detection result. Signed-off-by: Bob Song Link: https://patch.msgid.link/20260826115344.2128835-1-songxiebing@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/conexant.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c index 8e950c796209..7357dc91ac49 100644 --- a/sound/hda/codecs/conexant.c +++ b/sound/hda/codecs/conexant.c @@ -207,7 +207,7 @@ static void cx_remove(struct hda_codec *codec) snd_hda_gen_remove(codec); } -static void cx_process_headset_plugin(struct hda_codec *codec) +static void cx_process_headset_detect_plug_type(struct hda_codec *codec) { unsigned int val; unsigned int count = 0; @@ -241,10 +241,12 @@ static void cx_update_headset_mic_vref(struct hda_codec *codec, struct hda_jack_ * Check hp&mic tag to process headset plugin & plugout. */ mic_present = snd_hda_codec_read(codec, 0x19, 0, AC_VERB_GET_PIN_SENSE, 0x0); - if (!(mic_present & AC_PINSENSE_PRESENCE)) /* mic plugout */ + if (!(mic_present & AC_PINSENSE_PRESENCE)) { /* mic plugout */ snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20); - else - cx_process_headset_plugin(codec); + } else { + cx_process_headset_detect_plug_type(codec); + snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24); + } } static int cx_suspend(struct hda_codec *codec) From 4f49c3f8a5a86d237bb202ecb17c2802ddc8fd2f Mon Sep 17 00:00:00 2001 From: Hongling Zeng Date: Thu, 4 Jun 2026 15:43:25 +0800 Subject: [PATCH 439/641] ceph: Fix ERR_PTR(0) in ceph_mkdir() When mkdir succeeds, ceph_mkdir() sets ret to ERR_PTR(0) which is incorrect. It should return NULL instead for success. Fixes: 88d5baf69082 ("Change inode_operations.mkdir to return struct dentry *") Signed-off-by: Hongling Zeng Reviewed-by: Viacheslav Dubeyko Signed-off-by: Ilya Dryomov --- fs/ceph/dir.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c index ef9e92e362d3..b4b541a1180c 100644 --- a/fs/ceph/dir.c +++ b/fs/ceph/dir.c @@ -1174,7 +1174,7 @@ static struct dentry *ceph_mkdir(struct mnt_idmap *idmap, struct inode *dir, !req->r_reply_info.head->is_target && !req->r_reply_info.head->is_dentry) err = ceph_handle_notrace_create(dir, dentry); - ret = ERR_PTR(err); + ret = err ? ERR_PTR(err) : NULL; out_req: if (!IS_ERR(ret) && req->r_dentry != dentry) /* Some other dentry was spliced in */ From 888d33b208bd6929808abdc0728e3e5f744b60dc Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Fri, 29 May 2026 20:06:45 -0700 Subject: [PATCH 440/641] ceph: pass fscrypt `tname` buffers directly ceph_fname_to_usr() needs a temporary buffer for some operations (currently only base64-decoding ciphertext) and it is convenient to allow the caller to specify this buffer to avoid a heap allocation, so it has a (nullable) `tname` argument. Until now, this argument was a `struct fscrypt_str`; however, this is unnecessary for two reasons: 1. `tname->len` isn't used anywhere: ceph_fname_to_usr() assumes a buffer large enough to hold the ciphertext, and parse_reply_info_readdir() -- the only caller to use tname -- doesn't set it. 2. While the `tname` parameter is documented "may be NULL," parse_reply_info_readdir() always passes it but with `tname->name` sometimes NULL in violation of the contract, indicating that the unnecessary container creates actual confusion. Therefore, change the type to `unsigned char *` and pass the buffer directly. Signed-off-by: Sam Edwards Reviewed-by: Alex Markuze Signed-off-by: Ilya Dryomov --- fs/ceph/crypto.c | 9 ++++----- fs/ceph/crypto.h | 4 ++-- fs/ceph/mds_client.c | 6 +++--- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/fs/ceph/crypto.c b/fs/ceph/crypto.c index 64d240759277..7493a3acd7d0 100644 --- a/fs/ceph/crypto.c +++ b/fs/ceph/crypto.c @@ -300,7 +300,7 @@ out: * * Returns 0 on success or negative error code on error. */ -int ceph_fname_to_usr(const struct ceph_fname *fname, struct fscrypt_str *tname, +int ceph_fname_to_usr(const struct ceph_fname *fname, unsigned char *tname, struct fscrypt_str *oname, bool *is_nokey) { struct inode *dir = fname->dir; @@ -357,16 +357,15 @@ int ceph_fname_to_usr(const struct ceph_fname *fname, struct fscrypt_str *tname, ret = fscrypt_fname_alloc_buffer(NAME_MAX, &_tname); if (ret) goto out_inode; - tname = &_tname; + tname = _tname.name; } - declen = base64_decode(name, name_len, - tname->name, false, BASE64_IMAP); + declen = base64_decode(name, name_len, tname, false, BASE64_IMAP); if (declen <= 0) { ret = -EIO; goto out; } - iname.name = tname->name; + iname.name = tname; iname.len = declen; } else { iname.name = fname->ctext; diff --git a/fs/ceph/crypto.h b/fs/ceph/crypto.h index b748e2060bc9..79cb563fd887 100644 --- a/fs/ceph/crypto.h +++ b/fs/ceph/crypto.h @@ -115,7 +115,7 @@ static inline void ceph_fname_free_buffer(struct inode *parent, fscrypt_fname_free_buffer(fname); } -int ceph_fname_to_usr(const struct ceph_fname *fname, struct fscrypt_str *tname, +int ceph_fname_to_usr(const struct ceph_fname *fname, unsigned char *tname, struct fscrypt_str *oname, bool *is_nokey); int ceph_fscrypt_prepare_readdir(struct inode *dir); @@ -204,7 +204,7 @@ static inline void ceph_fname_free_buffer(struct inode *parent, } static inline int ceph_fname_to_usr(const struct ceph_fname *fname, - struct fscrypt_str *tname, + unsigned char *tname, struct fscrypt_str *oname, bool *is_nokey) { oname->name = fname->name; diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c index 3c692ad02c85..80c72f295bcb 100644 --- a/fs/ceph/mds_client.c +++ b/fs/ceph/mds_client.c @@ -491,11 +491,11 @@ static int parse_reply_info_readdir(void **p, void *end, struct inode *inode = d_inode(req->r_dentry); struct ceph_inode_info *ci = ceph_inode(inode); struct ceph_mds_reply_dir_entry *rde = info->dir_entries + i; - struct fscrypt_str tname = FSTR_INIT(NULL, 0); struct fscrypt_str oname = FSTR_INIT(NULL, 0); struct ceph_fname fname; u32 altname_len, _name_len; u8 *altname, *_name; + u8 *tname = NULL; /* dentry */ ceph_decode_32_safe(p, end, _name_len, bad); @@ -543,7 +543,7 @@ static int parse_reply_info_readdir(void **p, void *end, * always be shorter, which is 3/4 of origin * string. */ - tname.name = _name; + tname = _name; /* * Set oname to _name too, and this will be @@ -560,7 +560,7 @@ static int parse_reply_info_readdir(void **p, void *end, oname.len = altname_len; } rde->is_nokey = false; - err = ceph_fname_to_usr(&fname, &tname, &oname, &rde->is_nokey); + err = ceph_fname_to_usr(&fname, tname, &oname, &rde->is_nokey); if (err) { pr_err_client(cl, "unable to decode %.*s, got %d\n", _name_len, _name, err); From e939fc6a7bd969a58a150b7f188c1047138403e3 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Fri, 29 May 2026 20:06:46 -0700 Subject: [PATCH 441/641] ceph: properly decrypt filenames in vmalloc() buffers The fscrypt subsystem uses the scatterlist crypto API, inheriting its requirement that any buffers are in the linear mapping region. However, the messenger client uses kvmalloc() to create buffers for messages, which will occasionally place those buffers in the vmalloc() region when physical memory fragmentation doesn't permit a large enough kmalloc(). The various callers of ceph_fname_to_usr() directly pass (slices of) raw messages from the MDS without considering that the messages may be in vmalloc() buffers, resulting in oopses especially on non-x86 platforms (see 'Closes:' for more details and a reproducer). Make ceph_fname_to_usr() explicitly tolerant of vmalloc()-allocated fname->ctext, fname->name, and/or oname->name buffers, using `tname` (which, when non-null, must be a linear address; when null, is briefly allocated as necessary) as a bounce buffer to avoid passing any inappropriate addresses to fscrypt_fname_disk_to_usr(). Additionally change parse_reply_info_readdir() -- the only function to supply its own `tname` -- to follow the new "tname must never come from vmalloc()" rule by passing NULL when the message is not in the linear region. Though this causes a per-dentry kmalloc()+kfree(), this overhead exists only when processing the minority of messages that spill into vmalloc(). My (crude) testing puts this at only about 1 in 8,000 readdir messages. Still, if the overhead proves unreasonable in the future, it is easy enough to mitigate: a future change could allocate a bounce buffer in parse_reply_info_readdir() and use that as `tname` instead. Cc: stable@vger.kernel.org # 888d33b208bd: ceph: pass fscrypt `tname` buffers directly Cc: stable@vger.kernel.org Fixes: 457117f077c6 ("ceph: add helpers for converting names for userland presentation") Closes: https://lore.kernel.org/ceph-devel/20260415034020.11530-1-CFSworks@gmail.com/ Signed-off-by: Sam Edwards Reviewed-by: Alex Markuze Signed-off-by: Ilya Dryomov --- fs/ceph/crypto.c | 43 ++++++++++++++++++++++++++++++++++--------- fs/ceph/mds_client.c | 8 ++++++-- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/fs/ceph/crypto.c b/fs/ceph/crypto.c index 7493a3acd7d0..bc0a097a4cea 100644 --- a/fs/ceph/crypto.c +++ b/fs/ceph/crypto.c @@ -298,6 +298,10 @@ out: * Otherwise, base64 decode the string, and then ask fscrypt to format it * for userland presentation. * + * Though the fscrypt/crypto subsystems broadly expect all buffers to be in the + * linear-mapped region, this function slightly relaxes those requirements: + * fname->ctext, fname->name, and oname->name may be vmalloc(), but not tname. + * * Returns 0 on success or negative error code on error. */ int ceph_fname_to_usr(const struct ceph_fname *fname, unsigned char *tname, @@ -305,11 +309,15 @@ int ceph_fname_to_usr(const struct ceph_fname *fname, unsigned char *tname, { struct inode *dir = fname->dir; struct fscrypt_str _tname = FSTR_INIT(NULL, 0); + struct fscrypt_str _oname; struct fscrypt_str iname; char *name = fname->name; int name_len = fname->name_len; int ret; + if (WARN_ON_ONCE(tname && is_vmalloc_addr(tname))) + return -EIO; + /* Sanity check that the resulting name will fit in the buffer */ if (fname->name_len > NAME_MAX || fname->ctext_len > NAME_MAX) return -EIO; @@ -350,16 +358,18 @@ int ceph_fname_to_usr(const struct ceph_fname *fname, unsigned char *tname, goto out_inode; } + if (!tname && (fname->ctext_len == 0 || + unlikely(is_vmalloc_addr(fname->ctext)) || + unlikely(is_vmalloc_addr(oname->name)))) { + ret = fscrypt_fname_alloc_buffer(NAME_MAX, &_tname); + if (ret) + goto out_inode; + tname = _tname.name; + } + if (fname->ctext_len == 0) { int declen; - if (!tname) { - ret = fscrypt_fname_alloc_buffer(NAME_MAX, &_tname); - if (ret) - goto out_inode; - tname = _tname.name; - } - declen = base64_decode(name, name_len, tname, false, BASE64_IMAP); if (declen <= 0) { ret = -EIO; @@ -367,13 +377,28 @@ int ceph_fname_to_usr(const struct ceph_fname *fname, unsigned char *tname, } iname.name = tname; iname.len = declen; + } else if (unlikely(is_vmalloc_addr(fname->ctext))) { + memcpy(tname, fname->ctext, fname->ctext_len); + + iname.name = tname; + iname.len = fname->ctext_len; } else { iname.name = fname->ctext; iname.len = fname->ctext_len; } - ret = fscrypt_fname_disk_to_usr(dir, 0, 0, &iname, oname); - if (!ret && (dir != fname->dir)) { + _oname.name = unlikely(is_vmalloc_addr(oname->name)) ? tname : oname->name; + _oname.len = oname->len; + + ret = fscrypt_fname_disk_to_usr(dir, 0, 0, &iname, &_oname); + if (ret) + goto out; + + if (unlikely(is_vmalloc_addr(oname->name))) + memcpy(oname->name, _oname.name, _oname.len); + oname->len = _oname.len; + + if (dir != fname->dir) { char tmp_buf[BASE64_CHARS(NAME_MAX)]; name_len = snprintf(tmp_buf, sizeof(tmp_buf), "_%.*s_%llu", diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c index 80c72f295bcb..1cb95688c633 100644 --- a/fs/ceph/mds_client.c +++ b/fs/ceph/mds_client.c @@ -541,9 +541,13 @@ static int parse_reply_info_readdir(void **p, void *end, * to do the base64_decode in-place. It's * safe because the decoded string should * always be shorter, which is 3/4 of origin - * string. + * string. If this message was allocated with + * vmalloc() (happens, but rarely), leave it + * NULL and let ceph_fname_to_usr() allocate + * suitable temporary working space instead. */ - tname = _name; + if (likely(!is_vmalloc_addr(_name))) + tname = _name; /* * Set oname to _name too, and this will be From ad2d093781a0d9a9e49f4e102055d9e9b8f3b463 Mon Sep 17 00:00:00 2001 From: Marco Crivellari Date: Mon, 6 Jul 2026 18:04:50 +0200 Subject: [PATCH 442/641] ceph: Change system_unbound_wq with system_dfl_wq system_wq (per-CPU) and system_unbound_wq (unbound) are the older workqueue name, replaced by system_{percpu|dfl}_wq. The new workqueues have been introduced by: 128ea9f6ccfb ("workqueue: Add system_percpu_wq and system_dfl_wq") Usage of older workqueues will now trigger a pr_warn_once() because they are marked as deprecated as per commit: 64d8eae3f895 ("workqueue: Add warnings and fallback if system_{unbound}_wq is used") So change the used workqueue with the newer, keeping the same behavior. Suggested-by: Tejun Heo Signed-off-by: Marco Crivellari Reviewed-by: Alex Markuze Signed-off-by: Ilya Dryomov --- fs/ceph/mds_client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c index 1cb95688c633..32d5b59ccc4f 100644 --- a/fs/ceph/mds_client.c +++ b/fs/ceph/mds_client.c @@ -5757,7 +5757,7 @@ int ceph_mdsc_schedule_reset(struct ceph_mds_client *mdsc, strscpy(st->last_reason, msg, sizeof(st->last_reason)); spin_unlock(&st->lock); - if (WARN_ON_ONCE(!queue_work(system_unbound_wq, &mdsc->reset_work))) { + if (WARN_ON_ONCE(!queue_work(system_dfl_wq, &mdsc->reset_work))) { spin_lock(&st->lock); st->phase = CEPH_CLIENT_RESET_IDLE; st->last_errno = -EALREADY; From e33752c8510076f3ef63198ee2bce15ed1ae14d9 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 6 Jul 2026 16:59:25 +0200 Subject: [PATCH 443/641] ceph: skip __touch_cap() most of the time __touch_cap() moves one capability to the end of the LRU list; this list is sorted by access time for just one thing: ceph_trim_caps(). That function is supposed to discard the least-recently used capabilities. __touch_cap() is called extremely often - several times for every system call, but ceph_trim_caps() is only called rarely. __touch_cap() causes considerable lock contention on `ceph_mds_session.s_cap_lock`; this is a /proc/lock_stat I captured on one of our web servers for 5 minutes: class name con-bounces contentions waittime-min waittime-max waittime-total waittime-avg acq-bounces acquisitions holdtime-min holdtime-max holdtime-total holdtime-avg &s->s_cap_lock: 336304046 341686597 0.04 4905.76 418498578.76 1.22 892783632 1957814739 0.04 959.40 355752146.24 0.18 -------------- &s->s_cap_lock 339379730 [<00000000a2197200>] __ceph_caps_issued_mask+0x1bc/0x240 &s->s_cap_lock 1268054 [<00000000c96a24b7>] ceph_add_cap+0x234/0x3e0 &s->s_cap_lock 1021360 [<00000000aa76f996>] ceph_add_cap+0x108/0x3e0 &s->s_cap_lock 16042 [<0000000099463548>] __ceph_remove_cap+0x1f4/0x270 -------------- &s->s_cap_lock 338509619 [<00000000a2197200>] __ceph_caps_issued_mask+0x1bc/0x240 &s->s_cap_lock 1937864 [<00000000c96a24b7>] ceph_add_cap+0x234/0x3e0 &s->s_cap_lock 1203451 [<00000000aa76f996>] ceph_add_cap+0x108/0x3e0 &s->s_cap_lock 202 [<00000000888f212a>] __ceph_remove_cap+0x7c/0x270 In this /proc/lock_stat output, __touch_cap() is inlined in __ceph_caps_issued_mask(). It is responsible for 99% of all contentions. Since __touch_cap() is called so often, it is acceptable to just skip most calls. The most busy capabilities will still gravitate towards the end of the linked list, and if not, it doesn't hurt as much as the lock contention. This is still good enough for ceph_trim_caps(). This patch adds a static variable that gets incremented with each call, and 255 out of 256 calls will just be skipped. I didn't bother to make the increment atomic or use READ_ONCE because I don't think that makes a practical difference for this use case. Another /proc/lock_stat for 5 minutes with this patch (__touch_cap() is no longer inlined probably because it contains a static variable): class name con-bounces contentions waittime-min waittime-max waittime-total waittime-avg acq-bounces acquisitions holdtime-min holdtime-max holdtime-total holdtime-avg &s->s_cap_lock: 1043711 1065182 0.04 502.72 737472.88 0.69 10522578 25069948 0.04 796.44 11053669.64 0.44 -------------- &s->s_cap_lock 1043074 [<00000000f4367d73>] __touch_cap.isra.0+0x50/0xa8 &s->s_cap_lock 12147 [<0000000096f45706>] ceph_add_cap+0x234/0x3e0 &s->s_cap_lock 9472 [<0000000038a23e0f>] ceph_add_cap+0x108/0x3e0 &s->s_cap_lock 471 [<00000000e2eba934>] __ceph_remove_cap+0x1f4/0x270 -------------- &s->s_cap_lock 978499 [<00000000f4367d73>] __touch_cap.isra.0+0x50/0xa8 &s->s_cap_lock 57794 [<0000000038a23e0f>] ceph_add_cap+0x108/0x3e0 &s->s_cap_lock 27226 [<0000000096f45706>] ceph_add_cap+0x234/0x3e0 &s->s_cap_lock 1581 [<00000000e2eba934>] __ceph_remove_cap+0x1f4/0x270 __touch_cap() is still responsible for 91% of all contentions, but the number of contentions has been reduced by a factor of 320 and the total wait time by a factor of 567. Signed-off-by: Max Kellermann Reviewed-by: Alex Markuze Signed-off-by: Ilya Dryomov --- fs/ceph/caps.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/fs/ceph/caps.c b/fs/ceph/caps.c index d7283fb54cec..a0660541bf8d 100644 --- a/fs/ceph/caps.c +++ b/fs/ceph/caps.c @@ -871,6 +871,14 @@ static void __touch_cap(struct ceph_cap *cap) struct inode *inode = &cap->ci->netfs.inode; struct ceph_mds_session *s = cap->session; struct ceph_client *cl = s->s_mdsc->fsc->client; + static u8 skip_counter; + + if (data_race(++skip_counter)) + /* skip this call most of the time to reduce lock + * contention; the LRU list is still accurate enough + * for ceph_trim_caps() + */ + return; spin_lock(&s->s_cap_lock); if (!s->s_cap_iterator) { From ac9d69ae4835807fc1ecdab9c85fe7eca682de08 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 9 Jul 2026 12:48:56 +0200 Subject: [PATCH 444/641] ceph: use detach_cap_releases() in ceph_send_cap_releases() Eliminate some redundant code. Signed-off-by: Max Kellermann Reviewed-by: Xiubo Li Signed-off-by: Ilya Dryomov --- fs/ceph/mds_client.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c index 32d5b59ccc4f..c7b47c98b777 100644 --- a/fs/ceph/mds_client.c +++ b/fs/ceph/mds_client.c @@ -1804,16 +1804,19 @@ static void __open_export_target_sessions(struct ceph_mds_client *mdsc, * session caps */ -static void detach_cap_releases(struct ceph_mds_session *session, - struct list_head *target) +static int detach_cap_releases(struct ceph_mds_session *session, + struct list_head *target) { struct ceph_client *cl = session->s_mdsc->fsc->client; + const int num_cap_releases = session->s_num_cap_releases; lockdep_assert_held(&session->s_cap_lock); list_splice_init(&session->s_cap_releases, target); session->s_num_cap_releases = 0; doutc(cl, "mds%d\n", session->s_mds); + + return num_cap_releases; } static void dispose_cap_releases(struct ceph_mds_client *mdsc, @@ -2469,9 +2472,7 @@ static void ceph_send_cap_releases(struct ceph_mds_client *mdsc, spin_lock(&session->s_cap_lock); again: - list_splice_init(&session->s_cap_releases, &tmp_list); - num_cap_releases = session->s_num_cap_releases; - session->s_num_cap_releases = 0; + num_cap_releases = detach_cap_releases(session, &tmp_list); spin_unlock(&session->s_cap_lock); while (!list_empty(&tmp_list)) { From 9be23efacbac35ebd6ff1512cb22e69c25e4861d Mon Sep 17 00:00:00 2001 From: Xiubo Li Date: Thu, 23 Jul 2026 13:47:42 +0800 Subject: [PATCH 445/641] ceph: use GFP_NOFS for cap flush allocation in writeback path ceph_alloc_cap_flush() is called from ceph_writepages_start() inside the writeback layer, where other allocations in the same path (ceph_osdc_alloc_request, ceph_osdc_alloc_messages) already use GFP_NOFS. A GFP_KERNEL allocation here can trigger direct reclaim that recursively enters the filesystem writeback path: ceph_writepages_start() // inode A writeback ceph_alloc_cap_flush() kmem_cache_alloc(..., GFP_KERNEL) [direct reclaim] try_to_free_pages() shrink_slab() super_cache_scan() prune_icache_sb() inode_lru_isolate() iput() -> evict(inode_B) [inode_B has dirty pages] filemap_flush() ceph_writepages_start() // re-enters writeback ceph_alloc_cap_flush() -> RECURSION / STACK OVERFLOW All 11 callers of ceph_alloc_cap_flush() are in write or writeback contexts: writepages (x2), write_iter, fallocate, copy_file_range, setxattr, setattr, and page_mkwrite. Signed-off-by: Xiubo Li Reviewed-by: Viacheslav Dubeyko Reviewed-by: Alex Markuze Signed-off-by: Ilya Dryomov --- fs/ceph/caps.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ceph/caps.c b/fs/ceph/caps.c index a0660541bf8d..1e6ffe23fd08 100644 --- a/fs/ceph/caps.c +++ b/fs/ceph/caps.c @@ -1850,7 +1850,7 @@ struct ceph_cap_flush *ceph_alloc_cap_flush(void) { struct ceph_cap_flush *cf; - cf = kmem_cache_alloc(ceph_cap_flush_cachep, GFP_KERNEL); + cf = kmem_cache_alloc(ceph_cap_flush_cachep, GFP_NOFS); if (!cf) return NULL; From 699411a3534139e0edde1e7fc190c3eea0786ffc Mon Sep 17 00:00:00 2001 From: Xiubo Li Date: Wed, 12 Aug 2026 00:36:26 -0700 Subject: [PATCH 446/641] ceph: use GFP_KERNEL consistently in __ceph_pool_perm_get() __ceph_pool_perm_get() has six allocations for building OSD STAT requests, five of which used GFP_NOFS and one (the page vector allocation) used GFP_KERNEL, making them inconsistent. The function is only called from ceph_try_get_caps() and __ceph_get_caps(), both of which are in the user I/O path (read, write, fallocate, mmap fault), not in the writeback path. There is no risk of recursive writeback, so GFP_NOFS is unnecessarily restrictive. Use GFP_KERNEL consistently for all six allocations. Signed-off-by: Xiubo Li Reviewed-by: Alex Markuze Signed-off-by: Ilya Dryomov --- fs/ceph/addr.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fs/ceph/addr.c b/fs/ceph/addr.c index ecf33b66610c..702e5f8ab565 100644 --- a/fs/ceph/addr.c +++ b/fs/ceph/addr.c @@ -2464,7 +2464,7 @@ static int __ceph_pool_perm_get(struct ceph_inode_info *ci, } rd_req = ceph_osdc_alloc_request(&fsc->client->osdc, NULL, - 1, false, GFP_NOFS); + 1, false, GFP_KERNEL); if (!rd_req) { err = -ENOMEM; goto out_unlock; @@ -2477,12 +2477,12 @@ static int __ceph_pool_perm_get(struct ceph_inode_info *ci, rd_req->r_base_oloc.pool_ns = ceph_get_string(pool_ns); ceph_oid_printf(&rd_req->r_base_oid, "%llx.00000000", ci->i_vino.ino); - err = ceph_osdc_alloc_messages(rd_req, GFP_NOFS); + err = ceph_osdc_alloc_messages(rd_req, GFP_KERNEL); if (err) goto out_unlock; wr_req = ceph_osdc_alloc_request(&fsc->client->osdc, NULL, - 1, false, GFP_NOFS); + 1, false, GFP_KERNEL); if (!wr_req) { err = -ENOMEM; goto out_unlock; @@ -2493,7 +2493,7 @@ static int __ceph_pool_perm_get(struct ceph_inode_info *ci, ceph_oloc_copy(&wr_req->r_base_oloc, &rd_req->r_base_oloc); ceph_oid_copy(&wr_req->r_base_oid, &rd_req->r_base_oid); - err = ceph_osdc_alloc_messages(wr_req, GFP_NOFS); + err = ceph_osdc_alloc_messages(wr_req, GFP_KERNEL); if (err) goto out_unlock; @@ -2532,7 +2532,7 @@ static int __ceph_pool_perm_get(struct ceph_inode_info *ci, } pool_ns_len = pool_ns ? pool_ns->len : 0; - perm = kmalloc_flex(*perm, pool_ns, pool_ns_len + 1, GFP_NOFS); + perm = kmalloc_flex(*perm, pool_ns, pool_ns_len + 1, GFP_KERNEL); if (!perm) { err = -ENOMEM; goto out_unlock; From af59562a5b3d34fb3aa7753f2543393578d06dbf Mon Sep 17 00:00:00 2001 From: Xiubo Li Date: Thu, 23 Jul 2026 14:28:19 +0800 Subject: [PATCH 447/641] ceph: do not cache negative dentries for snapped directories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a LOOKUP/LOOKUPSNAP in a snapped directory returns ENOENT without a trace, ceph_finish_lookup() creates a negative dentry via d_add(dentry, NULL). For live directories this is fine — the dentry naturally expires. But for snapped directories, ceph_d_revalidate() unconditionally trusts all cached dentries (valid = 1), so a negative dentry created by a transient error persists forever, hiding entries that genuinely exist in the snapshot. Only cache negative dentries for live (non-snapshotted) parent directories. For snapped parents, skip the negative dentry so that VFS retries the lookup on the next access. Since the conditions that trigger a negative dentry (MDS transient error, local ENOENT shortcut, or MDS null dentry lease) are all rare in snapped directories, the performance impact of this change is negligible. Link: https://tracker.ceph.com/issues/78529 Reported-by: Andras Pataki Signed-off-by: Xiubo Li Reviewed-by: Viacheslav Dubeyko Signed-off-by: Ilya Dryomov --- fs/ceph/dir.c | 10 ++++++++-- fs/ceph/inode.c | 3 ++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c index b4b541a1180c..d5d8f935fb62 100644 --- a/fs/ceph/dir.c +++ b/fs/ceph/dir.c @@ -774,8 +774,13 @@ struct dentry *ceph_finish_lookup(struct ceph_mds_request *req, d_drop(dentry); err = -ENOENT; } else { - if (d_unhashed(dentry)) - d_add(dentry, NULL); + if (d_unhashed(dentry)) { + struct inode *parent = + d_inode(dentry->d_parent); + if (!parent || + ceph_snap(parent) == CEPH_NOSNAP) + d_add(dentry, NULL); + } } } } @@ -840,6 +845,7 @@ static struct dentry *ceph_lookup(struct inode *dir, struct dentry *dentry, dentry->d_name.len) && !is_root_ceph_dentry(dir, dentry) && ceph_test_mount_opt(fsc, DCACHE) && + ceph_snap(dir) == CEPH_NOSNAP && __ceph_dir_is_complete(ci) && __ceph_caps_issued_mask_metric(ci, CEPH_CAP_FILE_SHARED, 1)) { __ceph_touch_fmode(ci, mdsc, CEPH_FILE_MODE_RD); diff --git a/fs/ceph/inode.c b/fs/ceph/inode.c index 61d7c0b8161f..d52e2b389e0b 100644 --- a/fs/ceph/inode.c +++ b/fs/ceph/inode.c @@ -1814,7 +1814,8 @@ retry_lookup: ceph_dir_clear_ordered(dir); d_delete(dn); } else if (have_lease) { - if (d_unhashed(dn)) + if (d_unhashed(dn) && + ceph_snap(dir) == CEPH_NOSNAP) d_add(dn, NULL); } From a354d7eaa1a57f1532c8072a424cc2d339a73cc0 Mon Sep 17 00:00:00 2001 From: Xiubo Li Date: Tue, 14 Jul 2026 14:20:37 +0800 Subject: [PATCH 448/641] ceph: fix use-after-dereference of NULL ci in __ceph_remove_cap() The NULL check for "ci" in __ceph_remove_cap() was dead code because ci was dereferenced via &ci->netfs.inode before the check, and cap->session was dereferenced via session->s_mdsc->fsc->client even earlier. On a double-remove, both cap->ci and cap->session are set to NULL by the first call, so the second call would crash before ever reaching the guard. Move ci, session, cl, and inode initializations after the NULL check so that the early-return actually works. Signed-off-by: Xiubo Li Reviewed-by: Viacheslav Dubeyko Signed-off-by: Ilya Dryomov --- fs/ceph/caps.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/fs/ceph/caps.c b/fs/ceph/caps.c index 1e6ffe23fd08..f3110e8d19a8 100644 --- a/fs/ceph/caps.c +++ b/fs/ceph/caps.c @@ -1129,18 +1129,21 @@ int ceph_is_any_caps(struct inode *inode) */ void __ceph_remove_cap(struct ceph_cap *cap, bool queue_release) { - struct ceph_mds_session *session = cap->session; - struct ceph_client *cl = session->s_mdsc->fsc->client; - struct ceph_inode_info *ci = cap->ci; - struct inode *inode = &ci->netfs.inode; + struct ceph_mds_session *session; + struct ceph_client *cl; + struct ceph_inode_info *ci; + struct inode *inode; struct ceph_mds_client *mdsc; int removed = 0; /* 'ci' being NULL means the remove have already occurred */ - if (!ci) { - doutc(cl, "inode is NULL\n"); + ci = cap->ci; + if (!ci) return; - } + + session = cap->session; + cl = session->s_mdsc->fsc->client; + inode = &ci->netfs.inode; lockdep_assert_held(&ci->i_ceph_lock); From d2a8d446a09c74c8ddfe108b50dd791c889983fc Mon Sep 17 00:00:00 2001 From: Xiubo Li Date: Tue, 21 Jul 2026 13:06:53 +0800 Subject: [PATCH 449/641] ceph: revalidate ki_pos for O_APPEND writes after cap acquisition For O_APPEND writes, ki_pos is set to the current EOF via generic_write_checks() after fetching i_size from the MDS. However, ceph_get_caps() may need to wait for Fwx exclusive caps if the write extends the file (endoff > i_max_size). While waiting for Fwx, the previous Fwx holder (another client) may have already extended the file. When the MDS grants us Fwx, the cap grant message updates the local i_size, but ki_pos remains at the old EOF, causing the append write to land at a stale offset and overwrite data from the other client. Fix by re-reading i_size_read(inode) after ceph_get_caps() returns. At this point we hold Fwx exclusive caps, no other client can modify the file, and i_size reflects the true EOF from the MDS cap grant. No extra MDS round-trip is needed. Only adjust ki_pos when the EOF has actually changed. After adjusting ki_pos forward, the write range [pos, pos+count) may now exceed the i_max_size that was validated by ceph_get_caps() for the old range. Re-check against i_max_size and truncate the write if necessary to stay within the MDS-granted limit. Link: https://tracker.ceph.com/issues/7333 Fixes: 8e4473bb50a1 ("ceph: do not execute direct write in parallel if O_APPEND is specified") Signed-off-by: Xiubo Li Reviewed-by: Viacheslav Dubeyko Signed-off-by: Ilya Dryomov --- fs/ceph/file.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/fs/ceph/file.c b/fs/ceph/file.c index a4a2a4b6a027..a0b9c2b5a583 100644 --- a/fs/ceph/file.c +++ b/fs/ceph/file.c @@ -2477,6 +2477,54 @@ retry_snap: if (err < 0) goto out; + /* + * For O_APPEND writes we may have waited for Fwx exclusive caps + * while the previous Fwx holder (another client) extended the + * file. i_size has been updated via the cap grant message from + * the MDS, but ki_pos is still the old EOF. Re-read i_size here + * (no extra MDS round-trip needed) and adjust ki_pos to the true + * EOF. Since we hold Fwx, no other client can change the file. + */ + if (iocb->ki_flags & IOCB_APPEND) { + loff_t cur_eof = i_size_read(inode); + + if (cur_eof != pos) { + doutc(cl, + "%p %llx.%llx O_APPEND: pos adjusted %lld -> %lld\n", + inode, ceph_vinop(inode), pos, cur_eof); + iocb->ki_pos = cur_eof; + pos = cur_eof; + if (pos >= limit) { + err = -EFBIG; + goto out_caps; + } + iov_iter_truncate(from, limit - pos); + count = iov_iter_count(from); + + /* + * ceph_get_caps() validated the old endoff + * against i_max_size; adjusting ki_pos forward + * may have shifted the write range beyond the + * granted max_size. Re-check and truncate if + * necessary. + */ + spin_lock(&ci->i_ceph_lock); + if (pos + count > (loff_t)ci->i_max_size) { + loff_t max_size = ci->i_max_size; + + spin_unlock(&ci->i_ceph_lock); + if (pos >= max_size) { + err = -EFBIG; + goto out_caps; + } + iov_iter_truncate(from, max_size - pos); + count = iov_iter_count(from); + } else { + spin_unlock(&ci->i_ceph_lock); + } + } + } + err = file_update_time(file); if (err) goto out_caps; From 9ec08b7499a62c6d4afa93d36ab47a43fcad57d1 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 14 Jul 2026 07:51:39 -0400 Subject: [PATCH 450/641] libceph: validate OSD extent maps before cursor advance net/ceph/osd_client.c:osd_sparse_read() validates that the sparse-read data length matches the summed extent lengths, but it does not validate that each OSD-supplied extent is monotonic and lies inside the original request range. A malformed authenticated OSD reply can advertise a far-forward nonzero extent offset with a matching data length and make the client advance the message-data cursor beyond the request buffer. This reaches the BUG_ON(!*length) assertion in ceph_msg_data_next() from the client receive path. Impact: A malicious or compromised authenticated Ceph OSD peer can crash a kernel Ceph client via a malformed sparse-read reply. Reject sparse extent maps that overflow, move backwards, overlap, or extend outside the original sparse-read request before advancing the cursor. [ idryomov: perform sparse_extent_map_valid() check a bit earlier, in CEPH_SPARSE_READ_DATA_LEN instead of CEPH_SPARSE_READ_DATA_PRE state ] Cc: stable@vger.kernel.org Fixes: f628d7999727 ("libceph: add sparse read support to OSD client") Assisted-by: Codex:gpt-5-5-xhigh Signed-off-by: Michael Bommarito Reviewed-by: Viacheslav Dubeyko Signed-off-by: Ilya Dryomov --- net/ceph/osd_client.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/net/ceph/osd_client.c b/net/ceph/osd_client.c index 28d76c2f6b3e..f36ce5ae7568 100644 --- a/net/ceph/osd_client.c +++ b/net/ceph/osd_client.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -5802,6 +5803,31 @@ static inline void convert_extent_map(struct ceph_sparse_read *sr) } #endif +static bool sparse_extent_map_valid(struct ceph_sparse_read *sr) +{ + u64 req_end, pos; + int i; + + if (check_add_overflow(sr->sr_req_off, sr->sr_req_len, &req_end)) + return false; + + pos = sr->sr_req_off; + for (i = 0; i < sr->sr_count; i++) { + struct ceph_sparse_extent *ext = &sr->sr_extent[i]; + u64 end; + + if (ext->off < pos) + return false; + if (check_add_overflow(ext->off, ext->len, &end)) + return false; + if (end > req_end) + return false; + pos = end; + } + + return true; +} + static int osd_sparse_read(struct ceph_connection *con, struct ceph_msg_data_cursor *cursor, char **pbuf) @@ -5852,6 +5878,10 @@ next_op: fallthrough; case CEPH_SPARSE_READ_DATA_LEN: convert_extent_map(sr); + if (!sparse_extent_map_valid(sr)) { + pr_warn_ratelimited("invalid sparse extent map\n"); + return -EREMOTEIO; + } ret = sizeof(sr->sr_datalen); *pbuf = (char *)&sr->sr_datalen; sr->sr_state = CEPH_SPARSE_READ_DATA_PRE; From eff8013c5a8916613c742ae5a2cc341cb605c0ae Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Sat, 11 Jul 2026 11:07:05 -0400 Subject: [PATCH 451/641] ceph: bound copied dentry name length in NFS export get_name ceph_get_name() copies the MDS-supplied name into the caller's NAME_MAX-sized buffer with memcpy(name, rinfo->dname, rinfo->dname_len) and then writes name[rinfo->dname_len] = 0, without checking dname_len against NAME_MAX. A malicious or buggy MDS that returns a LOOKUPNAME reply with dname_len > NAME_MAX overflows the buffer. __get_snap_name() copies rde->name / rde->name_len the same unchecked way. Impact: a malicious or compromised Ceph MDS overflows the NAME_MAX name buffer in a client's NFS-export get_name path, a slab out-of-bounds write reported by KASAN. Reachable when a CephFS mount is re-exported over NFS. Add ceph_export_copy_name(), which rejects lengths above NAME_MAX with -ENAMETOOLONG before the copy, and use it in both ceph_get_name() and __get_snap_name(). Cc: stable@vger.kernel.org Fixes: 19913b4eac4a ("ceph: add get_name() NFS export callback") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Michael Bommarito Reviewed-by: Viacheslav Dubeyko Signed-off-by: Ilya Dryomov --- fs/ceph/export.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/fs/ceph/export.c b/fs/ceph/export.c index b2f2af104679..debb9634b9e3 100644 --- a/fs/ceph/export.c +++ b/fs/ceph/export.c @@ -442,6 +442,16 @@ static struct dentry *ceph_fh_to_parent(struct super_block *sb, return dentry; } +static int ceph_export_copy_name(char *name, const char *src, u32 len) +{ + if (len > NAME_MAX) + return -ENAMETOOLONG; + + memcpy(name, src, len); + name[len] = '\0'; + return 0; +} + static int __get_snap_name(struct dentry *parent, char *name, struct dentry *child) { @@ -513,9 +523,8 @@ static int __get_snap_name(struct dentry *parent, char *name, BUG_ON(!rde->inode.in); if (ceph_snap(inode) == le64_to_cpu(rde->inode.in->snapid)) { - memcpy(name, rde->name, rde->name_len); - name[rde->name_len] = '\0'; - err = 0; + err = ceph_export_copy_name(name, rde->name, + rde->name_len); goto out; } } @@ -580,8 +589,8 @@ static int ceph_get_name(struct dentry *parent, char *name, rinfo = &req->r_reply_info; if (!IS_ENCRYPTED(dir)) { - memcpy(name, rinfo->dname, rinfo->dname_len); - name[rinfo->dname_len] = 0; + err = ceph_export_copy_name(name, rinfo->dname, + rinfo->dname_len); } else { struct fscrypt_str oname = FSTR_INIT(NULL, 0); struct ceph_fname fname = { .dir = dir, @@ -595,10 +604,9 @@ static int ceph_get_name(struct dentry *parent, char *name, goto out; err = ceph_fname_to_usr(&fname, NULL, &oname, NULL); - if (!err) { - memcpy(name, oname.name, oname.len); - name[oname.len] = 0; - } + if (!err) + err = ceph_export_copy_name(name, oname.name, + oname.len); ceph_fname_free_buffer(dir, &oname); } out: From 68d541754d6cd3bb98d1fd8314f57e5eb533557d Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 7 Jul 2026 14:05:57 -0400 Subject: [PATCH 452/641] ceph: bound xattr value length in __build_xattrs() __build_xattrs() decodes the MDS-supplied xattr blob one attribute at a time. For each attribute it reads a 32-bit name length, advances past the name bytes, reads a 32-bit value length, records the value pointer, and advances past the value bytes. The two length fields are read with ceph_decode_32_safe(), but the value bytes themselves are advanced over with a bare "p += len" and no ceph_decode_need() check that "len" bytes remain in the blob. For every attribute except the last, the next iteration's ceph_decode_32_safe() on the following name length implicitly verifies that the previous value did not run past the blob end. The final attribute has no successor, so its decoded value length is never checked against the blob bounds. A malicious or compromised metadata server can set the last attribute's value length larger than the bytes actually present in the blob. The blob is a dedicated kvmalloc() allocation sized to the wire length (ceph_buffer_new() in ceph_fill_inode()). __set_xattr() records the oversized length in xattr->val_len verbatim, and a later getxattr(2) runs memcpy(value, xattr->val, xattr->val_len) into a user-supplied buffer, copying bytes past the end of the allocation back to user space. Impact: a malicious metadata server discloses adjacent kernel heap bytes to a local user via getxattr(2) on a CephFS file. Add the missing ceph_decode_need() so an out-of-bounds value length on the final attribute fails the decode and returns -EIO instead of being stored. Cc: stable@vger.kernel.org Fixes: 355da1eb7a1f ("ceph: inode operations") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Michael Bommarito Reviewed-by: Viacheslav Dubeyko Signed-off-by: Ilya Dryomov --- fs/ceph/xattr.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/ceph/xattr.c b/fs/ceph/xattr.c index 860fc8e1867d..cc4ffbbcb719 100644 --- a/fs/ceph/xattr.c +++ b/fs/ceph/xattr.c @@ -848,6 +848,7 @@ start: name = p; p += len; ceph_decode_32_safe(&p, end, len, bad); + ceph_decode_need(&p, end, len, bad); val = p; p += len; From 77933e22adfe813be2bd10be08d6e950103c3967 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 7 Jul 2026 14:05:58 -0400 Subject: [PATCH 453/641] ceph: bound MDSCapAuth path and fs_name decode in handle_session() handle_session() decodes the MDSCapAuth records carried by a CEPH_SESSION_OPEN message (msg_version >= 6). For each record the match.path and match.fs_name byte strings are read by first decoding a 32-bit length and then copying that many bytes with the bare ceph_decode_copy(). Unlike the surrounding fields, which all use the _safe decode variants, these two copies are not preceded by a ceph_decode_need() bounds check, and the enclosing MDSCapAuth and MDSCapMatch struct_len fields are skipped rather than enforced as an upper bound. A length larger than the bytes remaining in the message front makes ceph_decode_copy() read past the end of the front buffer. The message front is a dedicated allocation (ceph_msg_new2() -> kvmalloc), so the over-read runs off that object. A malicious or compromised MDS can trigger this with the first post-connect message on mount, with no client-side user interaction; under KASAN it is reported as a slab-out-of-bounds read in handle_session(). Impact: a malicious MDS can force the kernel client to read up to 4 GiB past the message front allocation during session setup, crashing the client (out-of-bounds read). Switch both copies to ceph_decode_copy_safe(), which performs the ceph_decode_need() bounds check before the copy and branches to the existing bad label, matching the rest of the decoder and the error path that frees the partially decoded cap_auths array. Cc: stable@vger.kernel.org Fixes: 1d17de9534cb ("ceph: save cap_auths in MDS client when session is opened") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Michael Bommarito Reviewed-by: Viacheslav Dubeyko Signed-off-by: Ilya Dryomov --- fs/ceph/mds_client.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c index c7b47c98b777..313281197ff4 100644 --- a/fs/ceph/mds_client.c +++ b/fs/ceph/mds_client.c @@ -4446,7 +4446,9 @@ static void handle_session(struct ceph_mds_session *session, pr_err_client(cl, "No memory for path\n"); goto fail; } - ceph_decode_copy(&p, cap_auths[i].match.path, _len); + ceph_decode_copy_safe(&p, end, + cap_auths[i].match.path, + _len, bad); /* Remove the tailing '/' */ while (_len && cap_auths[i].match.path[_len - 1] == '/') { @@ -4463,7 +4465,9 @@ static void handle_session(struct ceph_mds_session *session, pr_err_client(cl, "No memory for fs_name\n"); goto fail; } - ceph_decode_copy(&p, cap_auths[i].match.fs_name, _len); + ceph_decode_copy_safe(&p, end, + cap_auths[i].match.fs_name, + _len, bad); } ceph_decode_8_safe(&p, end, cap_auths[i].match.root_squash, bad); From a3eb169ee297aa99670ba927c659990bd1e453f3 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 7 Jul 2026 14:05:59 -0400 Subject: [PATCH 454/641] ceph: bound num_export_targets array for mds info v2/v3 ceph_mdsmap_decode() in fs/ceph/mdsmap.c reads num_export_targets from each per-mds info record and advances the decode cursor by num_export_targets * sizeof(u32) without first checking that many bytes remain. The only upper-bound check that catches a runaway cursor (*p > info_end) is gated on info_v >= 4, because info_end is left NULL for info_v 2 and 3. When the monitor sends an MDS map whose per-mds info version is 2 or 3 with an oversized num_export_targets, the cursor moves past the message front buffer and the later export-targets loop calls the unchecked ceph_decode_32() on out-of-bounds memory. A kernel client processes CEPH_MSG_MDS_MAP from its monitor session (net/ceph/mon_client.c dispatches it; fs/ceph/super.c routes it to ceph_mdsc_handle_mdsmap(), which sets end to the front buffer bound and calls ceph_mdsmap_decode()). A malicious or compromised monitor, or an on-path attacker on an unsigned/unencrypted messenger session, can therefore drive an out-of-bounds read in the client kernel; on x86_64 with KASAN it is reported as a slab-out-of-bounds read in ceph_mdsmap_decode(). The decoded values land in the internal info->export_targets[] array, so the consequence is a kernel out-of-bounds read, not an information leak to the attacker. Impact: a malicious or compromised Ceph monitor sending an MDS map with a per-mds info version of 2 or 3 and an oversized num_export_targets field triggers an out-of-bounds read in the CephFS client kernel. Add a ceph_decode_need() for the export-targets array before advancing the cursor, so the bound is enforced for every info_v >= 2, not only info_v >= 4. This mirrors the count-then-need idiom already used for m_data_pg_pools later in the same function. Compute the export-targets byte count with size_mul() and reuse that checked length when advancing the cursor, so the attacker-controlled num_export_targets multiplication fails closed on overflow rather than relying on the later kcalloc() guard. Cc: stable@vger.kernel.org Fixes: d463a43d69f4 ("ceph: CEPH_FEATURE_MDSENC support") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Michael Bommarito Reviewed-by: Viacheslav Dubeyko Signed-off-by: Ilya Dryomov --- fs/ceph/mdsmap.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/fs/ceph/mdsmap.c b/fs/ceph/mdsmap.c index 450a4dc9662e..4f0626753429 100644 --- a/fs/ceph/mdsmap.c +++ b/fs/ceph/mdsmap.c @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -126,6 +127,7 @@ struct ceph_mdsmap *ceph_mdsmap_decode(struct ceph_mds_client *mdsc, void **p, u8 mdsmap_v; u16 mdsmap_ev; u32 target; + size_t export_targets_len; m = kzalloc_obj(*m, GFP_NOFS); if (!m) @@ -224,8 +226,11 @@ struct ceph_mdsmap *ceph_mdsmap_decode(struct ceph_mds_client *mdsc, void **p, *p += namelen; if (info_v >= 2) { ceph_decode_32_safe(p, end, num_export_targets, bad); + export_targets_len = size_mul(num_export_targets, + sizeof(u32)); + ceph_decode_need(p, end, export_targets_len, bad); pexport_targets = *p; - *p += num_export_targets * sizeof(u32); + *p += export_targets_len; } else { num_export_targets = 0; } From 4bd3158bd62466d57ed72a3f7bc5f205fedd6919 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 7 Jul 2026 14:06:00 -0400 Subject: [PATCH 455/641] ceph: cap delegated inode count in ceph_parse_deleg_inos() ceph_parse_deleg_inos() decodes interval sets of delegated inode numbers from an MDS create-with-delegation reply. For each set it reads a 64-bit start and a 64-bit len with ceph_decode_64_safe(), which only validates that the eight bytes are present in the message, not the value, and then loops over len while inserting entries into s_delegated_inos. len is fully attacker controlled. A malicious or compromised MDS can send one huge interval, many intervals in one reply, duplicate intervals, or repeated replies that accumulate delegated inodes on the same session. The original code bounded none of these and could spin the insert loop or grow the xarray without limit. Bound both dimensions with a single enforcement point. Track the number of delegated inodes held by each MDS session in an atomic counter and grow it only in ceph_insert_deleg_ino(), which uses atomic_add_unless() to refuse to push the count past CEPH_MAX_DELEG_INOS. Because that helper is the only place the counter grows, the per-session population can never exceed the cap, so no separate per-session pre-check is needed. The counter is decremented when async create consumes a delegated inode or when an insert fails, incremented when a delegated inode is restored, initialized with the session xarray, and reset when reconnect destroys the xarray. A per-session cap alone still lets one reply spin the insert loop on duplicate ranges without growing the counter, so also cap the aggregate interval length accepted from a single reply. Together these bound both the loop trip count per reply and the xarray population across replies. The cap is a fixed, client-chosen constant rather than a value derived from the MDS. mds_client_prealloc_inos is a userspace MDS configuration option; it is never sent to the kernel client on the wire, and a server-supplied bound could not be trusted for a defensive limit in any case. The constant is set well above that option's documented default of 1000 (a generous multiple), so legitimate refill behavior is unaffected while the CPU and xarray memory a malformed delegation stream can consume stays bounded. Impact: a malicious or compromised Ceph MDS can no longer make a client spin through an unbounded delegated-inode interval or grow one session's delegated-inode xarray without limit. Cc: stable@vger.kernel.org Fixes: d48464878708 ("ceph: decode interval_sets for delegated inos") Suggested-by: Viacheslav Dubeyko Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Michael Bommarito Reviewed-by: Viacheslav Dubeyko Signed-off-by: Ilya Dryomov --- fs/ceph/mds_client.c | 59 +++++++++++++++++++++++++++++++++++++++----- fs/ceph/mds_client.h | 1 + fs/ceph/super.h | 9 +++++++ 3 files changed, 63 insertions(+), 6 deletions(-) diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c index 313281197ff4..9925e7e355e8 100644 --- a/fs/ceph/mds_client.c +++ b/fs/ceph/mds_client.c @@ -619,10 +619,36 @@ bad: #define DELEGATED_INO_AVAILABLE xa_mk_value(1) +static int ceph_insert_deleg_ino(struct ceph_mds_session *s, u64 ino) +{ + struct ceph_client *cl = s->s_mdsc->fsc->client; + int err; + + /* + * Cap how many delegated inodes a single session may hold. This is + * the only place that grows the count, so atomic_add_unless() bounds + * it at exactly CEPH_MAX_DELEG_INOS; s_num_deleg_inos can never exceed + * that. + */ + if (!atomic_add_unless(&s->s_num_deleg_inos, 1, CEPH_MAX_DELEG_INOS)) { + pr_warn_ratelimited_client(cl, + "MDS session already holds %d delegated inodes\n", + CEPH_MAX_DELEG_INOS); + return -EOVERFLOW; + } + + err = xa_insert(&s->s_delegated_inos, ino, DELEGATED_INO_AVAILABLE, + GFP_KERNEL); + if (err) + atomic_dec(&s->s_num_deleg_inos); + return err; +} + static int ceph_parse_deleg_inos(void **p, void *end, struct ceph_mds_session *s) { struct ceph_client *cl = s->s_mdsc->fsc->client; + u64 msg_deleg_inos = 0; u32 sets; ceph_decode_32_safe(p, end, sets, bad); @@ -640,16 +666,34 @@ static int ceph_parse_deleg_inos(void **p, void *end, start, len); continue; } + + /* + * Bound the number of inodes one reply may delegate. + * ceph_insert_deleg_ino() separately caps the per-session + * population, so this only has to stop one reply from spinning + * the insert loop under an attacker-controlled len. + */ + if (len > (u64)CEPH_MAX_DELEG_INOS || + msg_deleg_inos > (u64)CEPH_MAX_DELEG_INOS - len) { + pr_warn_ratelimited_client(cl, + "MDS reply delegates too many inodes (have %llu, +%llu, max %d)\n", + msg_deleg_inos, len, CEPH_MAX_DELEG_INOS); + return -EIO; + } + msg_deleg_inos += len; + while (len--) { - int err = xa_insert(&s->s_delegated_inos, start++, - DELEGATED_INO_AVAILABLE, - GFP_KERNEL); + int err = ceph_insert_deleg_ino(s, start++); + if (!err) { doutc(cl, "added delegated inode 0x%llx\n", start - 1); } else if (err == -EBUSY) { pr_warn_client(cl, "MDS delegated inode 0x%llx more than once.\n", start - 1); + } else if (err == -EOVERFLOW) { + /* ceph_insert_deleg_ino() already warned. */ + return -EIO; } else { return err; } @@ -667,16 +711,17 @@ u64 ceph_get_deleg_ino(struct ceph_mds_session *s) xa_for_each(&s->s_delegated_inos, ino, val) { val = xa_erase(&s->s_delegated_inos, ino); - if (val == DELEGATED_INO_AVAILABLE) + if (val == DELEGATED_INO_AVAILABLE) { + atomic_dec(&s->s_num_deleg_inos); return ino; + } } return 0; } int ceph_restore_deleg_ino(struct ceph_mds_session *s, u64 ino) { - return xa_insert(&s->s_delegated_inos, ino, DELEGATED_INO_AVAILABLE, - GFP_KERNEL); + return ceph_insert_deleg_ino(s, ino); } #else /* BITS_PER_LONG == 64 */ /* @@ -1063,6 +1108,7 @@ static struct ceph_mds_session *register_session(struct ceph_mds_client *mdsc, INIT_LIST_HEAD(&s->s_waiting); INIT_LIST_HEAD(&s->s_unsafe); xa_init(&s->s_delegated_inos); + atomic_set(&s->s_num_deleg_inos, 0); INIT_LIST_HEAD(&s->s_cap_releases); INIT_WORK(&s->s_cap_release_work, ceph_cap_release_work); @@ -5115,6 +5161,7 @@ static int send_mds_reconnect(struct ceph_mds_client *mdsc, /* Serialized by s_mutex against concurrent ceph_get_deleg_ino(). */ xa_destroy(&session->s_delegated_inos); + atomic_set(&session->s_num_deleg_inos, 0); if (session->s_state == CEPH_MDS_SESSION_CLOSED || session->s_state == CEPH_MDS_SESSION_REJECTED) { pr_info_client(cl, "mds%d skipping reconnect, session %s\n", diff --git a/fs/ceph/mds_client.h b/fs/ceph/mds_client.h index 0ece4c9e3529..3c62e3c3530b 100644 --- a/fs/ceph/mds_client.h +++ b/fs/ceph/mds_client.h @@ -300,6 +300,7 @@ struct ceph_mds_session { struct list_head s_waiting; /* waiting requests */ struct list_head s_unsafe; /* unsafe requests */ struct xarray s_delegated_inos; + atomic_t s_num_deleg_inos; }; /* diff --git a/fs/ceph/super.h b/fs/ceph/super.h index 1d6aab060780..6ce0b771c66b 100644 --- a/fs/ceph/super.h +++ b/fs/ceph/super.h @@ -641,6 +641,15 @@ static inline int ceph_ino_compare(struct inode *inode, void *data) #define CEPH_MDS_INO_LOG_OFFSET (2 * CEPH_MAX_MDS) #define CEPH_INO_SYSTEM_BASE ((6*CEPH_MAX_MDS) + (CEPH_MAX_MDS * CEPH_NUM_STRAY)) +/* + * Upper bound on the number of delegated inodes a single MDS session may + * hold. The MDS normally hands out a small preallocation window (the + * userspace mds_client_prealloc_inos option defaults to 1000) and refills + * it as the client consumes entries. This leaves generous headroom while + * bounding the CPU and memory a malformed delegation interval can consume. + */ +#define CEPH_MAX_DELEG_INOS 8192 + static inline bool ceph_vino_is_reserved(const struct ceph_vino vino) { if (vino.ino >= CEPH_INO_SYSTEM_BASE || From 6cd69ea0f04c481b7b104c32824546e7df1806a5 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 6 Jul 2026 09:38:09 +0200 Subject: [PATCH 456/641] ceph: make __ceph_remove_cap() static It's only used from within caps.c. Signed-off-by: Max Kellermann Reviewed-by: Alex Markuze Signed-off-by: Ilya Dryomov --- fs/ceph/caps.c | 2 +- fs/ceph/super.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/fs/ceph/caps.c b/fs/ceph/caps.c index f3110e8d19a8..78ed3fcf4e46 100644 --- a/fs/ceph/caps.c +++ b/fs/ceph/caps.c @@ -1127,7 +1127,7 @@ int ceph_is_any_caps(struct inode *inode) * caller should hold i_ceph_lock. * caller will not hold session s_mutex if called from destroy_inode. */ -void __ceph_remove_cap(struct ceph_cap *cap, bool queue_release) +static void __ceph_remove_cap(struct ceph_cap *cap, bool queue_release) { struct ceph_mds_session *session; struct ceph_client *cl; diff --git a/fs/ceph/super.h b/fs/ceph/super.h index 6ce0b771c66b..3d7f91bc29b2 100644 --- a/fs/ceph/super.h +++ b/fs/ceph/super.h @@ -1278,7 +1278,6 @@ extern void ceph_add_cap(struct inode *inode, unsigned issued, unsigned wanted, unsigned cap, unsigned seq, u64 realmino, int flags, struct ceph_cap **new_cap); -extern void __ceph_remove_cap(struct ceph_cap *cap, bool queue_release); extern void ceph_remove_cap(struct ceph_mds_client *mdsc, struct ceph_cap *cap, bool queue_release); extern void __ceph_remove_caps(struct ceph_inode_info *ci); From 8619a36ff55ac8723bc449332460368f9a090a77 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 6 Jul 2026 09:38:10 +0200 Subject: [PATCH 457/641] ceph: add helper function ceph_cap_is_removed() Having it as a wrapper allows replacing the implementation, which the next patch will do. Signed-off-by: Max Kellermann Reviewed-by: Alex Markuze Signed-off-by: Ilya Dryomov --- fs/ceph/caps.c | 8 +++----- fs/ceph/mds_client.c | 2 +- fs/ceph/super.h | 14 ++++++++++++++ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/fs/ceph/caps.c b/fs/ceph/caps.c index 78ed3fcf4e46..f859cf07f93b 100644 --- a/fs/ceph/caps.c +++ b/fs/ceph/caps.c @@ -1136,11 +1136,10 @@ static void __ceph_remove_cap(struct ceph_cap *cap, bool queue_release) struct ceph_mds_client *mdsc; int removed = 0; - /* 'ci' being NULL means the remove have already occurred */ - ci = cap->ci; - if (!ci) + if (ceph_cap_is_removed(cap)) return; + ci = cap->ci; session = cap->session; cl = session->s_mdsc->fsc->client; inode = &ci->netfs.inode; @@ -1212,8 +1211,7 @@ void ceph_remove_cap(struct ceph_mds_client *mdsc, struct ceph_cap *cap, struct ceph_inode_info *ci = cap->ci; struct ceph_fs_client *fsc; - /* 'ci' being NULL means the remove have already occurred */ - if (!ci) { + if (ceph_cap_is_removed(cap)) { doutc(mdsc->fsc->client, "inode is NULL\n"); return; } diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c index 9925e7e355e8..38657616e2a3 100644 --- a/fs/ceph/mds_client.c +++ b/fs/ceph/mds_client.c @@ -1956,7 +1956,7 @@ int ceph_iterate_session_caps(struct ceph_mds_session *session, spin_lock(&session->s_cap_lock); p = p->next; - if (!cap->ci) { + if (ceph_cap_is_removed(cap)) { doutc(cl, "finishing cap %p removal\n", cap); BUG_ON(cap->session != session); cap->session = NULL; diff --git a/fs/ceph/super.h b/fs/ceph/super.h index 3d7f91bc29b2..c3378493c42c 100644 --- a/fs/ceph/super.h +++ b/fs/ceph/super.h @@ -1278,6 +1278,20 @@ extern void ceph_add_cap(struct inode *inode, unsigned issued, unsigned wanted, unsigned cap, unsigned seq, u64 realmino, int flags, struct ceph_cap **new_cap); + +/** + * Determine whether __ceph_remove_cap() has been called on this #cap + * (but the object has not yet been freed because it is protected by + * `ceph_mds_session.s_cap_iterator`). + * + * Caller must lock either `ceph_inode_info.i_ceph_lock` or + * `ceph_mds_session.s_cap_lock`. + */ +static inline bool ceph_cap_is_removed(const struct ceph_cap *cap) +{ + return !cap->ci; +} + extern void ceph_remove_cap(struct ceph_mds_client *mdsc, struct ceph_cap *cap, bool queue_release); extern void __ceph_remove_caps(struct ceph_inode_info *ci); From af05588c9700de133aad9f8ba623ad0469e174fe Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 6 Jul 2026 09:38:11 +0200 Subject: [PATCH 458/641] ceph: mark cap remove with RB_CLEAR_NODE() instead of setting ci=NULL __ceph_remove_cap() erases the ceph_cap object from the RB tree, thus it seems natural to use RB_CLEAR_NODE() / RB_EMPTY_NODE() for the removal check. Signed-off-by: Max Kellermann Reviewed-by: Alex Markuze Signed-off-by: Ilya Dryomov --- fs/ceph/caps.c | 7 +++++-- fs/ceph/super.h | 16 ++++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/fs/ceph/caps.c b/fs/ceph/caps.c index f859cf07f93b..730180eebf77 100644 --- a/fs/ceph/caps.c +++ b/fs/ceph/caps.c @@ -1168,8 +1168,11 @@ static void __ceph_remove_cap(struct ceph_cap *cap, bool queue_release) cap->session = NULL; removed = 1; } - /* protect backpointer with s_cap_lock: see iterate_session_caps */ - cap->ci = NULL; + + /* protect removal marker with both i_ceph_lock and + s_cap_lock, so either one can be used to check for + removal */ + RB_CLEAR_NODE(&cap->ci_node); /* * s_cap_reconnect is protected by s_cap_lock. no one changes diff --git a/fs/ceph/super.h b/fs/ceph/super.h index c3378493c42c..55ec5cb0a033 100644 --- a/fs/ceph/super.h +++ b/fs/ceph/super.h @@ -203,7 +203,19 @@ struct ceph_fs_client { */ struct ceph_cap { struct ceph_inode_info *ci; - struct rb_node ci_node; /* per-ci cap tree */ + + /** + * Per-ci cap tree. Protected with + * `ceph_inode_info.i_ceph_lock`. + * + * Clearing this field with RB_CLEAR_NODE() requires holding + * both `ceph_inode_info.i_ceph_lock` and + * `ceph_mds_session->s_cap_lock`. Calling RB_EMPTY_NODE() + * (via ceph_cap_is_removed()) requires holding at least one + * of these. + */ + struct rb_node ci_node; + struct ceph_mds_session *session; struct list_head session_caps; /* per-session caplist */ u64 cap_id; /* unique cap id (mds provided) */ @@ -1289,7 +1301,7 @@ extern void ceph_add_cap(struct inode *inode, */ static inline bool ceph_cap_is_removed(const struct ceph_cap *cap) { - return !cap->ci; + return RB_EMPTY_NODE(&cap->ci_node); } extern void ceph_remove_cap(struct ceph_mds_client *mdsc, struct ceph_cap *cap, From 0cb176595797466da1792aaff1124b74d9df6e81 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 6 Jul 2026 09:38:12 +0200 Subject: [PATCH 459/641] ceph: pass inode pointer around instead of reloading it All these functions already have a ceph_inode_info pointer, so let's use that instead of letting every function reload it from RAM (i.e. `ceph_cap.ci`). This eliminates several memory accesses. Signed-off-by: Max Kellermann Reviewed-by: Alex Markuze Signed-off-by: Ilya Dryomov --- fs/ceph/caps.c | 58 +++++++++++++++++++++----------------------- fs/ceph/mds_client.c | 2 +- fs/ceph/super.h | 1 + 3 files changed, 30 insertions(+), 31 deletions(-) diff --git a/fs/ceph/caps.c b/fs/ceph/caps.c index 730180eebf77..eff7eecabb25 100644 --- a/fs/ceph/caps.c +++ b/fs/ceph/caps.c @@ -785,9 +785,9 @@ void ceph_add_cap(struct inode *inode, * generation of the MDS session (i.e. has not gone 'stale' due to * us losing touch with the mds). */ -static int __cap_is_valid(struct ceph_cap *cap) +static int __cap_is_valid(struct ceph_inode_info *ci, struct ceph_cap *cap) { - struct inode *inode = &cap->ci->netfs.inode; + struct inode *inode = &ci->netfs.inode; struct ceph_client *cl = cap->session->s_mdsc->fsc->client; unsigned long ttl; u32 gen; @@ -822,7 +822,7 @@ int __ceph_caps_issued(struct ceph_inode_info *ci, int *implemented) *implemented = 0; for (p = rb_first(&ci->i_caps); p; p = rb_next(p)) { cap = rb_entry(p, struct ceph_cap, ci_node); - if (!__cap_is_valid(cap)) + if (!__cap_is_valid(ci, cap)) continue; doutc(cl, "%p %llx.%llx cap %p issued %s\n", inode, ceph_vinop(inode), cap, ceph_cap_string(cap->issued)); @@ -855,7 +855,7 @@ int __ceph_caps_issued_other(struct ceph_inode_info *ci, struct ceph_cap *ocap) cap = rb_entry(p, struct ceph_cap, ci_node); if (cap == ocap) continue; - if (!__cap_is_valid(cap)) + if (!__cap_is_valid(ci, cap)) continue; have |= cap->issued; } @@ -866,9 +866,9 @@ int __ceph_caps_issued_other(struct ceph_inode_info *ci, struct ceph_cap *ocap) * Move a cap to the end of the LRU (oldest caps at list head, newest * at list tail). */ -static void __touch_cap(struct ceph_cap *cap) +static void __touch_cap(struct ceph_inode_info *ci, struct ceph_cap *cap) { - struct inode *inode = &cap->ci->netfs.inode; + struct inode *inode = &ci->netfs.inode; struct ceph_mds_session *s = cap->session; struct ceph_client *cl = s->s_mdsc->fsc->client; static u8 skip_counter; @@ -914,7 +914,7 @@ int __ceph_caps_issued_mask(struct ceph_inode_info *ci, int mask, int touch) for (p = rb_first(&ci->i_caps); p; p = rb_next(p)) { cap = rb_entry(p, struct ceph_cap, ci_node); - if (!__cap_is_valid(cap)) + if (!__cap_is_valid(ci, cap)) continue; if ((cap->issued & mask) == mask) { doutc(cl, "mask %p %llx.%llx cap %p issued %s (mask %s)\n", @@ -922,7 +922,7 @@ int __ceph_caps_issued_mask(struct ceph_inode_info *ci, int mask, int touch) ceph_cap_string(cap->issued), ceph_cap_string(mask)); if (touch) - __touch_cap(cap); + __touch_cap(ci, cap); return 1; } @@ -937,15 +937,15 @@ int __ceph_caps_issued_mask(struct ceph_inode_info *ci, int mask, int touch) struct rb_node *q; /* touch this + preceding caps */ - __touch_cap(cap); + __touch_cap(ci, cap); for (q = rb_first(&ci->i_caps); q != p; q = rb_next(q)) { cap = rb_entry(q, struct ceph_cap, ci_node); - if (!__cap_is_valid(cap)) + if (!__cap_is_valid(ci, cap)) continue; if (cap->issued & mask) - __touch_cap(cap); + __touch_cap(ci, cap); } } return 1; @@ -1099,7 +1099,7 @@ int __ceph_caps_mds_wanted(struct ceph_inode_info *ci, bool check) for (p = rb_first(&ci->i_caps); p; p = rb_next(p)) { cap = rb_entry(p, struct ceph_cap, ci_node); - if (check && !__cap_is_valid(cap)) + if (check && !__cap_is_valid(ci, cap)) continue; if (cap == ci->i_auth_cap) mds_wanted |= cap->mds_wanted; @@ -1127,11 +1127,10 @@ int ceph_is_any_caps(struct inode *inode) * caller should hold i_ceph_lock. * caller will not hold session s_mutex if called from destroy_inode. */ -static void __ceph_remove_cap(struct ceph_cap *cap, bool queue_release) +static void __ceph_remove_cap(struct ceph_inode_info *ci, struct ceph_cap *cap, bool queue_release) { struct ceph_mds_session *session; struct ceph_client *cl; - struct ceph_inode_info *ci; struct inode *inode; struct ceph_mds_client *mdsc; int removed = 0; @@ -1139,7 +1138,6 @@ static void __ceph_remove_cap(struct ceph_cap *cap, bool queue_release) if (ceph_cap_is_removed(cap)) return; - ci = cap->ci; session = cap->session; cl = session->s_mdsc->fsc->client; inode = &ci->netfs.inode; @@ -1209,9 +1207,9 @@ static void __ceph_remove_cap(struct ceph_cap *cap, bool queue_release) } void ceph_remove_cap(struct ceph_mds_client *mdsc, struct ceph_cap *cap, + struct ceph_inode_info *ci, bool queue_release) { - struct ceph_inode_info *ci = cap->ci; struct ceph_fs_client *fsc; if (ceph_cap_is_removed(cap)) { @@ -1227,7 +1225,7 @@ void ceph_remove_cap(struct ceph_mds_client *mdsc, struct ceph_cap *cap, !fsc->blocklisted && !ceph_inode_is_shutdown(&ci->netfs.inode)); - __ceph_remove_cap(cap, queue_release); + __ceph_remove_cap(ci, cap, queue_release); } struct cap_msg_args { @@ -1387,7 +1385,7 @@ void __ceph_remove_caps(struct ceph_inode_info *ci) while (p) { struct ceph_cap *cap = rb_entry(p, struct ceph_cap, ci_node); p = rb_next(p); - ceph_remove_cap(mdsc, cap, true); + ceph_remove_cap(mdsc, cap, ci, true); } spin_unlock(&ci->i_ceph_lock); } @@ -1400,11 +1398,11 @@ void __ceph_remove_caps(struct ceph_inode_info *ci) * Make note of max_size reported/requested from mds, revoked caps * that have now been implemented. */ -static void __prep_cap(struct cap_msg_args *arg, struct ceph_cap *cap, +static void __prep_cap(struct cap_msg_args *arg, struct ceph_inode_info *ci, + struct ceph_cap *cap, int op, int flags, int used, int want, int retain, int flushing, u64 flush_tid, u64 oldest_flush_tid) { - struct ceph_inode_info *ci = cap->ci; struct inode *inode = &ci->netfs.inode; struct ceph_client *cl = ceph_inode_to_client(inode); int held, revoking; @@ -2222,7 +2220,7 @@ retry: if (want & ~cap->mds_wanted) { if (want & ~(cap->mds_wanted | cap->issued)) goto ack; - if (!__cap_is_valid(cap)) + if (!__cap_is_valid(ci, cap)) goto ack; } @@ -2264,7 +2262,7 @@ ack: mds = cap->mds; /* remember mds, so we don't repeat */ - __prep_cap(&arg, cap, CEPH_CAP_OP_UPDATE, mflags, cap_used, + __prep_cap(&arg, ci, cap, CEPH_CAP_OP_UPDATE, mflags, cap_used, want, retain, flushing, flush_tid, oldest_flush_tid); spin_unlock(&ci->i_ceph_lock); @@ -2326,7 +2324,7 @@ retry_locked: flush_tid = __mark_caps_flushing(inode, session, true, &oldest_flush_tid); - __prep_cap(&arg, cap, CEPH_CAP_OP_FLUSH, CEPH_CLIENT_CAPS_SYNC, + __prep_cap(&arg, ci, cap, CEPH_CAP_OP_FLUSH, CEPH_CLIENT_CAPS_SYNC, __ceph_caps_used(ci), __ceph_caps_wanted(ci), (cap->issued | cap->implemented), flushing, flush_tid, oldest_flush_tid); @@ -2620,7 +2618,7 @@ static void __kick_flushing_caps(struct ceph_mds_client *mdsc, doutc(cl, "%p %llx.%llx cap %p tid %llu %s\n", inode, ceph_vinop(inode), cap, cf->tid, ceph_cap_string(cf->caps)); - __prep_cap(&arg, cap, CEPH_CAP_OP_FLUSH, + __prep_cap(&arg, ci, cap, CEPH_CAP_OP_FLUSH, (cf->tid < last_snap_flush ? CEPH_CLIENT_CAPS_PENDING_CAPSNAP : 0), __ceph_caps_used(ci), @@ -4131,7 +4129,7 @@ retry: goto out_unlock; if (target < 0) { - ceph_remove_cap(mdsc, cap, false); + ceph_remove_cap(mdsc, cap, ci, false); goto out_unlock; } @@ -4168,7 +4166,7 @@ retry: change_auth_cap_ses(ci, tcap->session); } } - ceph_remove_cap(mdsc, cap, false); + ceph_remove_cap(mdsc, cap, ci, false); goto out_unlock; } else if (tsession) { /* add placeholder for the export target */ @@ -4185,7 +4183,7 @@ retry: spin_unlock(&mdsc->cap_dirty_lock); } - ceph_remove_cap(mdsc, cap, false); + ceph_remove_cap(mdsc, cap, ci, false); goto out_unlock; } @@ -4301,7 +4299,7 @@ retry: inode, ceph_vinop(inode), peer, ocap->seq, ocap->mseq, mds, piseq, pmseq); } - ceph_remove_cap(mdsc, ocap, (ph->flags & CEPH_CAP_FLAG_RELEASE)); + ceph_remove_cap(mdsc, ocap, ci, (ph->flags & CEPH_CAP_FLAG_RELEASE)); } *old_issued = issued; @@ -4899,7 +4897,7 @@ int ceph_encode_inode_release(void **p, struct inode *inode, drop &= ~(used | dirty); cap = __get_cap_for_mds(ci, mds); - if (cap && __cap_is_valid(cap)) { + if (cap && __cap_is_valid(ci, cap)) { unless &= cap->issued; if (unless) { if (unless & CEPH_CAP_AUTH_EXCL) @@ -5058,7 +5056,7 @@ int ceph_purge_inode_cap(struct inode *inode, struct ceph_cap *cap, bool *invali cap, ci, inode, ceph_vinop(inode)); is_auth = (cap == ci->i_auth_cap); - __ceph_remove_cap(cap, false); + __ceph_remove_cap(ci, cap, false); if (is_auth) { struct ceph_cap_flush *cf; diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c index 38657616e2a3..4c38fe5a9a4b 100644 --- a/fs/ceph/mds_client.c +++ b/fs/ceph/mds_client.c @@ -2312,7 +2312,7 @@ static int trim_caps_cb(struct inode *inode, int mds, void *arg) if (oissued) { /* we aren't the only cap.. just remove us */ - ceph_remove_cap(mdsc, cap, true); + ceph_remove_cap(mdsc, cap, ci, true); (*remaining)--; } else { struct dentry *dentry; diff --git a/fs/ceph/super.h b/fs/ceph/super.h index 55ec5cb0a033..1902b7cc55d3 100644 --- a/fs/ceph/super.h +++ b/fs/ceph/super.h @@ -1305,6 +1305,7 @@ static inline bool ceph_cap_is_removed(const struct ceph_cap *cap) } extern void ceph_remove_cap(struct ceph_mds_client *mdsc, struct ceph_cap *cap, + struct ceph_inode_info *ci, bool queue_release); extern void __ceph_remove_caps(struct ceph_inode_info *ci); extern void ceph_put_cap(struct ceph_mds_client *mdsc, From 7af4c4f01305b0935adf6d4301b1ec407025485d Mon Sep 17 00:00:00 2001 From: Xiubo Li Date: Tue, 14 Jul 2026 16:13:43 +0800 Subject: [PATCH 460/641] ceph: fix UAF in __kick_flushing_caps() on cf entry freed during unlock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit list_for_each_entry() iterates ci->i_cap_flush_list but drops i_ceph_lock to send cap messages. During the unlock window, handle_cap_flush_ack() can acquire i_ceph_lock, detach cf entries with tid <= flush_tid from the list, release i_ceph_lock, and free them via ceph_free_cap_flush() outside any lock. When the original thread reacquires i_ceph_lock and the for-loop macro advances via cf = list_next_entry(cf, i_list), it dereferences cf->i_list.next on freed memory. The race timeline: __kick_flushing_caps() handle_cap_flush_ack() ----------------------- ----------------------- holds i_ceph_lock <--- iterates to cf (tid=10) prepares FLUSH message drops i_ceph_lock <--- __send_cap() ── FLUSH(tid=10) MDS sends FLUSH_ACK(tid=10) ---> acquires i_ceph_lock cf->tid(10) <= flush_tid(10), detaches cf from i_cap_flush_list drops i_ceph_lock ceph_free_cap_flush(cf) <- frees it! acquires i_ceph_lock <--- for-loop advances: cf = list_next_entry(cf, i_list) -- UAF on freed cf->i_list.next The cf was just sent by __kick_flushing_caps itself via __send_cap(). The MDS may respond with FLUSH_ACK quickly enough that handle_cap_flush_ack() frees cf before __kick_flushing_caps can finish the iteration. Fix by converting to a manual while loop: save the next pointer under i_ceph_lock before dropping it, then use the saved pointer after reacquiring, so the potentially-freed cf is never accessed again. Cc: stable@vger.kernel.org Signed-off-by: Xiubo Li Reviewed-by: Viacheslav Dubeyko Signed-off-by: Ilya Dryomov --- fs/ceph/caps.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/fs/ceph/caps.c b/fs/ceph/caps.c index eff7eecabb25..539a24965afe 100644 --- a/fs/ceph/caps.c +++ b/fs/ceph/caps.c @@ -2599,9 +2599,14 @@ static void __kick_flushing_caps(struct ceph_mds_client *mdsc, } } - list_for_each_entry(cf, &ci->i_cap_flush_list, i_list) { - if (cf->tid < first_tid) + cf = list_first_entry(&ci->i_cap_flush_list, struct ceph_cap_flush, i_list); + while (&cf->i_list != &ci->i_cap_flush_list) { + struct ceph_cap_flush *next; + + if (cf->tid < first_tid) { + cf = list_next_entry(cf, i_list); continue; + } cap = ci->i_auth_cap; if (!(cap && cap->session == session)) { @@ -2611,6 +2616,7 @@ static void __kick_flushing_caps(struct ceph_mds_client *mdsc, } first_tid = cf->tid + 1; + next = list_next_entry(cf, i_list); if (!cf->is_capsnap) { struct cap_msg_args arg; @@ -2651,6 +2657,7 @@ static void __kick_flushing_caps(struct ceph_mds_client *mdsc, } spin_lock(&ci->i_ceph_lock); + cf = next; } } From ee611a7509554c4ca1f54f6aefe592fb1df7ea70 Mon Sep 17 00:00:00 2001 From: Xiubo Li Date: Tue, 14 Jul 2026 16:13:44 +0800 Subject: [PATCH 461/641] ceph: fix UAF in check_new_map() on session freed during unlock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit check_new_map() iterates mdsc->sessions[] and for each active session drops mdsc->mutex to perform per-session operations. The forced-close path (rank removed from map) correctly takes a reference on s via ceph_get_mds_session() before releasing mdsc->mutex, but three other paths do not: Path A (address changed): mutex_unlock → mutex_lock(&s->s_mutex) Path B (reconnect): mutex_unlock → send_mds_reconnect(mdsc, s) Path C (active transition): mutex_unlock → mutex_lock(&s->s_mutex) Without the extra reference, another thread can acquire mdsc->mutex during the unlock window, call __unregister_session() which drops the last reference on s, and free it. The original thread then accesses freed memory via s->s_mutex. Fix by adding ceph_get_mds_session(s) before each mutex_unlock and ceph_put_mds_session(s) after the corresponding mutex_lock, matching the pattern already used in the forced-close path. Race timeline (Path A): Thread A (check_new_map) Thread B (another map update holds mdsc->mutex or session teardown) -------------------------- -------------------------- s = mdsc->sessions[i] (refcount == 1, held only by sessions[] array) mutex_unlock(&mdsc->mutex) ---> acquires mdsc->mutex __unregister_session(mdsc, s) sessions[i] = NULL ceph_put_mds_session(s) refcount: 1 -> 0 kfree(s) <--- freed! mutex_lock(&s->s_mutex) UAF on freed s->s_mutex Cc: stable@vger.kernel.org Signed-off-by: Xiubo Li Reviewed-by: Viacheslav Dubeyko Signed-off-by: Ilya Dryomov --- fs/ceph/mds_client.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c index 4c38fe5a9a4b..d11c7eeaf0bf 100644 --- a/fs/ceph/mds_client.c +++ b/fs/ceph/mds_client.c @@ -5890,9 +5890,11 @@ static void check_new_map(struct ceph_mds_client *mdsc, ceph_mdsmap_get_addr(newmap, i), sizeof(struct ceph_entity_addr))) { /* just close it */ + ceph_get_mds_session(s); mutex_unlock(&mdsc->mutex); mutex_lock(&s->s_mutex); mutex_lock(&mdsc->mutex); + ceph_put_mds_session(s); ceph_con_close(&s->s_con); mutex_unlock(&s->s_mutex); s->s_state = CEPH_MDS_SESSION_RESTARTING; @@ -5907,6 +5909,7 @@ static void check_new_map(struct ceph_mds_client *mdsc, newstate >= CEPH_MDS_STATE_RECONNECT) { int rc; + ceph_get_mds_session(s); mutex_unlock(&mdsc->mutex); clear_bit(i, targets); rc = send_mds_reconnect(mdsc, s); @@ -5915,6 +5918,7 @@ static void check_new_map(struct ceph_mds_client *mdsc, "mds%d reconnect failed: %d\n", i, rc); mutex_lock(&mdsc->mutex); + ceph_put_mds_session(s); } /* @@ -5927,9 +5931,11 @@ static void check_new_map(struct ceph_mds_client *mdsc, pr_info_client(cl, "mds%d recovery completed\n", s->s_mds); kick_requests(mdsc, i); + ceph_get_mds_session(s); mutex_unlock(&mdsc->mutex); mutex_lock(&s->s_mutex); mutex_lock(&mdsc->mutex); + ceph_put_mds_session(s); ceph_kick_flushing_caps(mdsc, s); mutex_unlock(&s->s_mutex); wake_up_session_caps(s, RECONNECT); From 1319b97dfe9eaa0e15132af95efe6513718f9123 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 8 Jul 2026 22:40:22 +0200 Subject: [PATCH 462/641] ceph: drop mdsc->mutex before decoding the MDS reply handle_reply() held `mdsc->mutex` across parse_reply_info(), i.e. across the full decode of the reply message. For large replies (a big readdir allocates and parses many dir_entries), this can take a while and blocks ceph_mdsc_submit_request() calls meanwhile. The decode does not need `mdsc->mutex`: parse_reply_info() mostly fills the request's `r_reply_info`. Create replies may also add delegated inode numbers to the session xarray, but that xarray is protected by its own lock and is not serialized by `mdsc->mutex` today. By the time we reach parse_reply_info(), all `mdsc->mutex`-protected state has already been updated under the lock (the request has either been unregistered (safe reply) or added to the session's unsafe list (unsafe reply)) and the request is pinned by the reference taken in lookup_get_request(). Drop `mdsc->mutex` before calling parse_reply_info() so reply decoding no longer blocks request submission. This only widens the existing unlocked window that already covers the heavier ceph_fill_trace() / ceph_readdir_prepopulate() processing, so no new races are introduced. Signed-off-by: Max Kellermann Reviewed-by: Xiubo Li Signed-off-by: Ilya Dryomov --- fs/ceph/mds_client.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c index d11c7eeaf0bf..a091f77cedaf 100644 --- a/fs/ceph/mds_client.c +++ b/fs/ceph/mds_client.c @@ -4142,13 +4142,19 @@ static void handle_reply(struct ceph_mds_session *session, struct ceph_msg *msg) list_add_tail(&req->r_unsafe_item, &req->r_session->s_unsafe); } + /* + * Now that all mutex-protected state has been updated above + * (the request has been unregistered or added to the + * session's unsafe list), we can unlock it. + */ + mutex_unlock(&mdsc->mutex); + doutc(cl, "tid %lld result %d\n", tid, result); if (test_bit(CEPHFS_FEATURE_REPLY_ENCODING, &session->s_features)) err = parse_reply_info(session, msg, req, (u64)-1); else err = parse_reply_info(session, msg, req, session->s_con.peer_features); - mutex_unlock(&mdsc->mutex); /* Must find target inode outside of mutexes to avoid deadlocks */ rinfo = &req->r_reply_info; From e7d7aa7b730178278109c41fa1b17b06873065d5 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Tue, 7 Jul 2026 23:42:28 +0200 Subject: [PATCH 463/641] ceph: do not repeat ceph_trim_dentries() if no progress possible ceph_cap_reclaim_work() re-queues itself for as long as ceph_trim_dentries() returns -EAGAIN, which happens whenever a lease walk exhausts its `nr_to_scan` budget. This creates a busy loop that consumes CPU without making any progress when there is nothing to reclaim: with no cap pressure (`count==0`) and every scanned lease still valid, each pass runs the full scan budget down to zero and returns `-EAGAIN`, only to be queued again immediately. The dir-lease walk made this worse. When `expire_dir_lease` is `false` (i.e. we have no intention of reclaiming dir leases), __dir_lease_check() returned `TOUCH` for every valid lease. `TOUCH` moves the dentry to the tail of the list and resets `di->time` via __dentry_dir_lease_touch(), so a walk over N valid leases pointlessly rewrote the list, refreshed the timestamps (preventing them from ever aging out) and always drained `nr_to_scan`, guaranteeing the `-EAGAIN` requeue. Fix this in three steps: - Return `KEEP` instead of `TOUCH` when `expire_dir_lease` is `false`. If we are not going to reclaim the lease, leave it in place instead of churning the list and resetting its timestamp; the walk then terminates naturally (or via `STOP` at the first fresh lease). - Only return `-EAGAIN` from the first (dentry-lease) walk when something was actually freed. A full batch that frees nothing means retrying the same list immediately is futile; fall through to the dir-lease walk instead. - After both walks, bail out with success (0) when nothing was freed and there is no cap pressure (`count==0`). There is no reason to keep retrying when we are not over the cap limit and made no progress. Under real cap pressure (`count>0`) the reclaim path is unchanged and still retries via `-EAGAIN`. Without this patch, I saw 500 ceph_trim_dentries() calls per second on our web servers. This is very visible in `/proc/lock_stat` (5 minute capture): class name con-bounces contentions waittime-min waittime-max waittime-total waittime-avg acq-bounces acquisitions holdtime-min holdtime-max holdtime-total holdtime-avg &mdsc->dentry_list_lock: 126180 128218 0.04 8063.44 15986965.20 124.69 1573354 5296812 0.04 8291.28 74164526.48 14.00 ----------------------- &mdsc->dentry_list_lock 111736 [<000000007b11e319>] __ceph_dentry_dir_lease_touch+0x7c/0xa8 &mdsc->dentry_list_lock 2631 [<0000000050597999>] __dentry_leases_walk+0x64/0x2c8 &mdsc->dentry_list_lock 3878 [<00000000c0022f62>] __ceph_dentry_lease_touch+0x5c/0xa8 &mdsc->dentry_list_lock 9973 [<000000002f27cb6f>] __dentry_lease_unlist+0x50/0xa0 ----------------------- &mdsc->dentry_list_lock 123621 [<0000000050597999>] __dentry_leases_walk+0x64/0x2c8 &mdsc->dentry_list_lock 1822 [<000000007b11e319>] __ceph_dentry_dir_lease_touch+0x7c/0xa8 &mdsc->dentry_list_lock 2720 [<000000002f27cb6f>] __dentry_lease_unlist+0x50/0xa0 &mdsc->dentry_list_lock 55 [<00000000c0022f62>] __ceph_dentry_lease_touch+0x5c/0xa8 With this patch: class name con-bounces contentions waittime-min waittime-max waittime-total waittime-avg acq-bounces acquisitions holdtime-min holdtime-max holdtime-total holdtime-avg &mdsc->dentry_list_lock: 1203 1215 0.16 408.88 33082.88 27.23 4320501 7357389 0.04 500.64 1961578.00 0.27 ----------------------- &mdsc->dentry_list_lock 1029 [<000000003c9aea8a>] __ceph_dentry_dir_lease_touch+0x7c/0xa8 &mdsc->dentry_list_lock 169 [<000000002038c577>] __dentry_lease_unlist+0x50/0xa0 &mdsc->dentry_list_lock 16 [<00000000c991106d>] __ceph_dentry_lease_touch+0x5c/0xa8 &mdsc->dentry_list_lock 1 [<00000000612fe15f>] __dentry_leases_walk+0x64/0x2c8 ----------------------- &mdsc->dentry_list_lock 158 [<000000002038c577>] __dentry_lease_unlist+0x50/0xa0 &mdsc->dentry_list_lock 858 [<000000003c9aea8a>] __ceph_dentry_dir_lease_touch+0x7c/0xa8 &mdsc->dentry_list_lock 182 [<00000000612fe15f>] __dentry_leases_walk+0x64/0x2c8 &mdsc->dentry_list_lock 17 [<00000000c991106d>] __ceph_dentry_lease_touch+0x5c/0xa8 __dentry_leases_walk() is almost gone. The total wait time is reduced by a factor of 483. That will give some latency gains to ceph_readdir(). Cc: stable@vger.kernel.org Fixes: 37c4efc1ddf9 ("ceph: periodically trim stale dentries") Signed-off-by: Max Kellermann Reviewed-by: Alex Markuze Signed-off-by: Ilya Dryomov --- fs/ceph/dir.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c index d5d8f935fb62..f4e0bf244fd2 100644 --- a/fs/ceph/dir.c +++ b/fs/ceph/dir.c @@ -1770,11 +1770,11 @@ static int __dir_lease_check(const struct dentry *dentry, if (ret > 0) { if (time_before(jiffies, di->time + lwc->dir_lease_ttl)) return STOP; + if (!lwc->expire_dir_lease) + return KEEP; /* Move dentry to tail of dir lease list if we don't want * to delete it. So dentries in the list are checked in a * round robin manner */ - if (!lwc->expire_dir_lease) - return TOUCH; if (dentry->d_lockref.count > 0 || (di->flags & CEPH_DENTRY_REFERENCED)) return TOUCH; @@ -1801,7 +1801,7 @@ int ceph_trim_dentries(struct ceph_mds_client *mdsc) lwc.dir_lease = false; lwc.nr_to_scan = CEPH_CAPS_PER_RELEASE * 2; freed = __dentry_leases_walk(mdsc, &lwc); - if (!lwc.nr_to_scan) /* more invalid leases */ + if (freed > 0 && !lwc.nr_to_scan) /* more invalid leases */ return -EAGAIN; if (lwc.nr_to_scan < CEPH_CAPS_PER_RELEASE) @@ -1811,6 +1811,10 @@ int ceph_trim_dentries(struct ceph_mds_client *mdsc) lwc.expire_dir_lease = freed < count; lwc.dir_lease_ttl = mdsc->fsc->mount_options->caps_wanted_delay_max * HZ; freed +=__dentry_leases_walk(mdsc, &lwc); + if (freed == 0 && count == 0) + /* no progress possible currently, retry futile */ + return 0; + if (!lwc.nr_to_scan) /* more to check */ return -EAGAIN; From 5f074d7f2938d7461facbbd073b9394b1496e73e Mon Sep 17 00:00:00 2001 From: Alex Markuze Date: Mon, 6 Jul 2026 13:11:27 +0000 Subject: [PATCH 464/641] ceph: make nearfull sync writes opt-in The kernel CephFS client has historically treated a cluster or pool NEARFULL condition as a request to force successful writes through generic_write_sync(). That effectively turns otherwise buffered writes into synchronous writes and can cause a severe throughput drop as soon as a single OSD or the file data pool crosses the nearfull threshold. On modern large clusters, NEARFULL is primarily an operator health signal rather than an immediate client-side capacity failure. Operators can still have substantial usable capacity while a cluster is rebalancing, splitting PGs, or expanding onto new devices. RBD, RGW and the userspace CephFS client do not impose this extra client-side sync-write throttle, so the kernel client behavior is surprising and operationally painful. Change the default behavior so NEARFULL no longer changes normal write-sync semantics. FULL and pool FULL still fail with -ENOSPC, and explicitly synchronous writes continue to be synced by generic_write_sync(). Add a nearfull_sync mount option for deployments that want the legacy backpressure behavior. When this option is set, successful writes are promoted to IOCB_DSYNC if the cluster or file data pool is marked NEARFULL, preserving the old behavior for conservative deployments. Link: https://tracker.ceph.com/issues/74849 Signed-off-by: Alex Markuze Reviewed-by: Xiubo Li Signed-off-by: Ilya Dryomov --- Documentation/filesystems/ceph.rst | 6 ++++++ fs/ceph/file.c | 8 +++++--- fs/ceph/super.c | 10 ++++++++++ fs/ceph/super.h | 1 + 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/Documentation/filesystems/ceph.rst b/Documentation/filesystems/ceph.rst index 6d2276a87a5a..ee2ca0c0c654 100644 --- a/Documentation/filesystems/ceph.rst +++ b/Documentation/filesystems/ceph.rst @@ -194,6 +194,12 @@ Mount Options copies. Currently, it's only used in copy_file_range, which will revert to the default VFS implementation if this option is used. + nearfull_sync + Force written data to stable storage when the cluster or file data pool is + marked NEARFULL. This restores the legacy client-side backpressure + behavior. By default, CephFS writes are not forced synchronous solely + because of NEARFULL. + recover_session= Set auto reconnect mode in the case where the client is blocklisted. The available modes are "no" and "clean". The default is "no". diff --git a/fs/ceph/file.c b/fs/ceph/file.c index a0b9c2b5a583..bd3e3f5c269e 100644 --- a/fs/ceph/file.c +++ b/fs/ceph/file.c @@ -2388,7 +2388,8 @@ out_end: * dropping our cap refs and allowing the pending snap to logically * complete _before_ this write occurs. * - * If we are near ENOSPC, write synchronously. + * If requested, nearfull writes are synced to preserve the legacy + * client-side backpressure behavior. */ static ssize_t ceph_write_iter(struct kiocb *iocb, struct iov_iter *from) { @@ -2604,8 +2605,9 @@ retry_snap: } if (written >= 0) { - if ((map_flags & CEPH_OSDMAP_NEARFULL) || - (pool_flags & CEPH_POOL_FLAG_NEARFULL)) + if (ceph_test_mount_opt(fsc, NEARFULL_SYNC) && + ((map_flags & CEPH_OSDMAP_NEARFULL) || + (pool_flags & CEPH_POOL_FLAG_NEARFULL))) iocb->ki_flags |= IOCB_DSYNC; written = generic_write_sync(iocb, written); } diff --git a/fs/ceph/super.c b/fs/ceph/super.c index c05fbd4237f8..15edea30dc8b 100644 --- a/fs/ceph/super.c +++ b/fs/ceph/super.c @@ -177,6 +177,7 @@ enum { Opt_wsync, Opt_pagecache, Opt_sparseread, + Opt_nearfull_sync, }; enum ceph_recover_session_mode { @@ -205,6 +206,7 @@ static const struct fs_parameter_spec ceph_mount_parameters[] = { fsparam_flag_no ("ino32", Opt_ino32), fsparam_string ("mds_namespace", Opt_mds_namespace), fsparam_string ("mon_addr", Opt_mon_addr), + fsparam_flag_no ("nearfull_sync", Opt_nearfull_sync), fsparam_flag_no ("poolperm", Opt_poolperm), fsparam_flag_no ("quotadf", Opt_quotadf), fsparam_u32 ("rasize", Opt_rasize), @@ -593,6 +595,12 @@ static int ceph_parse_mount_param(struct fs_context *fc, else fsopt->flags |= CEPH_MOUNT_OPT_SPARSEREAD; break; + case Opt_nearfull_sync: + if (result.negated) + fsopt->flags &= ~CEPH_MOUNT_OPT_NEARFULL_SYNC; + else + fsopt->flags |= CEPH_MOUNT_OPT_NEARFULL_SYNC; + break; case Opt_test_dummy_encryption: #ifdef CONFIG_FS_ENCRYPTION fscrypt_free_dummy_policy(&fsopt->dummy_enc_policy); @@ -749,6 +757,8 @@ static int ceph_show_options(struct seq_file *m, struct dentry *root) seq_puts(m, ",nopagecache"); if (fsopt->flags & CEPH_MOUNT_OPT_SPARSEREAD) seq_puts(m, ",sparseread"); + if (fsopt->flags & CEPH_MOUNT_OPT_NEARFULL_SYNC) + seq_puts(m, ",nearfull_sync"); fscrypt_show_test_dummy_encryption(m, ',', root->d_sb); diff --git a/fs/ceph/super.h b/fs/ceph/super.h index 1902b7cc55d3..0020ccd0f974 100644 --- a/fs/ceph/super.h +++ b/fs/ceph/super.h @@ -45,6 +45,7 @@ #define CEPH_MOUNT_OPT_ASYNC_DIROPS (1<<15) /* allow async directory ops */ #define CEPH_MOUNT_OPT_NOPAGECACHE (1<<16) /* bypass pagecache altogether */ #define CEPH_MOUNT_OPT_SPARSEREAD (1<<17) /* always do sparse reads */ +#define CEPH_MOUNT_OPT_NEARFULL_SYNC (1<<18) /* sync writes when nearfull */ #define CEPH_MOUNT_OPT_DEFAULT \ (CEPH_MOUNT_OPT_DCACHE | \ From f374967fcdf04001c9b66df1c19106fa83cd91f7 Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh Date: Fri, 31 Jul 2026 10:14:50 +0000 Subject: [PATCH 465/641] libceph: validate banner payload length When parsing the Ceph messenger v2 protocol banner, the `payload_len` field is decoded from the banner prefix. If a client sends a banner with a `payload_len` of 0, the kernel sets up a 0-length socket read. This violates an invariant in the state machine, triggering a warning in `populate_in_iter()`: ------------[ cut here ]------------ !iov_iter_count(&con->v2.in_iter) WARNING: net/ceph/messenger_v2.c:3129 at populate_in_iter net/ceph/messenger_v2.c:3129 [inline], CPU#1: kworker/1:3/5070 WARNING: net/ceph/messenger_v2.c:3129 at ceph_con_v2_try_read+0x6634/0x6810 net/ceph/messenger_v2.c:3159, CPU#1: kworker/1:3/5070 ... Call Trace: ceph_con_workfn+0x1f5/0x14a0 net/ceph/messenger.c:1575 process_one_work kernel/workqueue.c:3322 [inline] process_scheduled_works+0xa8e/0x14e0 kernel/workqueue.c:3405 worker_thread+0xa47/0xfb0 kernel/workqueue.c:3486 kthread+0x388/0x470 kernel/kthread.c:436 ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245 According to the msgr2 protocol specification, the banner payload is expected to contain at least two 64-bit integers (`server_feat` and `server_req_feat`). Therefore, `payload_len` must be at least 16 bytes. Fix this by adding a check in `process_banner_prefix()` to reject a `payload_len` smaller than 16 bytes. This prevents the 0-length read and correctly aborts the connection with a protocol error. Fixes: cd1a677cad99 ("libceph, ceph: implement msgr2.1 protocol (crc and secure modes)") Assisted-by: Gemini:gemini-3.5-flash Gemini:gemini-3.1-pro-preview syzbot Reported-by: syzbot+87c7c2d63c44e41c77a3@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=87c7c2d63c44e41c77a3 Link: https://syzkaller.appspot.com/ai_job?id=c8ca3d63-717a-4933-89ec-f3d761b8690d Signed-off-by: Aleksandr Nogikh Reviewed-by: Alex Markuze Signed-off-by: Ilya Dryomov --- net/ceph/messenger_v2.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/ceph/messenger_v2.c b/net/ceph/messenger_v2.c index 05f6eea299fc..b323b61e7023 100644 --- a/net/ceph/messenger_v2.c +++ b/net/ceph/messenger_v2.c @@ -2142,6 +2142,11 @@ static int process_banner_prefix(struct ceph_connection *con) payload_len = ceph_decode_16(&p); dout("%s con %p payload_len %d\n", __func__, con, payload_len); + if (payload_len < sizeof(u64) + sizeof(u64)) { + con->error_msg = "protocol error, bad banner payload len"; + return -EINVAL; + } + return prepare_read_banner_payload(con, payload_len); } From 2a2f98e17e1d322a94027daf0c6df88af2f9af50 Mon Sep 17 00:00:00 2001 From: Tal Zussman Date: Mon, 17 Aug 2026 15:29:17 -0400 Subject: [PATCH 466/641] libceph: remove ceph_put_page_vector() ceph_put_page_vector() was paired with ceph_get_direct_page_vector(), which was removed in commit 97a385e55829 ("libceph: remove ceph_get_direct_page_vector()"). Its only remaining caller, finish_netfs_read(), uses it to put a page vector allocated with iov_iter_get_pages_alloc2(), which is confusing. Open-code the put_page() loop and kvfree() there instead. The caller passed dirty = false, so this also removes the dead dirty branch and with it a call to the deprecated set_page_dirty_lock(). Signed-off-by: Tal Zussman Reviewed-by: Ilya Dryomov Signed-off-by: Ilya Dryomov --- fs/ceph/addr.c | 9 ++++++--- include/linux/ceph/libceph.h | 2 -- net/ceph/pagevec.c | 13 ------------- 3 files changed, 6 insertions(+), 18 deletions(-) diff --git a/fs/ceph/addr.c b/fs/ceph/addr.c index 702e5f8ab565..7e454b854a0b 100644 --- a/fs/ceph/addr.c +++ b/fs/ceph/addr.c @@ -255,9 +255,12 @@ static void finish_netfs_read(struct ceph_osd_request *req) } if (osd_data->type == CEPH_OSD_DATA_TYPE_PAGES) { - ceph_put_page_vector(osd_data->pages, - calc_pages_for(osd_data->alignment, - osd_data->length), false); + int num_pages = calc_pages_for(osd_data->alignment, + osd_data->length); + + for (int i = 0; i < num_pages; i++) + put_page(osd_data->pages[i]); + kvfree(osd_data->pages); } if (err > 0) { ceph_subvolume_metrics_record_io(fsc->mdsc, ceph_inode(inode), diff --git a/include/linux/ceph/libceph.h b/include/linux/ceph/libceph.h index 63e0e2aa1ce9..691e1bdece49 100644 --- a/include/linux/ceph/libceph.h +++ b/include/linux/ceph/libceph.h @@ -313,8 +313,6 @@ int ceph_wait_for_latest_osdmap(struct ceph_client *client, /* pagevec.c */ extern void ceph_release_page_vector(struct page **pages, int num_pages); -extern void ceph_put_page_vector(struct page **pages, int num_pages, - bool dirty); extern struct page **ceph_alloc_page_vector(int num_pages, gfp_t flags); extern void ceph_copy_from_page_vector(struct page **pages, void *data, diff --git a/net/ceph/pagevec.c b/net/ceph/pagevec.c index 858359873c4d..a6aa5b3b7a1e 100644 --- a/net/ceph/pagevec.c +++ b/net/ceph/pagevec.c @@ -10,19 +10,6 @@ #include -void ceph_put_page_vector(struct page **pages, int num_pages, bool dirty) -{ - int i; - - for (i = 0; i < num_pages; i++) { - if (dirty) - set_page_dirty_lock(pages[i]); - put_page(pages[i]); - } - kvfree(pages); -} -EXPORT_SYMBOL(ceph_put_page_vector); - void ceph_release_page_vector(struct page **pages, int num_pages) { int i; From c25aee9c630fb86f98d79eccb75765067079b972 Mon Sep 17 00:00:00 2001 From: Matthew Brown Date: Wed, 12 Aug 2026 18:13:21 +0100 Subject: [PATCH 467/641] ceph: fix leaked inode reference on writeback abort at umount ceph_dirty_folio() takes a wrbuffer claim on each newly dirtied folio: it bumps i_wrbuffer_ref (taking an ihold() on the 0->1 transition) and attaches the snap_context to folio->private. That claim is released only by ceph_put_wrbuffer_cap_refs(), which for a submitted write runs from writepages_finish(). In ceph_submit_write(), if ceph_inc_osd_stopping_blocker() fails -- which happens during umount -- the request is aborted before submission: the already-collected folios are only redirtied and unlocked, so writepages_finish() never runs and the claim is leaked. redirty_page_for_writepage() -> folio_redirty_for_writepage() -> filemap_dirty_folio() sets PG_dirty directly and does not go through ->dirty_folio, so ceph_dirty_folio() is not re-entered to rebalance it. Because every subsequent writeback also fails the osd_stopping_blocker, i_wrbuffer_ref never returns to 0, the ihold() is never dropped, and the inode cannot be evicted: VFS: Busy inodes after unmount of ceph kernel BUG at fs/super.c:650! Release the orphaned claim in the abort path before redirtying, via ceph_undo_wrbuffer_claim(): detach the snap_context, drop the wrbuffer reference (letting i_wrbuffer_ref reach 0 and iput() the inode), and drop the snap_context reference -- i.e. do what writepages_finish() would have done for these never-submitted folios. Only the locked_pages entries are undone; folios still in the fbatch were never dirty-cleared by this call (folio_clear_dirty_for_io() is the ownership-transfer point, and a successful move NULLs the fbatch slot), so they hold no claim this call owns. Cc: stable@vger.kernel.org Fixes: fd7449d937e7 ("ceph: fix generic/421 test failure") Signed-off-by: Matthew Brown Reviewed-by: Xiubo Li Signed-off-by: Ilya Dryomov --- fs/ceph/addr.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/fs/ceph/addr.c b/fs/ceph/addr.c index 7e454b854a0b..657c2cb0f881 100644 --- a/fs/ceph/addr.c +++ b/fs/ceph/addr.c @@ -1429,6 +1429,16 @@ void ceph_shift_unused_folios_left(struct folio_batch *fbatch) fbatch->nr = n; } +static void ceph_undo_wrbuffer_claim(struct inode *inode, struct folio *folio) +{ + struct ceph_snap_context *snapc = folio_detach_private(folio); + + if (!snapc) + return; + ceph_put_wrbuffer_cap_refs(ceph_inode(inode), 1, snapc); + ceph_put_snap_context(snapc); +} + static int ceph_submit_write(struct address_space *mapping, struct writeback_control *wbc, @@ -1492,6 +1502,7 @@ new_request: if (!page) continue; + ceph_undo_wrbuffer_claim(inode, page_folio(page)); redirty_page_for_writepage(wbc, page); unlock_page(page); } From aedc9053d909508a5f56c3f49f885fc030df4730 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Jean?= Date: Thu, 13 Aug 2026 14:00:00 +0200 Subject: [PATCH 468/641] ceph: reject export_targets ranks >= CEPH_MAX_MDS in mdsmap decode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MDSMap export_targets entries are monitor controlled. check_new_map() uses each entry as a bit number in a fixed stack bitmap, so a rank outside the protocol namespace can make set_bit() write past the end of the array. Reject ranks outside CEPH_MAX_MDS while decoding the map. Do not validate against possible_max_rank here because maps may legitimately reference ranks beyond a temporarily reduced max_mds. Cc: stable@vger.kernel.org Fixes: d517b3983dd3 ("ceph: reconnect to the export targets on new mdsmaps") Signed-off-by: Jérémy Jean Reviewed-by: Alex Markuze Signed-off-by: Alex Markuze Signed-off-by: Ilya Dryomov --- fs/ceph/mdsmap.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/ceph/mdsmap.c b/fs/ceph/mdsmap.c index 4f0626753429..53079ef34c3a 100644 --- a/fs/ceph/mdsmap.c +++ b/fs/ceph/mdsmap.c @@ -269,6 +269,10 @@ struct ceph_mdsmap *ceph_mdsmap_decode(struct ceph_mds_client *mdsc, void **p, goto nomem; for (j = 0; j < num_export_targets; j++) { target = ceph_decode_32(&pexport_targets); + if (target >= CEPH_MAX_MDS) { + err = -EIO; + goto corrupt; + } info->export_targets[j] = target; } } else { From 3cde4a8302301679937474a5f7a851394cc1bd11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Jean?= Date: Sat, 15 Aug 2026 21:46:37 +0000 Subject: [PATCH 469/641] libceph: reject buckets with mismatched CRUSH ids MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit crush_decode() stores bucket data by array slot, and the mapper later derives the per-bucket workspace index from the decoded bucket id. A malformed map can therefore make one bucket reuse another bucket's workspace by encoding an id different from -1 - slot. For uniform buckets, the second replica selection expands the source bucket's permutation into that aliased workspace buffer. If the source bucket is larger than the aliased bucket, the write runs past the smaller permutation array and can escape the kvmalloc'd CRUSH workspace. KASAN reports a slab OOB write of 4 bytes in bucket_perm_choose(). Reject buckets whose encoded id does not match their array slot. Valid CRUSH maps already use the canonical negative id corresponding to the bucket slot, so this restores the invariant expected by work->work[-1 - in->id] without changing valid map behavior. Cc: stable@vger.kernel.org Fixes: 66a0e2d579db ("crush: remove mutable part of CRUSH map") Assisted-by: Codex:gpt-5 Signed-off-by: Jérémy Jean Reviewed-by: Alex Markuze Signed-off-by: Ilya Dryomov --- net/ceph/osdmap.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/ceph/osdmap.c b/net/ceph/osdmap.c index d6282f0bcff8..cf34b35c9a90 100644 --- a/net/ceph/osdmap.c +++ b/net/ceph/osdmap.c @@ -517,6 +517,8 @@ static struct crush_map *crush_decode(void *pbyval, void *end) ceph_decode_need(p, end, 4*sizeof(u32), bad); b->id = ceph_decode_32(p); + if (b->id != -1 - i) + goto bad; b->type = ceph_decode_16(p); if (b->type == 0) goto bad; From 8fdf946445732c2bcd685abc8bd0e509d2ebc158 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Tue, 18 Aug 2026 20:40:05 +0200 Subject: [PATCH 470/641] ceph: force a cap message when a deferred revoke can't be acked immediately When the MDS revokes capabilities, handle_cap_grant() normally guarantees a response by setting `CHECK_CAPS_FLUSH_FORCE` (see commit 31634d7597d8 ("ceph: force sending a cap update msg back to MDS for revoke op")), so ceph_check_caps() sends a cap message even if the client would otherwise decide it has nothing to do. That guarantee is skipped whenever the revoke has to be deferred (via revoke_wait): revoking Fb while dirty data is still buffered (writeback is queued first) or revoking Fc while pages are cached (async invalidation is queued first). In those cases, the ack is left to the deferred completion (ceph_put_wrbuffer_cap_refs() after writeback, or the invalidate worker after invalidation); both of which call ceph_check_caps(ci,0) i.e. without `CHECK_CAPS_FLUSH_FORCE`. Nothing gets sent under one of the following conditions: - the inode is retaining caps because the file was used recently (file_wanted != 0; retain |= CEPH_CAP_ANY) - the revoked cap is still used because the page was re-cached (e.g. a file being re-read) - the MDS has meanwhile re-granted, so `issued==implemented` and the client sees nothing being revoked The client then never emits the cap message which the MDS is waiting for. The MDS blocks on the revoke indefinitely and logs, for minutes or hours: client.NNN isn't responding to mclientcaps(revoke), ino 0x... pending pAsxLsXsxFsxcrwb issued pAsxLsXsxFsxcrwb, sent 964.899182 seconds ago The client-side state at that point shows the full cap set still issued, nothing in the revoking/flushing sets. Thus nothing gets sent. This patch fixes it by remembering that a forced response is expected. When a revoke is deferred, set `CEPH_I_FLUSH_FORCE` on the inode. ceph_check_caps() replays it as `CHECK_CAPS_FLUSH_FORCE`, so whichever path re-checks the inode next (the writeback/invalidate completion, the delayed worker, or any other caller) is guaranteed to send a cap message to the MDS. __prep_cap() clears the flag once a message is actually built. This is the deferred-path counterpart of the existing `CHECK_CAPS_FLUSH_FORCE` handling; a normal (non-deferred) revoke still forces the response inline as before. Cc: stable@vger.kernel.org Fixes: 31634d7597d8 ("ceph: force sending a cap update msg back to MDS for revoke op") Fixes: 257e6172ab36 ("ceph: don't let check_caps skip sending responses for revoke msgs") Signed-off-by: Max Kellermann Reviewed-by: Alex Markuze Signed-off-by: Ilya Dryomov --- fs/ceph/caps.c | 63 +++++++++++++++++++++++++++++++++++++++++++------ fs/ceph/super.h | 5 ++++ 2 files changed, 61 insertions(+), 7 deletions(-) diff --git a/fs/ceph/caps.c b/fs/ceph/caps.c index 539a24965afe..bcb04c6cb92c 100644 --- a/fs/ceph/caps.c +++ b/fs/ceph/caps.c @@ -987,6 +987,27 @@ int __ceph_caps_revoking_other(struct ceph_inode_info *ci, return 0; } +/* + * Return true if any cap of this inode holds caps which the MDS has + * revoked, but which we have not released yet. + */ +static bool __ceph_is_any_revoking(const struct ceph_inode_info *ci) +{ + const struct rb_node *p; + + lockdep_assert_held(&ci->i_ceph_lock); + + for (p = rb_first(&ci->i_caps); p; p = rb_next(p)) { + const struct ceph_cap *cap = + rb_entry(p, struct ceph_cap, ci_node); + + if (cap->implemented & ~cap->issued) + return true; + } + + return false; +} + int __ceph_caps_used(struct ceph_inode_info *ci) { int used = 0; @@ -1431,6 +1452,9 @@ static void __prep_cap(struct cap_msg_args *arg, struct ceph_inode_info *ci, cap->implemented &= cap->issued | used; cap->mds_wanted = want; + if ((ci->i_ceph_flags & CEPH_I_FLUSH_FORCE) != 0 && !__ceph_is_any_revoking(ci)) + clear_bit(CEPH_I_FLUSH_FORCE_BIT, &ci->i_ceph_flags); + arg->session = cap->session; arg->ino = ceph_vino(inode).ino; arg->cid = cap->cap_id; @@ -2048,6 +2072,14 @@ void ceph_check_caps(struct ceph_inode_info *ci, int flags) if (ci->i_ceph_flags & CEPH_I_FLUSH) flags |= CHECK_CAPS_FLUSH; + /* + * A revoke whose response was deferred (see handle_cap_grant()) must + * still be acknowledged. Replay the forced flush here so that even a + * check triggered by writeback/invalidation completion sends a cap + * message to the MDS. + */ + if (ci->i_ceph_flags & CEPH_I_FLUSH_FORCE) + flags |= CHECK_CAPS_FLUSH_FORCE; retry: /* Caps wanted by virtue of active open files. */ file_wanted = __ceph_caps_file_wanted(ci); @@ -3774,13 +3806,30 @@ static void handle_cap_grant(struct inode *inode, BUG_ON(cap->issued & ~cap->implemented); /* don't let check_caps skip sending a response to MDS for revoke msgs */ - if (!revoke_wait && le32_to_cpu(grant->op) == CEPH_CAP_OP_REVOKE) { - cap->mds_wanted = 0; - flags |= CHECK_CAPS_FLUSH_FORCE; - if (cap == ci->i_auth_cap) - check_caps = 1; /* check auth cap only */ - else - check_caps = 2; /* check all caps */ + if (le32_to_cpu(grant->op) == CEPH_CAP_OP_REVOKE) { + if (revoke_wait) { + /* + * We can't ack the revoke yet: the response is deferred + * until the writeback or cache invalidation queued above + * completes. Set the CEPH_I_FLUSH_FORCE flag to remember + * that a forced cap message is owed so that deferred + * completion (ceph_put_wrbuffer_cap_refs() or the + * invalidate worker, both of which call ceph_check_caps()) + * actually sends one, even if by then the revoked caps look + * unused, the inode is retaining caps, or the MDS has + * re-granted them. Without this, the cap message is never + * sent and the MDS hangs ("isn't responding to + * mclientcaps(revoke)"). + */ + set_bit(CEPH_I_FLUSH_FORCE_BIT, &ci->i_ceph_flags); + } else { + cap->mds_wanted = 0; + flags |= CHECK_CAPS_FLUSH_FORCE; + if (cap == ci->i_auth_cap) + check_caps = 1; /* check auth cap only */ + else + check_caps = 2; /* check all caps */ + } } if (extra_info->inline_version > 0 && diff --git a/fs/ceph/super.h b/fs/ceph/super.h index 0020ccd0f974..72d4e30304dc 100644 --- a/fs/ceph/super.h +++ b/fs/ceph/super.h @@ -709,6 +709,10 @@ static inline struct inode *ceph_find_inode(struct super_block *sb, #define CEPH_I_ASYNC_CREATE_BIT (12) /* async create in flight for this */ #define CEPH_I_SHUTDOWN_BIT (13) /* inode is no longer usable */ #define CEPH_I_ASYNC_CHECK_CAPS_BIT (14) /* check caps after async creating finishes */ +#define CEPH_I_FLUSH_FORCE_BIT (15) /* a revoke's response was deferred; + * force a cap message to the MDS once + * the deferred work completes + */ #define CEPH_I_DIR_ORDERED (1 << CEPH_I_DIR_ORDERED_BIT) #define CEPH_I_FLUSH (1 << CEPH_I_FLUSH_BIT) @@ -721,6 +725,7 @@ static inline struct inode *ceph_find_inode(struct super_block *sb, #define CEPH_I_ODIRECT (1 << CEPH_I_ODIRECT_BIT) #define CEPH_I_ASYNC_CREATE (1 << CEPH_I_ASYNC_CREATE_BIT) #define CEPH_I_SHUTDOWN (1 << CEPH_I_SHUTDOWN_BIT) +#define CEPH_I_FLUSH_FORCE (1 << CEPH_I_FLUSH_FORCE_BIT) /* * Masks of ceph inode work. From d79fb758e7e2e7a181eab630c0545a56041c473d Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Wed, 26 Aug 2026 09:50:34 +0800 Subject: [PATCH 471/641] samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-modify ftrace_direct_init() assigns kthread_run()'s return value to simple_tsk without an IS_ERR() check. When kthread_run() fails it returns ERR_PTR(-ENOMEM), but init still returns 0, so the module loads with simple_tsk holding an error pointer. On unload, ftrace_direct_exit() then passes that ERR_PTR to kthread_stop(), leading to a null-pointer-dereference. Check the return value of kthread_run() with IS_ERR(); on failure, unregister the ftrace direct call and propagate the error code. Link: https://patch.msgid.link/20260826015034.10755-1-vulab@iscas.ac.cn Fixes: ae0cc3b7e7f5 ("ftrace/samples: Add a sample module that implements modify_ftrace_direct()") Suggested-by: Steven Rostedt Signed-off-by: Haotian Zhang Signed-off-by: Steven Rostedt --- samples/ftrace/ftrace-direct-modify.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/samples/ftrace/ftrace-direct-modify.c b/samples/ftrace/ftrace-direct-modify.c index 1ba1927b548e..164d9dd6fd92 100644 --- a/samples/ftrace/ftrace-direct-modify.c +++ b/samples/ftrace/ftrace-direct-modify.c @@ -320,9 +320,15 @@ static int __init ftrace_direct_init(void) ftrace_set_filter_ip(&direct, (unsigned long) my_ip, 0, 0); ret = register_ftrace_direct(&direct, my_tramp); - if (!ret) - simple_tsk = kthread_run(simple_thread, NULL, "event-sample-fn"); - return ret; + if (ret) + return ret; + simple_tsk = kthread_run(simple_thread, NULL, "event-sample-fn"); + if (IS_ERR(simple_tsk)) { + unregister_ftrace_direct(&direct, my_tramp, true); + return PTR_ERR(simple_tsk); + } + + return 0; } static void __exit ftrace_direct_exit(void) From 6727b7618f49401acf373fa3ec5712e2ec52e5cf Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Wed, 26 Aug 2026 09:50:50 +0800 Subject: [PATCH 472/641] samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-multi-modify ftrace_direct_multi_init() assigns kthread_run()'s return value to simple_tsk without an IS_ERR() check. When kthread_run() fails it returns ERR_PTR(-ENOMEM), but init still returns 0, so the module loads with simple_tsk holding an error pointer. On unload, ftrace_direct_multi_exit() then passes that ERR_PTR to kthread_stop(), leading to a null-pointer-dereference. Check the return value of kthread_run() with IS_ERR(); on failure, unregister the ftrace direct call and propagate the error code. Link: https://patch.msgid.link/20260826015050.10772-1-vulab@iscas.ac.cn Fixes: e1067a07cfbc ("ftrace/samples: Add module to test multi direct modify interface") Suggested-by: Steven Rostedt Signed-off-by: Haotian Zhang Signed-off-by: Steven Rostedt --- samples/ftrace/ftrace-direct-multi-modify.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/samples/ftrace/ftrace-direct-multi-modify.c b/samples/ftrace/ftrace-direct-multi-modify.c index 7a7822dfeb50..b03766c6217b 100644 --- a/samples/ftrace/ftrace-direct-multi-modify.c +++ b/samples/ftrace/ftrace-direct-multi-modify.c @@ -364,9 +364,15 @@ static int __init ftrace_direct_multi_init(void) ret = register_ftrace_direct(&direct, my_tramp); - if (!ret) - simple_tsk = kthread_run(simple_thread, NULL, "event-sample-fn"); - return ret; + if (ret) + return ret; + simple_tsk = kthread_run(simple_thread, NULL, "event-sample-fn"); + if (IS_ERR(simple_tsk)) { + unregister_ftrace_direct(&direct, my_tramp, true); + return PTR_ERR(simple_tsk); + } + + return 0; } static void __exit ftrace_direct_multi_exit(void) From 53676a5e28231186c9f56d87b7998b640699bc15 Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Sun, 23 Aug 2026 18:26:46 -0500 Subject: [PATCH 473/641] cifs: add revalidation on FSCTL failure in smb2_duplicate_extents() smb2_duplicate_extents() has no handling for FSCTL_DUPLICATE_EXTENTS_TO_FILE failure: when the FSCTL fails, local inode metadata may be stale from the pre-extension or from concurrent remote writes, but is never refreshed. Force revalidation on FSCTL failure and use i_size_read() for the pre-extension check. Fixes: cfc63fc8126a ("smb3: fix cached file size problems in duplicate extents (reflink)") Cc: stable@vger.kernel.org Signed-off-by: Frank Sorenson Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2ops.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 7d6738ffcb80..bea4876b58cb 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -2218,7 +2218,7 @@ smb2_duplicate_extents(const unsigned int xid, trgtfile->fid.volatile_fid, tcon->tid, tcon->ses->Suid, src_off, dest_off, len); inode = d_inode(trgtfile->dentry); - if (inode->i_size < dest_off + len) { + if (i_size_read(inode) < dest_off + len) { rc = smb2_set_file_size(xid, tcon, trgtfile, dest_off + len, false); if (rc) goto duplicate_extents_out; @@ -2235,7 +2235,10 @@ smb2_duplicate_extents(const unsigned int xid, if (ret_data_len > 0) cifs_dbg(FYI, "Non-zero response length in duplicate extents\n"); - if (rc == 0) { + if (rc) { + CIFS_I(inode)->time = 0; /* force reval */ + cifs_invalidate_cache(inode, 0); + } else { qrc = SMB2_query_info(xid, tcon, trgtfile->fid.persistent_fid, trgtfile->fid.volatile_fid, &file_inf); spin_lock(&inode->i_lock); From 15596a87fcc6fa07a162858e5fd00ba31752096b Mon Sep 17 00:00:00 2001 From: Shengyu Qu Date: Thu, 27 Aug 2026 01:20:50 +0800 Subject: [PATCH 474/641] ASoC: amd: acp-config: change quirks to cover all ASUS FA401EA variants Existing quirk doesn't cover all known existing FA401EA devices, so use "FA401EA" to cover all of them. Link: https://bugzilla.kernel.org/show_bug.cgi?id=221310#c49 Fixes: 27d090f3ccd4 ("ASoC: amd: acp: add ACP70 DMI override for new ASUS TUF platforms") Signed-off-by: Shengyu Qu Link: https://patch.msgid.link/20260826172050.15686-1-wiagn@4d2.org Signed-off-by: Mark Brown --- sound/soc/amd/acp-config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c index a27336b132f2..2d50be8190c1 100644 --- a/sound/soc/amd/acp-config.c +++ b/sound/soc/amd/acp-config.c @@ -54,7 +54,7 @@ static const struct dmi_system_id acp70_acpi_flag_override_table[] = { { .matches = { DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC"), - DMI_MATCH(DMI_PRODUCT_NAME, "TUF Gaming A14 FA401EA"), + DMI_MATCH(DMI_PRODUCT_NAME, "FA401EA"), }, }, { From c966d29e01bbf829f8bb4a39a49811c56cdb49c3 Mon Sep 17 00:00:00 2001 From: Daeho Jeong Date: Wed, 26 Aug 2026 08:36:44 -0700 Subject: [PATCH 475/641] f2fs: support resizable tail section and unify pinned allocation Currently, zoned block devices restrict pinned file allocations to conventional zones at the beginning of the storage (before first_seq_zone_segno), triggering range GC when conventional space is exhausted. On regular block devices, when preparing for future online filesystem resizing (e.g. partition shrinking), pinned files must not be allocated in the tail area that will be truncated, as pinned files cannot be relocated by GC. Specifying the resizable tail area size (in sections) allows uniform mount configuration across devices of different storage capacities. To support this, introduce a unified `pinned_area_max_secno` boundary abstraction in `f2fs_sb_info`: 1. Add `-o resizable_tail_secno=%u` mount option to specify the number of sections at the tail of the filesystem reserved for resizing. 2. In `f2fs_fill_super()`, initialize `sbi->pinned_area_max_secno` as: min(MAIN_SECS(sbi) - resizable_tail_sec, zoned_max_sec). 3. In `get_new_segment()`, restrict segment allocation for pinned files (`pinning == true`) to `0 .. sbi->pinned_area_max_secno - 1`. If no free section is available in the pinned area, return -EAGAIN. 4. In `f2fs_allocate_pinning_section()`, unify the range GC trigger to run `f2fs_gc_range()` up to `sbi->pinned_area_max_secno` whenever `sbi->pinned_area_max_secno < MAIN_SECS(sbi)` and allocation returns -EAGAIN. 5. Expose `/sys/fs/f2fs//pinned_area_max_secno` as a read-only sysfs node. Signed-off-by: Daeho Jeong Signed-off-by: Sunmin Jeong Reviewed-by: Wenjie Qi Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- Documentation/ABI/testing/sysfs-fs-f2fs | 7 +++++ Documentation/filesystems/f2fs.rst | 8 +++++- fs/f2fs/f2fs.h | 2 ++ fs/f2fs/segment.c | 35 ++++++++++++------------ fs/f2fs/segment.h | 1 + fs/f2fs/super.c | 36 +++++++++++++++++++++++++ fs/f2fs/sysfs.c | 2 ++ 7 files changed, 73 insertions(+), 18 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-fs-f2fs b/Documentation/ABI/testing/sysfs-fs-f2fs index 85194e4c7f01..0cebc89799dd 100644 --- a/Documentation/ABI/testing/sysfs-fs-f2fs +++ b/Documentation/ABI/testing/sysfs-fs-f2fs @@ -1013,3 +1013,10 @@ Description: Every time a write operation completes f2fs_write_end_io() is the maximum size of a write bio that is completed in atomic (atc) context. The default value for this attribute is UINT_MAX which means that this functionality is disabled by default. + +What: /sys/fs/f2fs//pinned_area_max_secno +Date: August 2026 +Contact: "Daeho Jeong" +Description: This is a read-only entry to show the upper bound section number + for pinned files. Pinned files will only be allocated within + sections 0 to pinned_area_max_secno - 1. diff --git a/Documentation/filesystems/f2fs.rst b/Documentation/filesystems/f2fs.rst index 1a5fd4afe609..a3c3b6948734 100644 --- a/Documentation/filesystems/f2fs.rst +++ b/Documentation/filesystems/f2fs.rst @@ -417,7 +417,13 @@ lookup_mode=%s Control the directory lookup behavior for casefolded auto F2FS determines the mode based on the on-disk `SB_ENC_NO_COMPAT_FALLBACK_FL` flag. - ================== ======================================== +resizable_tail_secno=%u Control the number of sections at the tail of the + filesystem reserved for online resizing. Pinned files + will only be allocated within sections 0 to + (MAIN_SECS - resizable_tail_secno) - 1. If set to 0 + (default), there is no tail restriction unless running + on a zoned block device where conventional zones are + used. ======================== ============================================================ Debugfs Entries diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index b0a9c14de595..16720f1f0a9c 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -255,6 +255,7 @@ struct f2fs_mount_info { block_t unusable_cap; /* Amount of space allowed to be * unusable when disabling checkpoint */ + unsigned int resizable_tail_secno; /* number of resizable tail sections */ /* For compression */ unsigned char compress_algorithm; /* algorithm type */ @@ -2005,6 +2006,7 @@ struct f2fs_sb_info { spinlock_t dev_lock; /* protect dirty_device */ bool aligned_blksize; /* all devices has the same logical blksize */ unsigned int first_seq_zone_segno; /* first segno in sequential zone */ + unsigned int pinned_area_max_secno; /* upper bound section for pinned files */ unsigned int bggc_io_aware; /* For adjust the BG_GC priority when pending IO */ unsigned int allocate_section_hint; /* the boundary position between devices */ unsigned int allocate_section_policy; /* determine the section writing priority */ diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c index 56decf9c691c..1e7e745be71d 100644 --- a/fs/f2fs/segment.c +++ b/fs/f2fs/segment.c @@ -2877,6 +2877,7 @@ static int get_new_segment(struct f2fs_sb_info *sbi, unsigned int old_zoneno = GET_ZONE_FROM_SEG(sbi, *newseg); unsigned int alloc_policy = sbi->allocate_section_policy; unsigned int alloc_hint = sbi->allocate_section_hint; + unsigned int max_secno = MAIN_SECS(sbi); bool init = true; bool looped = false; int i, devi; @@ -2908,7 +2909,7 @@ static int get_new_segment(struct f2fs_sb_info *sbi, */ if (f2fs_sb_has_blkzoned(sbi)) { /* Prioritize writing to conventional zones */ - if (sbi->blkzone_alloc_policy == BLKZONE_ALLOC_PRIOR_CONV || pinning) + if (sbi->blkzone_alloc_policy == BLKZONE_ALLOC_PRIOR_CONV) segno = 0; else segno = max(sbi->first_seq_zone_segno, *newseg); @@ -2924,19 +2925,24 @@ static int get_new_segment(struct f2fs_sb_info *sbi, alloc_hint > MAIN_SECS(sbi)) alloc_hint = MAIN_SECS(sbi); - if (alloc_policy == ALLOCATE_FORWARD_FROM_HINT && - hint < alloc_hint) - hint = alloc_hint; - else if (alloc_policy == ALLOCATE_FORWARD_WITHIN_HINT && - hint >= alloc_hint) + if (pinning) { + max_secno = sbi->pinned_area_max_secno; hint = 0; + } else if (alloc_policy == ALLOCATE_FORWARD_FROM_HINT && + hint < alloc_hint) { + hint = alloc_hint; + } else if (alloc_policy == ALLOCATE_FORWARD_WITHIN_HINT && + hint >= alloc_hint) { + hint = 0; + } find_other_zone: - secno = find_next_zero_bit(free_i->free_secmap, MAIN_SECS(sbi), hint); + secno = find_next_zero_bit(free_i->free_secmap, max_secno, hint); - if (secno >= MAIN_SECS(sbi)) { + if (secno >= max_secno) { if (looped) { - ret = -ENOSPC; + ret = (pinning && has_unpinned_area(sbi)) ? + -EAGAIN : -ENOSPC; f2fs_bug_on(sbi, !pinning); goto out_unlock; } @@ -3001,12 +3007,6 @@ got_it: goto out_unlock; } - /* no free section in conventional device or conventional zone */ - if (new_sec && pinning && - f2fs_is_sequential_zone_area(sbi, START_BLOCK(sbi, segno))) { - ret = -EAGAIN; - goto out_unlock; - } __set_inuse(sbi, segno); *newseg = segno; out_unlock: @@ -3472,8 +3472,9 @@ retry: err = f2fs_allocate_new_section(sbi, CURSEG_COLD_DATA_PINNED, false); f2fs_unlock_op(sbi, &lc); - if (f2fs_sb_has_blkzoned(sbi) && err == -EAGAIN && gc_required) { - err = f2fs_gc_range(sbi, 0, sbi->first_seq_zone_segno - 1, + if (has_unpinned_area(sbi) && err == -EAGAIN && gc_required) { + err = f2fs_gc_range(sbi, 0, + sbi->pinned_area_max_secno * SEGS_PER_SEC(sbi) - 1, true, ZONED_PIN_SEC_REQUIRED_COUNT, true); if (err) return err; diff --git a/fs/f2fs/segment.h b/fs/f2fs/segment.h index db1079169a23..5949aa5200ac 100644 --- a/fs/f2fs/segment.h +++ b/fs/f2fs/segment.h @@ -43,6 +43,7 @@ static inline void sanity_check_seg_type(struct f2fs_sb_info *sbi, #define MAIN_SEGS(sbi) (SM_I(sbi)->main_segments) #define MAIN_SECS(sbi) ((sbi)->total_sections) +#define has_unpinned_area(sbi) ((sbi)->pinned_area_max_secno < MAIN_SECS(sbi)) #define TOTAL_SEGS(sbi) \ (SM_I(sbi) ? SM_I(sbi)->segment_count : \ diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index 3bdb0f891c35..253a579e9d5b 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -235,6 +235,7 @@ enum { Opt_jqfmt, Opt_checkpoint, Opt_lookup_mode, + Opt_resizable_tail_secno, Opt_err, }; @@ -366,6 +367,7 @@ static const struct fs_parameter_spec f2fs_param_specs[] = { fsparam_flag("age_extent_cache", Opt_age_extent_cache), fsparam_enum("errors", Opt_errors, f2fs_param_errors), fsparam_enum("lookup_mode", Opt_lookup_mode, f2fs_param_lookup_mode), + fsparam_u32("resizable_tail_secno", Opt_resizable_tail_secno), {} }; @@ -404,6 +406,7 @@ static match_table_t f2fs_checkpoint_tokens = { #define F2FS_SPEC_errors (1 << 23) #define F2FS_SPEC_lookup_mode (1 << 24) #define F2FS_SPEC_reserve_node (1 << 25) +#define F2FS_SPEC_resizable_tail_secno (1 << 26) struct f2fs_fs_context { struct f2fs_mount_info info; @@ -551,6 +554,17 @@ static inline void adjust_unusable_cap_perc(struct f2fs_sb_info *sbi) F2FS_OPTION(sbi).unusable_cap_perc); } +static inline void adjust_pinned_area_boundary(struct f2fs_sb_info *sbi) +{ + sbi->pinned_area_max_secno = MAIN_SECS(sbi); + if (f2fs_sb_has_blkzoned(sbi) && sbi->first_seq_zone_segno != NULL_SEGNO) + sbi->pinned_area_max_secno = min(sbi->pinned_area_max_secno, + GET_SEC_FROM_SEG(sbi, sbi->first_seq_zone_segno)); + if (F2FS_OPTION(sbi).resizable_tail_secno) + sbi->pinned_area_max_secno = min(sbi->pinned_area_max_secno, + MAIN_SECS(sbi) - F2FS_OPTION(sbi).resizable_tail_secno); +} + static void init_once(void *foo) { struct f2fs_inode_info *fi = (struct f2fs_inode_info *) foo; @@ -1235,6 +1249,10 @@ static int f2fs_parse_param(struct fs_context *fc, struct fs_parameter *param) F2FS_CTX_INFO(ctx).lookup_mode = result.uint_32; ctx->spec_mask |= F2FS_SPEC_lookup_mode; break; + case Opt_resizable_tail_secno: + F2FS_CTX_INFO(ctx).resizable_tail_secno = result.uint_32; + ctx->spec_mask |= F2FS_SPEC_resizable_tail_secno; + break; } return 0; } @@ -1763,6 +1781,9 @@ static void f2fs_apply_options(struct fs_context *fc, struct super_block *sb) F2FS_OPTION(sbi).errors = F2FS_CTX_INFO(ctx).errors; if (ctx->spec_mask & F2FS_SPEC_lookup_mode) F2FS_OPTION(sbi).lookup_mode = F2FS_CTX_INFO(ctx).lookup_mode; + if (ctx->spec_mask & F2FS_SPEC_resizable_tail_secno) + F2FS_OPTION(sbi).resizable_tail_secno = + F2FS_CTX_INFO(ctx).resizable_tail_secno; f2fs_apply_compression(fc, sb); f2fs_apply_test_dummy_encryption(fc, sb); @@ -1771,6 +1792,13 @@ static void f2fs_apply_options(struct fs_context *fc, struct super_block *sb) static int f2fs_sanity_check_options(struct f2fs_sb_info *sbi, bool remount) { + unsigned int total_sections = le32_to_cpu(sbi->raw_super->section_count); + + if (F2FS_OPTION(sbi).resizable_tail_secno >= total_sections) { + f2fs_err(sbi, "Option resizable_tail_secno is larger than or equal to total sections (%u >= %u)", + F2FS_OPTION(sbi).resizable_tail_secno, total_sections); + return -EINVAL; + } if (f2fs_sb_has_device_alias(sbi) && !test_opt(sbi, READ_EXTENT_CACHE)) { f2fs_err(sbi, "device aliasing requires extent cache"); @@ -2544,6 +2572,10 @@ static int f2fs_show_options(struct seq_file *seq, struct dentry *root) else if (F2FS_OPTION(sbi).lookup_mode == LOOKUP_AUTO) seq_show_option(seq, "lookup_mode", "auto"); + if (F2FS_OPTION(sbi).resizable_tail_secno) + seq_printf(seq, ",resizable_tail_secno=%u", + F2FS_OPTION(sbi).resizable_tail_secno); + return 0; } @@ -2586,6 +2618,7 @@ static void default_options(struct f2fs_sb_info *sbi, bool remount) F2FS_OPTION(sbi).bggc_mode = BGGC_MODE_ON; F2FS_OPTION(sbi).memory_mode = MEMORY_MODE_NORMAL; F2FS_OPTION(sbi).errors = MOUNT_ERRORS_CONTINUE; + F2FS_OPTION(sbi).resizable_tail_secno = 0; set_opt(sbi, INLINE_XATTR); set_opt(sbi, INLINE_DATA); @@ -3045,6 +3078,7 @@ skip: sb->s_flags = (sb->s_flags & ~SB_POSIXACL) | (test_opt(sbi, POSIX_ACL) ? SB_POSIXACL : 0); + adjust_pinned_area_boundary(sbi); limit_reserve_root(sbi); fc->sb_flags = (flags & ~SB_LAZYTIME) | (sb->s_flags & SB_LAZYTIME); @@ -5287,6 +5321,8 @@ try_onemore: /* get segno of first zoned block device */ sbi->first_seq_zone_segno = get_first_seq_zone_segno(sbi); + adjust_pinned_area_boundary(sbi); + sbi->reserved_pin_section = f2fs_sb_has_blkzoned(sbi) ? ZONED_PIN_SEC_REQUIRED_COUNT : GET_SEC_FROM_SEG(sbi, overprovision_segments(sbi)); diff --git a/fs/f2fs/sysfs.c b/fs/f2fs/sysfs.c index 3201e2185fea..811e350a1430 100644 --- a/fs/f2fs/sysfs.c +++ b/fs/f2fs/sysfs.c @@ -1313,6 +1313,7 @@ F2FS_SBI_GENERAL_RW_ATTR(blkzone_alloc_policy); #endif F2FS_SBI_GENERAL_RW_ATTR(carve_out); F2FS_SBI_GENERAL_RW_ATTR(reserved_pin_section); +F2FS_SBI_GENERAL_RO_ATTR(pinned_area_max_secno); F2FS_SBI_GENERAL_RW_ATTR(bggc_io_aware); F2FS_SBI_GENERAL_RW_ATTR(max_lock_elapsed_time); F2FS_SBI_GENERAL_RW_ATTR(lock_duration_priority); @@ -1525,6 +1526,7 @@ static struct attribute *f2fs_attrs[] = { ATTR_LIST(max_read_extent_count), ATTR_LIST(carve_out), ATTR_LIST(reserved_pin_section), + ATTR_LIST(pinned_area_max_secno), ATTR_LIST(allocate_section_hint), ATTR_LIST(allocate_section_policy), ATTR_LIST(max_lock_elapsed_time), From 6faa235a649e78a82e3230b849607f446ba65ed5 Mon Sep 17 00:00:00 2001 From: Dennis Tighe Date: Sun, 23 Aug 2026 00:13:25 -0700 Subject: [PATCH 476/641] ntfs: compute bi_sector in 512-byte units bi_sector counts in 512 byte sectors and not in multiples of the volume's sector size. Under "normal" circumstances (with 512 byte sectors in NTFS) the current code works as is; however, when we have a 4k sector size on the volume the current usage of NTFS_B_TO_SECTOR() and ntfs_bytes_to_sector() end up converting to the number of 4k sectors after mount. Reads work today on 4k volumes as bdev-io.c as performing the shift correctly inline. With writes, we end up with significant silent disk corruption on these volumes. This fixes changes to use the new ntfs_bytes_to_bio_sector() function everywhere we're performing this calculation (including the existing read path). For the change in inode.c it removes a dead code block rather than updating. Fixes: 40796051991d ("ntfs: update in-memory, on-disk structures and headers") Assisted-by: Claude:claude-opus-5 Signed-off-by: Dennis Tighe Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/bdev-io.c | 2 +- fs/ntfs/compress.c | 2 +- fs/ntfs/inode.c | 10 ++-------- fs/ntfs/mft.c | 12 ++++++------ fs/ntfs/ntfs.h | 10 ++++------ 5 files changed, 14 insertions(+), 22 deletions(-) diff --git a/fs/ntfs/bdev-io.c b/fs/ntfs/bdev-io.c index 86db4d9298ed..4f27eed3b072 100644 --- a/fs/ntfs/bdev-io.c +++ b/fs/ntfs/bdev-io.c @@ -34,7 +34,7 @@ int ntfs_bdev_read(struct block_device *bdev, char *data, loff_t start, size_t s int error; struct bio *bio; blk_opf_t op; - sector_t sector = start >> SECTOR_SHIFT; + sector_t sector = ntfs_bytes_to_bio_sector(start); if (start & (SECTOR_SIZE - 1)) return -EINVAL; diff --git a/fs/ntfs/compress.c b/fs/ntfs/compress.c index 2225630b19d7..197d8607fc63 100644 --- a/fs/ntfs/compress.c +++ b/fs/ntfs/compress.c @@ -1414,7 +1414,7 @@ static int ntfs_write_cb(struct ntfs_inode *ni, loff_t pos, struct page **pages, bio_pos = ntfs_cluster_to_bytes(vol, bio_lcn); bio = bio_alloc(vol->sb->s_bdev, DIV_ROUND_UP(bio_size, PAGE_SIZE), REQ_OP_WRITE, GFP_NOIO); - bio->bi_iter.bi_sector = ntfs_bytes_to_sector(vol, bio_pos); + bio->bi_iter.bi_sector = ntfs_bytes_to_bio_sector(bio_pos); for (i = 0; bio_size; i++) { unsigned int len = min_t(unsigned int, bio_size, PAGE_SIZE); diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index 32edb4045178..5aedc045f65a 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -1852,7 +1852,7 @@ int ntfs_read_inode_mount(struct inode *vi) struct mft_record *m = NULL; struct attr_record *a; struct ntfs_attr_search_ctx *ctx; - unsigned int i, nr_blocks; + unsigned int i; int err; size_t new_rl_count; @@ -1896,11 +1896,6 @@ int ntfs_read_inode_mount(struct inode *vi) goto err_out; } - /* Determine the first block of the $MFT/$DATA attribute. */ - nr_blocks = ntfs_bytes_to_sector(vol, vol->mft_record_size); - if (!nr_blocks) - nr_blocks = 1; - /* Load $MFT/$DATA's first mft record. */ err = ntfs_bdev_read(sb->s_bdev, (char *)m, ntfs_cluster_to_bytes(vol, vol->mft_lcn), i); @@ -3780,8 +3775,7 @@ static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi, bio = bio_alloc(vol->sb->s_bdev, 1, REQ_OP_WRITE, GFP_NOIO); bio->bi_iter.bi_sector = - ntfs_bytes_to_sector(vol, - ntfs_cluster_to_bytes(vol, lcn) + + ntfs_bytes_to_bio_sector(ntfs_cluster_to_bytes(vol, lcn) + lcn_folio_off); length = min_t(unsigned long, diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index 69b007e574fc..7e58c99f1728 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -499,8 +499,8 @@ int ntfs_sync_mft_mirror(struct ntfs_volume *vol, const u64 mft_no, bio = bio_alloc(vol->sb->s_bdev, 1, REQ_OP_WRITE, GFP_NOIO); bio->bi_iter.bi_sector = - NTFS_B_TO_SECTOR(vol, NTFS_CLU_TO_B(vol, vol->mftmirr_lcn) + - lcn_folio_off + folio_ofs); + ntfs_bytes_to_bio_sector(NTFS_CLU_TO_B(vol, vol->mftmirr_lcn) + + lcn_folio_off + folio_ofs); if (bio_add_folio(bio, folio, vol->mft_record_size, folio_ofs)) err = submit_bio_wait(bio); @@ -592,8 +592,8 @@ int write_mft_record_nolock(struct ntfs_inode *ni, struct mft_record *m, int syn bio = bio_alloc(vol->sb->s_bdev, 1, REQ_OP_WRITE, GFP_NOIO); bio->bi_iter.bi_sector = - NTFS_B_TO_SECTOR(vol, NTFS_CLU_TO_B(vol, ni->mft_lcn[i]) + - clu_off); + ntfs_bytes_to_bio_sector(NTFS_CLU_TO_B(vol, ni->mft_lcn[i]) + + clu_off); if (!bio_add_folio(bio, folio, folio_size, ni->folio_ofs + offset)) { @@ -2742,8 +2742,8 @@ flush_bio: bio = bio_alloc(vol->sb->s_bdev, 1, REQ_OP_WRITE, GFP_NOIO); bio->bi_iter.bi_sector = - ntfs_bytes_to_sector(vol, - ntfs_cluster_to_bytes(vol, lcn) + off); + ntfs_bytes_to_bio_sector( + ntfs_cluster_to_bytes(vol, lcn) + off); } if (vol->cluster_size == NTFS_BLOCK_SIZE && diff --git a/fs/ntfs/ntfs.h b/fs/ntfs/ntfs.h index df5a75d506f6..45f77848a9cf 100644 --- a/fs/ntfs/ntfs.h +++ b/fs/ntfs/ntfs.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include "volume.h" @@ -71,8 +72,6 @@ #define NTFS_CLU_TO_POFS(vol, clu) (((u64)(clu) << (vol)->cluster_size_bits) & \ ~PAGE_MASK) -#define NTFS_B_TO_SECTOR(vol, b) ((b) >> ((vol)->sb)->s_blocksize_bits) - enum { NTFS_BLOCK_SIZE = 512, NTFS_BLOCK_SIZE_BITS = 9, @@ -154,11 +153,10 @@ static inline u64 ntfs_cluster_to_poff(const struct ntfs_volume *vol, return (clu << vol->cluster_size_bits) & ~PAGE_MASK; } -/* Convert byte offset to sector (block) number. */ -static inline sector_t ntfs_bytes_to_sector(const struct ntfs_volume *vol, - u64 bytes) +/* Convert a byte offset on the volume to a bio sector number. */ +static inline sector_t ntfs_bytes_to_bio_sector(u64 bytes) { - return bytes >> vol->sb->s_blocksize_bits; + return bytes >> SECTOR_SHIFT; } /* Global variables. */ From acb1095fd2db884b417cb70808c886e4b615ff05 Mon Sep 17 00:00:00 2001 From: Hongling Zeng Date: Mon, 24 Aug 2026 15:59:35 +0800 Subject: [PATCH 477/641] ntfs: fix memmove overlap in ntfs_new_attr_flags When the record shrinks while the payload offsets increase (e.g., enabling compression reduces padding, making arec_size < old_arec_size, but the header grows by 8 bytes), moving the name first can overwrite the old mapping_pairs before they are copied. Move mapping_pairs first in this case. Since mp_ofs is derived from name_ofs, they always change in the same direction. Checking name_ofs alone is sufficient. Fixes: fc053f05ca28 ("ntfs: add reparse and ea operations") Cc: stable@vger.kernel.org Signed-off-by: Hongling Zeng Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/ea.c | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c index 9f0222c172d9..0bc29bf1f050 100644 --- a/fs/ntfs/ea.c +++ b/fs/ntfs/ea.c @@ -753,15 +753,36 @@ static int ntfs_new_attr_flags(struct ntfs_inode *ni, __le32 fattr) old_arec_size = le32_to_cpu(a->length); /* - * Move payloads before shrinking the record. Otherwise resizing moves + * Move payloads before shrinking the record. Otherwise resizing moves * the following attribute over the old payload before it can be copied. + * + * When offsets increase, move mapping_pairs first to avoid name + * overwriting the start of mapping_pairs. */ if (arec_size < old_arec_size) { - if (a->name_length && name_ofs != old_name_ofs) - memmove((u8 *)a + name_ofs, (u8 *)a + old_name_ofs, - a->name_length * sizeof(__le16)); - if (mp_ofs != old_mp_ofs) - memmove((u8 *)a + mp_ofs, (u8 *)a + old_mp_ofs, mp_size); + if (name_ofs > old_name_ofs) { + /* Payload offsets increased: move mapping pairs first. */ + if (mp_ofs != old_mp_ofs) + memmove((u8 *)a + mp_ofs, + (u8 *)a + old_mp_ofs, + mp_size); + if (a->name_length && name_ofs != old_name_ofs) + memmove((u8 *)a + name_ofs, + (u8 *)a + old_name_ofs, + a->name_length * + sizeof(__le16)); + } else { + /* Payload offsets decreased or unchanged: move name first. */ + if (a->name_length && name_ofs != old_name_ofs) + memmove((u8 *)a + name_ofs, + (u8 *)a + old_name_ofs, + a->name_length * + sizeof(__le16)); + if (mp_ofs != old_mp_ofs) + memmove((u8 *)a + mp_ofs, + (u8 *)a + old_mp_ofs, + mp_size); + } } err = ntfs_attr_record_resize(ctx->mrec, a, arec_size); From 67aded1da114dc44808315f249bd9e7e440f799d Mon Sep 17 00:00:00 2001 From: Hongling Zeng Date: Thu, 27 Aug 2026 13:58:44 +0800 Subject: [PATCH 478/641] ntfs: fix race between fallocate and mmap reads The fallocate implementation only takes invalidate_lock for punch hole, collapse range, and insert range operations. For standard allocation modes (mode == 0, FALLOC_FL_KEEP_SIZE), the lock is not held. During ntfs_attr_fallocate(), new clusters are mapped to the runlist via ntfs_attr_map_cluster() before being zeroed by ntfs_dio_zero_range(). This creates a window where concurrent mmap page faults can read uninitialized disk data. Since mmap uses filemap_fault() which takes invalidate_lock in shared mode, it can fault in pages during this window and expose old disk contents to userspace. This is an information leak and data integrity issue. Fix by taking invalidate_lock for all fallocate operations, not just for punch/collapse/insert modes. This prevents concurrent page faults from accessing unzeroed clusters during the allocation window. Fixes: 495e90fa3348 ("ntfs: update attrib operations") Cc: stable@vger.kernel.org Reviewed-by: Baolin Liu Reviewed-by: Hyunchul Lee Signed-off-by: Hongling Zeng Signed-off-by: Namjae Jeon --- fs/ntfs/file.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c index 88747217ba61..1969e4f444f7 100644 --- a/fs/ntfs/file.c +++ b/fs/ntfs/file.c @@ -1116,7 +1116,6 @@ static long ntfs_fallocate(struct file *file, int mode, loff_t offset, loff_t le struct ntfs_volume *vol = ni->vol; int err = 0; loff_t old_size; - bool map_locked = false; if (mode & ~(NTFS_FALLOC_FL_SUPPORTED)) return -EOPNOTSUPP; @@ -1148,16 +1147,13 @@ static long ntfs_fallocate(struct file *file, int mode, loff_t offset, loff_t le inode_lock(vi); if (NInoCompressed(ni) || NInoEncrypted(ni) || NInoWofCompressed(ni)) { - err = -EOPNOTSUPP; - goto out; + inode_unlock(vi); + return -EOPNOTSUPP; } inode_dio_wait(vi); - if (mode & (FALLOC_FL_PUNCH_HOLE | FALLOC_FL_COLLAPSE_RANGE | - FALLOC_FL_INSERT_RANGE)) { - filemap_invalidate_lock(vi->i_mapping); - map_locked = true; - } + /* Take invalidate_lock for all fallocate operations to prevent races */ + filemap_invalidate_lock(vi->i_mapping); switch (mode & FALLOC_FL_MODE_MASK) { case FALLOC_FL_ALLOCATE_RANGE: @@ -1182,8 +1178,7 @@ static long ntfs_fallocate(struct file *file, int mode, loff_t offset, loff_t le err = file_modified(file); out: - if (map_locked) - filemap_invalidate_unlock(vi->i_mapping); + filemap_invalidate_unlock(vi->i_mapping); if (!err) { if (mode == 0 && NInoNonResident(ni) && offset > old_size) { From ac727d86fb84bdc9626ba9c756c26767459f3083 Mon Sep 17 00:00:00 2001 From: Baolin Liu Date: Thu, 27 Aug 2026 14:43:17 +0800 Subject: [PATCH 479/641] ntfs: leave HasEA flag untouched on setxattr failure In ntfs_set_ea(), the exit path unconditionally updates the HasEA flag based on ea_info_qsize. When an error occurs before ea_info_qsize is updated, NInoClearHasEA() hides existing on-disk EAs until the inode is evicted. Only update the flag on success. Fixes: fc053f05ca28 ("ntfs: add reparse and ea operations") Signed-off-by: Baolin Liu Signed-off-by: Namjae Jeon --- fs/ntfs/ea.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c index 0bc29bf1f050..3f4ba7667522 100644 --- a/fs/ntfs/ea.c +++ b/fs/ntfs/ea.c @@ -404,10 +404,12 @@ alloc_new_ea: *packed_ea_size = p_ea_info->ea_length; mark_mft_record_dirty(ni); out: - if (ea_info_qsize > 0) - NInoSetHasEA(ni); - else - NInoClearHasEA(ni); + if (!err) { + if (ea_info_qsize > 0) + NInoSetHasEA(ni); + else + NInoClearHasEA(ni); + } kvfree(ea_buf); kvfree(old_ea_buf); From f0feab6e9e008faa406d189fb40cb7ab1bed420a Mon Sep 17 00:00:00 2001 From: Daniele Ceraolo Spurio Date: Tue, 18 Aug 2026 21:35:20 +0000 Subject: [PATCH 480/641] drm/xe: Do not apply WA 14025883347 to media 3503 The database was updated and the WA is no longer listed as applicable to media 3503, so don't enable it there. Fixes: c57db41b8d2c ("drm/xe/guc: Add Wa_14025883347 for GuC DMA failure on reset") Signed-off-by: Daniele Ceraolo Spurio Cc: Sk Anirban Cc: Badal Nilawar Cc: Matt Roper Reviewed-by: Matt Roper Link: https://patch.msgid.link/20260818213520.283063-1-daniele.ceraolospurio@intel.com (cherry picked from commit fae59d5de5de39bc51ac2839f74970312e0c8905) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_wa_oob.rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_wa_oob.rules b/drivers/gpu/drm/xe/xe_wa_oob.rules index f02ac9bf7424..dd69ad07f7a9 100644 --- a/drivers/gpu/drm/xe/xe_wa_oob.rules +++ b/drivers/gpu/drm/xe/xe_wa_oob.rules @@ -63,7 +63,7 @@ 16026007364 MEDIA_VERSION(3000) 14020316580 MEDIA_VERSION(1301) -14025883347 MEDIA_VERSION_RANGE(1301, 3503) +14025883347 MEDIA_VERSION_RANGE(1301, 3500) GRAPHICS_VERSION_RANGE(2004, 3005) 16029380221 MEDIA_VERSION(3500) 22022079272 MEDIA_VERSION(3503) From 369ba0d1efe91cccabe98ae53c53b7425f327edf Mon Sep 17 00:00:00 2001 From: Balasubramani Vivekanandan Date: Wed, 19 Aug 2026 13:04:58 +0530 Subject: [PATCH 481/641] drm/xe/xe_gt_idle: Add CCS to the powergating info print While reading the main GT powergating info from debugfs, include both RCS and CCS engine masks. Fixes: 0914c1e45d3a1 ("drm/xe/xe_gt_idle: add debugfs entry for powergating info") Signed-off-by: Balasubramani Vivekanandan Link: https://patch.msgid.link/20260819073457.1812722-2-balasubramani.vivekanandan@intel.com Reviewed-by: Matt Roper Signed-off-by: Matt Roper (cherry picked from commit 8899e413c5ab85443ec9bbc50cffe924c6b596de) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_gt_idle.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_gt_idle.c b/drivers/gpu/drm/xe/xe_gt_idle.c index 04b24e1c8b78..7dc9873aef54 100644 --- a/drivers/gpu/drm/xe/xe_gt_idle.c +++ b/drivers/gpu/drm/xe/xe_gt_idle.c @@ -248,7 +248,8 @@ int xe_gt_idle_pg_print(struct xe_gt *gt, struct drm_printer *p) pg_status = xe_mmio_read32(>->mmio, POWERGATE_DOMAIN_STATUS); } - if (gt->info.engine_mask & XE_HW_ENGINE_RCS_MASK) { + if (gt->info.engine_mask & + (XE_HW_ENGINE_RCS_MASK | XE_HW_ENGINE_CCS_MASK)) { drm_printf(p, "Render Power Gating Enabled: %s\n", str_yes_no(pg_enabled & RENDER_POWERGATE_ENABLE)); From 5e977521d21717edb8e91d004434697d6e3f248c Mon Sep 17 00:00:00 2001 From: Arvind Yadav Date: Thu, 20 Aug 2026 12:24:45 +0530 Subject: [PATCH 482/641] drm/xe: Reject page faults from non-fault-mode scratch VMs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Having scratch enabled does not make a VM capable of handling recoverable page faults. Allowing scratch VMs through the ASID lookup also admits dma-fence mode VMs. If such a VM faults on an already valid VMA, the handler reports success without fixing the fault, causing the GPU to retry indefinitely. Only allow fault-mode VMs through the ASID lookup. Fault-mode VMs using scratch remain supported, while faults from 3D VMs are rejected. Fixes: ad9843aac91a ("drm/xe/madvise: Implement purgeable buffer object support") Cc: Matthew Brost Cc: Thomas Hellström Cc: Himal Prasad Ghimiray Suggested-by: Matthew Brost Signed-off-by: Arvind Yadav Reviewed-by: Matthew Brost Signed-off-by: Matthew Brost Link: https://patch.msgid.link/20260820065445.567228-1-arvind.yadav@intel.com (cherry picked from commit bfb24a06405b652d37831f3fb66b71d33a6605de) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_pagefault.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_pagefault.c b/drivers/gpu/drm/xe/xe_pagefault.c index dd3c068e1a39..dbf8f71d3328 100644 --- a/drivers/gpu/drm/xe/xe_pagefault.c +++ b/drivers/gpu/drm/xe/xe_pagefault.c @@ -158,7 +158,7 @@ static struct xe_vm *xe_pagefault_asid_to_vm(struct xe_device *xe, u32 asid) down_read(&xe->usm.lock); vm = xa_load(&xe->usm.asid_to_vm, asid); - if (vm && (xe_vm_in_fault_mode(vm) || xe_vm_has_scratch(vm))) + if (vm && xe_vm_in_fault_mode(vm)) xe_vm_get(vm); else vm = ERR_PTR(-EINVAL); From 3b446d169a93e6abae9a93535369fa18bbcbefd9 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 27 Aug 2026 15:59:32 +0200 Subject: [PATCH 483/641] Revert "ACPI: scan: Defer device power initialization" Revert commit dc948f8b384a ("ACPI: scan: Defer device power initialization") that is incomplete and may cause ACPI power management of devices to fail. The problem is that PCI devices are associated with the corresponding ACPI device objects before acpi_bus_attach() runs for them, so after commit dc948f8b384a, ACPI power management will not be initialized for them before making that association. Consequently, the reference counting of ACPI power resources may not work as expected going forward and power management issues may appear. If they appear, they may be elusive and hard to diagnose. While this is fixable, I am not sure if fixing it on top of commit dc948f8b384a is the best way to go, so it is better to revert that commit for now and revisit the whole thing in the next cycle. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/6029658.DvuYhMxLoT@rafael.j.wysocki --- drivers/acpi/power.c | 1 - drivers/acpi/scan.c | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/acpi/power.c b/drivers/acpi/power.c index 922ba9803a93..23a4e207a01e 100644 --- a/drivers/acpi/power.c +++ b/drivers/acpi/power.c @@ -954,7 +954,6 @@ struct acpi_device *acpi_add_power_resource(acpi_handle handle) INIT_LIST_HEAD(&resource->list_node); INIT_LIST_HEAD(&resource->dependents); device->power.state = ACPI_STATE_UNKNOWN; - device->flags.initialized = true; /* Evaluate the object to get the system level and resource order. */ status = acpi_evaluate_object(handle, NULL, NULL, &buffer); diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 7dcf784199fa..f48715ed827c 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -1144,6 +1144,9 @@ static void acpi_bus_get_power_flags(struct acpi_device *device) if (!list_empty(&device->power.states[ACPI_STATE_D3_HOT].resources)) device->power.states[ACPI_STATE_D3_COLD].flags.valid = 1; } + + if (acpi_bus_init_power(device)) + device->flags.power_manageable = 0; } static void acpi_bus_get_flags(struct acpi_device *device) @@ -1825,6 +1828,7 @@ void acpi_init_device_object(struct acpi_device *device, acpi_handle handle, acpi_set_pnp_ids(handle, &device->pnp, type); acpi_init_properties(device); acpi_bus_get_flags(device); + device->flags.initialized = true; device->flags.enumeration_by_parent = acpi_device_enumeration_by_parent(device); acpi_device_clear_enumerated(device); From 74e3b979ce8b78a690f8b94ccf2e2c965f7f5c11 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 27 Aug 2026 13:39:03 +0200 Subject: [PATCH 484/641] ALSA: control: Don't add invalid kcontrols to LED layer The kcontrol LED state layer tries to track the all associated kcontrol elements with naive assumptions that they are readable. But one can create a write-only element that has no get callback (even a user element can do it), and this may lead to a NULL dereference at the call chain of snd_ctl_led_notify(), as found by syzkaller. For avoiding the Oops, add a sanity check of the kcontrol's info and get callbacks, and just skip the invalid kcontrols before assigning the kctl to the LED layer. Reported-by: syzbot+b7fe2760ea6f1ee44b4d@syzkaller.appspotmail.com Closes: https://lore.kernel.org/6a9007b3.1d9ded08.62e62.00cd.GAE@google.com Fixes: 22d8de62f11b ("ALSA: control - add generic LED trigger module as the new control layer") Reviewed-by: Jaroslav Kysela Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260827113951.893291-1-tiwai@suse.de --- sound/core/control_led.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/core/control_led.c b/sound/core/control_led.c index 8cbacee57ce7..3d13bbec1c54 100644 --- a/sound/core/control_led.c +++ b/sound/core/control_led.c @@ -255,6 +255,8 @@ static int snd_ctl_led_set_id(int card_number, struct snd_ctl_elem_id *id, kctl = snd_ctl_find_id(card, id); if (!kctl) return -ENOENT; + if (!kctl->info || !kctl->get) + return -EINVAL; ioff = snd_ctl_get_ioff(kctl, id); vd = &kctl->vd[ioff]; access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK; From f5b8b9037df387394a73aab47c5437bbac975077 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Thu, 20 Aug 2026 00:27:12 +0200 Subject: [PATCH 485/641] arm64: compat: Fix decrementing LDM/STM alignment emulation The compat alignment emulator inherited unsigned long data addresses from the 32-bit ARM implementation. In do_alignment_ldmstm(), nr_regs is an unsigned int holding the transfer size. The function uses the same address addition for both transfer directions, negating nr_regs first for a decrementing LDM or STM. The 32-bit negation wraps before the addition, so the handler adds nearly 4 GiB instead of subtracting the transfer size. The resulting address lies outside the compat task's address space, so decrementing LDM/STM emulation fails, while incrementing forms work. For example, a backwards-moving copy routine using decrementing LDM/STM can take an alignment fault when called with unaligned pointers. The compat handler should emulate the transfer, but this bug instead causes SIGBUS. The offset negated in do_alignment_finish_ldst() is offset_union.un, which is already unsigned long and does not have this width mismatch. Make nr_regs unsigned long so its negation and the address arithmetic use the same width. Fixes: 3fc24ef32d3b ("arm64: compat: Implement misalignment fixups for multiword loads") Cc: stable@vger.kernel.org Suggested-by: Arnd Bergmann Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Karl Mehltretter Signed-off-by: Will Deacon --- arch/arm64/kernel/compat_alignment.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/kernel/compat_alignment.c b/arch/arm64/kernel/compat_alignment.c index b68e1d328d4c..9b58d0aa38d2 100644 --- a/arch/arm64/kernel/compat_alignment.c +++ b/arch/arm64/kernel/compat_alignment.c @@ -114,8 +114,8 @@ do_alignment_ldrdstrd(unsigned long addr, u32 instr, struct pt_regs *regs) static int do_alignment_ldmstm(unsigned long addr, u32 instr, struct pt_regs *regs) { - unsigned int rd, rn, nr_regs, regbits; - unsigned long eaddr, newaddr; + unsigned int rd, rn, regbits; + unsigned long eaddr, newaddr, nr_regs; unsigned int val; /* count the number of registers in the mask to be transferred */ From 8d2237e9d6902e234bb89aabb9cd6a9e91357223 Mon Sep 17 00:00:00 2001 From: Muhammad Usama Anjum Date: Tue, 25 Aug 2026 12:18:34 +0100 Subject: [PATCH 486/641] selftests/arm64: Print missing MTE TAP headers Most MTE tests set a TAP plan and emit results without first printing the TAP version header. Direct execution therefore starts with a plan such as "1..20" instead of "TAP version 13". The problem is particularly visible in the GCR_EL1 context-switch test. It prints its plan before forking 1,024 child processes. When stdout is fully buffered, the plan remains in the stdio buffer. Each child inherits the pending "1..1" line and flushes its copy from exit(), producing repeated plan lines. ksft_print_header() prints the TAP header and enables line buffering. Call it in every MTE test that is missing it. In the GCR_EL1 test, call it before the plan so the plan is flushed before the children are forked. In the remaining tests, call it before setup and prerequisite checks so early failures and whole-test skips also retain the header. Fixes: 29f080881601 ("kselftest/arm64: check GCR_EL1 after context switch") Signed-off-by: Muhammad Usama Anjum Reviewed-by: Vincenzo Frascino Reviewed-by: Mark Brown Signed-off-by: Will Deacon --- tools/testing/selftests/arm64/mte/check_buffer_fill.c | 2 ++ tools/testing/selftests/arm64/mte/check_child_memory.c | 2 ++ tools/testing/selftests/arm64/mte/check_gcr_el1_cswitch.c | 1 + tools/testing/selftests/arm64/mte/check_hugetlb_options.c | 2 ++ tools/testing/selftests/arm64/mte/check_ksm_options.c | 2 ++ tools/testing/selftests/arm64/mte/check_mmap_options.c | 2 ++ tools/testing/selftests/arm64/mte/check_tags_inclusion.c | 2 ++ tools/testing/selftests/arm64/mte/check_user_mem.c | 2 ++ 8 files changed, 15 insertions(+) diff --git a/tools/testing/selftests/arm64/mte/check_buffer_fill.c b/tools/testing/selftests/arm64/mte/check_buffer_fill.c index ff4e07503349..039b1d7d8566 100644 --- a/tools/testing/selftests/arm64/mte/check_buffer_fill.c +++ b/tools/testing/selftests/arm64/mte/check_buffer_fill.c @@ -406,6 +406,8 @@ int main(int argc, char *argv[]) size_t page_size = getpagesize(); int item = ARRAY_SIZE(sizes); + ksft_print_header(); + sizes[item - 3] = page_size - 1; sizes[item - 2] = page_size; sizes[item - 1] = page_size + 1; diff --git a/tools/testing/selftests/arm64/mte/check_child_memory.c b/tools/testing/selftests/arm64/mte/check_child_memory.c index 5e97ee792e4d..e6a8acca2a94 100644 --- a/tools/testing/selftests/arm64/mte/check_child_memory.c +++ b/tools/testing/selftests/arm64/mte/check_child_memory.c @@ -146,6 +146,8 @@ int main(int argc, char *argv[]) int err; int item = ARRAY_SIZE(sizes); + ksft_print_header(); + page_size = getpagesize(); if (!page_size) { ksft_print_msg("ERR: Unable to get page size\n"); diff --git a/tools/testing/selftests/arm64/mte/check_gcr_el1_cswitch.c b/tools/testing/selftests/arm64/mte/check_gcr_el1_cswitch.c index 325bca0de0f6..d23f154d3288 100644 --- a/tools/testing/selftests/arm64/mte/check_gcr_el1_cswitch.c +++ b/tools/testing/selftests/arm64/mte/check_gcr_el1_cswitch.c @@ -131,6 +131,7 @@ int main(int argc, char *argv[]) if (err) return err; + ksft_print_header(); ksft_set_plan(1); evaluate_test(mte_gcr_fork_test(), diff --git a/tools/testing/selftests/arm64/mte/check_hugetlb_options.c b/tools/testing/selftests/arm64/mte/check_hugetlb_options.c index aad1234c7e0f..23e4a7a9950c 100644 --- a/tools/testing/selftests/arm64/mte/check_hugetlb_options.c +++ b/tools/testing/selftests/arm64/mte/check_hugetlb_options.c @@ -230,6 +230,8 @@ int main(int argc, char *argv[]) void *map_ptr; unsigned long map_size; + ksft_print_header(); + err = mte_default_setup(); if (err) return err; diff --git a/tools/testing/selftests/arm64/mte/check_ksm_options.c b/tools/testing/selftests/arm64/mte/check_ksm_options.c index 0cf5faef1724..866f0929b664 100644 --- a/tools/testing/selftests/arm64/mte/check_ksm_options.c +++ b/tools/testing/selftests/arm64/mte/check_ksm_options.c @@ -132,6 +132,8 @@ int main(int argc, char *argv[]) { int err; + ksft_print_header(); + err = mte_default_setup(); if (err) return err; diff --git a/tools/testing/selftests/arm64/mte/check_mmap_options.c b/tools/testing/selftests/arm64/mte/check_mmap_options.c index c100af3012cb..492f2cd41f43 100644 --- a/tools/testing/selftests/arm64/mte/check_mmap_options.c +++ b/tools/testing/selftests/arm64/mte/check_mmap_options.c @@ -945,6 +945,8 @@ int main(int argc, char *argv[]) }, }; + ksft_print_header(); + err = mte_default_setup(); if (err) return err; diff --git a/tools/testing/selftests/arm64/mte/check_tags_inclusion.c b/tools/testing/selftests/arm64/mte/check_tags_inclusion.c index 4b764f2a8185..6b4fa6705d7c 100644 --- a/tools/testing/selftests/arm64/mte/check_tags_inclusion.c +++ b/tools/testing/selftests/arm64/mte/check_tags_inclusion.c @@ -175,6 +175,8 @@ int main(int argc, char *argv[]) { int err; + ksft_print_header(); + err = mte_default_setup(); if (err) return err; diff --git a/tools/testing/selftests/arm64/mte/check_user_mem.c b/tools/testing/selftests/arm64/mte/check_user_mem.c index fb7936c4e097..af343aa61732 100644 --- a/tools/testing/selftests/arm64/mte/check_user_mem.c +++ b/tools/testing/selftests/arm64/mte/check_user_mem.c @@ -201,6 +201,8 @@ int main(int argc, char *argv[]) int tag_offsets[] = {page_sz, MT_GRANULE_SIZE}; char test_name[TEST_NAME_MAX]; + ksft_print_header(); + page_sz = getpagesize(); if (!page_sz) { ksft_print_msg("ERR: Unable to get page size\n"); From 1a0dba077f34a2f8faa98308d30d4b546d073145 Mon Sep 17 00:00:00 2001 From: Muhammad Usama Anjum Date: Tue, 25 Aug 2026 12:18:35 +0100 Subject: [PATCH 487/641] selftests/arm64: Treat KSM merge_across_nodes as optional The MTE KSM test requires write access to KSM sysfs but does not check that it is running as root. It also unconditionally saves, enables and restores the merge_across_nodes attribute. The kernel only creates this attribute when CONFIG_NUMA=y, so a non-NUMA kernel prints the following message three times even though every KSM subtest passes: # ERR: missing /sys/kernel/mm/ksm/merge_across_nodes Skip the test when it is not running as root. Check that the optional attribute is readable and writable, treating ENOENT as its expected absence on non-NUMA kernels and skipping the test for other access failures. Only save, enable and restore the attribute when it is available. Check MTE availability before the privilege and sysfs checks so systems without MTE retain the existing feature-unavailable skip result. This preserves the existing behavior on NUMA kernels without requiring NUMA or reducing KSM coverage on single-node systems. Fixes: f981d8fa2646 ("kselftest/arm64: Verify KSM page merge for MTE pages") Signed-off-by: Muhammad Usama Anjum Reviewed-by: Vincenzo Frascino Reviewed-by: Mark Brown Signed-off-by: Will Deacon --- .../selftests/arm64/mte/check_ksm_options.c | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/arm64/mte/check_ksm_options.c b/tools/testing/selftests/arm64/mte/check_ksm_options.c index 866f0929b664..4855b737d550 100644 --- a/tools/testing/selftests/arm64/mte/check_ksm_options.c +++ b/tools/testing/selftests/arm64/mte/check_ksm_options.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -22,6 +23,20 @@ static size_t page_sz; static unsigned long ksm_sysfs[5]; +static bool has_merge_across_nodes; + +static bool merge_across_nodes_available(void) +{ + const char *path = PATH_KSM "merge_across_nodes"; + + if (!access(path, R_OK | W_OK)) + return true; + if (errno == ENOENT) + return false; + + ksft_exit_skip("Unable to read and write %s: %s\n", path, + strerror(errno)); +} static unsigned long read_sysfs(char *str) { @@ -56,8 +71,10 @@ static void write_sysfs(char *str, unsigned long val) static void mte_ksm_setup(void) { - ksm_sysfs[0] = read_sysfs(PATH_KSM "merge_across_nodes"); - write_sysfs(PATH_KSM "merge_across_nodes", 1); + if (has_merge_across_nodes) { + ksm_sysfs[0] = read_sysfs(PATH_KSM "merge_across_nodes"); + write_sysfs(PATH_KSM "merge_across_nodes", 1); + } ksm_sysfs[1] = read_sysfs(PATH_KSM "sleep_millisecs"); write_sysfs(PATH_KSM "sleep_millisecs", 0); ksm_sysfs[2] = read_sysfs(PATH_KSM "run"); @@ -70,7 +87,8 @@ static void mte_ksm_setup(void) static void mte_ksm_restore(void) { - write_sysfs(PATH_KSM "merge_across_nodes", ksm_sysfs[0]); + if (has_merge_across_nodes) + write_sysfs(PATH_KSM "merge_across_nodes", ksm_sysfs[0]); write_sysfs(PATH_KSM "sleep_millisecs", ksm_sysfs[1]); write_sysfs(PATH_KSM "run", ksm_sysfs[2]); write_sysfs(PATH_KSM "max_page_sharing", ksm_sysfs[3]); @@ -137,6 +155,11 @@ int main(int argc, char *argv[]) err = mte_default_setup(); if (err) return err; + + if (geteuid() != 0) + ksft_exit_skip("Please run the test as root\n"); + + has_merge_across_nodes = merge_across_nodes_available(); page_sz = getpagesize(); if (!page_sz) { ksft_print_msg("ERR: Unable to get page size\n"); From bb52892f9234e4ecd982fa51222aab33ce282f79 Mon Sep 17 00:00:00 2001 From: Muhammad Usama Anjum Date: Tue, 25 Aug 2026 12:18:36 +0100 Subject: [PATCH 488/641] selftests/arm64: Fix MTE prctl TAP plan The MTE prctl test emits one result from check_basic_read() followed by one result for each of the seven entries in mte_modes[]. However, the TAP plan only accounts for the array entries, producing: # Planned tests != run tests (7 != 8) Include the basic read check in the plan so that all eight emitted results are declared. Reviewed-by: Mark Brown Fixes: 1f488fb91378 ("kselftest/arm64/mte: Add MTE_STORE_ONLY testcases") Signed-off-by: Muhammad Usama Anjum Reviewed-by: Vincenzo Frascino Signed-off-by: Will Deacon --- tools/testing/selftests/arm64/mte/check_prctl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/arm64/mte/check_prctl.c b/tools/testing/selftests/arm64/mte/check_prctl.c index f7f320defa7b..d16a91117eef 100644 --- a/tools/testing/selftests/arm64/mte/check_prctl.c +++ b/tools/testing/selftests/arm64/mte/check_prctl.c @@ -119,7 +119,7 @@ int main(void) int i; ksft_print_header(); - ksft_set_plan(ARRAY_SIZE(mte_modes)); + ksft_set_plan(ARRAY_SIZE(mte_modes) + 1); check_basic_read(); for (i = 0; i < ARRAY_SIZE(mte_modes); i++) From 2bd533739234d79b74afabfece1abfe9c6d52c83 Mon Sep 17 00:00:00 2001 From: Muhammad Usama Anjum Date: Tue, 25 Aug 2026 12:18:37 +0100 Subject: [PATCH 489/641] selftests/arm64: Add MTE test config fragment The arm64 selftest collection has no Kconfig fragment, so kernels built with the selftest requirements are not guaranteed to provide the support used by these tests. Add a fragment covering all tests in arm64. Reviewed-by: Mark Brown Signed-off-by: Muhammad Usama Anjum Reviewed-by: Vincenzo Frascino Signed-off-by: Will Deacon --- tools/testing/selftests/arm64/config | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tools/testing/selftests/arm64/config diff --git a/tools/testing/selftests/arm64/config b/tools/testing/selftests/arm64/config new file mode 100644 index 000000000000..0fa975585392 --- /dev/null +++ b/tools/testing/selftests/arm64/config @@ -0,0 +1,17 @@ +CONFIG_ARM64_BTI=y +CONFIG_ARM64_GCS=y +CONFIG_ARM64_MTE=y +CONFIG_ARM64_POE=y +CONFIG_ARM64_PTR_AUTH=y +CONFIG_ARM64_SME=y +CONFIG_ARM64_SVE=y +CONFIG_ARM64_TAGGED_ADDR_ABI=y +CONFIG_HUGETLBFS=y +CONFIG_KSM=y +CONFIG_PROC_FS=y +CONFIG_SECCOMP=y +CONFIG_SECCOMP_FILTER=y +CONFIG_SHMEM=y +CONFIG_SYSCTL=y +CONFIG_SYSFS=y +CONFIG_TMPFS=y From 874ef9a6f2fc45d3f6021842d92fa5420fa4c825 Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Tue, 11 Aug 2026 14:10:06 +0200 Subject: [PATCH 490/641] i2c: designware: Global register definitions Moving the register definitions to a global header file include/linux/designware_i2c.h. That removes the need to duplicate them in the adaptation layers for this driver outside of drivers/i2c/busses/. There is at least one of those in drivers/gpu/drm/xe/xe_i2c.c. Suggested-by: Andy Shevchenko Suggested-by: Raag Jadav Reviewed-by: Raag Jadav Reviewed-by: Mika Westerberg Reviewed-by: Andy Shevchenko Signed-off-by: Heikki Krogerus Acked-by: Mika Westerberg Link: https://patch.msgid.link/20260811121008.1493015-2-heikki.krogerus@linux.intel.com Signed-off-by: Rodrigo Vivi (cherry picked from commit 2ab2fb31411a494e4579dfacda986a2672f80e65) Signed-off-by: Rodrigo Vivi --- MAINTAINERS | 1 + drivers/i2c/busses/i2c-designware-common.c | 2 + drivers/i2c/busses/i2c-designware-core.h | 85 +--------------- drivers/i2c/busses/i2c-designware-master.c | 2 + drivers/i2c/busses/i2c-designware-slave.c | 2 + include/linux/designware_i2c.h | 107 +++++++++++++++++++++ 6 files changed, 116 insertions(+), 83 deletions(-) create mode 100644 include/linux/designware_i2c.h diff --git a/MAINTAINERS b/MAINTAINERS index 928b3ba23a76..1d0685b2cfdb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -26253,6 +26253,7 @@ R: Andy Shevchenko L: linux-i2c@vger.kernel.org S: Supported F: drivers/i2c/busses/i2c-designware-* +F: include/linux/designware_i2c.h SYNOPSYS DESIGNWARE I2C DRIVER - AMDISP M: Nirujogi Pratap diff --git a/drivers/i2c/busses/i2c-designware-common.c b/drivers/i2c/busses/i2c-designware-common.c index e4dfa2ec58bb..a1eca6cd4b75 100644 --- a/drivers/i2c/busses/i2c-designware-common.c +++ b/drivers/i2c/busses/i2c-designware-common.c @@ -33,6 +33,8 @@ #include #include +#include + #include "i2c-designware-core.h" #define DW_IC_DEFAULT_BUS_CAPACITANCE_pF 100 diff --git a/drivers/i2c/busses/i2c-designware-core.h b/drivers/i2c/busses/i2c-designware-core.h index c71aa2dd368d..2c929a6e8da2 100644 --- a/drivers/i2c/busses/i2c-designware-core.h +++ b/drivers/i2c/busses/i2c-designware-core.h @@ -18,6 +18,8 @@ #include #include +#include + #define DW_IC_DEFAULT_FUNCTIONALITY (I2C_FUNC_I2C | \ I2C_FUNC_SMBUS_BYTE | \ I2C_FUNC_SMBUS_BYTE_DATA | \ @@ -25,23 +27,6 @@ I2C_FUNC_SMBUS_BLOCK_DATA | \ I2C_FUNC_SMBUS_I2C_BLOCK) -#define DW_IC_CON_MASTER BIT(0) -#define DW_IC_CON_SPEED_STD (1 << 1) -#define DW_IC_CON_SPEED_FAST (2 << 1) -#define DW_IC_CON_SPEED_HIGH (3 << 1) -#define DW_IC_CON_SPEED_MASK GENMASK(2, 1) -#define DW_IC_CON_10BITADDR_SLAVE BIT(3) -#define DW_IC_CON_10BITADDR_MASTER BIT(4) -#define DW_IC_CON_RESTART_EN BIT(5) -#define DW_IC_CON_SLAVE_DISABLE BIT(6) -#define DW_IC_CON_STOP_DET_IFADDRESSED BIT(7) -#define DW_IC_CON_TX_EMPTY_CTRL BIT(8) -#define DW_IC_CON_RX_FIFO_FULL_HLD_CTRL BIT(9) -#define DW_IC_CON_BUS_CLEAR_CTRL BIT(11) - -#define DW_IC_DATA_CMD_DAT GENMASK(7, 0) -#define DW_IC_DATA_CMD_FIRST_DATA_BYTE BIT(11) - /* * Register access parameters */ @@ -55,65 +40,9 @@ #define DW_IC_FIFO_RX_FIELD GENMASK(15, 8) #define DW_IC_FIFO_MIN_DEPTH 2 -/* - * Registers offset - */ -#define DW_IC_CON 0x00 -#define DW_IC_TAR 0x04 -#define DW_IC_SAR 0x08 -#define DW_IC_DATA_CMD 0x10 -#define DW_IC_SS_SCL_HCNT 0x14 -#define DW_IC_SS_SCL_LCNT 0x18 -#define DW_IC_FS_SCL_HCNT 0x1c -#define DW_IC_FS_SCL_LCNT 0x20 -#define DW_IC_HS_SCL_HCNT 0x24 -#define DW_IC_HS_SCL_LCNT 0x28 -#define DW_IC_INTR_STAT 0x2c -#define DW_IC_INTR_MASK 0x30 -#define DW_IC_RAW_INTR_STAT 0x34 -#define DW_IC_RX_TL 0x38 -#define DW_IC_TX_TL 0x3c -#define DW_IC_CLR_INTR 0x40 -#define DW_IC_CLR_RX_UNDER 0x44 -#define DW_IC_CLR_RX_OVER 0x48 -#define DW_IC_CLR_TX_OVER 0x4c -#define DW_IC_CLR_RD_REQ 0x50 -#define DW_IC_CLR_TX_ABRT 0x54 -#define DW_IC_CLR_RX_DONE 0x58 -#define DW_IC_CLR_ACTIVITY 0x5c -#define DW_IC_CLR_STOP_DET 0x60 -#define DW_IC_CLR_START_DET 0x64 -#define DW_IC_CLR_GEN_CALL 0x68 -#define DW_IC_ENABLE 0x6c -#define DW_IC_STATUS 0x70 -#define DW_IC_TXFLR 0x74 -#define DW_IC_RXFLR 0x78 -#define DW_IC_SDA_HOLD 0x7c -#define DW_IC_TX_ABRT_SOURCE 0x80 -#define DW_IC_ENABLE_STATUS 0x9c -#define DW_IC_CLR_RESTART_DET 0xa8 -#define DW_IC_SMBUS_INTR_MASK 0xcc -#define DW_IC_COMP_PARAM_1 0xf4 -#define DW_IC_COMP_VERSION 0xf8 #define DW_IC_SDA_HOLD_MIN_VERS 0x3131312A /* "111*" == v1.11* */ -#define DW_IC_COMP_TYPE 0xfc #define DW_IC_COMP_TYPE_VALUE 0x44570140 /* "DW" + 0x0140 */ -#define DW_IC_INTR_RX_UNDER BIT(0) -#define DW_IC_INTR_RX_OVER BIT(1) -#define DW_IC_INTR_RX_FULL BIT(2) -#define DW_IC_INTR_TX_OVER BIT(3) -#define DW_IC_INTR_TX_EMPTY BIT(4) -#define DW_IC_INTR_RD_REQ BIT(5) -#define DW_IC_INTR_TX_ABRT BIT(6) -#define DW_IC_INTR_RX_DONE BIT(7) -#define DW_IC_INTR_ACTIVITY BIT(8) -#define DW_IC_INTR_STOP_DET BIT(9) -#define DW_IC_INTR_START_DET BIT(10) -#define DW_IC_INTR_GEN_CALL BIT(11) -#define DW_IC_INTR_RESTART_DET BIT(12) -#define DW_IC_INTR_MST_ON_HOLD BIT(13) - #define DW_IC_INTR_DEFAULT_MASK (DW_IC_INTR_RX_FULL | \ DW_IC_INTR_TX_ABRT | \ DW_IC_INTR_STOP_DET) @@ -123,16 +52,6 @@ DW_IC_INTR_RX_UNDER | \ DW_IC_INTR_RD_REQ) -#define DW_IC_ENABLE_ENABLE BIT(0) -#define DW_IC_ENABLE_ABORT BIT(1) - -#define DW_IC_STATUS_ACTIVITY BIT(0) -#define DW_IC_STATUS_TFE BIT(2) -#define DW_IC_STATUS_RFNE BIT(3) -#define DW_IC_STATUS_MASTER_ACTIVITY BIT(5) -#define DW_IC_STATUS_SLAVE_ACTIVITY BIT(6) -#define DW_IC_STATUS_MASTER_HOLD_TX_FIFO_EMPTY BIT(7) - #define DW_IC_SDA_HOLD_RX_SHIFT 16 #define DW_IC_SDA_HOLD_RX_MASK GENMASK(23, 16) diff --git a/drivers/i2c/busses/i2c-designware-master.c b/drivers/i2c/busses/i2c-designware-master.c index 7a301c8b604e..a1bcc3797e4f 100644 --- a/drivers/i2c/busses/i2c-designware-master.c +++ b/drivers/i2c/busses/i2c-designware-master.c @@ -25,6 +25,8 @@ #include #include +#include + #include "i2c-designware-core.h" #define AMD_TIMEOUT_MIN_US 25 diff --git a/drivers/i2c/busses/i2c-designware-slave.c b/drivers/i2c/busses/i2c-designware-slave.c index ad0d5fbfa6d5..0abcc7757b23 100644 --- a/drivers/i2c/busses/i2c-designware-slave.c +++ b/drivers/i2c/busses/i2c-designware-slave.c @@ -19,6 +19,8 @@ #include #include +#include + #include "i2c-designware-core.h" int i2c_dw_reg_slave(struct i2c_client *slave) diff --git a/include/linux/designware_i2c.h b/include/linux/designware_i2c.h new file mode 100644 index 000000000000..53f37f18a722 --- /dev/null +++ b/include/linux/designware_i2c.h @@ -0,0 +1,107 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Synopsys DesignWare I2C register definitions + * + * Copyright (C) 2026, Intel Corporation + */ + +#ifndef __LINUX_DESIGNWARE_I2C_H +#define __LINUX_DESIGNWARE_I2C_H + +#include + +/* + * Registers offset + */ +#define DW_IC_CON 0x00 +#define DW_IC_TAR 0x04 +#define DW_IC_SAR 0x08 +#define DW_IC_DATA_CMD 0x10 +#define DW_IC_SS_SCL_HCNT 0x14 +#define DW_IC_SS_SCL_LCNT 0x18 +#define DW_IC_FS_SCL_HCNT 0x1c +#define DW_IC_FS_SCL_LCNT 0x20 +#define DW_IC_HS_SCL_HCNT 0x24 +#define DW_IC_HS_SCL_LCNT 0x28 +#define DW_IC_INTR_STAT 0x2c +#define DW_IC_INTR_MASK 0x30 +#define DW_IC_RAW_INTR_STAT 0x34 +#define DW_IC_RX_TL 0x38 +#define DW_IC_TX_TL 0x3c +#define DW_IC_CLR_INTR 0x40 +#define DW_IC_CLR_RX_UNDER 0x44 +#define DW_IC_CLR_RX_OVER 0x48 +#define DW_IC_CLR_TX_OVER 0x4c +#define DW_IC_CLR_RD_REQ 0x50 +#define DW_IC_CLR_TX_ABRT 0x54 +#define DW_IC_CLR_RX_DONE 0x58 +#define DW_IC_CLR_ACTIVITY 0x5c +#define DW_IC_CLR_STOP_DET 0x60 +#define DW_IC_CLR_START_DET 0x64 +#define DW_IC_CLR_GEN_CALL 0x68 +#define DW_IC_ENABLE 0x6c +#define DW_IC_STATUS 0x70 +#define DW_IC_TXFLR 0x74 +#define DW_IC_RXFLR 0x78 +#define DW_IC_SDA_HOLD 0x7c +#define DW_IC_TX_ABRT_SOURCE 0x80 +#define DW_IC_ENABLE_STATUS 0x9c +#define DW_IC_CLR_RESTART_DET 0xa8 +#define DW_IC_SMBUS_INTR_STAT 0xc8 +#define DW_IC_SMBUS_INTR_MASK 0xcc +#define DW_IC_CLR_SMBUS_INTR 0xd4 +#define DW_IC_COMP_PARAM_1 0xf4 +#define DW_IC_COMP_VERSION 0xf8 +#define DW_IC_COMP_TYPE 0xfc + +/* DW_IC_CON bits */ +#define DW_IC_CON_MASTER BIT(0) +#define DW_IC_CON_SPEED_STD (1 << 1) +#define DW_IC_CON_SPEED_FAST (2 << 1) +#define DW_IC_CON_SPEED_HIGH (3 << 1) +#define DW_IC_CON_SPEED_MASK GENMASK(2, 1) +#define DW_IC_CON_10BITADDR_SLAVE BIT(3) +#define DW_IC_CON_10BITADDR_MASTER BIT(4) +#define DW_IC_CON_RESTART_EN BIT(5) +#define DW_IC_CON_SLAVE_DISABLE BIT(6) +#define DW_IC_CON_STOP_DET_IFADDRESSED BIT(7) +#define DW_IC_CON_TX_EMPTY_CTRL BIT(8) +#define DW_IC_CON_RX_FIFO_FULL_HLD_CTRL BIT(9) +#define DW_IC_CON_BUS_CLEAR_CTRL BIT(11) + +/* DW_IC_DATA_CMD bits */ +#define DW_IC_DATA_CMD_DAT GENMASK(7, 0) +#define DW_IC_DATA_CMD_FIRST_DATA_BYTE BIT(11) + +/* DW_IC_INTR_* bits */ +#define DW_IC_INTR_RX_UNDER BIT(0) +#define DW_IC_INTR_RX_OVER BIT(1) +#define DW_IC_INTR_RX_FULL BIT(2) +#define DW_IC_INTR_TX_OVER BIT(3) +#define DW_IC_INTR_TX_EMPTY BIT(4) +#define DW_IC_INTR_RD_REQ BIT(5) +#define DW_IC_INTR_TX_ABRT BIT(6) +#define DW_IC_INTR_RX_DONE BIT(7) +#define DW_IC_INTR_ACTIVITY BIT(8) +#define DW_IC_INTR_STOP_DET BIT(9) +#define DW_IC_INTR_START_DET BIT(10) +#define DW_IC_INTR_GEN_CALL BIT(11) +#define DW_IC_INTR_RESTART_DET BIT(12) +#define DW_IC_INTR_MST_ON_HOLD BIT(13) + +/* DW_IC_ENABLE bits */ +#define DW_IC_ENABLE_ENABLE BIT(0) +#define DW_IC_ENABLE_ABORT BIT(1) + +/* DW_IC_STATUS bits */ +#define DW_IC_STATUS_ACTIVITY BIT(0) +#define DW_IC_STATUS_TFE BIT(2) +#define DW_IC_STATUS_RFNE BIT(3) +#define DW_IC_STATUS_MASTER_ACTIVITY BIT(5) +#define DW_IC_STATUS_SLAVE_ACTIVITY BIT(6) +#define DW_IC_STATUS_MASTER_HOLD_TX_FIFO_EMPTY BIT(7) + +/* DW_IC_SMBUS_INTR_* bits */ +#define DW_IC_SMBUS_INTR_ALERT BIT(10) + +#endif /* __LINUX_DESIGNWARE_I2C_H */ From f43fa4b8522ba6038b77e86e5f0d94be35effcde Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Tue, 11 Aug 2026 14:10:07 +0200 Subject: [PATCH 491/641] drm/xe/i2c: Fix the interrupt handling The platforms that support the interrupt from the I2C adapter can not handle the amount of interrupts the adapter generates because of the way the IRQ is routed in the hardware. The I2C controller driver has to be kept in polling mode because of that. The AMC MCU can still generate critical alerts that have to be handled. The interrupt from SMBus Alert is left enabled and handled separately in the Xe. The alerts from the AMC will cause the device to be declared wedged for now. Fixes: f0e53aadd702 ("drm/xe: Support for I2C attached MCUs") Cc: stable@vger.kernel.org Reviewed-by: Raag Jadav Co-developed-by: Ramesh Babu B Signed-off-by: Ramesh Babu B Signed-off-by: Heikki Krogerus Link: https://patch.msgid.link/20260811121008.1493015-3-heikki.krogerus@linux.intel.com Signed-off-by: Rodrigo Vivi (cherry picked from commit a55b76b8bc2c49b11d753c1c6d06ec3a2c61c85e) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/Makefile | 4 +- drivers/gpu/drm/xe/regs/xe_i2c_regs.h | 2 + drivers/gpu/drm/xe/xe_amc.c | 197 ++++++++++++++++++++++++++ drivers/gpu/drm/xe/xe_amc.h | 25 ++++ drivers/gpu/drm/xe/xe_i2c.c | 128 +++++++---------- drivers/gpu/drm/xe/xe_i2c.h | 13 +- 6 files changed, 282 insertions(+), 87 deletions(-) create mode 100644 drivers/gpu/drm/xe/xe_amc.c create mode 100644 drivers/gpu/drm/xe/xe_amc.h diff --git a/drivers/gpu/drm/xe/Makefile b/drivers/gpu/drm/xe/Makefile index 67ada1d6c2fb..c92468cb9b89 100644 --- a/drivers/gpu/drm/xe/Makefile +++ b/drivers/gpu/drm/xe/Makefile @@ -153,7 +153,9 @@ xe-y += xe_bb.o \ xe_wait_user_fence.o \ xe_wopcm.o -xe-$(CONFIG_I2C) += xe_i2c.o +xe-$(CONFIG_I2C) += xe_i2c.o \ + xe_amc.o + xe-$(CONFIG_DRM_XE_GPUSVM) += xe_svm.o xe-$(CONFIG_DRM_GPUSVM) += xe_userptr.o diff --git a/drivers/gpu/drm/xe/regs/xe_i2c_regs.h b/drivers/gpu/drm/xe/regs/xe_i2c_regs.h index f2e455e2bfe4..37550e4a20f8 100644 --- a/drivers/gpu/drm/xe/regs/xe_i2c_regs.h +++ b/drivers/gpu/drm/xe/regs/xe_i2c_regs.h @@ -20,4 +20,6 @@ #define I2C_CONFIG_CMD XE_REG(I2C_CONFIG_SPACE_OFFSET + PCI_COMMAND) #define I2C_CONFIG_PMCSR XE_REG(I2C_CONFIG_SPACE_OFFSET + 0x84) +#define I2C_REG(reg) XE_REG((reg) + I2C_MEM_SPACE_OFFSET) + #endif /* _XE_I2C_REGS_H_ */ diff --git a/drivers/gpu/drm/xe/xe_amc.c b/drivers/gpu/drm/xe/xe_amc.c new file mode 100644 index 000000000000..8ecadee6eea3 --- /dev/null +++ b/drivers/gpu/drm/xe/xe_amc.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2026 Intel Corporation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "regs/xe_i2c_regs.h" + +#include "xe_amc.h" +#include "xe_device.h" +#include "xe_i2c.h" +#include "xe_mmio.h" + +/** + * DOC: Add-In Management Controller (AMC) + * + * Handler for the SMBus Alerts from the AMC. All the alerts from AMC will cause + * the device to be declared wedged. + */ + +#define AMC_COMMAND 0x0f +#define AMC_GPU_I2C_ADDR 0x8f +#define AMC_VERSION_V1 0x01 +#define AMC_DESTINATION_ID 12 +#define AMC_SOURCE_ID 8 +#define AMC_FLAGS 0xc8 + +#define AMC_MSG_TYPE 0x7e +#define AMC_GET_ALERT_REASON 0x01 + +enum xe_amc_alert { + AMC_ALERT_UNKNOWN, + AMC_ALERT_FW_DOWNLOAD, + AMC_ALERT_THERMAL_TRIP, + AMC_ALERT_OOB_REQUEST, + AMC_ALERT_OOB_RESET, + AMC_ALERT_CATERR, +}; + +static const char * const amc_alert[] = { + [AMC_ALERT_FW_DOWNLOAD] = "Firmware Download", + [AMC_ALERT_THERMAL_TRIP] = "Thermal Trip", + [AMC_ALERT_OOB_REQUEST] = "OOB Request", + [AMC_ALERT_OOB_RESET] = "OOB Reset", + [AMC_ALERT_CATERR] = "Catastrophic", +}; + +struct xe_amc { + struct xe_i2c *i2c; + struct work_struct work; +}; + +struct amc_header { + u8 command; + u8 len; + u8 address; + u8 version; + u8 destination; + u8 source; + u8 flags; +} __packed; + +struct amc_message { + u8 type; + u16 vendor; + u8 command; +} __packed; + +struct amc_request { + struct amc_header header; + struct amc_message message; + u32 reserved; +} __packed; + +struct amc_response { + struct amc_header header; + struct amc_message message; + u8 error; + u8 value; +} __packed; + +static const struct amc_request amc_get_alert_reason = { + .header = { + .command = AMC_COMMAND, + .len = sizeof(struct amc_request) - 2, + .address = AMC_GPU_I2C_ADDR, + .version = AMC_VERSION_V1, + .destination = AMC_DESTINATION_ID, + .source = AMC_SOURCE_ID, + .flags = AMC_FLAGS, + }, + .message = { + .type = AMC_MSG_TYPE, + .vendor = htons(PCI_VENDOR_ID_INTEL), + .command = AMC_GET_ALERT_REASON, + }, +}; + +static void xe_amc_work(struct work_struct *work) +{ + const struct amc_request *request = &amc_get_alert_reason; + struct xe_amc *amc = from_work(amc, work, work); + u8 alert_reason = AMC_ALERT_UNKNOWN; + struct amc_response response; + struct i2c_client *client; + int ret; + + client = amc->i2c->client[XE_I2C_CLIENT_AMC]; + if (IS_ERR_OR_NULL(client)) + goto out_reassert_interrupt; + + ret = i2c_master_send(client, (u8 *)request, sizeof(*request)); + if (ret < 0) { + dev_err(&client->dev, "failed to send request (%d)\n", ret); + goto out_reassert_interrupt; + } + + /* AMC needs 20ms to generate the response. */ + fsleep(20 * USEC_PER_MSEC); + + ret = i2c_master_recv(client, (u8 *)&response, sizeof(response)); + if (ret < 0) { + dev_err(&client->dev, "failed to read response (%d)\n", ret); + goto out_reassert_interrupt; + } + + if (!response.header.len) { + dev_err(&client->dev, "empty response from AMC\n"); + goto out_reassert_interrupt; + } + + if (memcmp(&response.message, &request->message, sizeof(struct amc_message))) { + dev_err(&client->dev, "response does not match the request\n"); + goto out_reassert_interrupt; + } + + if (response.error) { + dev_err(&client->dev, "AMC error 0x%02x\n", response.error); + goto out_reassert_interrupt; + } + + alert_reason = response.value; + dev_dbg(&client->dev, "Alert reason: %d\n", alert_reason); + +out_reassert_interrupt: + xe_mmio_rmw32(amc->i2c->mmio, I2C_CONFIG_CMD, PCI_COMMAND_INTX_DISABLE, 0); + + switch (alert_reason) { + case AMC_ALERT_FW_DOWNLOAD: + case AMC_ALERT_THERMAL_TRIP: + case AMC_ALERT_OOB_REQUEST: + case AMC_ALERT_OOB_RESET: + case AMC_ALERT_CATERR: + dev_warn(amc->i2c->drm_dev, "AMC Alert: %s\n", amc_alert[alert_reason]); + xe_device_declare_wedged(i2c_client_to_xe_device(client)); + break; + default: + dev_warn(amc->i2c->drm_dev, "unknown AMC alert: %d\n", alert_reason); + break; + } +} + +void xe_amc_handle_alert(struct xe_i2c *i2c) +{ + queue_work(system_long_wq, &i2c->amc->work); +} + +int xe_amc_init(struct xe_i2c *i2c) +{ + struct xe_amc *amc; + + amc = kzalloc(sizeof(*amc), GFP_KERNEL); + if (!amc) + return -ENOMEM; + + INIT_WORK(&amc->work, xe_amc_work); + i2c->amc = amc; + amc->i2c = i2c; + + return 0; +} + +void xe_amc_exit(struct xe_i2c *i2c) +{ + if (i2c->amc) { + cancel_work_sync(&i2c->amc->work); + kfree(i2c->amc); + } +} diff --git a/drivers/gpu/drm/xe/xe_amc.h b/drivers/gpu/drm/xe/xe_amc.h new file mode 100644 index 000000000000..b1d5311fee53 --- /dev/null +++ b/drivers/gpu/drm/xe/xe_amc.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _XE_AMC_H_ +#define _XE_AMC_H_ + +#include + +#include "xe_device.h" + +struct xe_i2c; + +static inline struct xe_device *i2c_adapter_to_xe_device(struct i2c_adapter *adapter) +{ + return kdev_to_xe_device(adapter->dev.parent->parent); +} + +static inline struct xe_device *i2c_client_to_xe_device(struct i2c_client *client) +{ + return i2c_adapter_to_xe_device(client->adapter); +} + +int xe_amc_init(struct xe_i2c *i2c); +void xe_amc_exit(struct xe_i2c *i2c); +void xe_amc_handle_alert(struct xe_i2c *i2c); + +#endif /* _XE_AMC_H_ */ diff --git a/drivers/gpu/drm/xe/xe_i2c.c b/drivers/gpu/drm/xe/xe_i2c.c index a26c38bb17a1..32767570e43d 100644 --- a/drivers/gpu/drm/xe/xe_i2c.c +++ b/drivers/gpu/drm/xe/xe_i2c.c @@ -12,8 +12,6 @@ #include #include #include -#include -#include #include #include #include @@ -24,9 +22,12 @@ #include #include +#include + #include "regs/xe_i2c_regs.h" #include "regs/xe_irq_regs.h" +#include "xe_amc.h" #include "xe_device.h" #include "xe_i2c.h" #include "xe_mmio.h" @@ -61,16 +62,32 @@ static inline void xe_i2c_read_endpoint(struct xe_mmio *mmio, void *ep) val[1] = xe_mmio_read32(mmio, REG_SG_REMAP_ADDR_POSTFIX); } +static void xe_i2c_handle_smbus_alert(struct xe_i2c *i2c) +{ + u32 stat; + + stat = xe_mmio_read32(i2c->mmio, I2C_REG(DW_IC_SMBUS_INTR_STAT)); + if (!stat) + return; + + xe_mmio_write32(i2c->mmio, I2C_REG(DW_IC_CLR_SMBUS_INTR), stat); + + if (stat & DW_IC_SMBUS_INTR_ALERT && i2c->amc) + xe_amc_handle_alert(i2c); + else + xe_mmio_rmw32(i2c->mmio, I2C_CONFIG_CMD, PCI_COMMAND_INTX_DISABLE, 0); +} + static void xe_i2c_client_work(struct work_struct *work) { struct xe_i2c *i2c = container_of(work, struct xe_i2c, work); struct i2c_board_info info = { .type = "amc", .flags = I2C_CLIENT_HOST_NOTIFY, - .addr = i2c->ep.addr[1], + .addr = i2c->ep.addr[XE_I2C_CLIENT_AMC], }; - i2c->client[0] = i2c_new_client_device(i2c->adapter, &info); + i2c->client[XE_I2C_CLIENT_AMC] = i2c_new_client_device(i2c->adapter, &info); } static int xe_i2c_notifier(struct notifier_block *nb, unsigned long action, void *data) @@ -115,16 +132,6 @@ static int xe_i2c_register_adapter(struct xe_i2c *i2c) goto err_fwnode_remove; } - if (i2c->adapter_irq) { - struct resource res; - - res = DEFINE_RES_IRQ_NAMED(i2c->adapter_irq, "xe_i2c"); - - ret = platform_device_add_resources(pdev, &res, 1); - if (ret) - goto err_pdev_put; - } - pdev->dev.parent = i2c->drm_dev; pdev->dev.fwnode = fwnode; i2c->adapter_node = fwnode; @@ -166,7 +173,8 @@ bool xe_i2c_present(struct xe_device *xe) static bool xe_i2c_irq_present(struct xe_device *xe) { - return xe->i2c && xe->i2c->adapter_irq; + return xe->i2c && xe->i2c->ep.capabilities & XE_I2C_EP_CAP_IRQ && + !xe_survivability_mode_is_boot_enabled(xe); } /** @@ -179,18 +187,10 @@ static bool xe_i2c_irq_present(struct xe_device *xe) */ void xe_i2c_irq_handler(struct xe_device *xe, u32 master_ctl) { - struct xe_mmio *mmio = xe_root_tile_mmio(xe); - if (!(master_ctl & I2C_IRQ) || !xe_i2c_irq_present(xe)) return; - /* Forward interrupt to I2C adapter */ - generic_handle_irq_safe(xe->i2c->adapter_irq); - - /* Deassert after I2C adapter clears the interrupt */ - xe_mmio_rmw32(mmio, I2C_CONFIG_CMD, 0, PCI_COMMAND_INTX_DISABLE); - /* Reassert to allow subsequent interrupt generation */ - xe_mmio_rmw32(mmio, I2C_CONFIG_CMD, PCI_COMMAND_INTX_DISABLE, 0); + xe_i2c_handle_smbus_alert(xe->i2c); } void xe_i2c_irq_reset(struct xe_device *xe) @@ -215,45 +215,6 @@ void xe_i2c_irq_postinstall(struct xe_device *xe) xe_mmio_rmw32(mmio, I2C_CONFIG_CMD, PCI_COMMAND_INTX_DISABLE, 0); } -static int xe_i2c_irq_map(struct irq_domain *h, unsigned int virq, - irq_hw_number_t hw_irq_num) -{ - irq_set_chip_and_handler(virq, &dummy_irq_chip, handle_simple_irq); - return 0; -} - -static const struct irq_domain_ops xe_i2c_irq_ops = { - .map = xe_i2c_irq_map, -}; - -static int xe_i2c_create_irq(struct xe_device *xe) -{ - struct xe_i2c *i2c = xe->i2c; - struct irq_domain *domain; - - if (!(i2c->ep.capabilities & XE_I2C_EP_CAP_IRQ) || - xe_survivability_mode_is_boot_enabled(xe)) - return 0; - - domain = irq_domain_create_linear(dev_fwnode(i2c->drm_dev), 1, &xe_i2c_irq_ops, NULL); - if (!domain) - return -ENOMEM; - - i2c->adapter_irq = irq_create_mapping(domain, 0); - i2c->irqdomain = domain; - - return 0; -} - -static void xe_i2c_remove_irq(struct xe_i2c *i2c) -{ - if (!i2c->irqdomain) - return; - - irq_dispose_mapping(i2c->adapter_irq); - irq_domain_remove(i2c->irqdomain); -} - static int xe_i2c_read(void *context, unsigned int reg, unsigned int *val) { struct xe_i2c *i2c = context; @@ -267,8 +228,16 @@ static int xe_i2c_write(void *context, unsigned int reg, unsigned int val) { struct xe_i2c *i2c = context; - xe_mmio_write32(i2c->mmio, XE_REG(reg + I2C_MEM_SPACE_OFFSET), val); + switch (reg) { + case DW_IC_SMBUS_INTR_MASK: + /* Make sure the Alert is never masked. */ + val |= DW_IC_SMBUS_INTR_ALERT; + break; + default: + break; + } + xe_mmio_write32(i2c->mmio, I2C_REG(reg), val); return 0; } @@ -310,12 +279,15 @@ static void xe_i2c_remove(void *data) struct xe_i2c *i2c = data; unsigned int i; - for (i = 0; i < XE_I2C_MAX_CLIENTS; i++) + xe_amc_exit(i2c); + + for (i = 0; i < XE_I2C_MAX_CLIENTS; i++) { i2c_unregister_device(i2c->client[i]); + i2c->client[i] = NULL; + } bus_unregister_notifier(&i2c_bus_type, &i2c->bus_notifier); xe_i2c_unregister_adapter(i2c); - xe_i2c_remove_irq(i2c); } /** @@ -363,22 +335,18 @@ int xe_i2c_probe(struct xe_device *xe) if (ret) return ret; - ret = xe_i2c_create_irq(xe); - if (ret) - goto err_unregister_notifier; - ret = xe_i2c_register_adapter(i2c); - if (ret) - goto err_remove_irq; + if (ret) { + bus_unregister_notifier(&i2c_bus_type, &i2c->bus_notifier); + return ret; + } + + ret = xe_amc_init(i2c); + if (ret) { + xe_i2c_remove(i2c); + return ret; + } xe_i2c_irq_postinstall(xe); return devm_add_action_or_reset(drm_dev, xe_i2c_remove, i2c); - -err_remove_irq: - xe_i2c_remove_irq(i2c); - -err_unregister_notifier: - bus_unregister_notifier(&i2c_bus_type, &i2c->bus_notifier); - - return ret; } diff --git a/drivers/gpu/drm/xe/xe_i2c.h b/drivers/gpu/drm/xe/xe_i2c.h index 425d8160835f..b200966b0048 100644 --- a/drivers/gpu/drm/xe/xe_i2c.h +++ b/drivers/gpu/drm/xe/xe_i2c.h @@ -11,18 +11,21 @@ struct device; struct fwnode_handle; struct i2c_adapter; struct i2c_client; -struct irq_domain; struct platform_device; +struct xe_amc; struct xe_device; struct xe_mmio; -#define XE_I2C_MAX_CLIENTS 3 - #define XE_I2C_EP_COOKIE_DEVICE 0xde /* Endpoint Capabilities */ #define XE_I2C_EP_CAP_IRQ BIT(0) +enum XE_I2C_CLIENT { + XE_I2C_CLIENT_AMC = 1, + XE_I2C_MAX_CLIENTS = 3, +}; + struct xe_i2c_endpoint { u8 cookie; u8 capabilities; @@ -38,13 +41,11 @@ struct xe_i2c { struct notifier_block bus_notifier; struct work_struct work; - struct irq_domain *irqdomain; - int adapter_irq; - struct xe_i2c_endpoint ep; struct device *drm_dev; struct xe_mmio *mmio; + struct xe_amc *amc; }; #if IS_ENABLED(CONFIG_I2C) From 244abef7f280a6a84297bfab5fd2e77147bc419a Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Tue, 11 Aug 2026 14:10:08 +0200 Subject: [PATCH 492/641] drm/xe/i2c: Keep the i2c controller always enabled Some platforms make an assumption that the i2c controller's enabled state indicates also the power state of the controller. This can create a problem when the controller is in disabled state, because the hardware may assume incorrectly that it is then also in low-power state. To fix this, the controller is kept enabled by taking over the IC_ENABLE register. The controller has to be disabled when the configuration is updated and when the target address or the slave address are assigned, so disabling it when IC_CON, IC_TAR or IC_SAR registers are programmed, and then re-enabling it again. Fixes: f0e53aadd702 ("drm/xe: Support for I2C attached MCUs") Cc: stable@vger.kernel.org Signed-off-by: Heikki Krogerus Reviewed-by: Rodrigo Vivi Link: https://patch.msgid.link/20260811121008.1493015-4-heikki.krogerus@linux.intel.com Signed-off-by: Rodrigo Vivi (cherry picked from commit 76cc14e2faed1adae20f4ee144ead0e3a7566c49) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_i2c.c | 49 ++++++++++++++++++++++++++++++++++++- drivers/gpu/drm/xe/xe_i2c.h | 1 + 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_i2c.c b/drivers/gpu/drm/xe/xe_i2c.c index 32767570e43d..d8fa68206f41 100644 --- a/drivers/gpu/drm/xe/xe_i2c.c +++ b/drivers/gpu/drm/xe/xe_i2c.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -215,11 +216,40 @@ void xe_i2c_irq_postinstall(struct xe_device *xe) xe_mmio_rmw32(mmio, I2C_CONFIG_CMD, PCI_COMMAND_INTX_DISABLE, 0); } +/* See "Disabling DW_apb_i2c" in the DesignWare DW_abp_i2c databook. */ +static void xe_i2c_disable(struct xe_i2c *i2c) +{ + int timeout = 100; + u32 status; + + xe_mmio_rmw32(i2c->mmio, I2C_REG(DW_IC_ENABLE), DW_IC_ENABLE_ENABLE, 0); + + do { + status = xe_mmio_read32(i2c->mmio, I2C_REG(DW_IC_ENABLE_STATUS)); + if (!(status & DW_IC_ENABLE_ENABLE)) + return; + /* Can't sleep here. */ + udelay(25); + } while (timeout--); + + dev_warn(i2c->drm_dev, "timeout in disabling i2c adapter\n"); +} + static int xe_i2c_read(void *context, unsigned int reg, unsigned int *val) { struct xe_i2c *i2c = context; - *val = xe_mmio_read32(i2c->mmio, XE_REG(reg + I2C_MEM_SPACE_OFFSET)); + *val = xe_mmio_read32(i2c->mmio, I2C_REG(reg)); + + switch (reg) { + case DW_IC_ENABLE: + case DW_IC_ENABLE_STATUS: + FIELD_MODIFY(DW_IC_ENABLE_ENABLE, val, + i2c->ic_enable & DW_IC_ENABLE_ENABLE); + break; + default: + break; + } return 0; } @@ -229,6 +259,23 @@ static int xe_i2c_write(void *context, unsigned int reg, unsigned int val) struct xe_i2c *i2c = context; switch (reg) { + case DW_IC_CON: + case DW_IC_TAR: + case DW_IC_SAR: + /* Disable the controller. */ + xe_i2c_disable(i2c); + + /* Write the register. */ + xe_mmio_write32(i2c->mmio, I2C_REG(reg), val); + + /* Enable the controller. */ + xe_mmio_rmw32(i2c->mmio, I2C_REG(DW_IC_ENABLE), 0, DW_IC_ENABLE_ENABLE); + return 0; + case DW_IC_ENABLE: + i2c->ic_enable = val; + /* Other fields can be updated except the enable bit. */ + val |= DW_IC_ENABLE_ENABLE; + break; case DW_IC_SMBUS_INTR_MASK: /* Make sure the Alert is never masked. */ val |= DW_IC_SMBUS_INTR_ALERT; diff --git a/drivers/gpu/drm/xe/xe_i2c.h b/drivers/gpu/drm/xe/xe_i2c.h index b200966b0048..d63adacfefe7 100644 --- a/drivers/gpu/drm/xe/xe_i2c.h +++ b/drivers/gpu/drm/xe/xe_i2c.h @@ -37,6 +37,7 @@ struct xe_i2c { struct platform_device *pdev; struct i2c_adapter *adapter; struct i2c_client *client[XE_I2C_MAX_CLIENTS]; + unsigned int ic_enable; struct notifier_block bus_notifier; struct work_struct work; From a62212b35a214c2ff3bd1c785a440d7ad8205ec9 Mon Sep 17 00:00:00 2001 From: Anoop Vijay Date: Tue, 25 Aug 2026 10:28:28 -0700 Subject: [PATCH 493/641] drm/xe/sysctrl: Read mailbox phase bit from hardware The mailbox PHASE bit in SYSCTRL_MB_CTRL is toggled per-message and was tracked in software as sc->phase_bit, reset to 0 on error paths. If the cached value ever drifts from what the hardware last saw, all following messages carry the wrong phase and get silently misread by firmware. Drop the cache and read PHASE directly from SYSCTRL_MB_CTRL before each frame instead, removing xe_sysctrl_mailbox_init() and its call sites along with it. Fixes: 1f95f618182b ("drm/xe/xe_sysctrl: Add System Controller mailbox communication support") Signed-off-by: Anoop Vijay Reviewed-by: Umesh Nerlige Ramappa Reviewed-by: Rodrigo Vivi Link: https://patch.msgid.link/20260825172827.3801591-2-anoop.c.vijay@intel.com Signed-off-by: Rodrigo Vivi (cherry picked from commit 04984fcdbf6876c940c01026a7404c1e9cc91ba7) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_sysctrl.c | 7 +------ drivers/gpu/drm/xe/xe_sysctrl_mailbox.c | 22 ++++------------------ drivers/gpu/drm/xe/xe_sysctrl_mailbox.h | 1 - drivers/gpu/drm/xe/xe_sysctrl_types.h | 3 --- 4 files changed, 5 insertions(+), 28 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_sysctrl.c b/drivers/gpu/drm/xe/xe_sysctrl.c index 1db20be8158b..62ccc9be71b4 100644 --- a/drivers/gpu/drm/xe/xe_sysctrl.c +++ b/drivers/gpu/drm/xe/xe_sysctrl.c @@ -85,7 +85,6 @@ int xe_sysctrl_init(struct xe_device *xe) return ret; xe->soc_remapper.set_sysctrl_region(xe, SYSCTRL_MAILBOX_INDEX); - xe_sysctrl_mailbox_init(sc); INIT_WORK(&sc->work, xe_sysctrl_work); return devm_add_action_or_reset(xe->drm.dev, sysctrl_fini, xe); @@ -114,12 +113,10 @@ void xe_sysctrl_irq_handler(struct xe_device *xe, u32 master_ctl) * @xe: xe device instance * * Invoked during system resume (S3/S4 to S0) and runtime resume from D3cold. - * Restores SoC remapper configuration and reinitializes mailbox interface. + * Restores SoC remapper configuration. */ void xe_sysctrl_pm_resume(struct xe_device *xe) { - struct xe_sysctrl *sc = &xe->sc; - if (!xe->info.has_soc_remapper_sysctrl) return; @@ -127,6 +124,4 @@ void xe_sysctrl_pm_resume(struct xe_device *xe) return; xe->soc_remapper.set_sysctrl_region(xe, SYSCTRL_MAILBOX_INDEX); - - xe_sysctrl_mailbox_init(sc); } diff --git a/drivers/gpu/drm/xe/xe_sysctrl_mailbox.c b/drivers/gpu/drm/xe/xe_sysctrl_mailbox.c index e13eebaac1d0..72baf1aa4b3a 100644 --- a/drivers/gpu/drm/xe/xe_sysctrl_mailbox.c +++ b/drivers/gpu/drm/xe/xe_sysctrl_mailbox.c @@ -145,6 +145,7 @@ static int sysctrl_send_frames(struct xe_sysctrl *sc, struct xe_device *xe = sc_to_xe(sc); u32 ctrl_reg, total_frames, frame; size_t bytes_sent, frame_size; + bool phase; total_frames = DIV_ROUND_UP(cmd_size, XE_SYSCTRL_MB_FRAME_SIZE); @@ -153,7 +154,8 @@ static int sysctrl_send_frames(struct xe_sysctrl *sc, return -EBUSY; } - sc->phase_bit ^= 1; + ctrl_reg = xe_mmio_read32(sc->mmio, SYSCTRL_MB_CTRL); + phase = !(ctrl_reg & SYSCTRL_FRAME_PHASE); bytes_sent = 0; for (frame = 0; frame < total_frames; frame++) { @@ -161,7 +163,6 @@ static int sysctrl_send_frames(struct xe_sysctrl *sc, if (sysctrl_write_frame(sc, mbox_cmd + bytes_sent, frame_size)) { xe_err(xe, "sysctrl: Failed to write frame %u\n", frame); - sc->phase_bit = 0; return -EIO; } @@ -169,13 +170,12 @@ static int sysctrl_send_frames(struct xe_sysctrl *sc, REG_FIELD_PREP(SYSCTRL_FRAME_CURRENT_MASK, frame) | REG_FIELD_PREP(SYSCTRL_FRAME_TOTAL_MASK, total_frames - 1) | SYSCTRL_MB_CTRL_CMD | - (sc->phase_bit ? SYSCTRL_FRAME_PHASE : 0); + (phase ? SYSCTRL_FRAME_PHASE : 0); xe_mmio_write32(sc->mmio, SYSCTRL_MB_CTRL, ctrl_reg); if (!sysctrl_wait_bit_clear(sc, SYSCTRL_MB_CTRL_RUN_BUSY, timeout_ms)) { xe_err(xe, "sysctrl: Frame %u acknowledgment timeout\n", frame); - sc->phase_bit = 0; return -ETIMEDOUT; } @@ -321,20 +321,6 @@ void xe_sysctrl_create_command(struct xe_sysctrl_mailbox_command *command, u8 gr command->data_out_len = response_len; } -/** - * xe_sysctrl_mailbox_init - Initialize System Controller mailbox interface - * @sc: System controller structure - * - * Initialize system controller mailbox interface for communication. - */ -void xe_sysctrl_mailbox_init(struct xe_sysctrl *sc) -{ - u32 ctrl_reg; - - ctrl_reg = xe_mmio_read32(sc->mmio, SYSCTRL_MB_CTRL); - sc->phase_bit = (ctrl_reg & SYSCTRL_FRAME_PHASE) ? 1 : 0; -} - /** * xe_sysctrl_send_command() - Send mailbox command to System Controller * @sc: System Controller instance diff --git a/drivers/gpu/drm/xe/xe_sysctrl_mailbox.h b/drivers/gpu/drm/xe/xe_sysctrl_mailbox.h index fb434cc165b2..f0d5e3d7f5e3 100644 --- a/drivers/gpu/drm/xe/xe_sysctrl_mailbox.h +++ b/drivers/gpu/drm/xe/xe_sysctrl_mailbox.h @@ -26,7 +26,6 @@ struct xe_sysctrl_mailbox_command; void xe_sysctrl_create_command(struct xe_sysctrl_mailbox_command *command, u8 group_id, u8 cmd_id, void *request, size_t request_len, void *response, size_t response_len); -void xe_sysctrl_mailbox_init(struct xe_sysctrl *sc); int xe_sysctrl_send_command(struct xe_sysctrl *sc, struct xe_sysctrl_mailbox_command *cmd, size_t *rdata_len); diff --git a/drivers/gpu/drm/xe/xe_sysctrl_types.h b/drivers/gpu/drm/xe/xe_sysctrl_types.h index 66ba24f43017..98c2f473f7c6 100644 --- a/drivers/gpu/drm/xe/xe_sysctrl_types.h +++ b/drivers/gpu/drm/xe/xe_sysctrl_types.h @@ -26,9 +26,6 @@ struct xe_sysctrl { /** @cmd_lock: Mutex protecting mailbox command operations */ struct mutex cmd_lock; - /** @phase_bit: Message boundary phase toggle bit (0 or 1) */ - bool phase_bit; - /** @work: Pending events worker */ struct work_struct work; From afce9701d6423a63194a349d2f1e34c50ce76482 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Fri, 21 Aug 2026 22:07:58 +0200 Subject: [PATCH 494/641] MAINTAINERS: update rtc subsystem patchwork location The RTC subsystem is migrating it patchwork to kernel.org. Link: https://patch.msgid.link/202608212007582a463833@mail.local Signed-off-by: Alexandre Belloni --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 15011f5752a9..e187e54477cd 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22685,7 +22685,7 @@ REAL TIME CLOCK (RTC) SUBSYSTEM M: Alexandre Belloni L: linux-rtc@vger.kernel.org S: Maintained -Q: http://patchwork.ozlabs.org/project/rtc-linux/list/ +Q: https://patchwork.kernel.org/project/linux-rtc/list/ T: git git://git.kernel.org/pub/scm/linux/kernel/git/abelloni/linux.git F: Documentation/admin-guide/rtc.rst F: Documentation/devicetree/bindings/rtc/ From a4f3fbccb65de757569686baaf2b72e329096aba Mon Sep 17 00:00:00 2001 From: Kathiravan Thirumoorthy Date: Wed, 12 Aug 2026 14:00:55 +0530 Subject: [PATCH 495/641] i2c: qcom-geni: update frequency table to fix timing parameters In IPQ5424, to meet the setup and hold timing requirements in the standard mode, update the frequency table with the values recommended by HW design team. Also remove the stray space in the I2C_MAX_FAST_MODE_FREQ entry. Fixes: 85c34532849d ("i2c: qcom-geni: fix I2C frequency table to achieve accurate bus rates") Fixes: 506bb2ab0075 ("i2c: qcom-geni: Support systems with 32MHz serial engine clock") Signed-off-by: Kathiravan Thirumoorthy Cc: # v6.13+ Reviewed-by: Mukesh Savaliya Reviewed-by: Konrad Dybcio Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260812-ipq5424_i2c_scl_updates-v2-1-e09cd39d01d7@oss.qualcomm.com --- drivers/i2c/busses/i2c-qcom-geni.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c index 658636c1ee0e..00013b41a6f5 100644 --- a/drivers/i2c/busses/i2c-qcom-geni.c +++ b/drivers/i2c/busses/i2c-qcom-geni.c @@ -188,8 +188,8 @@ static const struct geni_i2c_clk_fld geni_i2c_clk_map_19p2mhz[] = { /* source_clock = 32 MHz */ static const struct geni_i2c_clk_fld geni_i2c_clk_map_32mhz[] = { - { I2C_MAX_STANDARD_MODE_FREQ, 8, 14, 18, 38 }, - { I2C_MAX_FAST_MODE_FREQ, 4, 3, 9, 19 }, + { I2C_MAX_STANDARD_MODE_FREQ, 12, 9, 10, 26 }, + { I2C_MAX_FAST_MODE_FREQ, 4, 3, 9, 19 }, { I2C_MAX_FAST_MODE_PLUS_FREQ, 2, 3, 5, 15 }, {} }; From 77549d01edecc20da73c8599e14648877198ce9b Mon Sep 17 00:00:00 2001 From: Hongbo Yao Date: Wed, 26 Aug 2026 15:05:47 +0800 Subject: [PATCH 496/641] i2c: designware: Enable interrupt mask workaround for HJMC3001 On HJMicro ARM64 servers, the DesignWare I2C controller does not retrigger a pending interrupt if the interrupt status changes after the current status bits have been cleared. The issue is exposed under heavy system load when the corresponding SPI is routed across sockets to a core in the remote socket. The interrupt is then lost and the I2C transfer times out. Enable ACCESS_INTR_MASK for HJMC3001. This toggles DW_IC_INTR_MASK before returning from the interrupt handler and retriggers any pending interrupt. Fixes: 6816ce57c479 ("i2c: designware: Add a new ACPI HID for HJMC01 I2C controller") Signed-off-by: Hongbo Yao Cc: # v6.13+ Acked-by: Mika Westerberg Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260826070547.268672-1-andy.xu@hj-micro.com --- drivers/i2c/busses/i2c-designware-platdrv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/i2c/busses/i2c-designware-platdrv.c b/drivers/i2c/busses/i2c-designware-platdrv.c index 6d6e81242f74..447af5523c2e 100644 --- a/drivers/i2c/busses/i2c-designware-platdrv.c +++ b/drivers/i2c/busses/i2c-designware-platdrv.c @@ -272,7 +272,7 @@ static const struct acpi_device_id dw_i2c_acpi_match[] = { { "HISI02A1", 0 }, { "HISI02A2", 0 }, { "HISI02A3", 0 }, - { "HJMC3001", 0 }, + { "HJMC3001", ACCESS_INTR_MASK }, { "HYGO0010", ACCESS_INTR_MASK }, { "INT33C2", 0 }, { "INT33C3", 0 }, From f98d4986482151a835b521a734722fe8dc5ca37d Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Wed, 12 Aug 2026 17:44:25 +0800 Subject: [PATCH 497/641] i2c: qcom-cci: fix autosuspend cleanup cci_probe() calls pm_runtime_use_autosuspend(), but the remove path does not call the matching pm_runtime_dont_use_autosuspend() before disabling runtime PM. If the autosuspend delay is set to a negative value while autosuspend is enabled, the runtime PM core increments usage_count to prevent runtime suspend. Without undoing the autosuspend setting during teardown, this reference is not dropped and usage_count remains unbalanced. Use devm_pm_runtime_set_active_enabled() to manage the runtime PM state. Its managed cleanup disables autosuspend and runtime PM and restores the suspended state on probe failure and driver removal. Remove the now redundant manual runtime PM cleanup. This issue was found by manual code inspection. Fixes: e517526195de ("i2c: Add Qualcomm CCI I2C driver") Signed-off-by: Guangshuo Li Cc: # v5.8+ Reviewed-by: Vladimir Zapolskiy Reviewed-by: Loic Poulain Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260812094425.3515179-1-lgs201920130244@gmail.com --- drivers/i2c/busses/i2c-qcom-cci.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/drivers/i2c/busses/i2c-qcom-cci.c b/drivers/i2c/busses/i2c-qcom-cci.c index bdeda3979c48..25b6e4e9e3fa 100644 --- a/drivers/i2c/busses/i2c-qcom-cci.c +++ b/drivers/i2c/busses/i2c-qcom-cci.c @@ -595,9 +595,11 @@ static int cci_probe(struct platform_device *pdev) goto disable_clocks; pm_runtime_set_autosuspend_delay(dev, MSEC_PER_SEC); + ret = devm_pm_runtime_set_active_enabled(dev); + if (ret) + goto disable_clocks; + pm_runtime_use_autosuspend(dev); - pm_runtime_set_active(dev); - pm_runtime_enable(dev); for (i = 0; i < cci->data->num_masters; i++) { if (!cci->master[i].cci) @@ -613,8 +615,6 @@ static int cci_probe(struct platform_device *pdev) return 0; error_i2c: - pm_runtime_disable(dev); - pm_runtime_dont_use_autosuspend(dev); for (--i ; i >= 0; i--) { if (cci->master[i].cci) { @@ -640,9 +640,6 @@ static void cci_remove(struct platform_device *pdev) cci_halt(cci, i); } } - - pm_runtime_disable(&pdev->dev); - pm_runtime_set_suspended(&pdev->dev); } static const struct cci_data cci_v1_data = { From 27c9445be86b1313746c46d659d3f823a5f7a218 Mon Sep 17 00:00:00 2001 From: Carlos Song Date: Mon, 3 Aug 2026 11:27:05 +0800 Subject: [PATCH 498/641] i2c: imx-lpi2c: avoid accessing target registers on master-only controllers Not all LPI2C controller instances implement the Target block. Since commit 90311787f483 ("i2c: imx-lpi2c: reset controller in probe stage"), the driver unconditionally resets both the Master and Target blocks during probe. On controllers that do not support target mode, accessing the Target registers triggers an asynchronous SError and prevents the driver from probing successfully. For example on i.MX8QM: SError Interrupt on CPU2, code 0x00000000bf000002 -- SError Hardware name: Freescale i.MX8QM MEK (DT) pc : lpi2c_imx_probe+0x280/0x594 lr : lpi2c_imx_probe+0x224/0x594 Kernel panic - not syncing: Asynchronous SError Interrupt The VERID register is implemented in the Master block and can be safely accessed on all controller variants. Its FEATURE field indicates whether target mode is supported. Read VERID during probe and use it to determine whether the Target block is present. Only access Target registers when target mode is supported and reject target registration requests with -EOPNOTSUPP otherwise. Fixes: 90311787f483 ("i2c: imx-lpi2c: reset controller in probe stage") Signed-off-by: Carlos Song Reviewed-by: Frank Li Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260803032705.2250373-1-carlos.song@oss.nxp.com --- drivers/i2c/busses/i2c-imx-lpi2c.c | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/drivers/i2c/busses/i2c-imx-lpi2c.c b/drivers/i2c/busses/i2c-imx-lpi2c.c index e1a4338bc51e..1cfd7a4c8237 100644 --- a/drivers/i2c/busses/i2c-imx-lpi2c.c +++ b/drivers/i2c/busses/i2c-imx-lpi2c.c @@ -29,6 +29,7 @@ #define DRIVER_NAME "imx-lpi2c" +#define LPI2C_VERID 0x00 /* i2c version ID */ #define LPI2C_PARAM 0x04 /* i2c RX/TX FIFO size */ #define LPI2C_MCR 0x10 /* i2c contrl register */ #define LPI2C_MSR 0x14 /* i2c status register */ @@ -136,6 +137,9 @@ #define I2C_PM_LONG_TIMEOUT_MS 1000 /* Avoid dead lock caused by big clock prepare lock */ #define I2C_DMA_THRESHOLD 8 /* bytes */ +/* Bit 0 indicates the presence of the target feature */ +#define VERID_FEATURE_TARGET_PRESENT BIT(0) + enum lpi2c_imx_mode { STANDARD, /* 100+Kbps */ FAST, /* 400+Kbps */ @@ -194,6 +198,7 @@ struct lpi2c_imx_struct { bool can_use_dma; struct lpi2c_imx_dma *dma; struct i2c_client *target; + bool target_supported; int irq; const struct imx_lpi2c_hwdata *hwdata; }; @@ -1330,6 +1335,10 @@ static int lpi2c_imx_register_target(struct i2c_client *client) struct lpi2c_imx_struct *lpi2c_imx = i2c_get_adapdata(client->adapter); int ret; + /* Reject target-mode registration on controllers that don't support it. */ + if (!lpi2c_imx->target_supported) + return -EOPNOTSUPP; + if (lpi2c_imx->target) return -EBUSY; @@ -1546,13 +1555,23 @@ static int lpi2c_imx_probe(struct platform_device *pdev) pm_runtime_enable(&pdev->dev); /* - * Reset all internal controller registers of both Master and Target - * to avoid effects of previous status. + * Reset all internal controller registers to avoid effects of any + * state left over from a previous stage (e.g. the bootloader). + * + * The Master block (MCR) is present on every controller, so reset it + * unconditionally. VERID shows whether the target feature is supported. + * Do not touch the Target block (SCR) on a master-only controller to + * avoid an asynchronous SError. */ writel(MCR_RST, lpi2c_imx->base + LPI2C_MCR); - writel(SCR_RST, lpi2c_imx->base + LPI2C_SCR); writel(0, lpi2c_imx->base + LPI2C_MCR); - writel(0, lpi2c_imx->base + LPI2C_SCR); + + lpi2c_imx->target_supported = !!(readl(lpi2c_imx->base + LPI2C_VERID) & + VERID_FEATURE_TARGET_PRESENT); + if (lpi2c_imx->target_supported) { + writel(SCR_RST, lpi2c_imx->base + LPI2C_SCR); + writel(0, lpi2c_imx->base + LPI2C_SCR); + } ret = devm_request_irq(&pdev->dev, lpi2c_imx->irq, lpi2c_imx_isr, IRQF_NO_SUSPEND, pdev->name, lpi2c_imx); From b15b548d52b43ba8ac4652bc2c7244a8dd1e9622 Mon Sep 17 00:00:00 2001 From: Vasileios Almpanis Date: Wed, 12 Aug 2026 11:14:48 +0200 Subject: [PATCH 499/641] i2c: core: fix debugfs UAF on adapter removal i2c_del_adapter() frees the adapter's debugfs directory before it unregisters the adapter device, but the new_device sysfs attribute stays writable until device_del(). A write racing with removal still reaches i2c_device_probe(), which passes the freed adap->debugfs to debugfs_create_dir() as the new client's parent: BUG: KASAN: slab-use-after-free in lookup_noperm_common+0x407/0x430 Read of size 4 at addr ffff88803ef87810 by task syz.0.61/6090 lookup_noperm_common+0x407/0x430 simple_start_creating+0x9c/0x110 debugfs_start_creating+0xdb/0x1a0 debugfs_create_dir+0x24/0x350 i2c_device_probe+0x814/0xbf0 It's technically possible to create a client after i2c_deregister_clients has run. That client will never be unregistered and make wait_for_completion hang. Close the window by removing the new_device attribute at the start of i2c_del_adapter(). device_remove_file() will drain any clients left. Fixes: 73febd775bdb ("i2c: create debugfs entry per adapter") Reported-by: syzbot+23ad911c819b923238b7@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=23ad911c819b923238b7 Signed-off-by: Vasileios Almpanis Cc: # v6.8+ Tested-by: syzbot+23ad911c819b923238b7@syzkaller.appspotmail.com Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260812-i2c-v2-1-5efaab4c3334@gmail.com --- drivers/i2c/i2c-core-base.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c index fb25704219c7..ddaacf876dad 100644 --- a/drivers/i2c/i2c-core-base.c +++ b/drivers/i2c/i2c-core-base.c @@ -1816,6 +1816,12 @@ void i2c_del_adapter(struct i2c_adapter *adap) return; } + /* + * This drains any in-flight writers, so all + * clients will be caught by i2c_deregister_clients(). + */ + device_remove_file(&adap->dev, &dev_attr_new_device); + i2c_acpi_remove_space_handler(adap); i2c_deregister_clients(adap); From 390f6bd8583d177029d9df4bea6667509e55a765 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Jean?= Date: Thu, 27 Aug 2026 18:43:22 +0000 Subject: [PATCH 500/641] tracing/user_events: Clear copied tracing state before fork duplication MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dup_task_struct() copies user_event_mm from the parent into the child, without grabbing a reference to it. user_event_mm_dup() should replace it, but it leaves that copied pointer unmodified if user_event_mm_alloc() fails. When the child exits, user_event_mm_remove() decrements a reference the child never owned, which ultimately frees user_event_mm, while the parent still as a stale pointer to it. This creates a UAF, which KASAN reports as: BUG: KASAN: slab-use-after-free in current_user_event_mm+0x51/0x1d0 Write of size 4 at addr ffff888005010d30 by task init/44 Call Trace: kasan_report+0xce/0x100 kasan_check_range+0x10f/0x1e0 current_user_event_mm+0x51/0x1d0 user_events_ioctl+0x82e/0x15c0 __x64_sys_ioctl+0x139/0x1c0 do_syscall_64+0xce/0x450 entry_SYSCALL_64_after_hwframe+0x77/0x7f Allocated by task 44: __kasan_kmalloc+0x8f/0xa0 __kmalloc_cache_noprof+0x180/0x3a0 user_event_mm_alloc+0x3c/0x1f0 current_user_event_mm+0x88/0x1d0 Freed by task 42: __kasan_slab_free+0x43/0x70 kfree+0x13a/0x390 process_one_work+0x696/0xf90 worker_thread+0x420/0xba0 The fix simply clears the copied pointer before any possible failure. In case of failure, the child then has nothing to free. Cc: stable@vger.kernel.org Fixes: 7235759084a4 ("tracing/user_events: Use remote writes for event enablement") Link: https://patch.msgid.link/20260827184321.2964601-2-Jeremy.Jean@oss.cyber.gouv.fr Assisted-by: Codex:gpt-5 Signed-off-by: Jérémy Jean Reviewed-by: Bradley Morgan Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_user.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kernel/trace/trace_events_user.c b/kernel/trace/trace_events_user.c index 2bbc89d4a266..93cda2f6f269 100644 --- a/kernel/trace/trace_events_user.c +++ b/kernel/trace/trace_events_user.c @@ -868,6 +868,9 @@ void user_event_mm_dup(struct task_struct *t, struct user_event_mm *old_mm) struct user_event_mm *mm = user_event_mm_alloc(t); struct user_event_enabler *enabler; + /* On failure, do not free parent's copy */ + t->user_event_mm = NULL; + if (!mm) return; From e0d3aed7b12cf37b74c7cc5265073d0263b49cde Mon Sep 17 00:00:00 2001 From: Ivan Immanuel Shaji Date: Tue, 25 Aug 2026 12:52:49 -0400 Subject: [PATCH 501/641] tracing: Fix retry exhaustion in simple ring buffer reader swap simple_ring_buffer_swap_reader_page() starts with retry set to 8 and post-decrements it only after a failed link replacement. On the final attempt, a successful replacement leaves retry at zero, while a failed replacement leaves it at -1. The current !retry test reverses both outcomes. It returns an error after a successful final replacement, leaving the link update complete but the reader bookkeeping unfinished. After a failed final replacement, it falls through and updates the head and reader pointers as though the replacement succeeded, which can corrupt the ring. Treat only a negative counter as exhaustion and return the documented -EBUSY error. Cc: stable@vger.kernel.org Fixes: 34e5b958bdad ("tracing: Introduce simple_ring_buffer") Link: https://patch.msgid.link/20260825-kernel-patch-1-v2-1-bb3461807a32@gmail.com Assisted-by: LLM sparse Reviewed-by: Vincent Donnefort Signed-off-by: Ivan Immanuel Shaji Signed-off-by: Steven Rostedt --- kernel/trace/simple_ring_buffer.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/trace/simple_ring_buffer.c b/kernel/trace/simple_ring_buffer.c index f4642f5adda3..49913bb0057a 100644 --- a/kernel/trace/simple_ring_buffer.c +++ b/kernel/trace/simple_ring_buffer.c @@ -160,8 +160,8 @@ int simple_ring_buffer_swap_reader_page(struct simple_rb_per_cpu *cpu_buffer) overrun = cpu_buffer->meta->overrun; } while (!simple_bpage_unset_head_link(last, reader, SIMPLE_RB_LINK_NORMAL) && retry--); - if (!retry) - return -EINVAL; + if (retry < 0) + return -EBUSY; cpu_buffer->head_page = simple_bpage_from_link(reader->link.next); cpu_buffer->head_page->link.prev = &reader->link; From 5eab74874d11160725c42ab676ba97a797a362eb Mon Sep 17 00:00:00 2001 From: Ivan Immanuel Shaji Date: Tue, 25 Aug 2026 12:52:50 -0400 Subject: [PATCH 502/641] ring-buffer: Stop remote reader update when page swap fails The remote swap_reader_page callback can return -EBUSY when the writer moves the head before the remote catches it, particularly during an event storm on a small buffer. __rb_get_reader_page_from_remote() currently warns about that failure but continues with the unchanged reader ID and rearranges the local page list as though the swap succeeded. Handle the callback failure as a recoverable error. Report it with pr_warn_ratelimited() and return NULL. Callers already handle a NULL reader page as a failed attempt. This avoids splicing the same page as both the previous and new reader without flooding the log under contention. Cc: stable@vger.kernel.org Fixes: 2e67fabd8b77 ("ring-buffer: Introduce ring-buffer remotes") Link: https://patch.msgid.link/20260825-kernel-patch-1-v2-2-bb3461807a32@gmail.com Assisted-by: LLM sparse Signed-off-by: Ivan Immanuel Shaji Signed-off-by: Steven Rostedt --- kernel/trace/ring_buffer.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c index 5fc009edc1ec..b7d076b6edcf 100644 --- a/kernel/trace/ring_buffer.c +++ b/kernel/trace/ring_buffer.c @@ -5805,8 +5805,11 @@ __rb_get_reader_page_from_remote(struct ring_buffer_per_cpu *cpu_buffer) prev_reader = cpu_buffer->subbuf_ids[cpu_buffer->meta_page->reader.id]; - WARN_ON_ONCE(cpu_buffer->remote->swap_reader_page(cpu_buffer->cpu, - cpu_buffer->remote->priv)); + if (cpu_buffer->remote->swap_reader_page(cpu_buffer->cpu, + cpu_buffer->remote->priv)) { + pr_warn_ratelimited("Remote reader page swap failed\n"); + return NULL; + } /* nr_pages doesn't include the reader page */ if (WARN_ON_ONCE(cpu_buffer->meta_page->reader.id > cpu_buffer->nr_pages)) return NULL; From 5046d2880fec7d49ebed2fd2866747ee1d06ad71 Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Thu, 27 Aug 2026 18:25:14 +0000 Subject: [PATCH 503/641] ipv4: avoid divide by zero in fib_rebalance fib_rebalance() computes the total eligible nexthop weight in one pass and programs upper bounds in a second pass. A concurrent change to ignore_routes_with_linkdown can make the first pass return zero while the second pass sees an eligible nexthop, resulting in division by zero. If the first pass reports a zero total, set each nexthop upper bound to -1 and skip the division. This matches the IPv6 fix in commit d2c26c2911dd ("ipv6: avoid divide by zero in rt6_multipath_rebalance") and preserves the lock-free rebalance path. Fixes: 0e884c78ee19 ("ipv4: L3 hash-based multipath") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zihan Xi Reviewed-by: Eric Dumazet Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260827182514.4667-2-zihanx@nebusec.ai Signed-off-by: Jakub Kicinski --- net/ipv4/fib_semantics.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ipv4/fib_semantics.c b/net/ipv4/fib_semantics.c index 0483519b7fb0..7a362f2e2c2b 100644 --- a/net/ipv4/fib_semantics.c +++ b/net/ipv4/fib_semantics.c @@ -874,7 +874,7 @@ static void fib_rebalance(struct fib_info *fi) change_nexthops(fi) { int upper_bound; - if (nexthop_nh->fib_nh_flags & RTNH_F_DEAD) { + if (!total || nexthop_nh->fib_nh_flags & RTNH_F_DEAD) { upper_bound = -1; } else if (ip_ignore_linkdown(nexthop_nh->fib_nh_dev) && nexthop_nh->fib_nh_flags & RTNH_F_LINKDOWN) { From dddf197f29ba5e47a476dde82bf542ca2e0d5e5e Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Wed, 26 Aug 2026 03:01:41 +0800 Subject: [PATCH 504/641] tipc: protect node reset trace dump with node lock The tipc_node_reset_links trace event asks tipc_node_dump() to walk the node's link entries. Unlike the other node events that request link data, this event runs without the node lock. This permits bearer teardown to free a link while the trace callback is dumping it: CPU 0 CPU 1 trace_tipc_node_reset_links() tipc_node_dump() l = n->links[0].link tipc_node_write_lock() kfree(l) n->links[0].link = NULL tipc_node_write_unlock() tipc_link_dump(l) tipc_link_dump() then dereferences the stale pointer. KASAN reported: BUG: KASAN: slab-use-after-free in tipc_link_dump Read of size 4 by task poc/115 Call Trace: tipc_link_dump+0x10cb/0x16b0 tipc_node_dump+0x4bb/0x740 trace_event_raw_event_tipc_node_class+0x258/0x360 tipc_node_reset_links+0x14d/0x1a0 tipc_rcv+0x13f5/0x3030 tipc_udp_recv+0x4e3/0x670 Allocated by task 0: tipc_link_create+0x1e1/0x1020 tipc_node_check_dest+0x7d2/0x11a0 tipc_disc_rcv+0xdbf/0x1430 Freed by task 89: kfree+0x131/0x3c0 tipc_node_link_down+0x267/0x4b0 tipc_node_delete_links+0xec/0x160 bearer_disable+0x107/0x260 Take the node write lock around the trace event. This serializes the dump against tipc_node_link_down(delete=true), which frees the link under the same write lock. Fixes: eb18a510b5cd ("tipc: add trace_events for tipc node") Cc: stable@vger.kernel.org Signed-off-by: Chengfeng Ye Reviewed-by: Tung Nguyen Link: https://patch.msgid.link/20260825190141.242219-1-nicoyip.dev@gmail.com Signed-off-by: Jakub Kicinski --- net/tipc/node.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/tipc/node.c b/net/tipc/node.c index 683a136e53ef..bd91378b7540 100644 --- a/net/tipc/node.c +++ b/net/tipc/node.c @@ -1333,7 +1333,9 @@ static void tipc_node_reset_links(struct tipc_node *n) pr_warn("Resetting all links to %x\n", n->addr); + tipc_node_write_lock(n); trace_tipc_node_reset_links(n, true, " "); + tipc_node_write_unlock_fast(n); for (i = 0; i < MAX_BEARERS; i++) { tipc_node_link_down(n, i, false); } From 7fcc2fe39fed1cb98a7374a113ff3800e8f9af80 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Tue, 25 Aug 2026 08:45:51 +0000 Subject: [PATCH 505/641] net: icmp: avoid invalid transport header access in icmp_send tracepoint syzbot reported a WARNING triggered by DEBUG_NET_WARN_ON_ONCE(): WARNING: at skb_transport_header include/linux/skbuff.h:3087 [inline] WARNING: at udp_hdr include/linux/udp.h:23 [inline] WARNING: at do_trace_event_raw_event_icmp_send include/trace/events/icmp.h:30 [inline] WARNING: at trace_event_raw_event_icmp_send+0x48c/0x6ec include/trace/events/icmp.h:11 Call trace: skb_transport_header include/linux/skbuff.h:3087 [inline] udp_hdr include/linux/udp.h:23 [inline] do_trace_event_raw_event_icmp_send include/trace/events/icmp.h:30 [inline] trace_event_raw_event_icmp_send+0x48c/0x6ec include/trace/events/icmp.h:11 __traceiter_icmp_send include/trace/events/icmp.h:11 [inline] __do_trace_icmp_send include/trace/events/icmp.h:11 [inline] trace_icmp_send+0x320/0x49c include/trace/events/icmp.h:11 __icmp_send+0xcfc/0x11d8 net/ipv4/icmp.c:1013 ipv4_send_dest_unreach net/ipv4/route.c:1280 [inline] ipv4_link_failure+0x57c/0x8dc net/ipv4/route.c:1287 dst_link_failure include/net/dst.h:438 [inline] vti_tunnel_xmit+0xe40/0x17a4 net/ipv4/ip_vti.c:307 TP_fast_assign() unconditionally calls udp_hdr(skb) before checking whether the packet is UDP. Furthermore, __icmp_send() can be invoked from paths (e.g., link failures, ARP errors, forwarding, AF_PACKET) where skb->transport_header was never initialized (~0U). Under CONFIG_DEBUG_NET=y, calling skb_transport_header(skb) triggers DEBUG_NET_WARN_ON_ONCE(!skb_transport_header_was_set(skb)). Fix this by: 1. Only parsing transport info when iph->protocol == IPPROTO_UDP. 2. Using skb_header_pointer() at skb_network_offset(skb) + (iph->ihl << 2) to safely fetch the UDP header without assuming transport_header is set. Fixes: db3efdcf70c7 ("net/ipv4: add tracepoint for icmp_send") Reported-by: syzbot+6d2762674103618994b0@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6a8d5538.91706f20.ef82.0009.GAE@google.com/T/#u Signed-off-by: Eric Dumazet Cc: Peilin He Cc: xu xin Cc: Steven Rostedt Reviewed-by: Jiayuan Chen Reviewed-by: David Ahern Link: https://patch.msgid.link/20260825084551.1562967-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- include/trace/events/icmp.h | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/include/trace/events/icmp.h b/include/trace/events/icmp.h index 09ae115099df..6937b778ae54 100644 --- a/include/trace/events/icmp.h +++ b/include/trace/events/icmp.h @@ -27,17 +27,20 @@ TRACE_EVENT(icmp_send, TP_fast_assign( struct iphdr *iph = ip_hdr(skb); - struct udphdr *uh = udp_hdr(skb); - int proto_4 = iph->protocol; + struct udphdr _uh, *uh = NULL; __be32 *p32; __entry->skbaddr = skb; __entry->type = type; __entry->code = code; - if (proto_4 != IPPROTO_UDP || (u8 *)uh < skb->head || - (u8 *)uh + sizeof(struct udphdr) - > skb_tail_pointer(skb)) { + if (iph->protocol == IPPROTO_UDP) + uh = skb_header_pointer(skb, + skb_network_offset(skb) + + (iph->ihl << 2), + sizeof(_uh), &_uh); + + if (!uh) { __entry->sport = 0; __entry->dport = 0; __entry->ulen = 0; From 2a004bfb62bdb847f25a8001e104bf33922a2cf4 Mon Sep 17 00:00:00 2001 From: Ilya Maximets Date: Thu, 27 Aug 2026 00:01:11 +0200 Subject: [PATCH 506/641] netlink: specs: fix the conntrack filter type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CTA_FILTER doesn't contain nested tuple attributes, instead it contains bit masks that specify which tuple attributes to filter on. The values for filtering are taken from the top-level CTA_TUPLE_ORIG and CTA_TUPLE_REPLY, which are also missing in the attribute list for the dump request. The bits themselves somehow are not in the public headers, so not defining them in the spec either for now. Once they are public in uAPI, they can be added here with enum-as-flags. Fixes: 23fc9311a526 ("netlink: specs: add conntrack dump and stats dump support") Cc: stable@vger.kernel.org Signed-off-by: Ilya Maximets Reviewed-by: Asbjørn Sloth Tønnesen Link: https://patch.msgid.link/20260826220444.4054714-2-i.maximets@ovn.org Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/conntrack.yaml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Documentation/netlink/specs/conntrack.yaml b/Documentation/netlink/specs/conntrack.yaml index db7cddcda50a..6ba28cb1c2ab 100644 --- a/Documentation/netlink/specs/conntrack.yaml +++ b/Documentation/netlink/specs/conntrack.yaml @@ -360,6 +360,17 @@ attribute-sets: name: tsoff type: u32 byte-order: big-endian + - + name: filter-attrs + attributes: + - + name: orig-flags + type: u32 + doc: bitmask of tuple fields to filter on, original direction + - + name: reply-flags + type: u32 + doc: bitmask of tuple fields to filter on, reply direction - name: conntrack-attrs attributes: @@ -466,7 +477,7 @@ attribute-sets: - name: filter type: nest - nested-attributes: tuple-attrs + nested-attributes: filter-attrs - name: status-mask type: u32 @@ -591,6 +602,8 @@ operations: request: value: 0x101 attributes: + - tuple-orig + - tuple-reply - mark - filter - status From 8b348496cbec0d5ca24a99f668096e2e16e8fbeb Mon Sep 17 00:00:00 2001 From: Ilya Maximets Date: Thu, 27 Aug 2026 00:01:12 +0200 Subject: [PATCH 507/641] netlink: specs: add missing mask attributes for conntrack dump MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 'mark-mask' and 'status-mask' are defined and supported by the conntrack dump, but missing from the list of arguments. While at it, the order of the arguments should follow the order of their definition in the enum ctattr_type. That appears to be a common convention for other spec files. Fixes: 23fc9311a526 ("netlink: specs: add conntrack dump and stats dump support") Cc: stable@vger.kernel.org Signed-off-by: Ilya Maximets Reviewed-by: Asbjørn Sloth Tønnesen Link: https://patch.msgid.link/20260826220444.4054714-3-i.maximets@ovn.org Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/conntrack.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Documentation/netlink/specs/conntrack.yaml b/Documentation/netlink/specs/conntrack.yaml index 6ba28cb1c2ab..b1eb102ab843 100644 --- a/Documentation/netlink/specs/conntrack.yaml +++ b/Documentation/netlink/specs/conntrack.yaml @@ -604,10 +604,12 @@ operations: attributes: - tuple-orig - tuple-reply - - mark - - filter - status + - mark - zone + - mark-mask + - filter + - status-mask reply: value: 0x100 attributes: From 18666c73afe95eeca8707c699b63f96ce3acda42 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 27 Aug 2026 09:59:36 +0000 Subject: [PATCH 508/641] tcp: use GFP_ATOMIC in tcp_send_active_reset() tcp_send_active_reset() can be called from contexts where gfp_any() (in tcp_disconnect()) or sk->sk_allocation (in __tcp_close() and mptcp_do_fastclose()) evaluates to GFP_KERNEL, which includes __GFP_FS and __GFP_DIRECT_RECLAIM. Allocating with GFP_KERNEL while holding the socket lock (sk_lock) creates a lockdep dependency: sk_lock -> fs_reclaim This causes false-positive lockdep circular locking warnings with storage subsystems (such as nvme-tcp) that acquire socket locks in block I/O paths and invoke tcp_disconnect() or close sockets upon teardown: set->srcu -> sk_lock -> fs_reclaim -> elevator_lock -> set->srcu Active resets are small RST packet headers that should never enter direct reclaim or block while holding socket locks. Use sk_gfp_mask(sk, GFP_ATOMIC | __GFP_NOWARN) inside tcp_send_active_reset() and remove its priority argument. This preserves __GFP_MEMALLOC access for SOCK_MEMALLOC sockets, suppresses allocation failure warnings, and aligns with other control packet allocations (e.g. tcp_send_fin(), __tcp_send_ack(), tcp_xmit_probe_skb()). Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Eric Dumazet Acked-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260827095936.551524-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- include/net/tcp.h | 3 +-- net/ipv4/tcp.c | 14 ++++++-------- net/ipv4/tcp_output.c | 4 ++-- net/ipv4/tcp_timer.c | 6 +++--- net/mptcp/protocol.c | 3 +-- net/mptcp/protocol.h | 2 +- 6 files changed, 14 insertions(+), 18 deletions(-) diff --git a/include/net/tcp.h b/include/net/tcp.h index 670c20876f26..436495ff2271 100644 --- a/include/net/tcp.h +++ b/include/net/tcp.h @@ -765,8 +765,7 @@ int tcp_fragment(struct sock *sk, enum tcp_queue tcp_queue, void tcp_send_probe0(struct sock *); int tcp_write_wakeup(struct sock *, int mib); void tcp_send_fin(struct sock *sk); -void tcp_send_active_reset(struct sock *sk, gfp_t priority, - enum sk_rst_reason reason); +void tcp_send_active_reset(struct sock *sk, enum sk_rst_reason reason); int tcp_send_synack(struct sock *); void tcp_push_one(struct sock *, unsigned int mss_now); void __tcp_send_ack(struct sock *sk, u32 rcv_nxt, u16 flags); diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index b4237d0e994d..93d723d8c109 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -3182,8 +3182,7 @@ void __tcp_close(struct sock *sk, long timeout) /* Unread data was tossed, zap the connection. */ NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONCLOSE); tcp_set_state(sk, TCP_CLOSE); - tcp_send_active_reset(sk, sk->sk_allocation, - SK_RST_REASON_TCP_ABORT_ON_CLOSE); + tcp_send_active_reset(sk, SK_RST_REASON_TCP_ABORT_ON_CLOSE); } else if (sock_flag(sk, SOCK_LINGER) && !sk->sk_lingertime) { /* Check zero linger _after_ checking for unread data. */ sk->sk_prot->disconnect(sk, 0); @@ -3257,7 +3256,7 @@ adjudge_to_death: struct tcp_sock *tp = tcp_sk(sk); if (READ_ONCE(tp->linger2) < 0) { tcp_set_state(sk, TCP_CLOSE); - tcp_send_active_reset(sk, GFP_ATOMIC, + tcp_send_active_reset(sk, SK_RST_REASON_TCP_ABORT_ON_LINGER); __NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONLINGER); @@ -3276,7 +3275,7 @@ adjudge_to_death: if (sk->sk_state != TCP_CLOSE) { if (tcp_check_oom(sk, 0)) { tcp_set_state(sk, TCP_CLOSE); - tcp_send_active_reset(sk, GFP_ATOMIC, + tcp_send_active_reset(sk, SK_RST_REASON_TCP_ABORT_ON_MEMORY); __NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONMEMORY); @@ -3377,14 +3376,14 @@ int tcp_disconnect(struct sock *sk, int flags) } else if (unlikely(tp->repair)) { WRITE_ONCE(sk->sk_err, ECONNABORTED); } else if (tcp_need_reset(old_state)) { - tcp_send_active_reset(sk, gfp_any(), SK_RST_REASON_TCP_STATE); + tcp_send_active_reset(sk, SK_RST_REASON_TCP_STATE); WRITE_ONCE(sk->sk_err, ECONNRESET); } else if (tp->snd_nxt != tp->write_seq && (1 << old_state) & (TCPF_CLOSING | TCPF_LAST_ACK)) { /* The last check adjusts for discrepancy of Linux wrt. RFC * states */ - tcp_send_active_reset(sk, gfp_any(), + tcp_send_active_reset(sk, SK_RST_REASON_TCP_DISCONNECT_WITH_DATA); WRITE_ONCE(sk->sk_err, ECONNRESET); } else if (old_state == TCP_SYN_SENT) @@ -5147,8 +5146,7 @@ int tcp_abort(struct sock *sk, int err) bh_lock_sock(sk); if (tcp_need_reset(sk->sk_state)) - tcp_send_active_reset(sk, GFP_ATOMIC, - SK_RST_REASON_TCP_STATE); + tcp_send_active_reset(sk, SK_RST_REASON_TCP_STATE); tcp_done_with_error(sk, err); bh_unlock_sock(sk); diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c index 6f4dca4a4de9..c5ffffee4349 100644 --- a/net/ipv4/tcp_output.c +++ b/net/ipv4/tcp_output.c @@ -3849,9 +3849,9 @@ void tcp_send_fin(struct sock *sk) * was unread data in the receive queue. This behavior is recommended * by RFC 2525, section 2.17. -DaveM */ -void tcp_send_active_reset(struct sock *sk, gfp_t priority, - enum sk_rst_reason reason) +void tcp_send_active_reset(struct sock *sk, enum sk_rst_reason reason) { + gfp_t priority = sk_gfp_mask(sk, GFP_ATOMIC | __GFP_NOWARN); struct sk_buff *skb; TCP_INC_STATS(sock_net(sk), TCP_MIB_OUTRSTS); diff --git a/net/ipv4/tcp_timer.c b/net/ipv4/tcp_timer.c index 1038e7ba9c2e..e56eae4bc341 100644 --- a/net/ipv4/tcp_timer.c +++ b/net/ipv4/tcp_timer.c @@ -126,7 +126,7 @@ static int tcp_out_of_resources(struct sock *sk, bool do_reset) (!tp->snd_wnd && !tp->packets_out)) do_reset = true; if (do_reset) - tcp_send_active_reset(sk, GFP_ATOMIC, + tcp_send_active_reset(sk, SK_RST_REASON_TCP_ABORT_ON_MEMORY); tcp_done(sk); __NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONMEMORY); @@ -809,7 +809,7 @@ static void tcp_keepalive_timer(struct timer_list *t) goto out; } } - tcp_send_active_reset(sk, GFP_ATOMIC, SK_RST_REASON_TCP_STATE); + tcp_send_active_reset(sk, SK_RST_REASON_TCP_STATE); goto death; } @@ -836,7 +836,7 @@ static void tcp_keepalive_timer(struct timer_list *t) icsk->icsk_probes_out > 0) || (user_timeout == 0 && icsk->icsk_probes_out >= keepalive_probes(tp))) { - tcp_send_active_reset(sk, GFP_ATOMIC, + tcp_send_active_reset(sk, SK_RST_REASON_TCP_KEEPALIVE_TIMEOUT); tcp_write_err(sk); goto out; diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index b474d03620a7..e1f08f71cdb1 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -3109,8 +3109,7 @@ static void mptcp_do_fastclose(struct sock *sk) */ inet_csk(ssk)->icsk_ack.rcv_mss = TCP_MIN_MSS; - tcp_send_active_reset(ssk, ssk->sk_allocation, - SK_RST_REASON_TCP_ABORT_ON_CLOSE); + tcp_send_active_reset(ssk, SK_RST_REASON_TCP_ABORT_ON_CLOSE); unlock: release_sock(ssk); } diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h index 06a107d4e839..87ccb84e9927 100644 --- a/net/mptcp/protocol.h +++ b/net/mptcp/protocol.h @@ -690,7 +690,7 @@ mptcp_send_active_reset_reason(struct sock *sk) enum sk_rst_reason reason; reason = sk_rst_convert_mptcp_reason(subflow->reset_reason); - tcp_send_active_reset(sk, GFP_ATOMIC, reason); + tcp_send_active_reset(sk, reason); } /* Made the fwd mem carried by the given skb available to the msk, From a5d946466a95621fa2769720d59ea336003aa1a5 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Wed, 26 Aug 2026 15:03:15 +0200 Subject: [PATCH 509/641] net: stmmac: fix dma mapping leak in stmmac_tso_xmit() In stmmac_tso_xmit(), if the DMA mapping of an skb fragment fails, the frame is dropped but the DMA mappings already created for the linear part and for the fragments mapped before the failure are never unmapped, leaking DMA mappings. Fix the leak by walking back over the descriptors used by the frame and releasing each of them with stmmac_free_tx_buffer(). Moreover, release the descriptors with stmmac_release_tx_desc() unmapping the DMA buffers. Fixes: f748be531d70 ("stmmac: support new GMAC4") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260826-stmmac_dma_unmap_tso-v1-1-a2753d1576ba@oss.qualcomm.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/stmicro/stmmac/stmmac_main.c | 53 ++++++++++++------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index f2fc89176654..d576059c04df 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -4319,6 +4319,7 @@ static bool stmmac_vlan_insert(struct stmmac_priv *priv, struct sk_buff *skb, /** * stmmac_tso_allocator - close entry point of the driver * @priv: driver private structure + * @entry: TX queue buffer index * @des: buffer start address * @total_len: total length to fill in descriptors * @last_segment: condition for the last descriptor @@ -4327,8 +4328,9 @@ static bool stmmac_vlan_insert(struct stmmac_priv *priv, struct sk_buff *skb, * This function fills descriptor and request new descriptors according to * buffer length to fill */ -static void stmmac_tso_allocator(struct stmmac_priv *priv, dma_addr_t des, - int total_len, bool last_segment, u32 queue) +static void stmmac_tso_allocator(struct stmmac_priv *priv, u32 *entry, + dma_addr_t des, int total_len, + bool last_segment, u32 queue) { struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue]; struct dma_desc *desc; @@ -4340,14 +4342,13 @@ static void stmmac_tso_allocator(struct stmmac_priv *priv, dma_addr_t des, while (tmp_len > 0) { dma_addr_t curr_addr; - tx_q->cur_tx = STMMAC_NEXT_ENTRY(tx_q->cur_tx, - priv->dma_conf.dma_tx_size); - WARN_ON(tx_q->tx_skbuff[tx_q->cur_tx]); + *entry = STMMAC_NEXT_ENTRY(*entry, priv->dma_conf.dma_tx_size); + WARN_ON(tx_q->tx_skbuff[*entry]); if (tx_q->tbs & STMMAC_TBS_AVAIL) - desc = &tx_q->dma_entx[tx_q->cur_tx].basic; + desc = &tx_q->dma_entx[*entry].basic; else - desc = &tx_q->dma_tx[tx_q->cur_tx]; + desc = &tx_q->dma_tx[*entry]; curr_addr = des + (total_len - tmp_len); stmmac_set_desc_addr(priv, desc, curr_addr); @@ -4486,7 +4487,7 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) { struct dma_desc *desc, *first, *mss_desc = NULL; struct stmmac_priv *priv = netdev_priv(dev); - unsigned int first_entry, tx_packets; + unsigned int first_entry, entry, tx_packets; struct stmmac_txq_stats *txq_stats; struct stmmac_tx_queue *tx_q; bool set_ic, is_last_segment; @@ -4549,22 +4550,24 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) } first_entry = tx_q->cur_tx; - WARN_ON(tx_q->tx_skbuff[first_entry]); + entry = first_entry; + + WARN_ON(tx_q->tx_skbuff[entry]); if (tx_q->tbs & STMMAC_TBS_AVAIL) - desc = &tx_q->dma_entx[first_entry].basic; + desc = &tx_q->dma_entx[entry].basic; else - desc = &tx_q->dma_tx[first_entry]; + desc = &tx_q->dma_tx[entry]; first = desc; /* first descriptor: fill Headers on Buf1 */ des = dma_map_single(priv->device, skb->data, skb_headlen(skb), DMA_TO_DEVICE); if (dma_mapping_error(priv->device, des)) - goto dma_map_err; + goto error; stmmac_set_desc_addr(priv, first, des); - stmmac_tso_allocator(priv, des + proto_hdr_len, pay_len, + stmmac_tso_allocator(priv, &entry, des + proto_hdr_len, pay_len, (nfrags == 0), queue); /* In case two or more DMA transmit descriptors are allocated for this @@ -4579,8 +4582,7 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) * this DMA buffer right after the DMA engine completely finishes the * full buffer transmission. */ - stmmac_set_tx_skb_dma_entry(tx_q, tx_q->cur_tx, des, skb_headlen(skb), - false); + stmmac_set_tx_skb_dma_entry(tx_q, entry, des, skb_headlen(skb), false); /* Prepare fragments */ for (i = 0; i < nfrags; i++) { @@ -4590,14 +4592,15 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) skb_frag_size(frag), DMA_TO_DEVICE); if (dma_mapping_error(priv->device, des)) - goto dma_map_err; + goto error_dma_unmap; - stmmac_tso_allocator(priv, des, skb_frag_size(frag), + stmmac_tso_allocator(priv, &entry, des, skb_frag_size(frag), (i == nfrags - 1), queue); - stmmac_set_tx_skb_dma_entry(tx_q, tx_q->cur_tx, des, + stmmac_set_tx_skb_dma_entry(tx_q, entry, des, skb_frag_size(frag), true); } + tx_q->cur_tx = entry; stmmac_set_tx_dma_last_segment(tx_q, tx_q->cur_tx); @@ -4702,7 +4705,19 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) return NETDEV_TX_OK; -dma_map_err: +error_dma_unmap: + for (;;) { + desc = stmmac_get_tx_desc(priv, tx_q, first_entry); + stmmac_release_tx_desc(priv, desc, priv->descriptor_mode); + stmmac_free_tx_buffer(priv, &priv->dma_conf, queue, + first_entry); + if (first_entry == entry) + break; + + first_entry = STMMAC_NEXT_ENTRY(first_entry, + priv->dma_conf.dma_tx_size); + } +error: dev_err(priv->device, "Tx dma map failed\n"); dev_kfree_skb(skb); priv->xstats.tx_dropped++; From e01620844c5c88b6fcf819d171df8e3976a0e76f Mon Sep 17 00:00:00 2001 From: Jerome Tollet Date: Mon, 24 Aug 2026 16:16:44 +0200 Subject: [PATCH 510/641] net/mlx5e: Prevent stale XSK buffer release on refill retry When an XDP redirect to an AF_XDP socket fails because its RX ring is full, the XSK core frees the buffer. During the subsequent batched refill of a legacy cyclic RQ, mlx5e also releases the WQE's XSK buffer before allocating a replacement. If that refill succeeds only partially, a WQE left without a replacement retains its old buffer pointer. The buffer can meanwhile be allocated to another WQE. A later refill retry can then free the live buffer through the stale pointer and publish the same UMEM frame twice. Mark the WQE as released immediately after the driver-side free. The flag is already cleared when a replacement buffer is assigned, so refill retries no longer release stale pointers. The failure is silent and produces no kernel warning or splat. A standalone legacy cyclic-RQ zero-copy libxsk reproducer, using 64-byte UDP traffic offered at 12 Mpps, detected it: stock stopped after 2,854,914 packets in 4.094 seconds, with 4,542 xdp_rx_ring_full events and 64 ownership/double-publication errors. With this change it processed 356,904,225 packets in 30 seconds despite 571,405 xdp_rx_ring_full events, with no ownership or data errors. Fixes: 3f93f82988bc ("net/mlx5e: RX, Defer page release in legacy rq for better recycling") Cc: stable@vger.kernel.org Suggested-by: Daniel Borkmann Reviewed-by: Dragos Tatulea Signed-off-by: Jerome Tollet Link: https://patch.msgid.link/20260824141645.23700-2-jtollet@cisco.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en_rx.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c b/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c index 206cf9db3466..7bd0606a5253 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c @@ -410,8 +410,11 @@ static inline void mlx5e_free_rx_wqe(struct mlx5e_rq *rq, static void mlx5e_xsk_free_rx_wqe(struct mlx5e_wqe_frag_info *wi) { - if (!(wi->flags & BIT(MLX5E_WQE_FRAG_SKIP_RELEASE))) - xsk_buff_free(*wi->xskp); + if (wi->flags & BIT(MLX5E_WQE_FRAG_SKIP_RELEASE)) + return; + + xsk_buff_free(*wi->xskp); + wi->flags |= BIT(MLX5E_WQE_FRAG_SKIP_RELEASE); } static void mlx5e_dealloc_rx_wqe(struct mlx5e_rq *rq, u16 ix) From 63811edf512584c946e5e96b100e9e280703bbb5 Mon Sep 17 00:00:00 2001 From: Jerome Tollet Date: Mon, 24 Aug 2026 16:16:45 +0200 Subject: [PATCH 511/641] net/mlx5e: Prevent stale XSK buffer release on MPWQE refill retry With AF_XDP on a striding RQ, mlx5e defers releasing XSK buffers until an MPWQE is refilled. If XSK allocation then returns -ENOMEM, actual_wq_head is not advanced and a later NAPI poll retries the same WQE. mlx5e_free_rx_mpwqe() leaves each released slot marked as releasable. On retry it can therefore call xsk_buff_free() again through stale pointers after the frames have returned to the XSK pool and been reallocated. Set all skip_release_bitmap bits in the common error path of mlx5e_xsk_alloc_rx_mpwqe(). This matches mlx5e_alloc_rx_mpwqe(). A successful allocation already clears the bitmap after replacing every buffer, so retries become idempotent without changing the success path. Fault injection forced three consecutive failures for one selected MPWQE. Both an early allocation failure and a partial 8-of-16-buffer unwind released the original 16 XSK buffers only once. Each error left a full bitmap, the following NAPI retry skipped the release, and a later successful allocation cleared it. A 20-second AF_XDP zero-copy pressure run exercised 1,575,262 buffer allocation failures without invalid descriptors, WQE errors, or kernel warnings. Fixes: 4c2a13236807 ("net/mlx5e: RX, Defer page release in striding rq for better recycling") Cc: stable@vger.kernel.org Signed-off-by: Jerome Tollet Reviewed-by: Dragos Tatulea Link: https://patch.msgid.link/20260824141645.23700-3-jtollet@cisco.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en/xsk/rx.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/xsk/rx.c b/drivers/net/ethernet/mellanox/mlx5/core/en/xsk/rx.c index 4f984f6a2cb9..55ec6387ab28 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/xsk/rx.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/xsk/rx.c @@ -3,6 +3,7 @@ #include "rx.h" #include "en/xdp.h" +#include #include #include @@ -156,6 +157,7 @@ err_reuse_batch: xsk_buff_free(xsk_buffs[batch]); err: + bitmap_fill(wi->skip_release_bitmap, rq->mpwqe.pages_per_wqe); rq->stats->buff_alloc_err++; return -ENOMEM; } From 28a57fb2c5df4deb42a06e52fd36c14b37aa0034 Mon Sep 17 00:00:00 2001 From: Dong Chenchen Date: Tue, 25 Aug 2026 20:39:09 +0800 Subject: [PATCH 512/641] net: iptunnel: fix stale transport header during tunnel decapsulation Syzbot reported a crash in qdisc_pkt_len_segs_init() caused by a stale transport_header offset after tunnel decapsulation. BUG: unable to handle page fault for address: ffffed102091a42e Oops: Oops: 0000 [#1] SMP KASAN NOPTI CPU: 0 UID: 0 PID: 340 Comm: qdisc_uaf_repro Not tainted 7.2.0-rc4-00061-g248951ddc14d #256 PREEMPT(full) Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 RIP: 0010:__asan_load2 qdisc_pkt_len_segs_init (net/core/dev.c:4145) __dev_queue_xmit (net/core/dev.c:4787) br_dev_queue_push_xmit (net/bridge/br_forward.c:53) br_handle_frame_finish (net/bridge/br_input.c:229) br_handle_frame (net/bridge/br_input.c:315) __netif_receive_skb_core.constprop.0 (net/core/dev.c:6099) __netif_receive_skb_list_core (net/core/dev.c:6287) netif_receive_skb_list_internal (net/core/dev.c:6445) napi_complete_done (net/core/dev.c:6813) gro_cell_poll (net/core/gro_cells.c:74) __napi_poll (net/core/dev.c:7735) net_rx_action (net/core/dev.c:7798 net/core/dev.c:7955) handle_softirqs (kernel/softirq.c:622) do_softirq (kernel/softirq.c:523 kernel/softirq.c:510 ) __local_bh_enable_ip (kernel/softirq.c:450) tun_get_user (drivers/net/tun.c:1986 (discriminator 1)) tun_chr_write_iter (drivers/net/tun.c:2032) The issue is completely latent until qdisc read transport header in commit 7fb4c1967011 ("net: pull headers in qdisc_pkt_len_segs_init()"). The crash requires four conditions to line up: 1. The incoming packet is encapsulated and carries GSO metadata. The outer transport header offset is stored in skb->transport_header while the packet is still in the outer tunnel context. 2. The tunnel receiver strips the outer headers. skb->data is advanced to the inner frame, but skb->transport_header is left pointing to the now-removed outer L4 header, so it becomes a negative offset relative to the new data. 3. The inner frame is not delivered to the local IP stack. Instead, it is forwarded at L2 by a bridge or HSR, so ip_rcv_core() never runs and the transport header is not reset to the inner L4 offset. 4. The forwarding path calls __dev_queue_xmit(), which enters qdisc_pkt_len_segs_init(). That function computes the GSO header length from skb_transport_offset(skb). Because the offset is negative, the unsigned cast overflows and pskb_may_pull(skb, hdr_len + sizeof(struct tcphdr)) reads past the end of the skb, triggering a KASAN fault or page fault. The issue specifically requires GSO packets (shinfo->gso_size != 0), which are processed/aggregated through gro_cells. Fix this by clearing transport_header to the ~0U sentinel in gro_cell for all tunnnel driver. GTP does not support GRO/GSO, drop the evil GSO packets in GTP directly. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: syzbot+83181a31faf9455499c5@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/69de2bee.a00a0220.475f0.0041.GAE@google.com/T/ Suggested-by: Eric Dumazet Signed-off-by: Dong Chenchen Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260825123909.1463121-1-dongchenchen2@huawei.com Signed-off-by: Jakub Kicinski --- drivers/net/gtp.c | 5 +++++ include/linux/skbuff.h | 5 +++++ net/core/gro_cells.c | 2 ++ 3 files changed, 12 insertions(+) diff --git a/drivers/net/gtp.c b/drivers/net/gtp.c index 298efc76a56b..69fe5717846b 100644 --- a/drivers/net/gtp.c +++ b/drivers/net/gtp.c @@ -318,6 +318,11 @@ static int gtp_inner_proto(struct sk_buff *skb, unsigned int hdrlen, static int gtp_rx(struct pdp_ctx *pctx, struct sk_buff *skb, unsigned int hdrlen, unsigned int role, __u16 inner_proto) { + if (skb_is_gso(skb)) { + netdev_dbg(pctx->dev, "GSO is not supported in GTP\n"); + goto err; + } + if (!gtp_check_ms(skb, pctx, hdrlen, role, inner_proto)) { netdev_dbg(pctx->dev, "No PDP ctx for this MS\n"); return 1; diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h index 671c13494566..421f6fc45451 100644 --- a/include/linux/skbuff.h +++ b/include/linux/skbuff.h @@ -3082,6 +3082,11 @@ static inline bool skb_transport_header_was_set(const struct sk_buff *skb) return skb->transport_header != (typeof(skb->transport_header))~0U; } +static inline void skb_unset_transport_header(struct sk_buff *skb) +{ + skb->transport_header = (typeof(skb->transport_header))~0U; +} + static inline unsigned char *skb_transport_header(const struct sk_buff *skb) { DEBUG_NET_WARN_ON_ONCE(!skb_transport_header_was_set(skb)); diff --git a/net/core/gro_cells.c b/net/core/gro_cells.c index 1b84385c04bd..d8c0a2867120 100644 --- a/net/core/gro_cells.c +++ b/net/core/gro_cells.c @@ -22,6 +22,8 @@ int gro_cells_receive(struct gro_cells *gcells, struct sk_buff *skb) if (unlikely(!(dev->flags & IFF_UP))) goto drop; + skb_unset_transport_header(skb); + if (!gcells->cells || skb_cloned(skb) || netif_elide_gro(dev)) { res = netif_rx(skb); goto unlock; From 13eb543cebef6d6c3ec42e31afe3856f51b7126b Mon Sep 17 00:00:00 2001 From: Victor Nogueira Date: Mon, 24 Aug 2026 12:39:00 -0300 Subject: [PATCH 513/641] net/sched: act_api: budget all shared attributes in notify skbs tcf_action_shared_attrs_size() is supposed to return an upper bound on the netlink attributes every action dump emits outside of TCA_ACT_OPTIONS, so that tcf_add_notify_msg(), tcf_del_notify_msg() and friends can allocate an skb large enough for the reply. It has fallen behind the dump path and is now an underestimate for every single action. Attributes, such as, TCA_ACT_IN_HW_COUNT and TCA_STATS_BASIC_HW are emitted unconditionally and never accounted for. TCA_STATS_PKT64, TCA_ACT_USED_HW_STATS, TCA_STATS_RATE_EST, TCA_STATS_RATE_EST64 require specific conditions, but are also not accounted for. Fix the issue by budgeting all of them so that we have a legitimate upper bound. Even tough for of them require specific conditions, they are cheap so, to avoid overcomplicating, we opted to account for them unconditionally as well to account for a real worst case scenario. Fixes: 4e76e75d6aba ("net sched actions: calculate add/delete event message size") Reported-by: Sashiko Closes: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260810164357.1653956-1-victor%40mojatatu.com Acked-by: Jamal Hadi Salim Signed-off-by: Victor Nogueira Link: https://patch.msgid.link/20260824153903.4143642-2-victor@mojatatu.com Signed-off-by: Jakub Kicinski --- net/sched/act_api.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/net/sched/act_api.c b/net/sched/act_api.c index b4415d358c91..766162b0b810 100644 --- a/net/sched/act_api.c +++ b/net/sched/act_api.c @@ -443,12 +443,21 @@ static size_t tcf_action_shared_attrs_size(const struct tc_action *act) + nla_total_size(IFNAMSIZ) /* TCA_ACT_KIND */ + cookie_len /* TCA_ACT_COOKIE */ + nla_total_size(sizeof(struct nla_bitfield32)) /* TCA_ACT_HW_STATS */ + /* TCA_ACT_USED_HW_STATS */ + + nla_total_size(sizeof(struct nla_bitfield32)) + + nla_total_size(sizeof(u32)) /* TCA_ACT_IN_HW_COUNT */ + nla_total_size(0) /* TCA_ACT_STATS nested */ + nla_total_size(sizeof(struct nla_bitfield32)) /* TCA_ACT_FLAGS */ /* TCA_STATS_BASIC */ + nla_total_size_64bit(sizeof(struct gnet_stats_basic)) - /* TCA_STATS_PKT64 */ - + nla_total_size_64bit(sizeof(u64)) + /* TCA_STATS_BASIC_HW */ + + nla_total_size_64bit(sizeof(struct gnet_stats_basic)) + /* TCA_STATS_PKT64, emitted by both of the basic copies above */ + + 2 * nla_total_size_64bit(sizeof(u64)) + /* TCA_STATS_RATE_EST */ + + nla_total_size_64bit(sizeof(struct gnet_stats_rate_est)) + /* TCA_STATS_RATE_EST64 */ + + nla_total_size_64bit(sizeof(struct gnet_stats_rate_est64)) /* TCA_STATS_QUEUE */ + nla_total_size_64bit(sizeof(struct gnet_stats_queue)) + nla_total_size(0) /* TCA_ACT_OPTIONS nested */ From e9ca46ebc3262b498626c4095826b8fa034bbf21 Mon Sep 17 00:00:00 2001 From: Victor Nogueira Date: Mon, 24 Aug 2026 12:39:01 -0300 Subject: [PATCH 514/641] net/sched: act_api: size the RTM_GETACTION reply from the actions tca_action_gd() already walks every requested action and accumulates attr_size += tcf_action_fill_size(act), then wraps the result in tcf_action_full_attrs_size(). For RTM_DELACTION that value is handed to tcf_del_notify_msg(), which allocates max(attr_size, NLMSG_GOODSIZE). For RTM_GETACTION it is silently discarded and tcf_get_notify() allocates a fixed NLMSG_GOODSIZE skb instead. Any action whose dump exceeds that fixed budget therefore cannot be read back. For example, act_pedit overruns the budget with 32 actions of four munge keys each, act_police with 32 policers once the optional rate/peakrate/result/avrate attributes are present Fix this by passing attr_size through and allocate the reply the way the add and delete paths do. Note on exposure: RTM_GETACTION is the only one of the three action commands that is not capability checked - tc_ctl_action() requires CAP_NET_ADMIN for RTM_NEWACTION and RTM_DELACTION only - so this turns a fixed NLMSG_GOODSIZE reply into a user sized allocation on an unprivileged path. It is bounded by TCA_ACT_MAX_PRIO actions per request, and tca_action_gd() does not reject duplicate indices, so a single large action can be requested 32 times; an act_bpf program near BPF_MAXINSNS is about 32KB of dump, or roughly 1MB for one request. Creating such an action still requires CAP_NET_ADMIN, and the add and delete paths have sized their skbs this way since the Fixes commit. Should this ever need bounding, GFP_KERNEL_ACCOUNT would charge the reply to the caller's memcg. Fixes: 4e76e75d6aba ("net sched actions: calculate add/delete event message size") Reported-by: Sashiko Closes: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260810164357.1653956-1-victor%40mojatatu.com Acked-by: Jamal Hadi Salim Signed-off-by: Victor Nogueira Link: https://patch.msgid.link/20260824153903.4143642-3-victor@mojatatu.com Signed-off-by: Jakub Kicinski --- net/sched/act_api.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/net/sched/act_api.c b/net/sched/act_api.c index 766162b0b810..20b6501fd33b 100644 --- a/net/sched/act_api.c +++ b/net/sched/act_api.c @@ -1697,12 +1697,12 @@ out_nlmsg_trim: static int tcf_get_notify(struct net *net, u32 portid, struct nlmsghdr *n, - struct tc_action *actions[], int event, + struct tc_action *actions[], size_t attr_size, int event, struct netlink_ext_ack *extack) { struct sk_buff *skb; - skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL); + skb = alloc_skb(max(attr_size, NLMSG_GOODSIZE), GFP_KERNEL); if (!skb) return -ENOBUFS; if (tca_get_fill(skb, actions, portid, n->nlmsg_seq, 0, event, @@ -2053,7 +2053,8 @@ tca_action_gd(struct net *net, struct nlattr *nla, struct nlmsghdr *n, attr_size = tcf_action_full_attrs_size(attr_size); if (event == RTM_GETACTION) - ret = tcf_get_notify(net, portid, n, actions, event, extack); + ret = tcf_get_notify(net, portid, n, actions, attr_size, event, + extack); else { /* delete */ ret = tcf_del_notify(net, n, actions, portid, attr_size, extack); if (ret) From 251367a0a3319fa565daf7468b0afd933b1f5ab1 Mon Sep 17 00:00:00 2001 From: Victor Nogueira Date: Mon, 24 Aug 2026 12:39:02 -0300 Subject: [PATCH 515/641] net/sched: act_api: fix skb sizing and action leak on reoffload delete tcf_reoffload_del_notify_msg() sizes the RTM_DELACTION skb with tcf_action_fill_size(action) alone. Unlike every other notification path it never wraps that in tcf_action_full_attrs_size(), so the nlmsg_put() header, struct tcamsg and the TCA_ACT_TAB nest that tca_get_fill() emits - 24 bytes on x86_64 - are not budgeted. As long as the single action stays well under NLMSG_GOODSIZE the floor in alloc_skb() hides this, but once its fill size crosses NLMSG_GOODSIZE the allocation is exactly 24 bytes short and tca_get_fill() runs out of tailroom. That is now easy to reach for an offloadable act_pedit with a large tcfp_nkeys, which commit 8e2efb3f45a5 ("net/sched: add get_fill_size callbacks for actions missing them") started accounting for properly. When that happens tcf_reoffload_del_notify() returns early, before tcf_idr_release_unsafe(), and tcf_action_reoffload_cb() discards the return value: if (tc_act_skip_sw(p->tcfa_flags) && !tc_act_in_hw(p)) tcf_reoffload_del_notify(net, p); The action has just lost its last hardware instance and is skip_sw, so it is left installed while processing no packets, and with no notification to tell userspace about it. An -ENOBUFS from alloc_skb() gets the same treatment. Fix this by budgeting the message header the way the add and delete paths do, and release the action even when the notification cannot be built - dropping the notification is strictly better than leaking a dead action, and there is no caller left to report the error to. Fixes: 13926d19a11e ("flow_offload: add reoffload process to update hw_count") Reported-by: Sashiko Closes: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260810164357.1653956-1-victor%40mojatatu.com Acked-by: Jamal Hadi Salim Signed-off-by: Victor Nogueira Reviewed-by: Pedro Tammela Link: https://patch.msgid.link/20260824153903.4143642-4-victor@mojatatu.com Signed-off-by: Jakub Kicinski --- net/sched/act_api.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/net/sched/act_api.c b/net/sched/act_api.c index 20b6501fd33b..37eced84dfa5 100644 --- a/net/sched/act_api.c +++ b/net/sched/act_api.c @@ -1867,11 +1867,13 @@ static int tcf_action_delete(struct net *net, struct tc_action *actions[]) static struct sk_buff *tcf_reoffload_del_notify_msg(struct net *net, struct tc_action *action) { - size_t attr_size = tcf_action_fill_size(action); struct tc_action *actions[TCA_ACT_MAX_PRIO] = { [0] = action, }; struct sk_buff *skb; + size_t attr_size; + + attr_size = tcf_action_full_attrs_size(tcf_action_fill_size(action)); skb = alloc_skb(max(attr_size, NLMSG_GOODSIZE), GFP_KERNEL); if (!skb) @@ -1888,15 +1890,18 @@ static struct sk_buff *tcf_reoffload_del_notify_msg(struct net *net, static int tcf_reoffload_del_notify(struct net *net, struct tc_action *action) { const struct tc_action_ops *ops = action->ops; - struct sk_buff *skb; + struct sk_buff *skb = NULL; int ret; - if (!rtnl_notify_needed(net, 0, RTNLGRP_TC)) { - skb = NULL; - } else { + if (rtnl_notify_needed(net, 0, RTNLGRP_TC)) { skb = tcf_reoffload_del_notify_msg(net, action); + /* The action has already lost its hardware instance and is + * skip_sw, so it must be released whether or not the + * notification can be built. Drop the notification rather + * than leave an action behind that processes no packets. + */ if (IS_ERR(skb)) - return PTR_ERR(skb); + skb = NULL; } ret = tcf_idr_release_unsafe(action); From 5271b79b7ad68dcb222e893773f92bdabf7750f3 Mon Sep 17 00:00:00 2001 From: "Cen Zhang (Microsoft Security FORGE Labs)" Date: Thu, 27 Aug 2026 19:55:10 -0400 Subject: [PATCH 516/641] tcp: fix use-after-free in do_tcp_getsockopt(TCP_CONGESTION) do_tcp_getsockopt() reads icsk->icsk_ca_ops->name without holding rcu_read_lock(). Since commit 0baf26b0fcd7 ("bpf: tcp: Support tcp_congestion_ops in bpf"), icsk_ca_ops can point to dynamically allocated BPF struct_ops memory that may be freed concurrently via setsockopt(TCP_CONGESTION), leading to a use-after-free. BUG: KASAN: slab-use-after-free in _copy_to_user+0x37/0x60 Read of size 16 at addr ffff888013505260 by task exploit/149 _copy_to_user+0x37/0x60 do_tcp_getsockopt+0x158a/0x2460 (net/ipv4/tcp.c:4585) tcp_getsockopt+0x91/0xf0 __sys_getsockopt+0xf7/0x170 Fix this by holding rcu_read_lock() around the ca_ops->name access, using READ_ONCE() to load icsk_ca_ops, and copying the name to a stack buffer before releasing the lock. Also annotate the relevant icsk_ca_ops stores with WRITE_ONCE() to fix the accompanying KCSAN data-race issue. Fixes: 0baf26b0fcd7 ("bpf: tcp: Support tcp_congestion_ops in bpf") Suggested-by: Eric Dumazet Reported-by: Xiang Mei (Microsoft) Link: https://lore.kernel.org/all/20260821182449.79785-2-blbllhy@gmail.com/ Cc: AutonomousCodeSecurity@microsoft.com Cc: stable@vger.kernel.org Reviewed-by: Eric Dumazet Signed-off-by: Cen Zhang (Microsoft Security FORGE Labs) Reviewed-by: Jiayuan Chen Reviewed-by: Matthieu Baerts (NGI0) Reviewed-by: Breno Leitao Link: https://patch.msgid.link/d3f97f1acbf0010898148be6e6406e4b8b4a5c84.1787870710.git.blbllhy@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv4/tcp.c | 14 +++++++++++--- net/ipv4/tcp_cong.c | 4 ++-- net/ipv4/tcp_dctcp.c | 2 +- net/ipv4/tcp_minisocks.c | 2 +- net/ipv4/tcp_output.c | 2 +- 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index 93d723d8c109..740999c9efff 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -4576,16 +4576,24 @@ int do_tcp_getsockopt(struct sock *sk, int level, val = !inet_csk_in_pingpong_mode(sk); break; - case TCP_CONGESTION: + case TCP_CONGESTION: { + char ca_name[TCP_CA_NAME_MAX] = {}; + if (copy_from_sockptr(&len, optlen, sizeof(int))) return -EFAULT; len = min_t(unsigned int, len, TCP_CA_NAME_MAX); if (copy_to_sockptr(optlen, &len, sizeof(int))) return -EFAULT; - if (copy_to_sockptr(optval, icsk->icsk_ca_ops->name, len)) + + rcu_read_lock(); + memcpy(ca_name, READ_ONCE(icsk->icsk_ca_ops)->name, + sizeof(ca_name)); + rcu_read_unlock(); + + if (copy_to_sockptr(optval, ca_name, len)) return -EFAULT; return 0; - + } case TCP_ULP: if (copy_from_sockptr(&len, optlen, sizeof(int))) return -EFAULT; diff --git a/net/ipv4/tcp_cong.c b/net/ipv4/tcp_cong.c index e9f6c77e0631..8e83ef81fc18 100644 --- a/net/ipv4/tcp_cong.c +++ b/net/ipv4/tcp_cong.c @@ -223,7 +223,7 @@ void tcp_assign_congestion_control(struct sock *sk) ca = rcu_dereference(net->ipv4.tcp_congestion_control); if (unlikely(!bpf_try_module_get(ca, ca->owner))) ca = &tcp_reno; - icsk->icsk_ca_ops = ca; + WRITE_ONCE(icsk->icsk_ca_ops, ca); rcu_read_unlock(); memset(icsk->icsk_ca_priv, 0, sizeof(icsk->icsk_ca_priv)); @@ -253,7 +253,7 @@ static void tcp_reinit_congestion_control(struct sock *sk, struct inet_connection_sock *icsk = inet_csk(sk); tcp_cleanup_congestion_control(sk); - icsk->icsk_ca_ops = ca; + WRITE_ONCE(icsk->icsk_ca_ops, ca); icsk->icsk_ca_setsockopt = 1; memset(icsk->icsk_ca_priv, 0, sizeof(icsk->icsk_ca_priv)); diff --git a/net/ipv4/tcp_dctcp.c b/net/ipv4/tcp_dctcp.c index 274e628e7cf8..99f68c2992d0 100644 --- a/net/ipv4/tcp_dctcp.c +++ b/net/ipv4/tcp_dctcp.c @@ -111,7 +111,7 @@ __bpf_kfunc static void dctcp_init(struct sock *sk) /* No ECN support? Fall back to Reno. Also need to clear * ECT from sk since it is set during 3WHS for DCTCP. */ - inet_csk(sk)->icsk_ca_ops = &dctcp_reno; + WRITE_ONCE(inet_csk(sk)->icsk_ca_ops, &dctcp_reno); INET_ECN_dontxmit(sk); } diff --git a/net/ipv4/tcp_minisocks.c b/net/ipv4/tcp_minisocks.c index f3fa0b18eda0..0ddfd5af6e58 100644 --- a/net/ipv4/tcp_minisocks.c +++ b/net/ipv4/tcp_minisocks.c @@ -507,7 +507,7 @@ void tcp_ca_openreq_child(struct sock *sk, const struct dst_entry *dst) ca = tcp_ca_find_key(ca_key); if (likely(ca && bpf_try_module_get(ca, ca->owner))) { icsk->icsk_ca_dst_locked = tcp_ca_dst_locked(dst); - icsk->icsk_ca_ops = ca; + WRITE_ONCE(icsk->icsk_ca_ops, ca); ca_got_dst = true; } rcu_read_unlock(); diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c index c5ffffee4349..d960e3de7d50 100644 --- a/net/ipv4/tcp_output.c +++ b/net/ipv4/tcp_output.c @@ -4092,7 +4092,7 @@ static void tcp_ca_dst_init(struct sock *sk, const struct dst_entry *dst) if (likely(ca && bpf_try_module_get(ca, ca->owner))) { bpf_module_put(icsk->icsk_ca_ops, icsk->icsk_ca_ops->owner); icsk->icsk_ca_dst_locked = tcp_ca_dst_locked(dst); - icsk->icsk_ca_ops = ca; + WRITE_ONCE(icsk->icsk_ca_ops, ca); } rcu_read_unlock(); } From 385e474086c2e7e29e2dded690be40dc273e20ee Mon Sep 17 00:00:00 2001 From: "Cen Zhang (Microsoft Security FORGE Labs)" Date: Thu, 27 Aug 2026 19:55:11 -0400 Subject: [PATCH 517/641] tcp: fix use-after-free in do_tcp_getsockopt(TCP_CC_INFO) do_tcp_getsockopt() reads icsk->icsk_ca_ops and dereferences the get_info function pointer without rcu_read_lock(). With BPF struct_ops congestion control, ca_ops can point to dynamically allocated memory that is freed concurrently, resulting in a use-after-free when the kernel dereferences or calls through the stale pointer. BUG: KASAN: slab-use-after-free in do_tcp_getsockopt+0x2037/0x23e0 Read of size 8 at addr ffff888013701258 by task exploit/149 do_tcp_getsockopt+0x2037/0x23e0 (net/ipv4/tcp.c:4564) tcp_getsockopt+0x91/0xf0 __sys_getsockopt+0xf7/0x170 Fix this by wrapping the ca_ops load and get_info call within rcu_read_lock()/rcu_read_unlock(), and using READ_ONCE() to load the icsk_ca_ops pointer. Fixes: 0baf26b0fcd7 ("bpf: tcp: Support tcp_congestion_ops in bpf") Suggested-by: Eric Dumazet Cc: AutonomousCodeSecurity@microsoft.com Cc: stable@vger.kernel.org Reviewed-by: Eric Dumazet Signed-off-by: Cen Zhang (Microsoft Security FORGE Labs) Reviewed-by: Jiayuan Chen Reviewed-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/65fd3816ed5d541d9edd4bf4fcf97104a2cf907a.1787870710.git.blbllhy@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv4/tcp.c | 4 +++- net/ipv4/tcp_dctcp.c | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index 740999c9efff..1c867a302444 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -4561,9 +4561,11 @@ int do_tcp_getsockopt(struct sock *sk, int level, if (copy_from_sockptr(&len, optlen, sizeof(int))) return -EFAULT; - ca_ops = icsk->icsk_ca_ops; + rcu_read_lock(); + ca_ops = READ_ONCE(icsk->icsk_ca_ops); if (ca_ops && ca_ops->get_info) sz = ca_ops->get_info(sk, ~0U, &attr, &info); + rcu_read_unlock(); len = min_t(unsigned int, len, sz); if (copy_to_sockptr(optlen, &len, sizeof(int))) diff --git a/net/ipv4/tcp_dctcp.c b/net/ipv4/tcp_dctcp.c index 99f68c2992d0..5b457f68a581 100644 --- a/net/ipv4/tcp_dctcp.c +++ b/net/ipv4/tcp_dctcp.c @@ -228,7 +228,7 @@ static size_t dctcp_get_info(struct sock *sk, u32 ext, int *attr, if (ext & (1 << (INET_DIAG_DCTCPINFO - 1)) || ext & (1 << (INET_DIAG_VEGASINFO - 1))) { memset(&info->dctcp, 0, sizeof(info->dctcp)); - if (inet_csk(sk)->icsk_ca_ops != &dctcp_reno) { + if (READ_ONCE(inet_csk(sk)->icsk_ca_ops) != &dctcp_reno) { info->dctcp.dctcp_enabled = 1; info->dctcp.dctcp_ce_state = (u16) ca->ce_state; info->dctcp.dctcp_alpha = ca->dctcp_alpha; From 2188569e7e1b0bc3f3b557dc97ab7a02befc11c8 Mon Sep 17 00:00:00 2001 From: Xin Long Date: Wed, 26 Aug 2026 15:49:04 -0400 Subject: [PATCH 518/641] sctp: fix a TOCTOU race in SCTP_CMD_TIMER_START The SCTP_CMD_TIMER_START handler checks timer_pending() before calling timer_reduce(). The timer can expire and detach between these operations, causing timer_reduce() to rearm the timer without taking the association reference required for the newly armed timer. The timer callback later unconditionally drops its association reference, which can leave the association reference count unbalanced and result in use-after-free during association teardown. Use the return value of timer_reduce() to determine whether the timer was actually armed. Take the association reference only when timer_reduce() successfully starts a new timer, closing the race between checking the timer state and rearming it. This issue was reported by Nico Yip (@_cyeaa_) working with TrendAI Zero Day Initiative. Fixes: 20a785aa52c8 ("sctp: Don't add the shutdown timer if its already been added") Reported-by: Zero Day Initiative Signed-off-by: Xin Long Link: https://patch.msgid.link/9d8f1b5c50329d5ea7c642128d35681abaa9ed20.1787773744.git.lucien.xin@gmail.com Signed-off-by: Jakub Kicinski --- net/sctp/sm_sideeffect.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/net/sctp/sm_sideeffect.c b/net/sctp/sm_sideeffect.c index 94716406d602..0d99b7e8c082 100644 --- a/net/sctp/sm_sideeffect.c +++ b/net/sctp/sm_sideeffect.c @@ -1545,17 +1545,8 @@ static int sctp_cmd_interpreter(enum sctp_event_type event_type, timeout = asoc->timeouts[cmd->obj.to]; BUG_ON(!timeout); - /* - * SCTP has a hard time with timer starts. Because we process - * timer starts as side effects, it can be hard to tell if we - * have already started a timer or not, which leads to BUG - * halts when we call add_timer. So here, instead of just starting - * a timer, if the timer is already started, and just mod - * the timer with the shorter of the two expiration times - */ - if (!timer_pending(timer)) + if (!timer_reduce(timer, jiffies + timeout)) sctp_association_hold(asoc); - timer_reduce(timer, jiffies + timeout); break; case SCTP_CMD_TIMER_RESTART: From 78004e9a87f240df03e2f73120d291763c32e0a7 Mon Sep 17 00:00:00 2001 From: Ali Ahmet Memis Date: Fri, 21 Aug 2026 01:45:27 +0000 Subject: [PATCH 519/641] openrisc: fix arbitrary kernel memory access via or1k_atomic syscall sys_or1k_atomic() (syscall 244 in the "or1k" ABI) takes two user pointers, v1 and v2, and swaps the words they point to in hand-written assembly. l.lwz r29,0(r4) l.lwz r27,0(r5) l.sw 0(r4),r27 l.sw 0(r5),r29 The pointers are not checked with access_ok(). The four memory accesses also have no exception table entries. A caller passes a kernel address as either pointer, and the syscall reads from and writes to it directly. This gives an unprivileged process a kernel read/write primitive. It overwrites kernel data such as the sys_call_table, gaining code execution in kernel context. Check both pointers before entering the critical section. Add fixups for the four memory accesses so faults on valid but unmapped user addresses return -EFAULT. [shorne@gmail.com: fix comment style] Fixes: 9d02a4283e9c ("OpenRISC: Boot code") Cc: stable@vger.kernel.org Signed-off-by: Ali Ahmet Memis Signed-off-by: Stafford Horne --- arch/openrisc/kernel/entry.S | 43 ++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/arch/openrisc/kernel/entry.S b/arch/openrisc/kernel/entry.S index c7e90b09645e..18e68680471e 100644 --- a/arch/openrisc/kernel/entry.S +++ b/arch/openrisc/kernel/entry.S @@ -1223,15 +1223,50 @@ _no_syscall_trace: * */ +/* Keep this literal; hi()/lo() can't use the UL-suffixed TASK_SIZE. */ +#define OR1K_ATOMIC_ADDR_LIMIT 0x7ffffffc + ENTRY(sys_or1k_atomic) /* FIXME: This ignores r3 and always does an XCHG */ + + /* Check both user pointers before accessing them. */ + l.movhi r13,hi(OR1K_ATOMIC_ADDR_LIMIT) + l.ori r13,r13,lo(OR1K_ATOMIC_ADDR_LIMIT) + l.sfgtu r4,r13 + l.bf 9f + l.nop + l.sfgtu r5,r13 + l.bf 9f + l.nop + DISABLE_INTERRUPTS(r17,r19) - l.lwz r29,0(r4) - l.lwz r27,0(r5) - l.sw 0(r4),r27 - l.sw 0(r5),r29 +10: l.lwz r29,0(r4) +11: l.lwz r27,0(r5) +12: l.sw 0(r4),r27 +13: l.sw 0(r5),r29 ENABLE_INTERRUPTS(r17) l.jr r9 l.or r11,r0,r0 + /* + * Either pointer was outside user space, or turned out to be + * unmapped/inaccessible when we actually touched it. + */ +9: l.jr r9 + l.addi r11,r0,-EFAULT + + .section .fixup, "ax" +14: + ENABLE_INTERRUPTS(r17) + l.j 9b + l.nop + .previous + + .section __ex_table, "a" + .long 10b, 14b + .long 11b, 14b + .long 12b, 14b + .long 13b, 14b + .previous + /* ============================================================[ EOF ]=== */ From 41a52ba4a5fe25b2cca431fe76fb5f3d8ad35139 Mon Sep 17 00:00:00 2001 From: Zhan Xusheng Date: Fri, 28 Aug 2026 12:58:43 +0800 Subject: [PATCH 520/641] ntfs: read WOF chunks outside the decompression lock WOF decompression uses four module-global workspaces, one per compression format, each with a static mutex. ntfs_read_wof_compressed_block() takes that mutex once and holds it across the whole chunk loop, so both block reads run inside it: mutex_lock(ws->lock); for each chunk { parse_wof_chunk_table(..., ws->input, ...); /* reads disk */ ntfs_read_wof_chunk(..., ws->input, ...); /* reads disk */ decompress into ws->output; } mutex_unlock(ws->lock); Readers of system-compressed files then serialise system-wide on the disk waits, not just on the decompressor scratch the lock exists for. One reader sleeping in submit_bio_wait() blocks all the rest. The waits dominate. Reading an 8 MiB xpress4k file (2048 chunks at a 48% compressed ratio, so 2048 acquisitions and 4096 block reads) and timing ws->lock against the part of it spent in ntfs_bdev_read(): backing store held of that in I/O held after virtio, host page cache 348 ms 321 ms (92%) 24.6 ms virtio, throttled 100 MB/s 978 ms 948 ms (96%) 36.6 ms The page-cache row is a lower bound, having no seek cost at all, and the share still grows with slower storage because only the wait scales while decompression stays near 26 ms. The reads are inside the lock only because they land in ws->input, a buffer shared through the workspace. Nothing else requires it: parse_wof_chunk_table() and ntfs_read_wof_chunk() already take the buffer as a parameter and both set *chunk_mem to a pointer inside it, so a caller-owned buffer works unchanged. Allocate that buffer per call, do both reads without the lock, and take the lock only around decompression, which is the step needing ws->output and ws->scratch. squashfs is arranged this way already: its squashfs_decompress() is handed a bio that has been read, and locks only for the CPU work. Block reads are unchanged in number, they just no longer run under the lock, and hold time stops tracking device speed. This also unnests two per-inode locks from the global one, runlist->lock taken by both reads and base_ni->mrec_lock taken for a resident stream. A resident chunk needs no I/O at all, yet used to queue behind a reader blocked in submit_bio_wait() and then take mrec_lock inside the global mutex. The buffer is 4608 bytes for xpress4k and at most 33280 for lzx32k. This path already does GFP_NOFS allocations per call in ntfs_attr_iget(), and in ntfs_attr_get_search_ctx() for a resident stream, so one more does not change how it behaves under memory pressure. The workspace keeps output and scratch, 4 KiB to 32 KiB and 6224 bytes (xpress) or 10240 (lzx), and its "already allocated" test moves from ws->input to ws->output. The lock is now taken per chunk rather than per call, which differs only for a folio spanning several chunks: a few more uncontended mutex operations in exchange for not holding it across the reads between them. Verified under QEMU against an uncompressed copy of the same data, on an 8 MiB file and a 100000 byte one, the latter covering the tail chunk that is not a full comp_unit. Signed-off-by: Zhan Xusheng Signed-off-by: Namjae Jeon --- fs/ntfs/wof.c | 127 +++++++++++++++++++++++++++++++------------------- 1 file changed, 79 insertions(+), 48 deletions(-) diff --git a/fs/ntfs/wof.c b/fs/ntfs/wof.c index 8f84c2212eee..9847259e5b1a 100644 --- a/fs/ntfs/wof.c +++ b/fs/ntfs/wof.c @@ -39,8 +39,6 @@ struct ntfs_wof_workspace { struct mutex *lock; const struct ntfs_codec_ops *codec; u32 comp_unit; - void *input; - size_t input_size; void *output; void *scratch; }; @@ -97,30 +95,36 @@ static struct ntfs_wof_workspace *ntfs_wof_workspace(u8 block_size_bits) } } +/* + * Size of the buffer a chunk is read into. A chunk is read straight off the + * device, so the buffer has to hold @comp_unit bytes plus the leading partial + * sector. + */ +static size_t ntfs_wof_input_size(const struct ntfs_wof_workspace *ws) +{ + return round_up((size_t)ws->comp_unit + 511, 512); +} + static int ntfs_wof_workspace_prepare(struct ntfs_wof_workspace *ws) { - void *input, *output, *scratch; + void *output, *scratch; size_t scratch_size; - if (ws->input) + if (ws->output) return 0; - ws->input_size = round_up((size_t)ws->comp_unit + 511, 512); scratch_size = ws->codec->scratch_size(ws->comp_unit); if (!scratch_size) return -EINVAL; - input = kvmalloc(ws->input_size, GFP_NOFS); output = kvmalloc(ws->comp_unit, GFP_NOFS); scratch = kvzalloc(scratch_size, GFP_NOFS); - if (!input || !output || !scratch) { - kvfree(input); + if (!output || !scratch) { kvfree(output); kvfree(scratch); return -ENOMEM; } - ws->input = input; ws->output = output; ws->scratch = scratch; return 0; @@ -134,10 +138,8 @@ void ntfs_wof_free_workspaces(void) struct ntfs_wof_workspace *ws = ntfs_wof_workspaces[i]; mutex_lock(ws->lock); - kvfree(ws->input); kvfree(ws->output); kvfree(ws->scratch); - ws->input = NULL; ws->output = NULL; ws->scratch = NULL; mutex_unlock(ws->lock); @@ -602,6 +604,51 @@ static int ntfs_wof_try_direct(struct ntfs_wof_workspace *ws, chunk_end, src, src_len, dst_len); } +/* + * Decompress one chunk into @folio. Only this step needs the workspace, so it + * is the only step that takes the workspace lock. + */ +static int ntfs_wof_decompress_chunk(struct ntfs_wof_workspace *ws, + struct ntfs_volume *vol, + struct address_space *mapping, + struct folio *folio, loff_t folio_start, + loff_t folio_end, u64 chunk_file_offset, + char *chunk_mem, u32 chunk_size, + u32 decomp_size) +{ + loff_t chunk_end = chunk_file_offset + decomp_size; + loff_t copy_start, copy_end; + int err; + + mutex_lock(ws->lock); + err = ntfs_wof_workspace_prepare(ws); + if (err) + goto out_unlock; + + err = ntfs_wof_try_direct(ws, mapping, folio, chunk_file_offset, + chunk_end, chunk_mem, chunk_size, + decomp_size); + if (err != -EAGAIN) + goto out_unlock; + + err = ntfs_wof_decode(ws, chunk_mem, chunk_size, ws->output, + decomp_size); + if (err) { + ntfs_error(vol->sb, "Decompression failed: %d", err); + err = -EINVAL; + goto out_unlock; + } + + copy_start = max_t(loff_t, folio_start, chunk_file_offset); + copy_end = min_t(loff_t, folio_end, chunk_file_offset + decomp_size); + memcpy_to_folio(folio, copy_start - folio_start, + ws->output + copy_start - chunk_file_offset, + copy_end - copy_start); +out_unlock: + mutex_unlock(ws->lock); + return err; +} + int ntfs_read_wof_compressed_block(struct folio *folio) { struct address_space *mapping = folio->mapping; @@ -613,6 +660,8 @@ int ntfs_read_wof_compressed_block(struct folio *folio) loff_t folio_start = folio_pos(folio); loff_t folio_end = folio_next_pos(folio); char *chunk_mem; + void *input; + size_t input_size; u32 decomp_size; u64 chunk_count, chunk_idx, last_chunk, chunk_offset; int err = 0; @@ -652,10 +701,12 @@ int ntfs_read_wof_compressed_block(struct folio *folio) goto out_iput; } - mutex_lock(ws->lock); - err = ntfs_wof_workspace_prepare(ws); - if (err) - goto out_unlock_ws; + input_size = ntfs_wof_input_size(ws); + input = kvmalloc(input_size, GFP_NOFS); + if (!input) { + err = -ENOMEM; + goto out_iput; + } chunk_idx = div_u64(folio_start, ws->comp_unit); last_chunk = @@ -663,55 +714,35 @@ int ntfs_read_wof_compressed_block(struct folio *folio) chunk_count = DIV_ROUND_UP_ULL(i_size, ws->comp_unit); for (; chunk_idx <= last_chunk; chunk_idx++) { u32 chunk_size; - u64 chunk_file_offset; - loff_t chunk_end, copy_start, copy_end; decomp_size = chunk_idx + 1 == chunk_count ? i_size - chunk_idx * ws->comp_unit : ws->comp_unit; err = parse_wof_chunk_table(ni, wof_ni, chunk_idx, chunk_count, decomp_size, &chunk_offset, - &chunk_size, ws->input, - ws->input_size); + &chunk_size, input, input_size); if (err) - goto out_unlock_ws; + goto out_free_input; err = ntfs_read_wof_chunk(vol, wof_ni, chunk_offset, chunk_size, - ws->input, ws->input_size, - &chunk_mem); + input, input_size, &chunk_mem); if (err) - goto out_unlock_ws; + goto out_free_input; - chunk_file_offset = chunk_idx * ws->comp_unit; - chunk_end = chunk_file_offset + decomp_size; - err = ntfs_wof_try_direct(ws, mapping, folio, chunk_file_offset, - chunk_end, chunk_mem, chunk_size, - decomp_size); - if (!err) - continue; - if (err != -EAGAIN) - goto out_unlock_ws; - - err = ntfs_wof_decode(ws, chunk_mem, chunk_size, ws->output, - decomp_size); - if (err) { - ntfs_error(vol->sb, "Decompression failed: %d", err); - err = -EINVAL; - goto out_unlock_ws; - } - copy_start = max_t(loff_t, folio_start, chunk_file_offset); - copy_end = min_t(loff_t, folio_end, - chunk_file_offset + decomp_size); - memcpy_to_folio(folio, copy_start - folio_start, - ws->output + copy_start - chunk_file_offset, - copy_end - copy_start); + err = ntfs_wof_decompress_chunk(ws, vol, mapping, folio, + folio_start, folio_end, + chunk_idx * ws->comp_unit, + chunk_mem, chunk_size, + decomp_size); + if (err) + goto out_free_input; } if (folio_end > i_size) folio_zero_segment(folio, i_size - folio_start, folio_size(folio)); -out_unlock_ws: - mutex_unlock(ws->lock); +out_free_input: + kvfree(input); out_iput: iput(wof_inode); out: From 03c6ecc4b4b13a3901f207152451fdd2d82e40c4 Mon Sep 17 00:00:00 2001 From: Jacopo Labardi Date: Sun, 30 Aug 2026 02:15:36 +0200 Subject: [PATCH 521/641] ntfs: fix FITRIM range alignment ntfs_trim_fs() aligns the start of a free extent up to the device discard granularity, but derives the discard length by aligning the original extent length down. When the free extent start is not discard-aligned, adding that length to the aligned start can extend the discard past the free extent and into allocated clusters. For example, with 4 KiB clusters and 32 KiB discard granularity, the free extent [4 KiB, 36 KiB) becomes the discard range [32 KiB, 64 KiB), so 28 KiB beyond the free extent may be discarded. Align the absolute end of the free extent down and derive the length from the two aligned endpoints. Skip extents that contain no full discard unit. Reproduced with a 4 KiB-cluster NTFS filesystem on scsi_debug configured for 32 KiB discard granularity and read-zero-after-trim. Before this change, FITRIM zeroed seven allocated 4 KiB clusters following an unaligned 32 KiB hole. With this change, the same data remains intact across FITRIM and remount. Fixes: 11ccc9107dc4 ("ntfs: update runlist handling and cluster allocator") Cc: stable@vger.kernel.org Assisted-by: OpenAI Codex:GPT-5.6 Sol Max Signed-off-by: Jacopo Labardi Signed-off-by: Namjae Jeon --- fs/ntfs/bitmap.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/fs/ntfs/bitmap.c b/fs/ntfs/bitmap.c index b1436b3151b9..1840b7d84c62 100644 --- a/fs/ntfs/bitmap.c +++ b/fs/ntfs/bitmap.c @@ -64,7 +64,7 @@ int ntfs_trim_fs(struct ntfs_volume *vol, struct fstrim_range *range) end = start_buf; while (end < end_buf) { - u64 aligned_start, aligned_count; + u64 aligned_start, aligned_end, aligned_count; u64 start = find_next_zero_bit(bitmap, end_buf - start_buf, end - start_buf) + start_buf; if (start >= end_buf) @@ -74,8 +74,10 @@ int ntfs_trim_fs(struct ntfs_volume *vol, struct fstrim_range *range) start - start_buf) + start_buf; aligned_start = ALIGN(ntfs_cluster_to_bytes(vol, start), dq); - aligned_count = - ALIGN_DOWN(ntfs_cluster_to_bytes(vol, end - start), dq); + aligned_end = ALIGN_DOWN(ntfs_cluster_to_bytes(vol, end), dq); + if (aligned_start >= aligned_end) + continue; + aligned_count = aligned_end - aligned_start; if (aligned_count >= range->minlen) { ret = blkdev_issue_discard(vol->sb->s_bdev, aligned_start >> 9, aligned_count >> 9, GFP_NOFS); From 729eb52aa17f480a14ec0e7df363deabd41e57a3 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Mon, 24 Aug 2026 01:18:16 +0900 Subject: [PATCH 522/641] coccinelle: pool_zalloc-simple: drop the pci_pool_alloc rules pci_pool_alloc() and pci_pool_zalloc() were removed by commit 88dee3b0efe4 ("PCI: Remove unused pci_pool wrappers"). So drop the pci_pool_alloc rules. No functional change. Signed-off-by: Sang-Heon Jeon Signed-off-by: Julia Lawall --- .../api/alloc/pool_zalloc-simple.cocci | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/scripts/coccinelle/api/alloc/pool_zalloc-simple.cocci b/scripts/coccinelle/api/alloc/pool_zalloc-simple.cocci index 9c61a23b34db..07af8c7433d1 100644 --- a/scripts/coccinelle/api/alloc/pool_zalloc-simple.cocci +++ b/scripts/coccinelle/api/alloc/pool_zalloc-simple.cocci @@ -5,7 +5,7 @@ // Copyright: (C) 2015 Intel Corp. // Options: --no-includes --include-headers // -// Keywords: dma_pool_zalloc, pci_pool_zalloc +// Keywords: dma_pool_zalloc // virtual context @@ -22,7 +22,7 @@ expression x; statement S; @@ -* x = \(dma_pool_alloc\|pci_pool_alloc\)(...); +* x = dma_pool_alloc(...); if ((x==NULL) || ...) S * memset(x,0, ...); @@ -41,17 +41,6 @@ statement S; if ((x==NULL) || ...) S - memset(x,0,...); -@depends on patch@ -expression x; -expression a,b,c; -statement S; -@@ - -- x = pci_pool_alloc(a,b,c); -+ x = pci_pool_zalloc(a,b,c); - if ((x==NULL) || ...) S -- memset(x,0,...); - //---------------------------------------------------------- // For org and report mode //---------------------------------------------------------- @@ -63,7 +52,7 @@ statement S; position p; @@ - x = @p\(dma_pool_alloc\|pci_pool_alloc\)(a,b,c); + x =@p dma_pool_alloc(a,b,c); if ((x==NULL) || ...) S memset(x,0, ...); From 3beb6e620fae118f1dc1092b08b2c82b4e762b8a Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Mon, 24 Aug 2026 01:18:17 +0900 Subject: [PATCH 523/641] coccinelle: kfree_mismatch: drop vmalloc_exec vmalloc_exec() was removed by commit 7a0e27b2a0ce ("mm: remove vmalloc_exec"). So drop it from the rules. No functional change. Signed-off-by: Sang-Heon Jeon Signed-off-by: Julia Lawall --- scripts/coccinelle/api/kfree_mismatch.cocci | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/coccinelle/api/kfree_mismatch.cocci b/scripts/coccinelle/api/kfree_mismatch.cocci index d46a9b3eb7b3..bc90d0c071ac 100644 --- a/scripts/coccinelle/api/kfree_mismatch.cocci +++ b/scripts/coccinelle/api/kfree_mismatch.cocci @@ -29,7 +29,7 @@ position kok, vok; } else { ... E = \(vmalloc\|vzalloc\|vmalloc_user\|vmalloc_node\| - vzalloc_node\|vmalloc_exec\|vmalloc_32\| + vzalloc_node\|vmalloc_32\| vmalloc_32_user\|__vmalloc\|__vmalloc_node_range\| __vmalloc_node\)(...)@vok ... @@ -42,7 +42,7 @@ position kok, vok; if (E == NULL) { ... E = \(vmalloc\|vzalloc\|vmalloc_user\|vmalloc_node\| - vzalloc_node\|vmalloc_exec\|vmalloc_32\| + vzalloc_node\|vmalloc_32\| vmalloc_32_user\|__vmalloc\|__vmalloc_node_range\| __vmalloc_node\)(...)@vok ... @@ -68,7 +68,7 @@ position f != free.fok; * E = \(kmalloc\|kzalloc\|krealloc\|kcalloc\|kmalloc_node\| * kzalloc_node\|kmalloc_array\|kmalloc_array_node\| * kcalloc_node\)(...)@a - ... when != if (...) { ... E = \(vmalloc\|vzalloc\|vmalloc_user\|vmalloc_node\|vzalloc_node\|vmalloc_exec\|vmalloc_32\|vmalloc_32_user\|__vmalloc\|__vmalloc_node_range\|__vmalloc_node\)(...); ... } + ... when != if (...) { ... E = \(vmalloc\|vzalloc\|vmalloc_user\|vmalloc_node\|vzalloc_node\|vmalloc_32\|vmalloc_32_user\|__vmalloc\|__vmalloc_node_range\|__vmalloc_node\)(...); ... } when != is_vmalloc_addr(E) when any * \(vfree\|vfree_atomic\|kvfree\)(E)@f @@ -82,7 +82,7 @@ position f != free.fok; E = \(kmalloc\|kzalloc\|krealloc\|kcalloc\|kmalloc_node\| kzalloc_node\|kmalloc_array\|kmalloc_array_node\| kcalloc_node\)(...)@a - ... when != if (...) { ... E = \(vmalloc\|vzalloc\|vmalloc_user\|vmalloc_node\|vzalloc_node\|vmalloc_exec\|vmalloc_32\|vmalloc_32_user\|__vmalloc\|__vmalloc_node_range\|__vmalloc_node\)(...); ... } + ... when != if (...) { ... E = \(vmalloc\|vzalloc\|vmalloc_user\|vmalloc_node\|vzalloc_node\|vmalloc_32\|vmalloc_32_user\|__vmalloc\|__vmalloc_node_range\|__vmalloc_node\)(...); ... } when != is_vmalloc_addr(E) when any - \(vfree\|vfree_atomic\|kvfree\)(E)@f @@ -95,7 +95,7 @@ position f != free.fok; @@ * E = \(vmalloc\|vzalloc\|vmalloc_user\|vmalloc_node\|vzalloc_node\| -* vmalloc_exec\|vmalloc_32\|vmalloc_32_user\|__vmalloc\| +* vmalloc_32\|vmalloc_32_user\|__vmalloc\| * __vmalloc_node_range\|__vmalloc_node\)(...)@a ... when != is_vmalloc_addr(E) when any @@ -108,7 +108,7 @@ position f != free.fok; @@ E = \(vmalloc\|vzalloc\|vmalloc_user\|vmalloc_node\|vzalloc_node\| - vmalloc_exec\|vmalloc_32\|vmalloc_32_user\|__vmalloc\| + vmalloc_32\|vmalloc_32_user\|__vmalloc\| __vmalloc_node_range\|__vmalloc_node\)(...)@a ... when != is_vmalloc_addr(E) when any From 5264281879ef19b68f61846a7455a627f10b92e8 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Mon, 24 Aug 2026 01:18:18 +0900 Subject: [PATCH 524/641] coccinelle: atomic_as_refcounter: drop atomic_long_dec_and_lock atomic_long_dec_and_lock() has never existed. So drop it from the rules. No functional change. Signed-off-by: Sang-Heon Jeon Signed-off-by: Julia Lawall --- scripts/coccinelle/api/atomic_as_refcounter.cocci | 4 ---- 1 file changed, 4 deletions(-) diff --git a/scripts/coccinelle/api/atomic_as_refcounter.cocci b/scripts/coccinelle/api/atomic_as_refcounter.cocci index bbe5b2932933..af82b9e094aa 100644 --- a/scripts/coccinelle/api/atomic_as_refcounter.cocci +++ b/scripts/coccinelle/api/atomic_as_refcounter.cocci @@ -26,8 +26,6 @@ identifier fname6 =~ ".*call_rcu.*"; atomic_dec_and_test@p1(&(a)->x) | atomic_dec_and_lock@p1(&(a)->x, ...) -| - atomic_long_dec_and_lock@p1(&(a)->x, ...) | atomic_long_dec_and_test@p1(&(a)->x) | @@ -69,8 +67,6 @@ identifier fname =~ ".*free.*"; atomic_dec_and_test@p1(&(a)->x) | atomic_dec_and_lock@p1(&(a)->x, ...) -| - atomic_long_dec_and_lock@p1(&(a)->x, ...) | atomic_long_dec_and_test@p1(&(a)->x) | From f83b8a58695cbf419beaa3f63b9d3e264792d8ea Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Mon, 24 Aug 2026 01:18:19 +0900 Subject: [PATCH 525/641] coccinelle: ifnulldev_put: update outdated helper names dev_put_track() and dev_hold_track() were renamed to netdev_put() and netdev_hold() by commit d62607c3fe45 ("net: rename reference+tracking helpers"). So update the names. Signed-off-by: Sang-Heon Jeon Signed-off-by: Julia Lawall --- scripts/coccinelle/free/ifnulldev_put.cocci | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/coccinelle/free/ifnulldev_put.cocci b/scripts/coccinelle/free/ifnulldev_put.cocci index 2bd2e8fae485..adb08252f98d 100644 --- a/scripts/coccinelle/free/ifnulldev_put.cocci +++ b/scripts/coccinelle/free/ifnulldev_put.cocci @@ -23,13 +23,13 @@ expression E; | dev_put(E); | - dev_put_track(E, ...); + netdev_put(E, ...); | __dev_hold(E); | dev_hold(E); | - dev_hold_track(E, ...); + netdev_hold(E, ...); ) @r depends on context || report || org @ @@ -38,8 +38,8 @@ position p; @@ * if (E != NULL) -* \(__dev_put@p\|dev_put@p\|dev_put_track@p\|__dev_hold@p\|dev_hold@p\| -* dev_hold_track@p\)(E, ...); +* \(__dev_put@p\|dev_put@p\|netdev_put@p\|__dev_hold@p\|dev_hold@p\| +* netdev_hold@p\)(E, ...); @script:python depends on org@ p << r.p; From ef6a1dca8de41a408019310649e6d1b7b21ac4bc Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sun, 30 Aug 2026 14:36:11 +0200 Subject: [PATCH 526/641] coccinelle: ifnulldev_put: update error message Update the report and org mode messages to reflect the new function names. Signed-off-by: Julia Lawall --- scripts/coccinelle/free/ifnulldev_put.cocci | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/coccinelle/free/ifnulldev_put.cocci b/scripts/coccinelle/free/ifnulldev_put.cocci index adb08252f98d..1430d68ff268 100644 --- a/scripts/coccinelle/free/ifnulldev_put.cocci +++ b/scripts/coccinelle/free/ifnulldev_put.cocci @@ -45,11 +45,11 @@ position p; p << r.p; @@ -cocci.print_main("NULL check before dev_{put, hold} functions is not needed", p) +cocci.print_main("NULL check before (net)dev_{put, hold} functions is not needed", p) @script:python depends on report@ p << r.p; @@ -msg = "WARNING: NULL check before dev_{put, hold} functions is not needed." +msg = "WARNING: NULL check before (net)dev_{put, hold} functions is not needed." coccilib.report.print_report(p[0], msg) From 1519dc88c87f5346dae0464d7d6da1b6bf1f6e8e Mon Sep 17 00:00:00 2001 From: Huiwen He Date: Fri, 28 Aug 2026 15:19:29 +0800 Subject: [PATCH 527/641] smb/client: validate new EOF for insert range smb3_insert_range() does not check if the new file size (i_size + len) is valid. This allows FALLOC_FL_INSERT_RANGE to bypass RLIMIT_FSIZE, exceed s_maxbytes, or produce a size outside the loff_t range. Use check_add_overflow() to calculate the new EOF. Validate it with inode_newsize_ok() before modifying the file. Reproducer, using a file on a CIFS mount: bash -c ' FILE=/mnt/cifs/repro trap "" SIGXFSZ ulimit -f 3072 # RLIMIT_FSIZE = 3 MiB # A regular write is stopped at 3 MiB. dd if=/dev/zero of="$FILE" bs=1M count=4 status=none stat -c "size after write: %s" "$FILE" # Insert 2 MiB into a 2 MiB file. truncate -s 2M "$FILE" fallocate -i -o 0 -l 2M "$FILE" stat -c "size after insert: %s" "$FILE" ' Before this change, the regular write stops at the 3 MiB limit, but insert range grows the file to 4 MiB: dd: error writing '/mnt/cifs/repro': File too large size after write: 3145728 size after insert: 4194304 After this change, insert range also fails at the limit and leaves the 2 MiB file unchanged: dd: error writing '/mnt/cifs/repro': File too large size after write: 3145728 fallocate: fallocate failed: File too large size after insert: 2097152 Fixes: 7fe6fe95b936 ("cifs: add FALLOC_FL_INSERT_RANGE support") Signed-off-by: Huiwen He Reviewed-by: ChenXiaoSong Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2ops.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index bea4876b58cb..1ee67b4f1c77 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -3985,7 +3985,8 @@ static long smb3_insert_range(struct file *file, struct cifs_tcon *tcon, struct cifsFileInfo *cfile = file->private_data; struct inode *inode = file_inode(file); struct cifsInodeInfo *cifsi = CIFS_I(inode); - __u64 count, old_eof, new_eof; + u64 count; + loff_t old_eof, new_eof; xid = get_xid(); @@ -3995,8 +3996,15 @@ static long smb3_insert_range(struct file *file, struct cifs_tcon *tcon, goto out; } + if (check_add_overflow(old_eof, len, &new_eof)) { + rc = -EFBIG; + goto out; + } + rc = inode_newsize_ok(inode, new_eof); + if (rc) + goto out; + count = old_eof - off; - new_eof = old_eof + len; filemap_invalidate_lock(inode->i_mapping); rc = filemap_write_and_wait_range(inode->i_mapping, off, new_eof - 1); From 88972e35750792e717af287dc71f42a03b5cbce4 Mon Sep 17 00:00:00 2001 From: Huiwen He Date: Fri, 28 Aug 2026 15:19:30 +0800 Subject: [PATCH 528/641] smb/client: validate new EOF for zero range When FALLOC_FL_ZERO_RANGE is used without FALLOC_FL_KEEP_SIZE, smb3_zero_range() may extend EOF without checking RLIMIT_FSIZE, allowing the file to grow beyond the caller's file-size limit. Fix this by calling inode_newsize_ok() before sending the zero-range request when the operation would extend EOF. Reproducer, using a file on a CIFS mount: bash -c ' FILE=/mnt/cifs/repro trap "" SIGXFSZ ulimit -f 3072 truncate -s 2M "$FILE" fallocate --zero-range -o 0 -l 4M "$FILE" echo "fallocate rc=$?" stat -c "file size=%s" "$FILE" ' Before this change, the operation succeeds despite the 3 MiB limit: fallocate rc=0 file size=4194304 After this change, fallocate fails and leaves the file at 2 MiB. Fixes: 72c419d9b073 ("cifs: fix smb3_zero_range so it can expand the file-size when required") Signed-off-by: Huiwen He Reviewed-by: ChenXiaoSong Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2ops.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 1ee67b4f1c77..f41fc71f1ba7 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -3444,6 +3444,13 @@ static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon, trace_smb3_zero_enter(xid, cfile->fid.persistent_fid, tcon->tid, ses->Suid, offset, len); + new_size = offset + len; + if (!keep_size && i_size_read(inode) < new_size) { + rc = inode_newsize_ok(inode, new_size); + if (rc) + goto out; + } + filemap_invalidate_lock(inode->i_mapping); netfs_read_sizes(inode, &i_size, &remote_i_size, &zero_point); @@ -3474,7 +3481,6 @@ static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon, /* * do we also need to change the size of the file? */ - new_size = offset + len; if (keep_size == false && (unsigned long long)i_size_read(inode) < new_size) { rc = SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid, cfile->fid.volatile_fid, cfile->pid, new_size); @@ -3491,6 +3497,7 @@ static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon, zero_range_exit: filemap_invalidate_unlock(inode->i_mapping); + out: free_xid(xid); if (rc) trace_smb3_zero_err(xid, cfile->fid.persistent_fid, tcon->tid, From cd03ce4950d80147ac8f20bc03c42b75b0352407 Mon Sep 17 00:00:00 2001 From: Huiwen He Date: Fri, 28 Aug 2026 15:19:31 +0800 Subject: [PATCH 529/641] smb/client: mark file sparse before emulating insert range The SMB client emulates FALLOC_FL_INSERT_RANGE with SET_EOF, COPYCHUNK and SET_ZERO_DATA. SET_ZERO_DATA creates a hole only when the file is sparse. On a non-sparse file, it clears the inserted range but leaves its blocks allocated, causing the extent count check in xfstests generic/064 to fail. Fix this by marking the file sparse before modifying it. This patch produces the expected sparse extents in xfstests generic/064 only when the server-reported block size is compatible with the server's deallocation granularity. For ksmbd, the reported block size follows the backing filesystem, and the test passes. For Samba, the test passes with a block size matching the backend granularity, for example, 4 KiB on Btrfs, but not with the default 1 KiB value. For Windows Server 2022, 4 KiB inserts do not generate holes, while aligned inserts of 64 KiB or larger do. Fixes: 7fe6fe95b936 ("cifs: add FALLOC_FL_INSERT_RANGE support") Signed-off-by: Huiwen He Reviewed-by: ChenXiaoSong Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2ops.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index f41fc71f1ba7..ac730eab382b 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -4013,6 +4013,11 @@ static long smb3_insert_range(struct file *file, struct cifs_tcon *tcon, count = old_eof - off; + /* SET_ZERO_DATA creates a hole only in a sparse file. */ + rc = smb2_set_sparse(xid, tcon, cfile, inode, true); + if (rc) + goto out; + filemap_invalidate_lock(inode->i_mapping); rc = filemap_write_and_wait_range(inode->i_mapping, off, new_eof - 1); if (rc < 0) From 0923ae9f23cc9460b0df6fc124cd56ec4436411b Mon Sep 17 00:00:00 2001 From: Huiwen He Date: Fri, 28 Aug 2026 15:19:32 +0800 Subject: [PATCH 530/641] smb/client: fix data corruption in emulated insert range smb3_insert_range() shifts [off, EOF) right with COPYCHUNK, copying from low to high offsets. When the ranges overlap, the copy can overwrite source data that has not yet been copied. For a 1 MiB insert at offset 0: offset: 0 1M 2M 3M 4M 5M before: | A | B | C | D | expected: | hole | A | B | C | D | current: | hole | A | A | A | A | (corrupted) Let x be the insertion offset, L the total length to move, delta the insert length, and C the normal chunk size allowed by the server. Insert range maps [x, x + L) -> [x + delta, x + delta + L). When delta >= L, the complete source and target ranges are disjoint, so the normal copy order and chunk size are safe: offset: 0 4 8 12 16 20 24 28 32 source: [--S0--][--S1--][--S2--][--S3--] target: [--T0--][--T1--][--T2--][--T3--] When delta < L, the complete source and target ranges overlap, so the copy must proceed from EOF backwards. There are two subcases. If delta >= C, each corresponding source and target chunk is disjoint. The 1 MiB example has L = 4 MiB and delta = C = 1 MiB: offset: 0 1M 2M 3M 4M 5M source: [--S0--][--S1--][--S2--][--S3--] target: [--T0--][--T1--][--T2--][--T3--] Copying S0 from [0, 1M) to [1M, 2M) overwrites S1 before it is copied. Processing chunks from EOF backwards prevents this inter-chunk overwrite. If delta < C, the source and target ranges of a normal chunk also overlap. For example, with L = 16, delta = 2 and C = 4: offset: 0 2 4 6 8 10 12 14 16 18 source: [--S0--][--S1--][--S2--][--S3--] target: [--T0--][--T1--][--T2--][--T3--] Here S0 and T0 overlap over [2,4), S1 and T1 over [6,8), and so on. Backward ordering cannot control how the server copies bytes inside one descriptor, so the chunk size must be limited to delta. Fix this by copying overlapping right shifts from EOF backwards. Limit the chunk size to delta when delta < C so that each chunk's source and target ranges do not overlap. Using larger chunks would require a way to identify servers that safely handle overlapping COPYCHUNK descriptors. Therefore: delta >= L: keep the normal copy order and chunk size delta < L: delta >= C: copy backwards and keep the normal chunk size delta < C: copy backwards and limit the chunk size to delta Only the delta < C subcase requires reducing the chunk size for data integrity. Reproducer: bash -c ' MNT=/mnt/scratch # Generate four 1 MiB random blocks: [A][B][C][D]. dd if=/dev/urandom of=/tmp/src bs=1M count=4 status=none # With C = 1 MiB, test delta = C and delta < C. for delta in 1M 1K; do truncate -s 0 /tmp/expected truncate -s "$delta" /tmp/expected cat /tmp/src >> /tmp/expected cp /tmp/src "$MNT/file" fallocate --insert-range -o 0 -l "$delta" "$MNT/file" if cmp -s /tmp/expected "$MNT/file"; then echo "delta=$delta: OK" else echo "delta=$delta: CORRUPTED" fi done ' The corruption reproduces with Samba and ksmbd, while Windows handles the overlapping COPYCHUNK ranges safely. The 1 MiB case tests delta >= C, while the 1 KiB case tests delta < C. Before this change, the reproducer reports: delta=1M: CORRUPTED delta=1K: CORRUPTED After this change, it passes against both ksmbd and Samba: delta=1M: OK delta=1K: OK Fixes: 7fe6fe95b936 ("cifs: add FALLOC_FL_INSERT_RANGE support") Signed-off-by: Huiwen He Reviewed-by: ChenXiaoSong Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2ops.c | 132 ++++++++++++++++++++++++++++++++-------- 1 file changed, 106 insertions(+), 26 deletions(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index ac730eab382b..2fe5223b8a26 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -1839,31 +1839,31 @@ free_vars: * * @tcon: destination file tcon * @bytes_left: how many bytes are left to copy + * @chunk_size: maximum size of a single chunk * * Return: maximum number of chunks with which Chunks[] can be filled. */ static inline u32 -calc_chunk_count(struct cifs_tcon *tcon, u64 bytes_left) +calc_chunk_count(struct cifs_tcon *tcon, u64 bytes_left, u32 chunk_size) { u32 max_chunks = READ_ONCE(tcon->max_chunks); u32 max_bytes_copy = READ_ONCE(tcon->max_bytes_copy); - u32 max_bytes_chunk = READ_ONCE(tcon->max_bytes_chunk); u64 need; u32 allowed; - if (!max_bytes_chunk || !max_bytes_copy || !max_chunks) + if (!chunk_size || !max_bytes_copy || !max_chunks) return 0; /* chunks needed for the remaining bytes */ - need = DIV_ROUND_UP_ULL(bytes_left, max_bytes_chunk); + need = DIV_ROUND_UP_ULL(bytes_left, chunk_size); /* chunks allowed per cc request */ - allowed = DIV_ROUND_UP(max_bytes_copy, max_bytes_chunk); + allowed = DIV_ROUND_UP(max_bytes_copy, chunk_size); return (u32)umin(need, umin(max_chunks, allowed)); } /** - * smb2_copychunk_range - server-side copy of data range + * __smb2_copychunk_range - server-side copy of data range * * @xid: transaction id * @src_file: source file @@ -1875,15 +1875,15 @@ calc_chunk_count(struct cifs_tcon *tcon, u64 bytes_left) * Obtains a resume key for @src_file and issues FSCTL_SRV_COPYCHUNK_WRITE * IOCTLs, splitting the request into chunks limited by tcon->max_*. * - * Return: @len on success; negative errno on failure. + * Return: 0 on success; negative errno on failure. */ -static ssize_t -smb2_copychunk_range(const unsigned int xid, - struct cifsFileInfo *src_file, - struct cifsFileInfo *dst_file, - u64 src_off, - u64 len, - u64 dst_off) +static int +__smb2_copychunk_range(const unsigned int xid, + struct cifsFileInfo *src_file, + struct cifsFileInfo *dst_file, + u64 src_off, + u64 len, + u64 dst_off) { int rc = 0; unsigned int ret_data_len = 0; @@ -1891,12 +1891,14 @@ smb2_copychunk_range(const unsigned int xid, struct copychunk_ioctl_rsp *cc_rsp = NULL; struct cifs_tcon *tcon; struct srv_copychunk *chunk; - u32 chunks, chunk_count, chunk_bytes; + u32 chunks, chunk_count, chunk_bytes, chunk_size; u32 copy_bytes, copy_bytes_left; u32 chunks_written, bytes_written; u64 total_bytes_left = len; u64 src_off_prev, dst_off_prev; + u64 max_chunk = 0; u32 retries = 0; + bool reverse = false; tcon = tlink_tcon(dst_file->tlink); @@ -1904,8 +1906,50 @@ smb2_copychunk_range(const unsigned int xid, dst_file->fid.volatile_fid, tcon->tid, tcon->ses->Suid, src_off, dst_off, len); + /* + * Same-file left shifts are safe in forward order. For a right shift, + * let L be the copy length, delta the distance between the source and + * destination, and C the normal chunk size: + * + * delta >= L: copy forwards using C + * delta < L: + * delta >= C: copy backwards using C + * delta < C: copy backwards with chunks limited to delta + * + * Copying backwards prevents one chunk from overwriting data needed by + * a later chunk. Limiting the chunk size to delta prevents an individual + * chunk from overlapping itself. + * This limit can be removed once all supported servers handle overlapping + * descriptors safely. + * + * A small right shift over a large range may therefore require many + * chunks. + */ + if (src_file == dst_file && dst_off > src_off) { + u64 delta = dst_off - src_off; + + if (delta < len) { + reverse = true; + max_chunk = delta; + } + } + + /* + * A backward copy walks the offsets down from the end of the range. + * Do this once, outside the retry loop, so a retry does not move the + * offsets again. + */ + if (reverse) { + src_off += len; + dst_off += len; + } + retry: - chunk_count = calc_chunk_count(tcon, total_bytes_left); + chunk_size = READ_ONCE(tcon->max_bytes_chunk); + if (max_chunk && max_chunk < chunk_size) + chunk_size = (u32)max_chunk; + + chunk_count = calc_chunk_count(tcon, total_bytes_left, chunk_size); if (!chunk_count) { rc = -EOPNOTSUPP; goto out; @@ -1946,16 +1990,21 @@ retry: while (copy_bytes_left > 0 && chunks < chunk_count) { chunk = &cc_req->Chunks[chunks++]; + chunk_bytes = umin(copy_bytes_left, chunk_size); + if (reverse) { + src_off -= chunk_bytes; + dst_off -= chunk_bytes; + } + chunk->SourceOffset = cpu_to_le64(src_off); chunk->TargetOffset = cpu_to_le64(dst_off); - - chunk_bytes = umin(copy_bytes_left, tcon->max_bytes_chunk); - chunk->Length = cpu_to_le32(chunk_bytes); /* Buffer is zeroed, no need to set chunk->Reserved = 0 */ - src_off += chunk_bytes; - dst_off += chunk_bytes; + if (!reverse) { + src_off += chunk_bytes; + dst_off += chunk_bytes; + } copy_bytes_left -= chunk_bytes; copy_bytes += chunk_bytes; @@ -2003,6 +2052,18 @@ retry: goto out; } + /* + * A successful COPYCHUNK should copy every descriptor (MS-SMB2 + * 3.3.5.15.6). Reject a short backward copy because the rewind + * below only supports forward copying. + */ + if (unlikely(reverse && bytes_written < copy_bytes)) { + cifs_tcon_dbg(VFS, "Copychunk short write %u/%u (reverse)\n", + bytes_written, copy_bytes); + rc = -EIO; + goto out; + } + /* Partial write: rewind */ if (bytes_written < copy_bytes) { u32 delta = copy_bytes - bytes_written; @@ -2064,10 +2125,27 @@ out: trace_smb3_copychunk_done(xid, src_file->fid.volatile_fid, dst_file->fid.volatile_fid, tcon->tid, tcon->ses->Suid, src_off, dst_off, len); - return len; + return 0; } } +static ssize_t +smb2_copychunk_range(const unsigned int xid, + struct cifsFileInfo *src_file, + struct cifsFileInfo *dst_file, + u64 src_off, + u64 len, + u64 dst_off) +{ + int rc; + + rc = __smb2_copychunk_range(xid, src_file, dst_file, src_off, len, + dst_off); + if (rc) + return rc; + return len; +} + static int smb2_flush_file(const unsigned int xid, struct cifs_tcon *tcon, struct cifs_fid *fid) @@ -3992,7 +4070,6 @@ static long smb3_insert_range(struct file *file, struct cifs_tcon *tcon, struct cifsFileInfo *cfile = file->private_data; struct inode *inode = file_inode(file); struct cifsInodeInfo *cifsi = CIFS_I(inode); - u64 count; loff_t old_eof, new_eof; xid = get_xid(); @@ -4011,8 +4088,6 @@ static long smb3_insert_range(struct file *file, struct cifs_tcon *tcon, if (rc) goto out; - count = old_eof - off; - /* SET_ZERO_DATA creates a hole only in a sparse file. */ rc = smb2_set_sparse(xid, tcon, cfile, inode, true); if (rc) @@ -4036,7 +4111,12 @@ static long smb3_insert_range(struct file *file, struct cifs_tcon *tcon, spin_unlock(&inode->i_lock); fscache_resize_cookie(cifs_inode_cookie(inode), i_size_read(inode)); - rc = smb2_copychunk_range(xid, cfile, cfile, off, count, off + len); + /* + * Move [off, old_eof) right by len. The helper copies backwards if the + * source and destination ranges overlap. + */ + rc = __smb2_copychunk_range(xid, cfile, cfile, off, old_eof - off, + off + len); if (rc < 0) goto out_2; spin_lock(&inode->i_lock); From 7811701d6af7db76481a82b9bc3c4adf7863acf5 Mon Sep 17 00:00:00 2001 From: Huiwen He Date: Fri, 28 Aug 2026 15:19:33 +0800 Subject: [PATCH 531/641] smb/client: fix integer truncation in collapse range smb3_collapse_range() stores the ssize_t return value of smb2_copychunk_range() in an int. A successful copy larger than INT_MAX is truncated to a negative value and treated as an error. Reproducer: MNT=/mnt/scratch truncate -s 2056M "$MNT/file" fallocate --collapse-range -o 1M -l 1M "$MNT/file" Fix this by using __smb2_copychunk_range(), which reports success as zero instead of returning the copied byte count. Before this change, the reproducer fails with: fallocate: fallocate failed: Success and the file size remains unchanged at 2056 MiB. After this change, the reproducer succeeds and the file size becomes the expected 2055 MiB. Fixes: 5476b5dd82c8 ("cifs: add support for FALLOC_FL_COLLAPSE_RANGE") Signed-off-by: Huiwen He Reviewed-by: ChenXiaoSong Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2ops.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 2fe5223b8a26..1d476a8af863 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -4036,8 +4036,8 @@ static long smb3_collapse_range(struct file *file, struct cifs_tcon *tcon, spin_unlock(&inode->i_lock); netfs_wait_for_outstanding_io(inode); - rc = smb2_copychunk_range(xid, cfile, cfile, off + len, - old_eof - off - len, off); + rc = __smb2_copychunk_range(xid, cfile, cfile, off + len, + old_eof - off - len, off); if (rc < 0) goto out_2; From 01261a6fa48b62f5ead8e88aaca1e27cb9ab9032 Mon Sep 17 00:00:00 2001 From: Huiwen He Date: Fri, 28 Aug 2026 15:19:34 +0800 Subject: [PATCH 532/641] smb/client: fix stale page cache in insert/collapse range smb3_insert_range() and smb3_collapse_range() use truncate_pagecache_range() to invalidate the affected page cache. However, if off or old_eof is not page-aligned, the boundary pages are only partially zeroed and remain uptodate. As a result, the client may return stale data after a successful insert/collapse range operation. For example, with 4K pages: page 0 page 1 page 2 0------4K 4K------8K 8K------12K ^ ^ off=2K old_eof=10K Page 1 is removed from the page cache, while the boundary pages are only partially zeroed. After COPYCHUNK moves the data on the server, these cached pages may still return stale data. This can be reproduced on a CIFS mount: bash -c ' FILE=/mnt/scratch/repro # Use a 6 KiB file so EOF is not page-aligned. dd if=/dev/urandom of=/tmp/src bs=1K count=6 status=none # Expected: a 4 KiB hole followed by the original data. rm -f /tmp/expected truncate -s 4K /tmp/expected cat /tmp/src >> /tmp/expected cp /tmp/src "$FILE" # Prime the page cache before moving data on the server. cat "$FILE" > /dev/null fallocate --insert-range -o 0 -l 4K "$FILE" if cmp -s /tmp/expected "$FILE"; then echo "readback: OK" else echo "readback: STALE DATA" fi ' Fix this by writing back dirty data and discarding the page cache from the start of the page containing off to EOF before moving data on the server. Fixes: 9c8b7a293f50 ("smb3: fix temporary data corruption in insert range") Fixes: fa30a81f255a ("smb3: fix temporary data corruption in collapse range") Signed-off-by: Huiwen He Reviewed-by: ChenXiaoSong Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2ops.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 1d476a8af863..cd5e31b578f9 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -4026,15 +4026,22 @@ static long smb3_collapse_range(struct file *file, struct cifs_tcon *tcon, } filemap_invalidate_lock(inode->i_mapping); - rc = filemap_write_and_wait_range(inode->i_mapping, off, old_eof - 1); + rc = filemap_write_and_wait_range(inode->i_mapping, + round_down(off, PAGE_SIZE), + old_eof - 1); if (rc < 0) goto out_2; - truncate_pagecache_range(inode, off, old_eof); + netfs_wait_for_outstanding_io(inode); + /* + * Invalidate cached folios from the page containing off to EOF before + * moving data on the server, so subsequent reads do not see stale data. + */ + truncate_pagecache_range(inode, round_down(off, PAGE_SIZE), -1); + spin_lock(&inode->i_lock); netfs_write_zero_point(inode, old_eof); spin_unlock(&inode->i_lock); - netfs_wait_for_outstanding_io(inode); rc = __smb2_copychunk_range(xid, cfile, cfile, off + len, old_eof - off - len, off); @@ -4094,11 +4101,17 @@ static long smb3_insert_range(struct file *file, struct cifs_tcon *tcon, goto out; filemap_invalidate_lock(inode->i_mapping); - rc = filemap_write_and_wait_range(inode->i_mapping, off, new_eof - 1); + rc = filemap_write_and_wait_range(inode->i_mapping, + round_down(off, PAGE_SIZE), + old_eof - 1); if (rc < 0) goto out_2; - truncate_pagecache_range(inode, off, old_eof); netfs_wait_for_outstanding_io(inode); + /* + * Invalidate cached folios from the page containing off to EOF before + * moving data on the server, so subsequent reads do not see stale data. + */ + truncate_pagecache_range(inode, round_down(off, PAGE_SIZE), -1); rc = SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid, cfile->fid.volatile_fid, cfile->pid, new_eof); From 448ba0ae65ca61064183564d2983c9aa59bd6ba7 Mon Sep 17 00:00:00 2001 From: Huiwen He Date: Fri, 28 Aug 2026 15:19:35 +0800 Subject: [PATCH 533/641] smb/client: invalidate fscache for fallocate range operations smb3_zero_range(), smb3_punch_hole(), smb3_insert_range(), and smb3_collapse_range() modify file contents through server-side range operations. These operations discard the affected page cache, but leave the FS-Cache cookie valid, so a later read may return data cached before the range operation. Fix this by invalidating FS-Cache after outstanding I/O has completed and before modifying the file on the server. Run the following as root on a CIFS mount with fsc enabled and an active CacheFiles backend: bash -c ' MNT=/mnt/cifs FILE="$MNT/repro" # Generate four 1 MiB random blocks: [A][B][C][D]. dd if=/dev/urandom of=/tmp/src bs=1M count=4 status=none # Expected contents after zeroing B: [A][zero][C][D]. cp /tmp/src /tmp/expected dd if=/dev/zero of=/tmp/expected bs=1M seek=1 count=1 \ conv=notrunc status=none cp /tmp/src "$FILE" # Populate FS-Cache, then discard the page cache. sync echo 1 > /proc/sys/vm/drop_caches cat "$FILE" > /dev/null sync echo 1 > /proc/sys/vm/drop_caches fallocate --zero-range -o 1M -l 1M "$FILE" if cmp -s /tmp/expected "$FILE"; then echo "readback: OK" else echo "readback: STALE DATA" fi ' Before this change, the readback differs from /tmp/expected: readback: STALE DATA After this change, it matches: readback: OK Fixes: 30175628bf7f ("[SMB3] Enable fallocate -z support for SMB3 mounts") Fixes: 31742c5a3317 ("enable fallocate punch hole ("fallocate -p") for SMB3") Fixes: 5476b5dd82c8 ("cifs: add support for FALLOC_FL_COLLAPSE_RANGE") Fixes: 7fe6fe95b936 ("cifs: add FALLOC_FL_INSERT_RANGE support") Signed-off-by: Huiwen He Suggested-by: Namjae Jeon Reviewed-by: ChenXiaoSong Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2ops.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index cd5e31b578f9..cb4fd09f996e 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -3552,6 +3552,9 @@ static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon, if (keep_size == false && !CIFS_CACHE_READ(cifsi)) goto zero_range_exit; + fscache_invalidate(cifs_inode_cookie(inode), NULL, + i_size_read(inode), 0); + rc = smb3_zero_data(file, tcon, offset, len, xid); if (rc < 0) goto zero_range_exit; @@ -3621,6 +3624,8 @@ static long smb3_punch_hole(struct file *file, struct cifs_tcon *tcon, */ truncate_pagecache_range(inode, offset, offset + len - 1); netfs_wait_for_outstanding_io(inode); + fscache_invalidate(cifs_inode_cookie(inode), NULL, + i_size_read(inode), 0); cifs_dbg(FYI, "Offset %lld len %lld\n", offset, len); @@ -4038,6 +4043,7 @@ static long smb3_collapse_range(struct file *file, struct cifs_tcon *tcon, * moving data on the server, so subsequent reads do not see stale data. */ truncate_pagecache_range(inode, round_down(off, PAGE_SIZE), -1); + fscache_invalidate(cifs_inode_cookie(inode), NULL, old_eof, 0); spin_lock(&inode->i_lock); netfs_write_zero_point(inode, old_eof); @@ -4112,6 +4118,7 @@ static long smb3_insert_range(struct file *file, struct cifs_tcon *tcon, * moving data on the server, so subsequent reads do not see stale data. */ truncate_pagecache_range(inode, round_down(off, PAGE_SIZE), -1); + fscache_invalidate(cifs_inode_cookie(inode), NULL, old_eof, 0); rc = SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid, cfile->fid.volatile_fid, cfile->pid, new_eof); From d83a21bb26015bfdd79b0440fe816b271b8bbab3 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 25 Aug 2026 10:30:43 +0200 Subject: [PATCH 534/641] smb: client: transport: Fix debug printing in __release_mid() Long time ago during upgrading printk():s to the respective pr_() calls one misconversion happened and nobody has noticed that. So, previously printk(KERN_DEBUG) + printk() worked as one long debug print since the trailing '\n' is only present in the followup printk() format string. The culprit change missed that and split the message to two on the different levels. Restore the original behaviour to make users be less confused in the most likely never happen cases of partially getting that message. Fixes: 0b456f04bcdf ("cifs: convert printk(LEVEL...) to pr_") Signed-off-by: Andy Shevchenko Signed-off-by: Paulo Alcantara --- fs/smb/client/transport.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/fs/smb/client/transport.c b/fs/smb/client/transport.c index fdf4e50c27ce..e266859818a4 100644 --- a/fs/smb/client/transport.c +++ b/fs/smb/client/transport.c @@ -101,12 +101,11 @@ void __release_mid(struct TCP_Server_Info *server, struct mid_q_entry *midEntry) trace_smb3_slow_rsp(smb_cmd, midEntry->mid, midEntry->pid, midEntry->when_sent, midEntry->when_received); if (cifsFYI & CIFS_TIMER) { - pr_debug("slow rsp: cmd %d mid %llu", - midEntry->command, midEntry->mid); - cifs_info("A: 0x%lx S: 0x%lx R: 0x%lx\n", - now - midEntry->when_alloc, - now - midEntry->when_sent, - now - midEntry->when_received); + pr_debug("slow rsp: cmd %d mid %llu A: 0x%lx S: 0x%lx R: 0x%lx\n", + midEntry->command, midEntry->mid, + now - midEntry->when_alloc, + now - midEntry->when_sent, + now - midEntry->when_received); } } #endif From 69499395867332364ed4ac8b546537eb3bab6ebb Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Mon, 24 Aug 2026 16:59:12 -0300 Subject: [PATCH 535/641] smb: client: fix multiuser mount with krb5 Customer reported that they could no longer mount their SMB shares with multiuser mount option and krb5. Turned out that the client wasn't duplicating username option when creating multiuser connections, therefore failing to retrieve credentials as cifs.upcall(8) couldn't find them in keytab. Fix this by duplicating username option (if set) from original fs context before creating multiuser connections with krb5. Reproducer: ``` $ ktutil ktutil: add_entry -password -p testuser -k 1 -e aes256-cts Password for testuser@ZELDA.TEST: ktutil: write_kt /etc/krb5.keytab ktutil: quit $ klist -ke Keytab name: FILE:/etc/krb5.keytab KVNO Principal ---- ---------------------------------------------------------------- 1 testuser@ZELDA.TEST (aes256-cts-hmac-sha1-96) $ mount.cifs //w22-root2/scratch /mnt/1 -o \ uid=1000,sec=krb5,username=testuser@ZELDA.TEST,multiuser mount error(13): Permission denied Refer to the mount.cifs(8) manual page (e.g. man mount.cifs) and kernel log messages (dmesg) ``` Reported-by: Jacob Shivers Fixes: 12b4c5d98cd7 ("smb: client: fix krb5 mount with username option") Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: Namjae Jeon Cc: stable@vger.kernel.org Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/connect.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c index bcd7f1ae99ba..b6e98eb31673 100644 --- a/fs/smb/client/connect.c +++ b/fs/smb/client/connect.c @@ -4189,14 +4189,25 @@ cifs_setup_session(const unsigned int xid, struct cifs_ses *ses, return rc; } -static int -cifs_set_vol_auth(struct smb3_fs_context *ctx, struct cifs_ses *ses) +static int set_fs_context_auth(struct smb3_fs_context *ctx, + struct cifs_ses *ses) { ctx->sectype = ses->sectype; - /* krb5 is special, since we don't need username or pw */ - if (ctx->sectype == Kerberos) + /* + * krb5 is special as we might need to pass username (passwordless) down + * to cifs.upcall(8) for keytab. + */ + if (ctx->sectype == Kerberos) { + if (ses->user_name && ses->user_name[0]) { + ctx->username = kstrndup(ses->user_name, + CIFS_MAX_USERNAME_LEN, + GFP_KERNEL); + if (!ctx->username) + return -ENOMEM; + } return 0; + } return cifs_set_cifscreds(ctx, ses); } @@ -4236,7 +4247,7 @@ cifs_construct_tcon(struct cifs_sb_info *cifs_sb, kuid_t fsuid) ctx->dfs_root_ses = master_tcon->ses->dfs_root_ses; ctx->unicode = master_tcon->ses->unicode; - rc = cifs_set_vol_auth(ctx, master_tcon->ses); + rc = set_fs_context_auth(ctx, master_tcon->ses); if (rc) { tcon = ERR_PTR(rc); goto out; From 1dac61e2c29d1a71784604b48cf3b1234a65ec29 Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Wed, 26 Aug 2026 21:13:36 -0500 Subject: [PATCH 536/641] smb: client: fix heap overflow in cifs_do_set_acl() cifs_set_acl() validates ACL size using posix_acl_xattr_size(): 4 + (count * 8) // 4-byte header + 8 bytes per ACE cifs_do_set_acl() then calls posix_acl_to_cifs() to write the CIFS wire format into the same buffer: 6 + (count * 10) // 6-byte header + 10 bytes per ACE An ACL that passes the xattr-based check in cifs_set_acl() can overflow the heap when posix_acl_to_cifs() writes the larger CIFS format. Validate the CIFS format size against the remaining buffer space and USHRT_MAX before converting--data_count is __u16, so sizes above USHRT_MAX truncate the on-wire packet length, causing the server to apply a partial ACL. Replace MaxDataCount = 1000 with min(CIFSMaxBufSize, USHRT_MAX). Fixes: dc1af4c4b4721 ("cifs: implement set acl method") Cc: stable@vger.kernel.org Signed-off-by: Frank Sorenson Signed-off-by: Paulo Alcantara --- fs/smb/client/cifssmb.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index f5aad5f61dce..230af243247c 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -3555,6 +3555,7 @@ int cifs_do_set_acl(const unsigned int xid, struct cifs_tcon *tcon, int rc = 0; int bytes_returned = 0; __u16 params, byte_count, data_count, param_offset, offset; + size_t cifs_acl_size, bytes_available; cifs_dbg(FYI, "In SetPosixACL (Unix) for path %s\n", fileName); setAclRetry: @@ -3574,8 +3575,7 @@ setAclRetry: } params = 6 + name_len; pSMB->MaxParameterCount = cpu_to_le16(2); - /* BB find max SMB size from sess */ - pSMB->MaxDataCount = cpu_to_le16(1000); + pSMB->MaxDataCount = cpu_to_le16(min_t(unsigned int, CIFSMaxBufSize, USHRT_MAX)); pSMB->MaxSetupCount = 0; pSMB->Reserved = 0; pSMB->Flags = 0; @@ -3587,6 +3587,15 @@ setAclRetry: parm_data = ((char *)pSMB) + offset; pSMB->ParameterOffset = cpu_to_le16(param_offset); + /* make sure we can fit the larger cifs_posix_aces in the buffer */ + cifs_acl_size = sizeof(struct cifs_posix_acl) + + (acl->a_count * sizeof(struct cifs_posix_ace)); + bytes_available = (CIFSMaxBufSize + MAX_HEADER_SIZE(tcon->ses->server)) - offset; + if (cifs_acl_size > bytes_available || cifs_acl_size > USHRT_MAX) { + rc = -E2BIG; + goto setACLerrorExit; + } + /* convert to on the wire format for POSIX ACL */ data_count = posix_acl_to_cifs(parm_data, acl, acl_type); From fe39cd9d48f2346605f3746e0cc19e89d5f373eb Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Wed, 26 Aug 2026 20:57:38 -0500 Subject: [PATCH 537/641] cifs: don't update i_size in cifs_do_truncate without a cached handle If find_writable_file() returns null, cifs_file_flush will return 0 without issuing set_file_size, and the outer 'if (!rc)' block will set i_size to 0 before telling the server to truncate. If the cifs_open() then fails, the inode will have size 0, while the server file is unchanged. Move the netfs_resize_file() and cifs_setsize() into the 'if (cfile)', so they only run after a successful set_file_size. In the no-handle else branch, evict stale pages with truncate_inode_pages before the O_TRUNC open to dispose of old cache pages, and let the open response set the i_size. Fixes: 110fee6b9bb5 ("smb: client: fix missing timestamp updates with O_TRUNC") Cc: stable@vger.kernel.org Signed-off-by: Frank Sorenson Acked-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/file.c | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c index 389083f9ce00..100acc76e9be 100644 --- a/fs/smb/client/file.c +++ b/fs/smb/client/file.c @@ -1012,10 +1012,26 @@ static int cifs_do_truncate(const unsigned int xid, struct dentry *dentry) server = tcon->ses->server; rc = server->ops->set_file_size(xid, tcon, cfile, 0, false); - } - if (!rc) { - netfs_resize_file(&cinode->netfs, 0, true); - cifs_setsize(inode, 0); + if (!rc) { + inode_lock(inode); + filemap_invalidate_lock(inode->i_mapping); + netfs_resize_file(&cinode->netfs, 0, true); + cifs_setsize(inode, 0); + filemap_invalidate_unlock(inode->i_mapping); + inode_unlock(inode); + cifs_invalidate_cache(inode, 0); + } + } else { + /* + * No cached handle; evict stale pages so they can't + * be served after the file is later extended; let + * the server's O_TRUNC open response set the i_size + */ + inode_lock(inode); + filemap_invalidate_lock(inode->i_mapping); + truncate_inode_pages(inode->i_mapping, 0); + filemap_invalidate_unlock(inode->i_mapping); + inode_unlock(inode); cifs_invalidate_cache(inode, 0); } } From cbb4c6d9af7dde462f8c9261bdcd35c47a0b4054 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Fri, 28 Aug 2026 09:15:01 -0700 Subject: [PATCH 538/641] CREDITS/mailmap: add some info about Darrick J. Wong Add myself to CREDITS because apparently I've never done that; and update mailmap so that all my old email addresses get remapped to the kernel.org redirector. Signed-off-by: "Darrick J. Wong" Signed-off-by: Linus Torvalds --- .mailmap | 4 ++++ CREDITS | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/.mailmap b/.mailmap index eeb885837e88..6803f3bd2865 100644 --- a/.mailmap +++ b/.mailmap @@ -242,6 +242,10 @@ Daniele Alessandrelli Danila Tikhonov Danilo Krummrich +Darrick J. Wong +Darrick J. Wong +Darrick J. Wong +Darrick J. Wong David Brownell David Collins David Gow diff --git a/CREDITS b/CREDITS index f8a33916e2a5..a1455b471051 100644 --- a/CREDITS +++ b/CREDITS @@ -4469,6 +4469,13 @@ S: The Netherlands N: Thomas Woller D: CS461x Cirrus Logic sound driver +N: Darrick J. Wong +E: djwong@kernel.org +D: ext4 filesystem +D: XFS filesystem and online fsck +D: Various hwmon drivers +S: USA + N: David Woodhouse E: dwmw2@infradead.org D: JFFS2 file system, Memory Technology Device subsystem, From cee9395acd8043be0644b25c34bfa86623f2b935 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 30 Aug 2026 13:34:40 -0700 Subject: [PATCH 539/641] Linux 7.3-rc1 --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 7860ce1fe501..4ad67b737af7 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ # SPDX-License-Identifier: GPL-2.0 VERSION = 7 -PATCHLEVEL = 2 +PATCHLEVEL = 3 SUBLEVEL = 0 -EXTRAVERSION = +EXTRAVERSION = -rc1 NAME = Baby Opossum Posse # *DOCUMENTATION* From 2cb0b0b1ed69430bf73740377ea0a1c44c50db63 Mon Sep 17 00:00:00 2001 From: Henry Martin Date: Fri, 28 Aug 2026 12:24:25 +0800 Subject: [PATCH 540/641] sctp: fix soft lockup from unpadded ASCONF-ACK parameter iteration sctp_verify_asconf() walks ASCONF-ACK parameters with sctp_walk_params(), which advances by SCTP_PAD4(length), while the consumer sctp_get_asconf_response() iterates the same parameters advancing by the raw length, without padding. A single odd-length parameter desynchronises the two walks and makes the consumer interpret attacker-controlled bytes at a misaligned offset. When those bytes yield a length of zero, the while loop over asconf_ack_len makes no progress, spinning forever in softirq context, and the watchdog reports a soft lockup. All reads stay within the received skb, so the lockup is a pure remote denial of service. A remote peer can trigger it with a crafted ASCONF-ACK on an ADD-IP enabled association with an outstanding ASCONF (RFC 5061 section 4.1.2 requires the chunk to be authenticated, but the predefined empty key id 0 allows the peer to compute the same association HMAC from publicly exchanged parameters, so the gate does not help). The SCTP_PARAM_ERR_CAUSE case of sctp_verify_asconf() also performs no length check, letting a parameter without a complete error header reach the consumer, which reads errhdr.cause past the end of the parameter, an out-of-bounds read. Reject SCTP_PARAM_ERR_CAUSE parameters shorter than sizeof(struct sctp_addip_param) + sizeof(struct sctp_errhdr) at the verifier, and advance the consumer iterator with the same padding rule as the verifier to keep the two walks in lockstep. The verifier change guarantees a complete error header in every ERR_CAUSE parameter the consumer can see, so the consumer's asconf_ack_len check is dropped and it returns err_param->cause directly. The consumer padding fix is still required because odd lengths remain valid for SCTP_PARAM_ERR_CAUSE per RFC 5061. The issue was found by ZeroHive, a vulnerability hunting agent at Tencent Yunding Lab. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Henry Martin Acked-by: Xin Long Link: https://patch.msgid.link/20260828042431.3873725-1-bsdhenrymartin@gmail.com Signed-off-by: Jakub Kicinski --- net/sctp/sm_make_chunk.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/net/sctp/sm_make_chunk.c b/net/sctp/sm_make_chunk.c index 236e25abc7a4..84a4c97d0f75 100644 --- a/net/sctp/sm_make_chunk.c +++ b/net/sctp/sm_make_chunk.c @@ -3215,6 +3215,9 @@ bool sctp_verify_asconf(const struct sctp_association *asoc, *errp = param.p; switch (param.p->type) { case SCTP_PARAM_ERR_CAUSE: + if (length < sizeof(struct sctp_addip_param) + + sizeof(struct sctp_errhdr)) + return false; break; case SCTP_PARAM_IPV4_ADDRESS: if (length != sizeof(struct sctp_ipv4addr_param)) @@ -3448,20 +3451,15 @@ static __be16 sctp_get_asconf_response(struct sctp_chunk *asconf_ack, case SCTP_PARAM_ERR_CAUSE: length = sizeof(*asconf_ack_param); err_param = (void *)asconf_ack_param + length; - asconf_ack_len -= length; - if (asconf_ack_len > 0) - return err_param->cause; - else - return SCTP_ERROR_INV_PARAM; - break; + return err_param->cause; default: return SCTP_ERROR_INV_PARAM; } } length = ntohs(asconf_ack_param->param_hdr.length); - asconf_ack_param = (void *)asconf_ack_param + length; - asconf_ack_len -= length; + asconf_ack_param = (void *)asconf_ack_param + SCTP_PAD4(length); + asconf_ack_len -= SCTP_PAD4(length); } return err_code; From ac08d183dac0441e41f77bbad50798fe609d90f1 Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Fri, 28 Aug 2026 09:29:18 +0800 Subject: [PATCH 541/641] raw: annotate disconnect-side IPv4 match writers raw_v4_match() reads inet_daddr, inet_rcv_saddr and sk_bound_dev_if locklessly under RCU. Bind and connect writers are annotated, but __udp_disconnect() still clears the same fields using plain stores. Commit 18f116931f52e ("raw: annotate lockless match fields in raw_v4_match()") added the lockless readers and annotated the raw bind and datagram connect writers. Its v4 revision intentionally left the shared disconnect-side IPv4 writers for follow-up cleanup. Complete that follow-up by using WRITE_ONCE() for the disconnect-side stores, including the inet_rcv_saddr reset in inet_reset_saddr(), to pair with the lockless raw socket matcher. Fixes: 0daf07e52709 ("raw: convert raw sockets to RCU") Link: https://lore.kernel.org/netdev/20260716142958.3064224-1-runyu.xiao@seu.edu.cn/ Suggested-by: Runyu Xiao Signed-off-by: Jackie Liu Signed-off-by: Xuanqiang Luo Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260828012918.1461-1-xuanqiang.luo@linux.dev Signed-off-by: Jakub Kicinski --- include/net/ip.h | 3 ++- net/ipv4/udp.c | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/include/net/ip.h b/include/net/ip.h index a8f57b4f4aa2..6f602df72ee6 100644 --- a/include/net/ip.h +++ b/include/net/ip.h @@ -704,7 +704,8 @@ static inline void ip_ipgre_mc_map(__be32 naddr, const unsigned char *broadcast, static __inline__ void inet_reset_saddr(struct sock *sk) { - inet_sk(sk)->inet_rcv_saddr = inet_sk(sk)->inet_saddr = 0; + inet_sk(sk)->inet_saddr = 0; + WRITE_ONCE(inet_sk(sk)->inet_rcv_saddr, 0); #if IS_ENABLED(CONFIG_IPV6) if (sk->sk_family == PF_INET6) { struct ipv6_pinfo *np = inet6_sk(sk); diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c index af9603217444..6ff5670bf6ed 100644 --- a/net/ipv4/udp.c +++ b/net/ipv4/udp.c @@ -2166,10 +2166,10 @@ int __udp_disconnect(struct sock *sk, int flags) */ sk->sk_state = TCP_CLOSE; - inet->inet_daddr = 0; + WRITE_ONCE(inet->inet_daddr, 0); inet->inet_dport = 0; sock_rps_reset_rxhash(sk); - sk->sk_bound_dev_if = 0; + WRITE_ONCE(sk->sk_bound_dev_if, 0); if (!(sk->sk_userlocks & SOCK_BINDADDR_LOCK)) { inet_reset_saddr(sk); if (sk->sk_prot->rehash && From ac8d6b28d48c5d951dcd923d33e461588e762a6d Mon Sep 17 00:00:00 2001 From: James Nugraha Date: Fri, 28 Aug 2026 09:22:19 +1000 Subject: [PATCH 542/641] net: amd-xgbe: discard rx packets with bad FCS amd-xgbe driver currently sets the MAC_RCR.DCRCC bit whenever RX is enabled. This disables hardware FCS validation, causing packets with bad FCS to be accepted unconditionally. This change unsets DCRCC so that packets with bad FCS will be dropped, in-line with typical behaviours of many other network controllers. Tests: - Verified that packets with bad FCS are now dropped. - Verified that receiving packets with bad FCS will increment the `rx_crc_errors` counter. Fixes: c5aa9e3b8156 ("amd-xgbe: Initial AMD 10GbE platform driver") Signed-off-by: James Nugraha Link: https://patch.msgid.link/20260827232220.69907-1-aslan.jnn@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/amd/xgbe/xgbe-dev.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/net/ethernet/amd/xgbe/xgbe-dev.c b/drivers/net/ethernet/amd/xgbe/xgbe-dev.c index 2de974213090..e2e850c1b90b 100644 --- a/drivers/net/ethernet/amd/xgbe/xgbe-dev.c +++ b/drivers/net/ethernet/amd/xgbe/xgbe-dev.c @@ -3400,7 +3400,7 @@ static void xgbe_enable_rx(struct xgbe_prv_data *pdata) XGMAC_IOWRITE(pdata, MAC_RQC0R, reg_val); /* Enable MAC Rx */ - XGMAC_IOWRITE_BITS(pdata, MAC_RCR, DCRCC, 1); + XGMAC_IOWRITE_BITS(pdata, MAC_RCR, DCRCC, 0); XGMAC_IOWRITE_BITS(pdata, MAC_RCR, CST, 1); XGMAC_IOWRITE_BITS(pdata, MAC_RCR, ACS, 1); XGMAC_IOWRITE_BITS(pdata, MAC_RCR, RE, 1); @@ -3411,7 +3411,6 @@ static void xgbe_disable_rx(struct xgbe_prv_data *pdata) unsigned int i; /* Disable MAC Rx */ - XGMAC_IOWRITE_BITS(pdata, MAC_RCR, DCRCC, 0); XGMAC_IOWRITE_BITS(pdata, MAC_RCR, CST, 0); XGMAC_IOWRITE_BITS(pdata, MAC_RCR, ACS, 0); XGMAC_IOWRITE_BITS(pdata, MAC_RCR, RE, 0); From 6cfc1b90cb86f4aabc69fb8e30128e07e2cdfa3a Mon Sep 17 00:00:00 2001 From: Charles Vosburgh Date: Thu, 27 Aug 2026 17:32:53 -0400 Subject: [PATCH 543/641] sctp: validate chunk length in the inqueue parser SCTP chunks always include a four-byte generic header, but sctp_inq_pop() currently accepts shorter declared lengths. A zero-length chunk leaves chunk_end at the current header. When ASCONF is covered by the association's SCTP-AUTH policy, sctp_assoc_bh_rcv() can continue before the state machine performs its normal chunk-length check. sctp_inq_pop() then returns the same malformed chunk repeatedly and the receive softirq can lock up. A remote SCTP peer can trigger this after establishing an association on a kernel built with CONFIG_IP_SCTP and configured with net.sctp.addip_enable=1 and net.sctp.auth_enable=1. The reproducer did not require application credentials, a shared SCTP AUTH key, or net.sctp.addip_noauth_enable=1. On commit f967455fb2a5 ("seg6: reset IP6CB after IPv6 decapsulation"), one zero-length ASCONF caused repeated watchdog soft-lockup reports in a two-vCPU KVM guest. All 3 pre-trigger health probes succeeded, while 36 of 37 post-trigger probes failed. With this change, all 37 post-trigger probes succeeded and no equivalent soft-lockup signature appeared. Reject chunks shorter than the generic SCTP header at the shared inqueue parser boundary. Mark the packet for discard before either caller can continue processing it, while preserving the four-byte generic minimum. Declared-length 1 through 4 controls and kernel-generated ASCONF traffic remained healthy. The patched sctp_hello selftest passed for IPv4 and IPv6. The complete private reproducer and validation evidence are available directly to maintainers on request. Fixes: bbd0d59809f9 ("[SCTP]: Implement the receive and verification of AUTH chunk") Cc: stable@vger.kernel.org Signed-off-by: Charles Vosburgh Acked-by: Xin Long Link: https://patch.msgid.link/20260827-sctp-zero-chunk-inqueue-v2-1-2e7669c6a6cb@gmail.com Signed-off-by: Jakub Kicinski --- net/sctp/inqueue.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/net/sctp/inqueue.c b/net/sctp/inqueue.c index 5f988b3a8814..d666cec6b194 100644 --- a/net/sctp/inqueue.c +++ b/net/sctp/inqueue.c @@ -212,8 +212,10 @@ new_skb: chunk->chunk_end = ((__u8 *)ch) + SCTP_PAD4(ntohs(ch->length)); skb_pull(chunk->skb, sizeof(*ch)); chunk->subh.v = NULL; /* Subheader is no longer valid. */ - - if (chunk->chunk_end + sizeof(*ch) <= skb_tail_pointer(chunk->skb)) { + if (unlikely(ntohs(ch->length) < sizeof(*ch))) { + chunk->pdiscard = 1; + } else if (chunk->chunk_end + sizeof(*ch) <= + skb_tail_pointer(chunk->skb)) { /* This is not a singleton */ chunk->singleton = 0; } else if (chunk->chunk_end > skb_tail_pointer(chunk->skb)) { From 4aa61c88b4e292e10abdfd791334b8272108d68a Mon Sep 17 00:00:00 2001 From: Baul Lee Date: Thu, 27 Aug 2026 02:36:04 +0900 Subject: [PATCH 544/641] vxlan: mdb: Fix use-after-free in vxlan_mdb_remote_src_del() vxlan_mdb_is_valid_source(), which validates MDBE_ATTR_SOURCE and every MDBE_ATTR_SRC_LIST member, accepts the all-zeros address. A source list is only accepted on a (*, G) entry, whose source is the all-zeros address, and for each member of the list an (S, G) entry is derived from it by substituting the source. Entries are keyed by a plain memcmp() of struct vxlan_mdb_entry_key, so if MDBE_ATTR_SOURCE is present and holds the all-zeros address and the source list holds it as well, the derived (S, G) key is byte-identical to the (*, G) key and resolves to the same entry. Omitting MDBE_ATTR_SOURCE is not equivalent, as the key is then left with a zero address family. vxlan_mdb_remote_src_del() removes the forwarding entry of a source before freeing the source entry: vxlan_mdb_remote_src_fwd_del(vxlan, group, remote, &ent->addr); vxlan_mdb_remote_src_entry_del(ent); With the keys aliased, the first call deletes the remote of the entry that owns 'ent' instead of a separate (S, G) entry, and frees 'ent'. The second call then runs on the freed entry, and its hlist_del() reads ->pprev and ->next out of it and writes through them. Adding the (*, G) entry with NLM_F_REPLACE and no source list marks the all-zeros source for deletion and reaches this from the sweep at the end of vxlan_mdb_remote_srcs_replace(). BUG: KASAN: slab-use-after-free in __vxlan_mdb_add+0x1cd/0xd70 Read of size 8 at addr ffff888102852500 by task poc/84 __vxlan_mdb_add+0x1cd/0xd70 vxlan_mdb_add+0xc0/0x140 rtnl_mdb_add+0x157/0x2a0 rtnetlink_rcv_msg+0x207/0x5a0 Allocated by task 84: __kmalloc_cache_noprof+0x153/0x360 vxlan_mdb_remote_srcs_add+0x2eb/0x440 __vxlan_mdb_add+0x803/0xd70 Freed by task 84: kfree+0x14c/0x3b0 vxlan_mdb_remote_del+0x129/0x1a0 __vxlan_mdb_del+0x4f/0xe0 vxlan_mdb_remote_src_fwd_del.isra.0+0x162/0x1b0 __vxlan_mdb_add+0x1c5/0xd70 The MDB operations are netns-scoped, so an unprivileged user can perform them in a new user and network namespace. Reject the all-zeros address in vxlan_mdb_is_valid_source(), which covers both call sites. A (*, G) entry is expressed by omitting the source, so nothing legitimate is refused. Discovered by XBOW, triaged by Baul Lee Fixes: a3a48de5eade ("vxlan: mdb: Add MDB control path support") Signed-off-by: Baul Lee Reviewed-by: Ido Schimmel Reviewed-by: Nikolay Aleksandrov Link: https://patch.msgid.link/20260826173604.90158-1-baul.lee@xbow.com Signed-off-by: Jakub Kicinski --- drivers/net/vxlan/vxlan_mdb.c | 8 ++++++++ tools/testing/selftests/net/test_vxlan_mdb.sh | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/drivers/net/vxlan/vxlan_mdb.c b/drivers/net/vxlan/vxlan_mdb.c index d71e1925ecfd..841f42ffecb9 100644 --- a/drivers/net/vxlan/vxlan_mdb.c +++ b/drivers/net/vxlan/vxlan_mdb.c @@ -354,6 +354,10 @@ static bool vxlan_mdb_is_valid_source(const struct nlattr *attr, __be16 proto, NL_SET_ERR_MSG_MOD(extack, "IPv4 multicast source address is not allowed"); return false; } + if (ipv4_is_zeronet(nla_get_in_addr(attr))) { + NL_SET_ERR_MSG_MOD(extack, "IPv4 all-zeros source address is not allowed"); + return false; + } break; #if IS_ENABLED(CONFIG_IPV6) case htons(ETH_P_IPV6): { @@ -368,6 +372,10 @@ static bool vxlan_mdb_is_valid_source(const struct nlattr *attr, __be16 proto, NL_SET_ERR_MSG_MOD(extack, "IPv6 multicast source address is not allowed"); return false; } + if (ipv6_addr_any(&src)) { + NL_SET_ERR_MSG_MOD(extack, "IPv6 all-zeros source address is not allowed"); + return false; + } break; } #endif diff --git a/tools/testing/selftests/net/test_vxlan_mdb.sh b/tools/testing/selftests/net/test_vxlan_mdb.sh index 58da5de99ac4..f9600aabd4a2 100755 --- a/tools/testing/selftests/net/test_vxlan_mdb.sh +++ b/tools/testing/selftests/net/test_vxlan_mdb.sh @@ -685,6 +685,9 @@ star_g_common() run_cmd "bridge -n $ns1 mdb add dev vx0 port vx0 grp $grp permanent filter_mode exclude source_list $grp dst $vtep_ip src_vni 10010" log_test $? 255 "Invalid source in source list" + run_cmd "bridge -n $ns1 mdb add dev vx0 port vx0 grp $grp permanent filter_mode exclude source_list $all_zeros_grp dst $vtep_ip src_vni 10010" + log_test $? 255 "All-zeros source in source list" + run_cmd "bridge -n $ns1 mdb add dev vx0 port vx0 grp $grp permanent source_list $src1 dst $vtep_ip src_vni 10010" log_test $? 255 "Source list without filter mode" } @@ -784,6 +787,9 @@ sg_common() run_cmd "bridge -n $ns1 mdb add dev vx0 port vx0 grp $grp src $grp permanent dst $vtep_ip src_vni 10010" log_test $? 255 "(S, G) with an invalid source list" + run_cmd "bridge -n $ns1 mdb add dev vx0 port vx0 grp $grp src $all_zeros_grp permanent dst $vtep_ip src_vni 10010" + log_test $? 255 "(S, G) with an all-zeros source" + run_cmd "bridge -n $ns1 mdb add dev vx0 port vx0 grp $all_zeros_grp src $src permanent dst $vtep_ip src_vni 10010" log_test $? 255 "All-zeros group with source" } From a8455260b2e9c024d1872ac1c094793d55a7e537 Mon Sep 17 00:00:00 2001 From: Maciej Fijalkowski Date: Fri, 28 Aug 2026 18:49:18 +0200 Subject: [PATCH 545/641] ipvlan: unregister upper devices outside pnodes_lock syzbot reported the following circular locking dependency: xs->mutex -> netdev lock -> pnodes_lock -> net->xdp.lock -> xs->mutex The pnodes_lock -> net->xdp.lock edge is recorded when ipvlan_device_event(NETDEV_UNREGISTER) calls unregister_netdevice_many() while holding pnodes_lock. A nested NETDEV_UNREGISTER notification for an IPvlan device enters xsk_notifier(), which acquires net->xdp.lock. Keep pnodes_lock only while marking the upper devices as dying, removing them from port->ipvlans, and queueing them for unregistration. Once the devices have been detached from the protected list, release pnodes_lock before unregister_netdevice_many() invokes notifier callbacks. The port remains alive across unregistration because ipvlan_device_event() holds the reference acquired by ipvlan_port_get(). The dying flag prevents a concurrent ->dellink() callback from deleting a queued device again. Fixes: 35add1093e2f ("ipvlan: Protect ipvl_port.ipvlans with mutex.") Reported-by: syzbot+aa48b5fe7bfda62d1682@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=aa48b5fe7bfda62d1682 Signed-off-by: Maciej Fijalkowski Reviewed-by: Eric Dumazet Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260828164918.451364-1-maciej.fijalkowski@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ipvlan/ipvlan_main.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/ipvlan/ipvlan_main.c b/drivers/net/ipvlan/ipvlan_main.c index 4939cf67b336..f29864db662a 100644 --- a/drivers/net/ipvlan/ipvlan_main.c +++ b/drivers/net/ipvlan/ipvlan_main.c @@ -848,7 +848,6 @@ static int ipvlan_device_event(struct notifier_block *unused, __ipvlan_link_delete(net, ipvlan->dev, &lst_kill); } - unregister_netdevice_many(&lst_kill); break; } case NETDEV_FEAT_CHANGE: @@ -899,6 +898,9 @@ static int ipvlan_device_event(struct notifier_block *unused, mutex_unlock(&port->pnodes_lock); + /* Avoid invoking nested netdevice notifiers under pnodes_lock. */ + unregister_netdevice_many(&lst_kill); + ipvlan_port_put(port); return ret; From 1376afc7660bad2a1a5ee0876898312a486cf8bd Mon Sep 17 00:00:00 2001 From: Kiran Kumar K Date: Tue, 25 Aug 2026 10:47:25 +0530 Subject: [PATCH 546/641] octeontx2-af: fix CN20K default MCAM rule removal on port cleanup npc_mcam_free_all_entries() disables every MCAM entry mapped to a port before freeing it. On CN20K, that also disables the default broadcast, multicast, promiscuous, and unicast rules, which causes packet drops when all rules are removed per port. Only disable and free non-default entries. Leave CN20K default rules enabled when freeing the remaining port entries. Fixes: 013717353c03 ("octeontx2-af: npc: cn20k: Tear down default MCAM rules explicitly on free") Signed-off-by: Kiran Kumar K Signed-off-by: Ratheesh Kannoth Signed-off-by: David S. Miller --- drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c index 60922944675b..c34f8d86cc8a 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c @@ -2957,10 +2957,9 @@ static void npc_mcam_free_all_entries(struct rvu *rvu, struct npc_mcam *mcam, } } - /* Disable the entry */ - npc_enable_mcam_entry(rvu, mcam, blkaddr, index, false); - if (!cn20k_dft_rl) { + /* Disable the entry */ + npc_enable_mcam_entry(rvu, mcam, blkaddr, index, false); mcam->entry2pfvf_map[index] = NPC_MCAM_INVALID_MAP; /* Free the entry in bitmap */ npc_mcam_clear_bit(mcam, index); From 399aa12450a61a5c73dc77e73f069ece9687c95d Mon Sep 17 00:00:00 2001 From: Aleksandr Khromov Date: Mon, 24 Aug 2026 13:22:46 +0300 Subject: [PATCH 547/641] ksmbd: zero the FS_OBJECT_ID_INFORMATION buffer before filling it in smb2_get_info_filesystem() reports 64 bytes for FS_OBJECT_ID_INFORMATION, that is the whole of struct object_id_info, but writes only 46 of them: - objid[] is 16 bytes, and when the volume UUID is not available only sizeof(stfs.f_fsid) (8) bytes are copied into it; - extended_info.version_string[] is STRING_LENGTH (28) bytes, and only strlen("1.1.0") (5) bytes are copied into it. The response buffer is zeroed on allocation (kvzalloc() in smb2_allocate_rsp_buf()), so for a standalone request the remaining 31 bytes are zero. In a compound request they need not be. The offset of the next response is advanced by the length pinned for the previous one, so if a preceding command wrote its reply into the buffer and then failed, smb2_set_err_rsp() pins only the short error response and the next reply lands inside the area that has already been written. Only the header is cleared there: memset((char *)rsp_hdr, 0, sizeof(struct smb2_hdr) + 2); The client then receives up to 31 bytes of a response it was not meant to see, including one that failed with an access denied error. Clear the structure before filling it in. As a side effect version_string is now NUL terminated. Fixes: e2f34481b24d ("cifsd: add server-side procedures for SMB3") Suggested-by: ChenXiaoSong Cc: stable@vger.kernel.org Signed-off-by: Aleksandr Khromov Signed-off-by: Namjae Jeon --- fs/smb/server/smb2pdu.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index a8046f477d54..486cd745dd21 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -7444,6 +7444,7 @@ static int smb2_get_info_filesystem(struct ksmbd_work *work, struct object_id_info *info; info = (struct object_id_info *)(rsp->Buffer); + memset(info, 0, sizeof(*info)); if (path.mnt->mnt_sb->s_uuid_len == 16) memcpy(info->objid, path.mnt->mnt_sb->s_uuid.b, From c0cd3fc6824122014da2b3b0cb7ddeaa2946ec8e Mon Sep 17 00:00:00 2001 From: Aleksandr Khromov Date: Mon, 24 Aug 2026 21:23:32 +0900 Subject: [PATCH 548/641] ksmbd: initialize FileSystemControlFlags in FS_CONTROL_INFORMATION smb2_get_info_filesystem() reports 48 bytes for FS_CONTROL_INFORMATION, that is the whole of struct smb2_fs_control_info, but never assigns FileSystemControlFlags. Those four bytes go to the client as they are found in the response buffer. The buffer is zeroed on allocation, so a standalone request leaks nothing. A compound request can leak: the offset of the next response is advanced by the length pinned for the previous one, so a reply that was written into the buffer and then dropped in favour of the short error response of smb2_set_err_rsp() stays there, and the next reply is laid over it with only the header cleared. ksmbd does not implement quota tracking, so report no control flags. Fixes: e2f34481b24d ("cifsd: add server-side procedures for SMB3") Cc: stable@vger.kernel.org Signed-off-by: Aleksandr Khromov Signed-off-by: Namjae Jeon --- fs/smb/server/smb2pdu.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index 486cd745dd21..2fbd9010513e 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -7500,6 +7500,7 @@ static int smb2_get_info_filesystem(struct ksmbd_work *work, info->FreeSpaceStopFiltering = 0; info->DefaultQuotaThreshold = cpu_to_le64(SMB2_NO_FID); info->DefaultQuotaLimit = cpu_to_le64(SMB2_NO_FID); + info->FileSystemControlFlags = 0; info->Padding = 0; rsp->OutputBufferLength = cpu_to_le32(48); fixed_len = 48; From db2267b27c054a6c2151ff7fbb67927e784f31d6 Mon Sep 17 00:00:00 2001 From: Aleksandr Khromov Date: Tue, 25 Aug 2026 10:19:21 +0900 Subject: [PATCH 549/641] ksmbd: fill in FileSysIdentifier in FS_POSIX_INFORMATION smb2_get_info_filesystem() reports 56 bytes for FS_POSIX_INFORMATION, that is the whole of FILE_SYSTEM_POSIX_INFO, but never assigns FileSysIdentifier. Those eight bytes go to the client as they are found in the response buffer. The buffer is zeroed on allocation, so a standalone request leaks nothing. A compound request can leak: the offset of the next response is advanced by the length pinned for the previous one, so a reply that was written into the buffer and then dropped in favour of the short error response of smb2_set_err_rsp() stays there, and the next reply is laid over it with only the header cleared. Report the file system id statfs() returned, which is what the field is for. FileSysIdentifier is __le64 and f_fsid is a pair of ints, so assemble the value first, val[0] as the low half, and convert it on the way out. Fixes: e2f34481b24d ("cifsd: add server-side procedures for SMB3") Cc: stable@vger.kernel.org Signed-off-by: Aleksandr Khromov Signed-off-by: Namjae Jeon --- fs/smb/server/smb2pdu.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index 2fbd9010513e..08cb215c3729 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -7523,6 +7523,9 @@ static int smb2_get_info_filesystem(struct ksmbd_work *work, info->UserBlocksAvail = cpu_to_le64(stfs.f_bavail); info->TotalFileNodes = cpu_to_le64(stfs.f_files); info->FreeFileNodes = cpu_to_le64(stfs.f_ffree); + info->FileSysIdentifier = + cpu_to_le64((u64)(u32)stfs.f_fsid.val[1] << 32 | + (u32)stfs.f_fsid.val[0]); rsp->OutputBufferLength = cpu_to_le32(56); fixed_len = 56; } From edcd92df5e1f94e89f8cd410ce41c5cb56e24453 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Wed, 26 Aug 2026 23:03:01 +0900 Subject: [PATCH 550/641] MAINTAINERS: Add Paulo Alcantara as an SMBDIRECT co-maintainer Steve French passed away recently. He was a long-time maintainer of Linux's SMB support and will be greatly missed. Add Paulo Alcantara as a co-maintainer of SMBDIRECT. Acked-by: Paulo Alcantara Signed-off-by: Paulo Alcantara Acked-by: Stefan Metzmacher Signed-off-by: Namjae Jeon --- MAINTAINERS | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index e05570c393c5..7e1d14406619 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -25105,10 +25105,9 @@ F: Documentation/admin-guide/LSM/Smack.rst F: security/smack/ SMBDIRECT (RDMA Stream Transport with Read/Write-Offload, MS-SMBD) -M: Steve French -M: Steve French M: Namjae Jeon M: Namjae Jeon +M: Paulo Alcantara R: Stefan Metzmacher R: Tom Talpey L: linux-cifs@vger.kernel.org From 5c944895a94d0317669f1b3409deb0161eaf916b Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Wed, 26 Aug 2026 23:36:28 +0900 Subject: [PATCH 551/641] MAINTAINERS: Update the KSMBD entry Steve French passed away recently. He was a long-time maintainer of Linux's SMB support and will be greatly missed. Update the KSMBD entry to no longer list Steve French as a maintainer. Signed-off-by: Namjae Jeon --- MAINTAINERS | 2 -- 1 file changed, 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 7e1d14406619..2a81760e697b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14194,8 +14194,6 @@ F: tools/testing/selftests/ KERNEL SMB3 SERVER (KSMBD) M: Namjae Jeon M: Namjae Jeon -M: Steve French -M: Steve French R: Sergey Senozhatsky R: Tom Talpey R: ChenXiaoSong From d12168084c8c1b6d883c8eca5853929ac5136a9e Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Fri, 28 Aug 2026 10:46:44 +0900 Subject: [PATCH 552/641] ksmbd: safely drain sessions during logoff SMB3 multichannel allows requests for one session to run on multiple connections. Wait for all channels bound to a session before freeing shared session objects. A deferred byte-range lock remains counted as a running request and only wakes when its file closes. Wake blocked locks during the drain without unpublishing or modifying their file objects. Synchronous CANCEL requests must invoke their cancellation callback to wake pending operations, while CHANGE_NOTIFY completion remains specific to the asynchronous path. Serialize session teardown with channel registration and previous-session cleanup, and use atomic work-state transitions so LOGOFF, CANCEL, and connection teardown invoke cancellation callbacks only once. Fixes: 76e98a158b20 ("ksmbd: fix race condition between destroy_previous_session() and smb2 operations()") Reported-by: Cheryl Babcock Signed-off-by: Namjae Jeon --- fs/smb/server/connection.c | 8 ++++-- fs/smb/server/mgmt/user_session.c | 13 ++++++++- fs/smb/server/mgmt/user_session.h | 1 + fs/smb/server/smb2pdu.c | 47 +++++++++++++++++++++++++++---- fs/smb/server/vfs_cache.c | 17 +++++++++-- fs/smb/server/vfs_cache.h | 1 + 6 files changed, 77 insertions(+), 10 deletions(-) diff --git a/fs/smb/server/connection.c b/fs/smb/server/connection.c index 91fdd1ddc61f..4cb92d6599ee 100644 --- a/fs/smb/server/connection.c +++ b/fs/smb/server/connection.c @@ -13,6 +13,7 @@ #include "mgmt/ksmbd_ida.h" #include "mgmt/user_session.h" #include "connection.h" +#include "vfs_cache.h" #include "compress.h" #include "transport_tcp.h" #include "transport_rdma.h" @@ -384,12 +385,12 @@ static void ksmbd_conn_cancel_async_requests(struct ksmbd_conn *conn) spin_lock(&conn->request_lock); list_for_each_entry_safe(work, tmp, &conn->async_requests, async_request_entry) { - if (work->state != KSMBD_WORK_ACTIVE) + if (cmpxchg(&work->state, KSMBD_WORK_ACTIVE, + KSMBD_WORK_CANCELLED) != KSMBD_WORK_ACTIVE) continue; ksmbd_debug(CONN, "Cancel async request id %d\n", work->async_id); - work->state = KSMBD_WORK_CANCELLED; if (work->cancel_fn) work->cancel_fn(work->cancel_argv); } @@ -473,6 +474,9 @@ retry_idle: if (retry_count >= max_timeout) return -EIO; + /* A blocked byte-range lock cannot drain until teardown wakes it. */ + ksmbd_wake_session_blocked_works(sess); + down_read(&conn_list_lock); hash_for_each(conn_list, bkt, conn, hlist) { if (ksmbd_session_is_bound_to_conn(sess, conn)) { diff --git a/fs/smb/server/mgmt/user_session.c b/fs/smb/server/mgmt/user_session.c index 7022d5d656b4..2eb8f730e99e 100644 --- a/fs/smb/server/mgmt/user_session.c +++ b/fs/smb/server/mgmt/user_session.c @@ -666,10 +666,21 @@ void destroy_previous_session(struct ksmbd_conn *conn, memcmp(user->passkey, prev_user->passkey, user->passkey_sz)) goto out; + down_write(&prev_sess->chann_lock); + if (prev_sess->tearing_down) { + up_write(&prev_sess->chann_lock); + goto out; + } + prev_sess->tearing_down = true; + up_write(&prev_sess->chann_lock); + ksmbd_all_conn_set_status(prev_sess, KSMBD_SESS_NEED_RECONNECT); err = ksmbd_conn_wait_idle_sess(conn, prev_sess); if (err) { - ksmbd_all_conn_set_status(prev_sess, KSMBD_SESS_NEED_SETUP); + down_write(&prev_sess->chann_lock); + prev_sess->tearing_down = false; + up_write(&prev_sess->chann_lock); + ksmbd_all_conn_set_status(prev_sess, KSMBD_SESS_GOOD); goto out; } diff --git a/fs/smb/server/mgmt/user_session.h b/fs/smb/server/mgmt/user_session.h index f8a24c33f7fe..3e52d4cc1324 100644 --- a/fs/smb/server/mgmt/user_session.h +++ b/fs/smb/server/mgmt/user_session.h @@ -42,6 +42,7 @@ struct ksmbd_session { bool sign; bool enc; + bool tearing_down; int state; __u8 *Preauth_HashValue; diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index 08cb215c3729..ba0fe25bf366 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -97,6 +97,11 @@ static int register_session_channel(struct ksmbd_session *sess, int rc = 0; down_write(&sess->chann_lock); + if (sess->tearing_down) { + rc = -ESHUTDOWN; + goto out; + } + if (xa_load(&sess->ksmbd_chann_list, (long)conn)) goto out; @@ -3086,17 +3091,41 @@ int smb2_session_logoff(struct ksmbd_work *work) smb2_set_err_rsp(work); return -ENOENT; } + + down_write(&sess->chann_lock); + if (sess->tearing_down) { + up_write(&sess->chann_lock); + ksmbd_conn_unlock(conn); + rsp->hdr.Status = STATUS_USER_SESSION_DELETED; + smb2_set_err_rsp(work); + return -ENOENT; + } + sess->tearing_down = true; + up_write(&sess->chann_lock); + ksmbd_all_conn_set_status(sess, KSMBD_SESS_NEED_RECONNECT); ksmbd_conn_unlock(conn); + err = ksmbd_conn_wait_idle_sess(conn, sess); + if (err) { + down_write(&sess->chann_lock); + sess->tearing_down = false; + up_write(&sess->chann_lock); + ksmbd_all_conn_set_status(sess, KSMBD_SESS_GOOD); + rsp->hdr.Status = STATUS_UNEXPECTED_IO_ERROR; + smb2_set_err_rsp(work); + return err; + } + ksmbd_close_session_fds(work); - ksmbd_conn_wait_idle(conn); if (ksmbd_tree_conn_session_logoff(sess)) { ksmbd_debug(SMB, "Invalid tid %d\n", req->hdr.Id.SyncId.TreeId); rsp->hdr.Status = STATUS_NETWORK_NAME_DELETED; smb2_set_err_rsp(work); - return -ENOENT; + err = -ENOENT; + } else { + err = 0; } down_write(&conn->session_lock); @@ -3106,6 +3135,9 @@ int smb2_session_logoff(struct ksmbd_work *work) ksmbd_all_conn_set_status(sess, KSMBD_SESS_NEED_SETUP); + if (err) + return err; + rsp->StructureSize = cpu_to_le16(4); err = ksmbd_iov_pin_rsp(work, rsp, sizeof(struct smb2_logoff_rsp)); if (err) { @@ -9685,14 +9717,14 @@ int smb2_cancel(struct ksmbd_work *work) * still on conn->async_requests with a live cancel_fn * pointing at the freed file_lock. */ - if (iter->state != KSMBD_WORK_ACTIVE) + if (cmpxchg(&iter->state, KSMBD_WORK_ACTIVE, + KSMBD_WORK_CANCELLED) != KSMBD_WORK_ACTIVE) break; ksmbd_debug(SMB, "smb2 with AsyncId %llu cancelled command = 0x%x\n", le64_to_cpu(hdr->Id.AsyncId), le16_to_cpu(chdr->Command)); - iter->state = KSMBD_WORK_CANCELLED; if (iter->cancel_fn == smb2_notify_cancel_fn) cancelled_notify = smb2_notify_cancel_claim(iter->cancel_argv); @@ -9721,11 +9753,16 @@ int smb2_cancel(struct ksmbd_work *work) iter == work) continue; + if (cmpxchg(&iter->state, KSMBD_WORK_ACTIVE, + KSMBD_WORK_CANCELLED) != KSMBD_WORK_ACTIVE) + break; + ksmbd_debug(SMB, "smb2 with mid %llu cancelled command = 0x%x\n", le64_to_cpu(hdr->MessageId), le16_to_cpu(chdr->Command)); - iter->state = KSMBD_WORK_CANCELLED; + if (iter->cancel_fn) + iter->cancel_fn(iter->cancel_argv); break; } spin_unlock(&conn->request_lock); diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c index 81626d204249..fd2c595f0486 100644 --- a/fs/smb/server/vfs_cache.c +++ b/fs/smb/server/vfs_cache.c @@ -846,12 +846,25 @@ static void set_close_state_blocked_works(struct ksmbd_file *fp) spin_lock(&fp->f_lock); list_for_each_entry(cancel_work, &fp->blocked_works, fp_entry) { - cancel_work->state = KSMBD_WORK_CLOSED; - cancel_work->cancel_fn(cancel_work->cancel_argv); + if (xchg(&cancel_work->state, KSMBD_WORK_CLOSED) == + KSMBD_WORK_ACTIVE) + cancel_work->cancel_fn(cancel_work->cancel_argv); } spin_unlock(&fp->f_lock); } +void ksmbd_wake_session_blocked_works(struct ksmbd_session *sess) +{ + struct ksmbd_file_table *ft = &sess->file_table; + struct ksmbd_file *fp; + unsigned int id; + + read_lock(&ft->lock); + idr_for_each_entry(ft->idr, fp, id) + set_close_state_blocked_works(fp); + read_unlock(&ft->lock); +} + int ksmbd_close_fd(struct ksmbd_work *work, u64 id) { struct ksmbd_file *fp; diff --git a/fs/smb/server/vfs_cache.h b/fs/smb/server/vfs_cache.h index 502efb16f05f..1884f6deb9d0 100644 --- a/fs/smb/server/vfs_cache.h +++ b/fs/smb/server/vfs_cache.h @@ -226,6 +226,7 @@ void ksmbd_stop_durable_scavenger(void); bool ksmbd_durable_scavenger_active(void); void ksmbd_close_tree_conn_fds(struct ksmbd_work *work); void ksmbd_close_session_fds(struct ksmbd_work *work); +void ksmbd_wake_session_blocked_works(struct ksmbd_session *sess); int ksmbd_close_inode_fds(struct ksmbd_work *work, struct inode *inode); int ksmbd_init_global_file_table(void); void ksmbd_free_global_file_table(void); From 73f860489e3be2245598d1819226304fc5b87291 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Tue, 25 Aug 2026 09:31:35 +0900 Subject: [PATCH 553/641] ksmbd: zero pipe read compound padding Compound response handling extends the last response iov to an eight-byte boundary. smb2_read_pipe() allocates only the payload size, so the alignment padding can expose up to seven bytes of uninitialized kernel heap memory. Allocate the aligned size and clear the unused tail before pinning the response buffer. Fixes: e2b76ab8b5c9 ("ksmbd: add support for read compound") Reported-by: Cheryl Babcock Signed-off-by: Namjae Jeon --- fs/smb/server/smb2pdu.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index ba0fe25bf366..8c589110460a 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -8657,13 +8657,18 @@ static noinline int smb2_read_pipe(struct ksmbd_work *work) } aux_payload_buf = - kvmalloc(rpc_resp->payload_sz, KSMBD_DEFAULT_GFP); + kvmalloc(ALIGN(rpc_resp->payload_sz, 8), + KSMBD_DEFAULT_GFP); if (!aux_payload_buf) { err = -ENOMEM; goto out; } memcpy(aux_payload_buf, rpc_resp->payload, rpc_resp->payload_sz); + if (rpc_resp->payload_sz & 7) + memset(aux_payload_buf + rpc_resp->payload_sz, 0, + ALIGN(rpc_resp->payload_sz, 8) - + rpc_resp->payload_sz); nbytes = rpc_resp->payload_sz; err = ksmbd_iov_pin_rsp_read(work, (void *)rsp, From c61dc7b1b4a3234b4aa3965502908a292238805c Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Tue, 25 Aug 2026 09:32:07 +0900 Subject: [PATCH 554/641] ksmbd: propagate DACL parsing errors parse_dacl() silently accepts truncated ACEs and allocation failures, allowing set_info_sec() to continue with an incomplete ACL conversion. Return parsing and allocation errors to parse_sec_desc() so malformed security descriptors are rejected before inode attributes or ACL xattrs are updated. Fixes: e2f34481b24d ("cifsd: add server-side procedures for SMB3") Reported-by: Cheryl Babcock Signed-off-by: Namjae Jeon --- fs/smb/server/smbacl.c | 63 +++++++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c index 8ad2e5a5cca8..4496098eb559 100644 --- a/fs/smb/server/smbacl.c +++ b/fs/smb/server/smbacl.c @@ -383,10 +383,10 @@ void free_acl_state(struct posix_acl_state *state) kfree(state->groups); } -static void parse_dacl(struct mnt_idmap *idmap, - struct smb_acl *pdacl, char *end_of_acl, - struct smb_sid *pownersid, struct smb_sid *pgrpsid, - struct smb_fattr *fattr) +static int parse_dacl(struct mnt_idmap *idmap, + struct smb_acl *pdacl, char *end_of_acl, + struct smb_sid *pownersid, struct smb_sid *pgrpsid, + struct smb_fattr *fattr) { int i, ret; u16 num_aces = 0; @@ -400,13 +400,13 @@ static void parse_dacl(struct mnt_idmap *idmap, bool owner_found = false, group_found = false, others_found = false; if (!pdacl) - return; + return 0; /* validate that we do not go past end of acl */ if (end_of_acl < (char *)pdacl + sizeof(struct smb_acl) || end_of_acl < (char *)pdacl + le16_to_cpu(pdacl->size)) { pr_err("ACL too small to parse DACL\n"); - return; + return -EINVAL; } ksmbd_debug(SMB, "DACL revision %d size %d num aces %d\n", @@ -418,31 +418,31 @@ static void parse_dacl(struct mnt_idmap *idmap, num_aces = le16_to_cpu(pdacl->num_aces); if (num_aces <= 0) - return; + return 0; dacl_size = le16_to_cpu(pdacl->size); if (dacl_size < sizeof(struct smb_acl)) - return; + return -EINVAL; if (num_aces > (dacl_size - sizeof(struct smb_acl)) / (offsetof(struct smb_ace, sid) + offsetof(struct smb_sid, sub_auth) + sizeof(__le16))) - return; + return -EINVAL; ret = init_acl_state(&acl_state, num_aces); if (ret) - return; + return ret; ret = init_acl_state(&default_acl_state, num_aces); if (ret) { free_acl_state(&acl_state); - return; + return ret; } ppace = kmalloc_objs(struct smb_ace *, num_aces, KSMBD_DEFAULT_GFP); if (!ppace) { free_acl_state(&default_acl_state); free_acl_state(&acl_state); - return; + return -ENOMEM; } /* @@ -451,8 +451,10 @@ static void parse_dacl(struct mnt_idmap *idmap, * user/group/other have no permissions */ for (i = 0; i < num_aces; ++i) { - if (end_of_acl - acl_base < acl_size) - break; + if (end_of_acl - acl_base < acl_size) { + ret = -EINVAL; + goto out; + } ppace[i] = (struct smb_ace *)(acl_base + acl_size); acl_base = (char *)ppace[i]; @@ -465,8 +467,10 @@ static void parse_dacl(struct mnt_idmap *idmap, (end_of_acl - acl_base < acl_size + sizeof(__le32) * ppace[i]->sid.num_subauth) || (le16_to_cpu(ppace[i]->size) < - acl_size + sizeof(__le32) * ppace[i]->sid.num_subauth)) - break; + acl_size + sizeof(__le32) * ppace[i]->sid.num_subauth)) { + ret = -EINVAL; + goto out; + } acl_size = le16_to_cpu(ppace[i]->size); ppace[i]->access_req = @@ -541,7 +545,6 @@ static void parse_dacl(struct mnt_idmap *idmap, ((acl_mode & 0700) >> 6) | 0004; } } - kfree(ppace); if (owner_found) { /* The owner must be set to at least read-only. */ @@ -584,10 +587,12 @@ static void parse_dacl(struct mnt_idmap *idmap, fattr->cf_acls = posix_acl_alloc(acl_state.users->n + acl_state.groups->n + 4, KSMBD_DEFAULT_GFP); - if (fattr->cf_acls) { - cf_pace = fattr->cf_acls->a_entries; - posix_state_to_acl(&acl_state, cf_pace); + if (!fattr->cf_acls) { + ret = -ENOMEM; + goto out; } + cf_pace = fattr->cf_acls->a_entries; + posix_state_to_acl(&acl_state, cf_pace); } } @@ -598,14 +603,20 @@ static void parse_dacl(struct mnt_idmap *idmap, fattr->cf_dacls = posix_acl_alloc(default_acl_state.users->n + default_acl_state.groups->n + 4, KSMBD_DEFAULT_GFP); - if (fattr->cf_dacls) { - cf_pdace = fattr->cf_dacls->a_entries; - posix_state_to_acl(&default_acl_state, cf_pdace); + if (!fattr->cf_dacls) { + ret = -ENOMEM; + goto out; } + cf_pdace = fattr->cf_dacls->a_entries; + posix_state_to_acl(&default_acl_state, cf_pdace); } } + ret = 0; +out: + kfree(ppace); free_acl_state(&acl_state); free_acl_state(&default_acl_state); + return ret; } static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap, @@ -966,8 +977,10 @@ int parse_sec_desc(struct mnt_idmap *idmap, struct smb_ntsd *pntsd, if (dacloffset < sizeof(struct smb_ntsd)) return -EINVAL; - parse_dacl(idmap, dacl_ptr, end_of_acl, - owner_sid_ptr, group_sid_ptr, fattr); + rc = parse_dacl(idmap, dacl_ptr, end_of_acl, + owner_sid_ptr, group_sid_ptr, fattr); + if (rc) + return rc; } return 0; From feca5e70fc963b088377b20879e8cd8237c2fd7d Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Tue, 25 Aug 2026 09:32:23 +0900 Subject: [PATCH 555/641] ksmbd: rate limit unmapped SID errors A client can include many structurally valid but unmapped SIDs in a DACL. Logging every mapping failure lets one request generate hundreds of kernel error messages. Rate limit the message to prevent an authenticated client from flooding the kernel log. Fixes: e2f34481b24d ("cifsd: add server-side procedures for SMB3") Reported-by: Cheryl Babcock Signed-off-by: Namjae Jeon --- fs/smb/server/smbacl.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c index 4496098eb559..1fad6ccf3a72 100644 --- a/fs/smb/server/smbacl.c +++ b/fs/smb/server/smbacl.c @@ -528,8 +528,8 @@ static int parse_dacl(struct mnt_idmap *idmap, temp_fattr.cf_uid = INVALID_UID; ret = sid_to_id(idmap, &ppace[i]->sid, SIDOWNER, &temp_fattr); if (ret || uid_eq(temp_fattr.cf_uid, INVALID_UID)) { - pr_err("%s: Error %d mapping Owner SID to uid\n", - __func__, ret); + pr_err_ratelimited("%s: Error %d mapping Owner SID to uid\n", + __func__, ret); continue; } From f25e93768fcc5d8287e50b1ec52a42e4c276df34 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Fri, 28 Aug 2026 08:39:57 +0900 Subject: [PATCH 556/641] ksmbd: prevent out-of-bounds reads in share config responses Validate IPC share configuration payload sizes before consuming variable-length fields. Bound veto list parsing and account for the separator byte when deriving the path length. Fixes: a677ebd8ca2f ("ksmbd: validate payload size in ipc response") Reported-by: Kanishka De Silva Reported-by: Farhad Alemi Signed-off-by: Namjae Jeon --- fs/smb/server/mgmt/share_config.c | 40 +++++++++++++++++++++---------- fs/smb/server/transport_ipc.c | 21 ++++++++++------ 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/fs/smb/server/mgmt/share_config.c b/fs/smb/server/mgmt/share_config.c index b2d9580bddc6..cc9f18ede80d 100644 --- a/fs/smb/server/mgmt/share_config.c +++ b/fs/smb/server/mgmt/share_config.c @@ -146,9 +146,9 @@ static struct ksmbd_share_config *__share_lookup(const char *name) static int parse_veto_list(struct ksmbd_share_config *share, char *veto_list, - int veto_list_sz) + size_t veto_list_sz) { - int sz = 0; + size_t sz; if (!veto_list_sz) return 0; @@ -156,7 +156,7 @@ static int parse_veto_list(struct ksmbd_share_config *share, while (veto_list_sz > 0) { struct ksmbd_veto_pattern *p; - sz = strlen(veto_list); + sz = strnlen(veto_list, veto_list_sz); if (!sz) break; @@ -164,7 +164,7 @@ static int parse_veto_list(struct ksmbd_share_config *share, if (!p) return -ENOMEM; - p->pattern = kstrdup(veto_list, KSMBD_DEFAULT_GFP); + p->pattern = kstrndup(veto_list, sz, KSMBD_DEFAULT_GFP); if (!p->pattern) { kfree(p); return -ENOMEM; @@ -172,6 +172,9 @@ static int parse_veto_list(struct ksmbd_share_config *share, list_add(&p->list, &share->veto_list); + if (sz == veto_list_sz) + break; + veto_list += sz + 1; veto_list_sz -= (sz + 1); } @@ -224,17 +227,28 @@ static struct ksmbd_share_config *share_config_request(struct ksmbd_work *work, } if (!test_share_config_flag(share, KSMBD_SHARE_FLAG_PIPE)) { - int path_len = PATH_MAX; + size_t path_len; - if (resp->payload_sz) - path_len = resp->payload_sz - resp->veto_list_sz; - - share->path = kstrndup(ksmbd_share_config_path(resp), path_len, - KSMBD_DEFAULT_GFP); - if (!share->path) { - ret = -ENOMEM; + if (resp->payload_sz <= resp->veto_list_sz) { + ret = -EINVAL; } else { - ret = 0; + path_len = resp->payload_sz - resp->veto_list_sz; + if (resp->veto_list_sz) + path_len--; + + if (!path_len) { + ret = -EINVAL; + } else { + share->path = kstrndup( + ksmbd_share_config_path(resp), + path_len, KSMBD_DEFAULT_GFP); + if (!share->path) + ret = -ENOMEM; + else + ret = 0; + } + } + if (share->path) { share->path_sz = strlen(share->path); while (share->path_sz > 1 && share->path[share->path_sz - 1] == '/') diff --git a/fs/smb/server/transport_ipc.c b/fs/smb/server/transport_ipc.c index 4b0b572a3e1b..e550aa41ad2c 100644 --- a/fs/smb/server/transport_ipc.c +++ b/fs/smb/server/transport_ipc.c @@ -532,14 +532,21 @@ static int ipc_validate_msg(struct ipc_msg_table_entry *entry) if (entry->msg_sz < sizeof(struct ksmbd_share_config_response)) return -EINVAL; - if (resp->payload_sz) { - if (resp->payload_sz < resp->veto_list_sz) - return -EINVAL; + if (strnlen(resp->share_name, sizeof(resp->share_name)) == + sizeof(resp->share_name)) + return -EINVAL; - if (check_add_overflow(sizeof(struct ksmbd_share_config_response), - resp->payload_sz, &msg_sz)) - return -EINVAL; - } + if (resp->veto_list_sz > resp->payload_sz) + return -EINVAL; + + if (resp->flags != KSMBD_SHARE_FLAG_INVALID && + !(resp->flags & KSMBD_SHARE_FLAG_PIPE) && + resp->payload_sz <= resp->veto_list_sz) + return -EINVAL; + + if (check_add_overflow(sizeof(struct ksmbd_share_config_response), + resp->payload_sz, &msg_sz)) + return -EINVAL; break; } case KSMBD_EVENT_LOGIN_REQUEST_EXT: From a506290f59e1c6ce9ac0a13158640bb8fee93471 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Fri, 28 Aug 2026 09:24:49 +0900 Subject: [PATCH 557/641] ksmbd: fix listener task lifetime on netdev events The listener thread exits when its listening socket is shutdown. The netdevice notifier shuts down the socket before calling kthread_stop(), so the task_struct can be freed before kthread_stop() gets its reference. Create the listener in a stopped state and hold an extra task_struct reference until kthread_stop_put() completes. Also stop and release listeners before freeing their interface records during TCP teardown. Fixes: 3316a8fc840d ("ksmbd: server: avoid busy polling in accept loop") Reported-by: Farhad Alemi Signed-off-by: Namjae Jeon --- fs/smb/server/transport_tcp.c | 36 ++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/fs/smb/server/transport_tcp.c b/fs/smb/server/transport_tcp.c index 832e93084605..4968cfc1a572 100644 --- a/fs/smb/server/transport_tcp.c +++ b/fs/smb/server/transport_tcp.c @@ -39,6 +39,7 @@ struct tcp_transport { static const struct ksmbd_transport_ops ksmbd_tcp_transport_ops; static void tcp_stop_kthread(struct task_struct *kthread); +static void ksmbd_tcp_stop_listener(struct interface *iface); static struct interface *alloc_iface(char *ifname); static void ksmbd_tcp_disconnect(struct ksmbd_transport *t); @@ -321,13 +322,20 @@ static int ksmbd_tcp_run_kthread(struct interface *iface) int rc; struct task_struct *kthread; - kthread = kthread_run(ksmbd_kthread_fn, (void *)iface, "ksmbd-%s", - iface->name); + kthread = kthread_create(ksmbd_kthread_fn, (void *)iface, "ksmbd-%s", + iface->name); if (IS_ERR(kthread)) { rc = PTR_ERR(kthread); return rc; } + + /* + * The listener can exit after its socket is shutdown, so keep the + * task_struct alive until the caller has stopped it. + */ + get_task_struct(kthread); iface->ksmbd_kthread = kthread; + wake_up_process(kthread); return 0; } @@ -598,12 +606,7 @@ static int ksmbd_netdev_event(struct notifier_block *nb, unsigned long event, if (iface && iface->state == IFACE_STATE_CONFIGURED) { ksmbd_debug(CONN, "netdev-down event: netdev(%s) is going down\n", iface->name); - kernel_sock_shutdown(iface->ksmbd_socket, SHUT_RDWR); - tcp_stop_kthread(iface->ksmbd_kthread); - iface->ksmbd_kthread = NULL; - sock_release(iface->ksmbd_socket); - iface->ksmbd_socket = NULL; - + ksmbd_tcp_stop_listener(iface); iface->state = IFACE_STATE_DOWN; break; } @@ -631,11 +634,25 @@ static void tcp_stop_kthread(struct task_struct *kthread) if (!kthread) return; - ret = kthread_stop(kthread); + ret = kthread_stop_put(kthread); if (ret) pr_err("failed to stop forker thread\n"); } +static void ksmbd_tcp_stop_listener(struct interface *iface) +{ + if (iface->ksmbd_socket) + kernel_sock_shutdown(iface->ksmbd_socket, SHUT_RDWR); + + tcp_stop_kthread(iface->ksmbd_kthread); + iface->ksmbd_kthread = NULL; + + if (iface->ksmbd_socket) { + sock_release(iface->ksmbd_socket); + iface->ksmbd_socket = NULL; + } +} + void ksmbd_tcp_destroy(void) { struct interface *iface, *tmp; @@ -643,6 +660,7 @@ void ksmbd_tcp_destroy(void) unregister_netdevice_notifier(&ksmbd_netdev_notifier); list_for_each_entry_safe(iface, tmp, &iface_list, entry) { + ksmbd_tcp_stop_listener(iface); list_del(&iface->entry); kfree(iface->name); kfree(iface); From ba9572bc43d04d71ba52ae7f20645f1eafe86875 Mon Sep 17 00:00:00 2001 From: Alon Shakevsky Date: Sat, 29 Aug 2026 06:27:46 +0000 Subject: [PATCH 558/641] ksmbd: validate normalized name response length FILE_NORMALIZED_NAME_INFORMATION converts the open file path to UTF-16. smb2_allocate_rsp_buf() leaves these responses in the 448-byte small buffer, and get_file_normalized_name_info() converts the path without checking the remaining space. An authenticated client can query a long path and make smbConvertToUTF16() write beyond work->response_buf. Use the large response buffer for normalized-name queries. Before conversion, verify that the response has room for the worst-case UTF-16 output and its terminator. Fixes: 10aeff72ab82 ("ksmbd: support normalized name information") Assisted-by: Antiproof:GPT-5.6-Sol Signed-off-by: Alon Shakevsky Signed-off-by: Namjae Jeon --- fs/smb/server/smb2pdu.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index 8c589110460a..d656832d82ef 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -878,7 +878,8 @@ int smb2_allocate_rsp_buf(struct ksmbd_work *work) req = smb_get_msg(work->request_buf); if ((req->InfoType == SMB2_O_INFO_FILE && (req->FileInfoClass == FILE_FULL_EA_INFORMATION || - req->FileInfoClass == FILE_ALL_INFORMATION)) || + req->FileInfoClass == FILE_ALL_INFORMATION || + req->FileInfoClass == FILE_NORMALIZED_NAME_INFORMATION)) || req->InfoType == SMB2_O_INFO_SECURITY) sz = large_sz; } @@ -6789,7 +6790,7 @@ static int get_file_normalized_name_info(struct ksmbd_work *work, { struct smb2_file_alt_name_info *file_info; char *filename, *normalized, *stream_name; - int conv_len, filename_len; + int buf_free_len, conv_len, filename_len; if (work->conn->dialect < SMB311_PROT_ID) { rsp->hdr.Status = STATUS_NOT_SUPPORTED; @@ -6813,6 +6814,14 @@ static int get_file_normalized_name_info(struct ksmbd_work *work, return -ENOMEM; filename_len = strlen(normalized); + buf_free_len = smb2_resp_buf_len(work, sizeof(*rsp) + + sizeof(*file_info)); + if (buf_free_len < 0 || + (size_t)buf_free_len < (filename_len + 1) * sizeof(__le16)) { + kfree(normalized); + return -EINVAL; + } + file_info = (struct smb2_file_alt_name_info *)rsp->Buffer; conv_len = smbConvertToUTF16((__le16 *)file_info->FileName, normalized, filename_len, From 4dc8f4ee2d46d5d1e749ddd1b94912c72e796162 Mon Sep 17 00:00:00 2001 From: Hongling Zeng Date: Wed, 26 Aug 2026 13:59:53 +0800 Subject: [PATCH 559/641] ntfs: handle signal interruption in fallocate The ntfs_attr_fallocate() function checks for pending signals during allocation loops and exits early via 'out' label. However, when a signal interrupts the operation with err == 0, the function returns 0 (success) instead of -EINTR. The signal_pending() checks at the allocation loops jump to 'out' without setting err = -EINTR, so the function returns success even when interrupted by a signal. Set err = -EINTR when jumping to the signal exit path, and only override when no other error is pending. This ensures: - Allocation interrupted by signal returns -EINTR - Allocation that completed successfully before signal arrived returns 0 - Other errors are preserved and not overwritten by -EINTR Fixes: 495e90fa3348 ("ntfs: update attrib operations") Cc: stable@vger.kernel.org Signed-off-by: Hongling Zeng Reviewed-by: Baolin Liu Signed-off-by: Namjae Jeon --- fs/ntfs/attrib.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index b3e941423a3f..848a0d338b89 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -5709,7 +5709,7 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo } if (signal_pending(current)) - goto out; + goto signal_out; vcn += alloc_cnt; try_alloc_cnt -= alloc_cnt; @@ -5730,7 +5730,7 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo up_write(&ni->runlist.lock); mutex_unlock(&ni->mrec_lock); if (err || signal_pending(current)) - goto out; + goto signal_out; vcn += alloc_cnt; try_alloc_cnt -= alloc_cnt; @@ -5756,4 +5756,8 @@ out_unmap: mutex_unlock(&ni->mrec_lock); out: return err >= 0 ? 0 : err; +signal_out: + if (!err) + err = -EINTR; + goto out; } From 9cc5761b8f28f9cef72061094eb5e37e2cd44d97 Mon Sep 17 00:00:00 2001 From: Hongling Zeng Date: Mon, 31 Aug 2026 16:30:14 +0800 Subject: [PATCH 560/641] ntfs: take invalidate_lock in ntfs_setattr_size() ntfs_setattr_size() updates i_size and resizes the on-disk attribute without holding mapping->invalidate_lock. Page faults take the lock shared, so a fault racing the resize can resolve a VCN against the transient runlist state of ntfs_non_resident_attr_expand() and fail with a spurious SIGBUS, and can interleave with the size-change epilogue (truncate_pagecache(), i_size_write(), pagecache_isize_extended()). Take invalidate_lock exclusively around the whole resize after inode_dio_wait(), matching the fallocate path and other filesystems such as xfs, which wraps truncate in its mmaplock (= invalidate_lock). Fixes: 9c87959601e8 ("ntfs: update file operations") Cc: stable@vger.kernel.org Reviewed-by: Hyunchul Lee Reviewed-by: Baolin Liu Signed-off-by: Hongling Zeng Signed-off-by: Namjae Jeon --- fs/ntfs/file.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c index 1969e4f444f7..585ab2145797 100644 --- a/fs/ntfs/file.c +++ b/fs/ntfs/file.c @@ -270,18 +270,25 @@ static int ntfs_setattr_size(struct inode *vi, struct iattr *attr) return err; inode_dio_wait(vi); + + /* + * Serialize with page faults and pagecache instantiation so that + * readers cannot observe the size change until the attribute + * updates below have completed. + */ + filemap_invalidate_lock(vi->i_mapping); if (attr->ia_size > old_size) { truncate_pagecache(vi, old_size); i_size_write(vi, attr->ia_size); pagecache_isize_extended(vi, old_size, attr->ia_size); - } else + } else { truncate_setsize(vi, attr->ia_size); + } err = ntfs_truncate_vfs(vi, attr->ia_size, old_size); - if (err) { + if (err) i_size_write(vi, old_size); - return err; - } + filemap_invalidate_unlock(vi->i_mapping); return err; } From 0fecc393f2060e6bc25138df32cb923ec7071c6b Mon Sep 17 00:00:00 2001 From: Hongling Zeng Date: Mon, 31 Aug 2026 16:32:52 +0800 Subject: [PATCH 561/641] ntfs: take invalidate_lock in ntfs_filemap_page_mkwrite() ntfs_filemap_page_mkwrite() calls iomap_page_mkwrite() without holding mapping->invalidate_lock, so a concurrent truncate or fallocate can be in the middle of invalidating pagecache and rewriting the runlist while the write fault maps blocks and dirties the folio. This races with ntfs_attr_fallocate(), which merges clusters into the in-memory runlist, drops the runlist lock, and only afterwards zeroes the newly allocated clusters on disk; and with the punch-hole/insert/collapse paths that free clusters after truncating the cache. Per Documentation/filesystems/locking.rst, ->page_mkwrite() must ensure there are no truncate/invalidate races, "usually mapping->invalidate_lock is suitable for proper serialization". xfs takes its mmaplock (= the invalidate_lock rwsem) shared in exactly this path. Take invalidate_lock shared around iomap_page_mkwrite(). The read-only fault path is already covered because filemap_fault() itself grabs invalidate_lock shared on instantiation/read paths; only page_mkwrite was bypassing it in this driver. Fixes: 9c87959601e8 ("ntfs: update file operations") Cc: stable@vger.kernel.org Reviewed-by: Hyunchul Lee Reviewed-by: Baolin Liu Signed-off-by: Hongling Zeng Co-developed-by: Namjae Jeon Signed-off-by: Namjae Jeon --- fs/ntfs/file.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c index 585ab2145797..8164326b7812 100644 --- a/fs/ntfs/file.c +++ b/fs/ntfs/file.c @@ -676,6 +676,7 @@ out_lock: static vm_fault_t ntfs_filemap_page_mkwrite(struct vm_fault *vmf) { struct inode *inode = file_inode(vmf->vma->vm_file); + struct address_space *mapping = inode->i_mapping; vm_fault_t ret; if (NInoWofCompressed(NTFS_I(inode))) @@ -684,7 +685,14 @@ static vm_fault_t ntfs_filemap_page_mkwrite(struct vm_fault *vmf) sb_start_pagefault(inode->i_sb); file_update_time(vmf->vma->vm_file); + /* + * Serialize against truncate/fallocate which hold the lock + * exclusively while invalidating pagecache and changing extents. + */ + filemap_invalidate_lock_shared(mapping); ret = iomap_page_mkwrite(vmf, &ntfs_page_mkwrite_iomap_ops, NULL); + filemap_invalidate_unlock_shared(mapping); + sb_end_pagefault(inode->i_sb); return ret; } @@ -1185,13 +1193,15 @@ static long ntfs_fallocate(struct file *file, int mode, loff_t offset, loff_t le err = file_modified(file); out: + if (!err && mode == 0 && NInoNonResident(ni) && + offset > old_size) { + truncate_pagecache(vi, old_size); + pagecache_isize_extended(vi, old_size, offset); + } + filemap_invalidate_unlock(vi->i_mapping); + if (!err) { - if (mode == 0 && NInoNonResident(ni) && - offset > old_size) { - truncate_pagecache(vi, old_size); - pagecache_isize_extended(vi, old_size, offset); - } NInoSetFileNameDirty(ni); inode_set_mtime_to_ts(vi, inode_set_ctime_current(vi)); mark_inode_dirty(vi); From a8603b52b39f520ea8a34def74c23fba87396d3e Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Fri, 28 Aug 2026 19:08:09 -0300 Subject: [PATCH 562/641] smb: client: fix data corruption with concurrent writes and O_TRUNC cifs_do_truncate() flushes dirty pages with filemap_write_and_wait() and truncates the file on the server, but in the old code both operations ran without holding i_rwsem or invalidate_lock. A concurrent buffered write via netfs_perform_write() -- which only needs i_rwsem shared -- could dirty new pages after the flush but before the local truncation, and those pages would be silently discarded by cifs_setsize() -> truncate_pagecache(). Fix by acquiring inode_lock (exclusive i_rwsem) and filemap_invalidate_lock at the top of cifs_do_truncate(), so the entire flush-truncate-resize sequence is atomic with respect to: - buffered writes (blocked by exclusive i_rwsem, since netfs_start_io_write takes i_rwsem shared), - read page faults (blocked by exclusive invalidate_lock, since filemap_fault takes it shared), - writeback collection (blocked by netfs_wb_begin/netfs_wb_end around the server truncate and local resize, since netfs_writepages also acquires the wb lock). Fixes: 110fee6b9bb5 ("smb: client: fix missing timestamp updates with O_TRUNC") Signed-off-by: Paulo Alcantara Reviewed-by: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: stable@vger.kernel.org --- fs/smb/client/file.c | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c index 100acc76e9be..61f9c6ccc6be 100644 --- a/fs/smb/client/file.c +++ b/fs/smb/client/file.c @@ -999,42 +999,50 @@ static int cifs_do_truncate(const unsigned int xid, struct dentry *dentry) struct cifs_tcon *tcon; int rc; - rc = filemap_write_and_wait(inode->i_mapping); - if (is_interrupt_error(rc)) + rc = inode_lock_killable(inode); + if (rc) return -ERESTARTSYS; + + filemap_invalidate_lock(inode->i_mapping); + + rc = filemap_write_and_wait(inode->i_mapping); + if (is_interrupt_error(rc)) { + rc = -ERESTARTSYS; + goto out; + } mapping_set_error(inode->i_mapping, rc); cfile = find_writable_file(cinode, FIND_FSUID_ONLY); rc = cifs_file_flush(xid, inode, cfile); if (!rc) { if (cfile) { + struct netfs_inode *ictx = netfs_inode(inode); + tcon = tlink_tcon(cfile->tlink); server = tcon->ses->server; + netfs_wb_begin(ictx, false); rc = server->ops->set_file_size(xid, tcon, cfile, 0, false); if (!rc) { - inode_lock(inode); - filemap_invalidate_lock(inode->i_mapping); netfs_resize_file(&cinode->netfs, 0, true); cifs_setsize(inode, 0); - filemap_invalidate_unlock(inode->i_mapping); - inode_unlock(inode); cifs_invalidate_cache(inode, 0); } + netfs_wb_end(ictx); } else { /* * No cached handle; evict stale pages so they can't * be served after the file is later extended; let * the server's O_TRUNC open response set the i_size */ - inode_lock(inode); - filemap_invalidate_lock(inode->i_mapping); truncate_inode_pages(inode->i_mapping, 0); - filemap_invalidate_unlock(inode->i_mapping); - inode_unlock(inode); cifs_invalidate_cache(inode, 0); } } + +out: + filemap_invalidate_unlock(inode->i_mapping); + inode_unlock(inode); if (cfile) cifsFileInfo_put(cfile); return rc; From 4aa2c106aef4bf3dfd97c30842db0767b26e8428 Mon Sep 17 00:00:00 2001 From: Yunpeng Tian Date: Sun, 30 Aug 2026 18:46:56 -0700 Subject: [PATCH 563/641] smb: client: reject SetEA requests that do not fit the request buffer CIFSSMBSetEA() copies the caller's extended attribute value into the SMB request buffer without checking that it fits. The requirement is stated in the source but was never implemented: /*BB add length check to see if it would fit in negotiated SMB buffer size BB */ /* if (ea_value_len > buffer_size - 512 (enough for header)) */ if (ea_value_len) memcpy(parm_data->list.name + name_len + 1, ea_value, ea_value_len); The only bound applied on the way in is in cifs_xattr_set(): #define MAX_EA_VALUE_SIZE CIFSMaxBufSize ... if (size > MAX_EA_VALUE_SIZE) CIFSMaxBufSize is the full payload capacity of the buffer, so a value of exactly that size leaves no room for the SMB header, the TRANS2 parameter block, the fealist header and the EA name that are written ahead of it in the same object. SendReceive() already enforces the correct limit on this very length: if (in_len > CIFSMaxBufSize + MAX_CIFS_HDR_SIZE) but it is called after the copy has taken place. An unprivileged setxattr(2) on an SMB1 mount with a 250-byte name and a 16384-byte value writes 16384 bytes starting 345 bytes into a 16588-byte cifs_request object, ending 141 bytes past it: BUG: KASAN: slab-out-of-bounds in CIFSSMBSetEA+0xabc/0xde0 Write of size 16384 at addr ffff888003aa0159 by task init/68 __asan_memcpy+0x3c/0x60 CIFSSMBSetEA+0xabc/0xde0 cifs_xattr_set+0xd3a/0xff0 __vfs_setxattr+0x13e/0x1a0 The buggy address is located 345 bytes inside of allocated 16588-byte region Apply SendReceive()'s limit to the assembled request before the copy rather than after it, and widen the byte counters so the sum cannot wrap before it is tested. byte_count is also tested against U16_MAX, because it is stored in the 16-bit pSMB->ByteCount. That becomes reachable when CIFSMaxBufSize is raised at module load, where it may be set as high as 1024*127: with a 5-byte EA name and a 65521-byte value, count is exactly U16_MAX while byte_count is 65556, and cpu_to_le16() would truncate it to 20 and transmit a frame whose ByteCount does not match its length. Testing byte_count covers count as well, since byte_count is the larger of the two and count's only 16-bit consumer is written after this point. check_add_overflow() is evaluated first so that total_len is assigned before it is reported. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: Yunpeng Tian Reported-by: Mingda Zhang Reported-by: Gongming Wang Reported-by: Qinrun Dai Cc: stable@vger.kernel.org Signed-off-by: Yunpeng Tian Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/cifssmb.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index 230af243247c..f8aa9e7b4bc6 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -6334,8 +6334,10 @@ CIFSSMBSetEA(const unsigned int xid, struct cifs_tcon *tcon, int name_len; int rc = 0; int bytes_returned = 0; - __u16 params, param_offset, byte_count, offset, count; + __u16 params, param_offset; + unsigned int byte_count, offset, count; int remap = cifs_remap(cifs_sb); + unsigned int total_len; cifs_dbg(FYI, "In SetEA\n"); SetEARetry: @@ -6387,6 +6389,13 @@ SetEARetry: pSMB->Reserved3 = 0; pSMB->SubCommand = cpu_to_le16(TRANS2_SET_PATH_INFORMATION); byte_count = 3 /* pad */ + params + count; + if (check_add_overflow(in_len, byte_count, &total_len) || + byte_count > U16_MAX || + total_len > CIFSMaxBufSize + MAX_CIFS_HDR_SIZE) { + cifs_dbg(VFS, "EA request too large: %u bytes\n", total_len); + cifs_buf_release(pSMB); + return -E2BIG; + } pSMB->DataCount = cpu_to_le16(count); parm_data->list_len = cpu_to_le32(count); parm_data->list.EA_flags = 0; From 8e359920216689b3b79e0fe8961a77fe312a511f Mon Sep 17 00:00:00 2001 From: Etienne Perot Date: Fri, 28 Aug 2026 21:52:51 +0000 Subject: [PATCH 564/641] cgroup: fix spurious SIGKILL of CLONE_INTO_CGROUP children Since commit b69bb476dee9 ("cgroup: fix race between fork and cgroup.kill"), the fork path snapshots the kill_seq of the child's future cgroup into kargs->kill_seq, and cgroup_post_fork() SIGKILLs the child if that cgroup's kill_seq has changed in the meantime, to catch forks racing with a cgroup.kill sweep. For CLONE_INTO_CGROUP, however, the snapshot in cgroup_css_set_fork() is taken before the target cgroup has been resolved: kargs->cgrp is always NULL at this point (it is only set at the end of the function). So the "if (kargs->cgrp)" branch is dead code and the snapshot always records the kill_seq of the parent's cgroup. cgroup_post_fork() then compares it with the kill_seq of the target cgroup, so the child gets SIGKILLed whenever the two cgroups have been killed a different number of times. As a result, once cgroup.kill has been written to a cgroup, every child subsequently cloned into it with clone3(CLONE_INTO_CGROUP) is killed on the spot, for as long as the cgroup exists: kill_seq is not exposed to userspace and never resets. Re-snapshot kill_seq from the target cgroup once it has been resolved, and drop the dead branch at the early snapshot site. This does not reopen the race fixed by b69bb476dee9. For CLONE_INTO_CGROUP, everything from the snapshot to the check in cgroup_post_fork() runs with cgroup_mutex held, and kill_seq is only ever incremented under cgroup_mutex. tj: Updated the comment above kill_seq to reflect the new serialization rules as suggested by Shakeel Butt. Fixes: b69bb476dee9 ("cgroup: fix race between fork and cgroup.kill") Cc: stable@vger.kernel.org Cc: Shakeel Butt Assisted-by: LLM Signed-off-by: Etienne Perot Signed-off-by: Tejun Heo --- include/linux/cgroup-defs.h | 5 ++++- kernel/cgroup/cgroup.c | 6 ++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/include/linux/cgroup-defs.h b/include/linux/cgroup-defs.h index 7a631a257613..3754d697854b 100644 --- a/include/linux/cgroup-defs.h +++ b/include/linux/cgroup-defs.h @@ -527,7 +527,10 @@ struct cgroup { int nr_threaded_children; /* # of live threaded child cgroups */ - /* sequence number for cgroup.kill, serialized by css_set_lock. */ + /* + * Sequence number for cgroup.kill. Incremented with both cgroup_mutex + * and css_set_lock held. Readers hold either one. + */ unsigned int kill_seq; struct kernfs_node *kn; /* cgroup kernfs entry */ diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c index f87fc4550081..353c8f83439a 100644 --- a/kernel/cgroup/cgroup.c +++ b/kernel/cgroup/cgroup.c @@ -6777,10 +6777,7 @@ static int cgroup_css_set_fork(struct kernel_clone_args *kargs) spin_lock_irq(&css_set_lock); cset = task_css_set(current); get_css_set(cset); - if (kargs->cgrp) - kargs->kill_seq = kargs->cgrp->kill_seq; - else - kargs->kill_seq = cset->dfl_cgrp->kill_seq; + kargs->kill_seq = cset->dfl_cgrp->kill_seq; spin_unlock_irq(&css_set_lock); if (!(kargs->flags & CLONE_INTO_CGROUP)) { @@ -6844,6 +6841,7 @@ static int cgroup_css_set_fork(struct kernel_clone_args *kargs) put_css_set(cset); kargs->cgrp = dst_cgrp; + kargs->kill_seq = dst_cgrp->kill_seq; return ret; err: From 3f4b7d1a49c5c826f3be9b684313eea5b83ac232 Mon Sep 17 00:00:00 2001 From: Etienne Perot Date: Fri, 28 Aug 2026 21:52:52 +0000 Subject: [PATCH 565/641] selftests/cgroup: test clone3() into a previously killed cgroup Once cgroup.kill had been written to a cgroup, a stale kill_seq snapshot (taken in cgroup_css_set_fork() before the target cgroup was resolved) caused every child subsequently cloned into that cgroup with clone3(CLONE_INTO_CGROUP) to be SIGKILLed on the spot. Add a regression test: create a cgroup, kill it while it is empty, then clone a child into it and check that the child runs and exits cleanly. On a kernel without the fix, the test fails: not ok 4 test_cgkill_clone_into_killed The test is skipped on kernels without clone3() or without CLONE_INTO_CGROUP. Cc: Shakeel Butt Assisted-by: LLM Signed-off-by: Etienne Perot Signed-off-by: Tejun Heo --- tools/testing/selftests/cgroup/test_kill.c | 55 ++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/tools/testing/selftests/cgroup/test_kill.c b/tools/testing/selftests/cgroup/test_kill.c index 99cafd9dc013..bac1ddd8cb94 100644 --- a/tools/testing/selftests/cgroup/test_kill.c +++ b/tools/testing/selftests/cgroup/test_kill.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "kselftest.h" @@ -261,6 +262,59 @@ cleanup: return ret; } +/* + * Test that a cgroup that was killed in the past can still be the target + * of clone3(CLONE_INTO_CGROUP): writing cgroup.kill must only kill the + * tasks in the cgroup at the time of the write, not tasks cloned into + * it afterwards. + */ +static int test_cgkill_clone_into_killed(const char *root) +{ + pid_t pid; + int cgroup_fd = -EBADF; + int ret = KSFT_FAIL; + char *cgroup = NULL; + + cgroup = cg_name(root, "cg_test_clone_into_killed"); + if (!cgroup) + goto cleanup; + + if (cg_create(cgroup)) + goto cleanup; + + /* Kill the cgroup while it is still empty. */ + if (cg_write(cgroup, "cgroup.kill", "1")) + goto cleanup; + + cgroup_fd = dirfd_open_opath(cgroup); + if (cgroup_fd < 0) + goto cleanup; + + pid = clone_into_cgroup(cgroup_fd); + if (pid < 0) { + if (errno == ENOSYS) + ret = KSFT_SKIP; + goto cleanup; + } + + if (pid == 0) + exit(EXIT_SUCCESS); + + /* The child must not be SIGKILLed; it has to exit cleanly. */ + if (clone_reap(pid, WEXITED) != EXIT_SUCCESS) + goto cleanup; + + ret = KSFT_PASS; + +cleanup: + if (cgroup_fd >= 0) + close(cgroup_fd); + if (cgroup) + cg_destroy(cgroup); + free(cgroup); + return ret; +} + #define T(x) { x, #x } struct cgkill_test { int (*fn)(const char *root); @@ -269,6 +323,7 @@ struct cgkill_test { T(test_cgkill_simple), T(test_cgkill_tree), T(test_cgkill_forkbomb), + T(test_cgkill_clone_into_killed), }; #undef T From 23761359861ca4bb087540937dfea8b0716914c2 Mon Sep 17 00:00:00 2001 From: Wanwu Li Date: Thu, 27 Aug 2026 16:07:36 +0800 Subject: [PATCH 566/641] sched_ext: Fix timer pinning and return value in scx_central central_timerfn() re-arms the timer with a hardcoded BPF_F_TIMER_CPU_PIN flag and ignores the return value, defeating start_central_timer()'s -EINVAL fallback for kernels without the flag (<6.7): on such kernels the first tick kills the timer permanently with no diagnostic. Honor timer_pinned and check the return like the initial arm does. Fixes: 22a920209ab6 ("sched_ext: Implement tickless support") Signed-off-by: Wanwu Li Signed-off-by: Tejun Heo --- tools/sched_ext/scx_central.bpf.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/sched_ext/scx_central.bpf.c b/tools/sched_ext/scx_central.bpf.c index 64dd60b3e922..65dae9e45400 100644 --- a/tools/sched_ext/scx_central.bpf.c +++ b/tools/sched_ext/scx_central.bpf.c @@ -299,6 +299,7 @@ static int central_timerfn(void *map, int *key, struct bpf_timer *timer) u64 now = scx_bpf_now(); u64 nr_to_kick = nr_queued; s32 i, curr_cpu; + int ret; curr_cpu = bpf_get_smp_processor_id(); if (timer_pinned && (curr_cpu != central_cpu)) { @@ -332,7 +333,10 @@ static int central_timerfn(void *map, int *key, struct bpf_timer *timer) scx_bpf_kick_cpu(cpu, SCX_KICK_PREEMPT); } - bpf_timer_start(timer, TIMER_INTERVAL_NS, BPF_F_TIMER_CPU_PIN); + ret = bpf_timer_start(timer, TIMER_INTERVAL_NS, + timer_pinned ? BPF_F_TIMER_CPU_PIN : 0); + if (ret) + scx_bpf_error("bpf_timer_start failed (%d)", ret); __sync_fetch_and_add(&nr_timers, 1); return 0; } From b6ee92d7f7f0498d1f776d0b125a2f6bcedf0891 Mon Sep 17 00:00:00 2001 From: Wanwu Li Date: Thu, 27 Aug 2026 16:07:37 +0800 Subject: [PATCH 567/641] sched_ext: Fix vtime delta loss in scx_flatcg cgroup migration fcg_cgroup_move() lost the signed vtime offset across cgroup migration in the mechanical conversion to time helpers: time_delta() clamps negative deltas to 0, so a queued task (whose dsq_vtime is normally behind the source frontier) loses its accumulated vtime credit and lands exactly at the destination frontier instead of keeping its relative position. Restore the wrapping signed subtraction. Fixes: 62addc6dbf36 ("sched_ext: Use time helpers in BPF schedulers") Signed-off-by: Wanwu Li Signed-off-by: Tejun Heo --- tools/sched_ext/scx_flatcg.bpf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/sched_ext/scx_flatcg.bpf.c b/tools/sched_ext/scx_flatcg.bpf.c index 64cf4dd964d6..454ebb820c5e 100644 --- a/tools/sched_ext/scx_flatcg.bpf.c +++ b/tools/sched_ext/scx_flatcg.bpf.c @@ -937,7 +937,7 @@ void BPF_STRUCT_OPS(fcg_cgroup_move, struct task_struct *p, if (!(from_cgc = find_cgrp_ctx(from)) || !(to_cgc = find_cgrp_ctx(to))) return; - delta = time_delta(p->scx.dsq_vtime, from_cgc->tvtime_now); + delta = (s64)(p->scx.dsq_vtime - from_cgc->tvtime_now); scx_bpf_task_set_dsq_vtime(p, to_cgc->tvtime_now + delta); } From 84590dbb9f3519e865ee8396494ac7186b625fef Mon Sep 17 00:00:00 2001 From: Wanwu Li Date: Thu, 27 Aug 2026 16:07:38 +0800 Subject: [PATCH 568/641] sched_ext: Check bpf_timer_start return values in scx_qmap monitor_timerfn(), lowpri_timerfn() and round_robin_timerfn() ignore bpf_timer_start()'s return value: a failed re-arm silently stops the periodic heartbeat, starving every task parked in LOWPRI_DSQ (lowpri) or freezing cid rotation (round-robin). Check the returns and raise scx_bpf_error(), matching the init paths. Signed-off-by: Wanwu Li Signed-off-by: Tejun Heo --- tools/sched_ext/scx_qmap.bpf.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tools/sched_ext/scx_qmap.bpf.c b/tools/sched_ext/scx_qmap.bpf.c index 5bb8b90a275a..9f6e61d7ca07 100644 --- a/tools/sched_ext/scx_qmap.bpf.c +++ b/tools/sched_ext/scx_qmap.bpf.c @@ -1246,7 +1246,8 @@ static int monitor_timerfn(void *map, int *key, struct bpf_timer *timer) scx_read_event(&events, SCX_EV_BYPASS_ACTIVATE)); } - bpf_timer_start(timer, ONE_SEC_IN_NS, 0); + if (bpf_timer_start(timer, ONE_SEC_IN_NS, 0)) + scx_bpf_error("failed to re-arm stats timer"); return 0; } @@ -1268,7 +1269,8 @@ struct { static int lowpri_timerfn(void *map, int *key, struct bpf_timer *timer) { scx_bpf_dsq_reenq(LOWPRI_DSQ, 0); - bpf_timer_start(timer, LOWPRI_INTV_NS, 0); + if (bpf_timer_start(timer, LOWPRI_INTV_NS, 0)) + scx_bpf_error("failed to re-arm lowpri timer"); return 0; } @@ -1747,7 +1749,8 @@ static void rr_advance(void) static int round_robin_timerfn(void *map, int *key, struct bpf_timer *timer) { rr_advance(); - bpf_timer_start(timer, round_robin_ns, 0); + if (bpf_timer_start(timer, round_robin_ns, 0)) + scx_bpf_error("failed to re-arm round-robin timer"); return 0; } From 4881a13521886076e8d6d677f274320dcf66fea4 Mon Sep 17 00:00:00 2001 From: Wanwu Li Date: Thu, 27 Aug 2026 17:14:11 +0800 Subject: [PATCH 569/641] sched_ext: Fix several comment issues Fix several comment issues found during review: __setschduler_prio() -> __setscheduler_class() scx_iter_scx_dsq_new() -> bpf_iter_scx_dsq_new() scx_next_task_scx() -> set_next_task_scx() Signed-off-by: Wanwu Li Signed-off-by: Tejun Heo --- kernel/sched/ext/ext.c | 10 +++++----- kernel/sched/ext/internal.h | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index c539d15cda63..76a3f4ea237c 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -876,9 +876,9 @@ struct task_struct *scx_task_iter_next_locked(struct scx_task_iter *iter) * unloading. The init_tasks ("swappers") should be excluded * from the iteration because: * - * - It's unsafe to use __setschduler_prio() on an init_task to - * determine the sched_class to use as it won't preserve its - * idle_sched_class. + * - It's unsafe to use __setscheduler_class() on an init_task + * to determine the sched_class to use as it won't preserve + * its idle_sched_class. * * - ops.init/exit_task() can easily be confused if called with * init_tasks as they, e.g., share PID 0. @@ -5514,7 +5514,7 @@ static const struct kset_uevent_ops scx_uevent_ops = { }; /* - * Used by sched_fork() and __setscheduler_prio() to pick the matching + * Used by sched_fork() and __setscheduler_class() to pick the matching * sched_class. dl/rt are already handled. */ bool task_should_scx(int policy) @@ -9771,7 +9771,7 @@ __bpf_kfunc struct task_struct *bpf_iter_scx_dsq_next(struct bpf_iter_scx_dsq *i * bpf_iter_scx_dsq_destroy - Destroy a DSQ iterator * @it: iterator to destroy * - * Undo scx_iter_scx_dsq_new(). + * Undo bpf_iter_scx_dsq_new(). */ __bpf_kfunc void bpf_iter_scx_dsq_destroy(struct bpf_iter_scx_dsq *it) { diff --git a/kernel/sched/ext/internal.h b/kernel/sched/ext/internal.h index 53e136a47924..0967b99a4948 100644 --- a/kernel/sched/ext/internal.h +++ b/kernel/sched/ext/internal.h @@ -442,7 +442,7 @@ struct sched_ext_ops { * * Note that this callback may be called from a CPU other than the * one the task is going to run on. This can happen when a task - * property is changed (i.e., affinity), since scx_next_task_scx(), + * property is changed (i.e., affinity), since set_next_task_scx(), * which triggers this callback, may run on a CPU different from * the task's assigned CPU. * From ea2ee8b222306208d2b094d1a11894da6c106d42 Mon Sep 17 00:00:00 2001 From: Kiran K Date: Tue, 25 Aug 2026 22:53:00 +0530 Subject: [PATCH 570/641] Bluetooth: btintel_pcie: Clear automask on spurious interrupts On spurious interrupt where the TX and RX causes are not set, driver was not clearing the auto mask which can block all the interrupts. Driver needs to clear the automask even if no causes are set. Fixes: c2b636b3f788 ("Bluetooth: btintel_pcie: Add support for PCIe transport") Signed-off-by: Kiran K Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel_pcie.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index 005c77a4f5eb..eec95e5f3dbb 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -1696,6 +1696,9 @@ static irqreturn_t btintel_pcie_irq_msix_handler(int irq, void *dev_id) if (unlikely(!(intr_fh | intr_hw))) { /* Ignore interrupt, inta == 0 */ + bt_warn_ratelimited("Bluetooth: btintel_pcie: Received spurious interrupt\n"); + btintel_pcie_wr_reg32(data, BTINTEL_PCIE_CSR_MSIX_AUTOMASK_ST, + BIT(entry->entry)); return IRQ_NONE; } From 068e5a0bc57e57d24cbf38def29cc5fb4db9a0df Mon Sep 17 00:00:00 2001 From: Liang Luo Date: Tue, 25 Aug 2026 13:50:53 +0800 Subject: [PATCH 571/641] sched_ext: Fix missing @slice and @vtime descriptions in finish_dispatch() kernel-doc Commit 13f1eae3b662 ("sched_ext: Synchronize slice and dsq_vtime writes") added the slice and vtime parameters to finish_dispatch() but did not update its kernel-doc, which produces warnings: Warning: function parameter 'slice' not described in 'finish_dispatch' Warning: function parameter 'vtime' not described in 'finish_dispatch' Describe both parameters using the same wording as dispatch_to_local_dsq(), which receives the same values. Signed-off-by: Liang Luo Signed-off-by: Tejun Heo --- kernel/sched/ext/ext.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 76a3f4ea237c..713aa26b2828 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -2806,6 +2806,8 @@ static void dispatch_to_local_dsq(struct scx_sched *sch, struct rq *rq, * @p: task to finish dispatching * @qseq_at_dispatch: qseq when @p started getting dispatched * @dsq_id: destination DSQ ID + * @slice: slice carried by the insert verdict, 0 keeps the current value + * @vtime: vtime carried by the insert verdict, committed on PRIQ inserts * @enq_flags: %SCX_ENQ_* * * Dispatching to local DSQs may need to wait for queueing to complete or From 068c35b5d0546c8625b3d7c61910f73775cf1216 Mon Sep 17 00:00:00 2001 From: Jiacheng Xu Date: Tue, 25 Aug 2026 15:03:15 +0800 Subject: [PATCH 572/641] workqueue: reject watchdog thresholds that overflow jiffies The watchdog threshold is supplied in seconds but is multiplied by HZ before being used as a jiffies interval. Reject values that exceed MAX_JIFFY_OFFSET / HZ so the multiplication cannot wrap and the time_after() comparisons remain within their supported range. The check is performed before changing the threshold or watchdog timer. Zero remains the value used to disable the watchdog. Fixes: 82607adcf9cdf ("workqueue: implement lockup detector") Signed-off-by: Jiacheng Xu Signed-off-by: Tejun Heo --- kernel/workqueue.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kernel/workqueue.c b/kernel/workqueue.c index 0ee73dcd4a14..b8bec1689b7a 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -8035,6 +8035,9 @@ static int wq_watchdog_param_set_thresh(const char *val, if (ret) return ret; + if (thresh > MAX_JIFFY_OFFSET / HZ) + return -ERANGE; + if (system_percpu_wq) wq_watchdog_set_thresh(thresh); else From a086c0892969bf8a0151b0f12bd14a68827c88b2 Mon Sep 17 00:00:00 2001 From: Laxman Acharya Padhya Date: Mon, 31 Aug 2026 15:44:21 +0545 Subject: [PATCH 573/641] Bluetooth: btintel: validate version TLV value lengths btintel_parse_version_tlv() verifies that a complete TLV is present in the response, but it does not ensure that the value is long enough for the specific TLV type. A short value can therefore cause an out-of-bounds read through get_unaligned_le16(), get_unaligned_le32(), or memcpy(). Reject values shorter than the minimum required by each known TLV type. Also reject responses that do not contain the Command Complete Status field. Fixes: 57375beef71a ("Bluetooth: btintel: Add infrastructure to read controller information") Reviewed-by: Ali Ahmet Memis Signed-off-by: Laxman Acharya Padhya Tested-by: Kiran K Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel.c | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c index bcb2514b7bc0..352a1c83cd08 100644 --- a/drivers/bluetooth/btintel.c +++ b/drivers/bluetooth/btintel.c @@ -571,12 +571,44 @@ int btintel_version_info_tlv(struct hci_dev *hdev, } EXPORT_SYMBOL_GPL(btintel_version_info_tlv); +static u8 btintel_version_tlv_min_len(u8 type) +{ + switch (type) { + case INTEL_TLV_CNVI_TOP: + case INTEL_TLV_CNVR_TOP: + case INTEL_TLV_CNVI_BT: + case INTEL_TLV_CNVR_BT: + case INTEL_TLV_BUILD_NUM: + case INTEL_TLV_GIT_SHA1: + return sizeof(u32); + case INTEL_TLV_DEV_REV_ID: + case INTEL_TLV_TIME_STAMP: + return sizeof(u16); + case INTEL_TLV_IMAGE_TYPE: + case INTEL_TLV_BUILD_TYPE: + case INTEL_TLV_SECURE_BOOT: + case INTEL_TLV_OTP_LOCK: + case INTEL_TLV_API_LOCK: + case INTEL_TLV_DEBUG_LOCK: + case INTEL_TLV_LIMITED_CCE: + case INTEL_TLV_SBE_TYPE: + return sizeof(u8); + case INTEL_TLV_MIN_FW: + return 3; + case INTEL_TLV_OTP_BDADDR: + return sizeof(bdaddr_t); + default: + return 0; + } +} + int btintel_parse_version_tlv(struct hci_dev *hdev, struct intel_version_tlv *version, struct sk_buff *skb) { /* Consume Command Complete Status field */ - skb_pull(skb, 1); + if (!skb_pull(skb, 1)) + return -EINVAL; /* Event parameters contain multiple TLVs. Read each of them * and only keep the required data. Also, it use existing legacy @@ -596,6 +628,9 @@ int btintel_parse_version_tlv(struct hci_dev *hdev, if (skb->len < tlv->len + sizeof(*tlv)) return -EINVAL; + if (tlv->len < btintel_version_tlv_min_len(tlv->type)) + return -EINVAL; + switch (tlv->type) { case INTEL_TLV_CNVI_TOP: version->cnvi_top = get_unaligned_le32(tlv->val); From ac8aa9e0ec93a12a60230066f199f49c3b9aac3d Mon Sep 17 00:00:00 2001 From: Laxman Acharya Padhya Date: Mon, 31 Aug 2026 15:44:22 +0545 Subject: [PATCH 574/641] Bluetooth: btintel: bound firmware ID by TLV length The firmware ID is treated as a NUL-terminated string even though the TLV length is its only boundary. If the value does not contain a NUL terminator, snprintf() can read beyond the received response. Limit the conversion to the advertised TLV value length. Fixes: 164c62f958f8 ("Bluetooth: btintel: Add firmware ID to firmware name") Reviewed-by: Ali Ahmet Memis Signed-off-by: Laxman Acharya Padhya Tested-by: Kiran K Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c index 352a1c83cd08..2f87047168d7 100644 --- a/drivers/bluetooth/btintel.c +++ b/drivers/bluetooth/btintel.c @@ -702,7 +702,7 @@ int btintel_parse_version_tlv(struct hci_dev *hdev, break; case INTEL_TLV_FW_ID: snprintf(version->fw_id, sizeof(version->fw_id), - "%s", tlv->val); + "%.*s", tlv->len, tlv->val); break; default: /* Ignore rest of information */ From 3a74624b5deae7f5e2b98e638687fbf9594a9781 Mon Sep 17 00:00:00 2001 From: Laxman Acharya Padhya Date: Mon, 31 Aug 2026 15:44:23 +0545 Subject: [PATCH 575/641] Bluetooth: btintel: propagate version TLV parsing errors btintel_read_version_tlv() ignores the parser return value, so setup continues with partially initialized version data after a malformed TLV causes parsing to stop. Return the parser error to the caller so an invalid response fails setup instead of being treated as successful. Keep this behavioral change separate from the bounds checks so it can be reverted independently if an existing controller sends malformed data. Signed-off-by: Laxman Acharya Padhya Reviewed-by: Ali Ahmet Memis Tested-by: Kiran K Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c index 2f87047168d7..909a265fd906 100644 --- a/drivers/bluetooth/btintel.c +++ b/drivers/bluetooth/btintel.c @@ -721,6 +721,7 @@ static int btintel_read_version_tlv(struct hci_dev *hdev, { struct sk_buff *skb; const u8 param[1] = { 0xFF }; + int err; if (!version) return -EINVAL; @@ -739,10 +740,10 @@ static int btintel_read_version_tlv(struct hci_dev *hdev, return -EIO; } - btintel_parse_version_tlv(hdev, version, skb); + err = btintel_parse_version_tlv(hdev, version, skb); kfree_skb(skb); - return 0; + return err; } /* ------- REGMAP IBT SUPPORT ------- */ From 57938bbdb9bf7fd41cbd5cd509ec10c4b22bec18 Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh Date: Fri, 28 Aug 2026 08:55:09 +0000 Subject: [PATCH 576/641] Bluetooth: hci_core: Fix race condition during device registration In hci_register_dev(), the power_on work item is queued to hdev->req_workqueue before initializing hdev->adv_monitors_idr and registering the MSFT extension via msft_register(). For devices marked with quirks such as HCI_QUIRK_RAW_DEVICE, the HCI_UNCONFIGURED flag is set on the device. When the power_on work item runs concurrently on another CPU, hci_power_on() detects that the device is unconfigured and immediately invokes hci_dev_do_close(), which calls msft_do_close(). Concurrently, msft_register() allocates the msft structure and exposes it to hdev->msft_data prior to calling mutex_init(&msft->filter_lock). If msft_do_close() executes while hdev->msft_data is already assigned but the mutex has not yet been initialized, mutex_lock(&msft->filter_lock) operates on an uninitialized mutex, triggering a DEBUG_LOCKS warning: DEBUG_LOCKS_WARN_ON(lock->magic != lock) WARNING: kernel/locking/mutex.c:625 at __mutex_lock_common kernel/locking/mutex.c:625 [inline] WARNING: kernel/locking/mutex.c:625 at __mutex_lock+0x12d8/0x1550 kernel/locking/mutex.c:821 ... Call Trace: msft_do_close+0x308/0x7b0 net/bluetooth/msft.c:693 hci_dev_close_sync+0x86b/0x10a0 net/bluetooth/hci_sync.c:5522 hci_dev_do_close net/bluetooth/hci_core.c:499 [inline] hci_power_on+0x32c/0x750 net/bluetooth/hci_core.c:937 process_one_work kernel/workqueue.c:3322 [inline] process_scheduled_works+0xa8e/0x14e0 kernel/workqueue.c:3405 worker_thread+0x92d/0xe10 kernel/workqueue.c:3486 kthread+0x388/0x470 kernel/kthread.c:436 ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245 Fix this by moving the queue_work() call in hci_register_dev() to after idr_init(&hdev->adv_monitors_idr) and msft_register(hdev) so that device structures and extensions are fully initialized before asynchronous tasks can access them. Additionally, assign hdev->msft_data in msft_register() only after mutex_init(&msft->filter_lock) has completed. Fixes: 9e14606d8f38 ("Bluetooth: msft: Extended monitor tracking by address filter") Assisted-by: Gemini:gemini-3.7-flash Gemini:gemini-3.1-pro-preview syzbot Reported-by: syzbot+14ce1b05b7d5a989abbe@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=14ce1b05b7d5a989abbe Link: https://syzkaller.appspot.com/ai_job?id=2bc9e8aa-ca6d-43e2-be2c-fd5d9f649d7e Signed-off-by: Aleksandr Nogikh Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_core.c | 4 ++-- net/bluetooth/msft.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 35a1be57e386..d7355c73f93e 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -2632,11 +2632,11 @@ int hci_register_dev(struct hci_dev *hdev) if (error) BT_WARN("register suspend notifier failed error:%d\n", error); - queue_work(hdev->req_workqueue, &hdev->power_on); - idr_init(&hdev->adv_monitors_idr); msft_register(hdev); + queue_work(hdev->req_workqueue, &hdev->power_on); + return id; err_wqueue: diff --git a/net/bluetooth/msft.c b/net/bluetooth/msft.c index ded68568e6c9..d9dd722db3eb 100644 --- a/net/bluetooth/msft.c +++ b/net/bluetooth/msft.c @@ -769,8 +769,8 @@ void msft_register(struct hci_dev *hdev) INIT_LIST_HEAD(&msft->handle_map); INIT_LIST_HEAD(&msft->address_filters); - hdev->msft_data = msft; mutex_init(&msft->filter_lock); + hdev->msft_data = msft; } void msft_release(struct hci_dev *hdev) From 4ef05db5b08b176a551b4a6287372045998806b0 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 30 Aug 2026 20:11:36 +0300 Subject: [PATCH 577/641] Bluetooth: L2CAP: fix chan mode for LE_CONN_REQ + EXT_FLOWCTL pchan l2cap_new_connection() sets default value of channel mode to match the parent channel. l2cap_le_connect_req() left this at the default, and created L2CAP_MODE_EXT_FLOWCTL channels if listening pchan has that mode. This causes FLAG_DEFER_SETUP channels to reply to L2CAP_LE_CONN_REQ with L2CAP_ECRED_CONN_RSP, which is incorrect. It can also result to stack OOB write (of l2cap_alloc_cid determined values) in l2cap_ecred_rsp_defer(), as l2cap_le_connect_req() does not limit maximum number of deferred channels or check for duplicate ident. Fix by setting chan->mode correctly in l2cap_le_connect_req(). Also check channel mode in l2cap_ecred_rsp_defer(), and do WARN_ON_ONCE instead of OOB write to make it less brittle. Fixes: 15f02b910562 ("Bluetooth: L2CAP: Add initial code for Enhanced Credit Based Mode") Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/l2cap_core.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index ee459dd411f5..1c0b7884dc27 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -3894,6 +3894,9 @@ static void l2cap_ecred_rsp_defer(struct l2cap_chan *chan, void *data) struct l2cap_ecred_conn_rsp *rsp_flex = container_of(&rsp->pdu.rsp, struct l2cap_ecred_conn_rsp, hdr); + if (chan->mode != L2CAP_MODE_EXT_FLOWCTL) + return; + /* Check if channel for outgoing connection or if it wasn't deferred * since in those cases it must be skipped. */ @@ -3904,6 +3907,10 @@ static void l2cap_ecred_rsp_defer(struct l2cap_chan *chan, void *data) /* Reset ident so only one response is sent */ chan->ident = 0; + /* Unreachable, check in l2cap_ecred_conn_req. If reached, drop rest */ + if (WARN_ON_ONCE(rsp->count >= ARRAY_SIZE(rsp->pdu.scid))) + rsp->pdu.rsp.result = cpu_to_le16(L2CAP_CR_LE_NO_MEM); + /* Include all channels pending with the same ident */ if (!rsp->pdu.rsp.result) rsp_flex->dcid[rsp->count++] = cpu_to_le16(chan->scid); @@ -5063,6 +5070,7 @@ static int l2cap_le_connect_req(struct l2cap_conn *conn, __set_chan_timer(chan, chan->ops->get_sndtimeo(chan)); chan->ident = cmd->ident; + chan->mode = L2CAP_MODE_LE_FLOWCTL; if (test_bit(FLAG_DEFER_SETUP, &chan->flags)) { l2cap_state_change(chan, BT_CONNECT2); From 56c2b5831d39dc84aad2573dc3e197af1a872a05 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 30 Aug 2026 15:04:01 +0300 Subject: [PATCH 578/641] Bluetooth: L2CAP: fix out-of-bounds write in l2cap_ecred_connect l2cap_chan_connect() tries to ensure there are no more than L2CAP_ECRED_CONN_SCID_MAX pending ECRED channels, so they fit in the same L2CAP_ECRED_CONN_REQ that l2cap_ecred_connect() constructs. However, the check only counts deferred channels. If 6 L2CAP sockets are connected at the same time in order DDDDND (D=deferred, N=non-deferred), the last can bump the total to max+1. It results to one __le16 written out of bounds of the scid array, and an invalid ECRED_CONN_REQ being sent. Fix by leaving room for the non-deferred pending ECRED channels in the counting in l2cap_chan_connect(), so the limit can't be exceeded. Move counting under same critical section where the channel is added. Although race conditions involving this appear unreachable, it's easier to see. Also add WARN_ON_ONCE check in l2cap_ecred_defer_connect() to make this less brittle. Fixes: da49b602f7f7 ("Bluetooth: L2CAP: Use DEFER_SETUP to group ECRED connections") Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/l2cap_core.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 1c0b7884dc27..60833fa2835b 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -1337,7 +1337,7 @@ static void l2cap_le_connect(struct l2cap_chan *chan) struct l2cap_ecred_conn_data { struct { struct l2cap_ecred_conn_req_hdr req; - __le16 scid[5]; + __le16 scid[L2CAP_ECRED_CONN_SCID_MAX]; } __packed pdu; struct l2cap_chan *chan; struct pid *pid; @@ -1365,6 +1365,10 @@ static void l2cap_ecred_defer_connect(struct l2cap_chan *chan, void *data) if (test_and_set_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags)) return; + /* Unreachable, checked in l2cap_connect (+timer drops it if reached) */ + if (WARN_ON_ONCE(conn->count >= ARRAY_SIZE(conn->pdu.scid))) + return; + l2cap_ecred_init(chan, 0); /* Set the same ident so we can match on the rsp */ @@ -7377,6 +7381,9 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid, goto done; } + mutex_lock(&conn->lock); + l2cap_chan_lock(chan); + if (chan->mode == L2CAP_MODE_EXT_FLOWCTL) { struct l2cap_chan_data data; @@ -7384,19 +7391,20 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid, data.pid = chan->ops->get_peer_pid(chan); data.count = 1; - l2cap_chan_list(conn, l2cap_chan_by_pid, &data); + __l2cap_chan_list(conn, l2cap_chan_by_pid, &data); + + /* Leave room for non-deferred channel that ends the group. */ + if (test_bit(FLAG_DEFER_SETUP, &chan->flags)) + data.count += 1; /* Check if there isn't too many channels being connected */ if (data.count > L2CAP_ECRED_CONN_SCID_MAX) { hci_conn_drop(hcon); err = -EPROTO; - goto done; + goto chan_unlock; } } - mutex_lock(&conn->lock); - l2cap_chan_lock(chan); - if (cid && __l2cap_get_chan_by_dcid(conn, cid)) { hci_conn_drop(hcon); err = -EBUSY; From 0d77683237270702fa93489ca759c89b4e970554 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 30 Aug 2026 15:04:02 +0300 Subject: [PATCH 579/641] Bluetooth: L2CAP: clear FLAG_DEFER_SETUP only for same PID/PSM l2cap_ecred_defer_connect() clears FLAG_DEFER_SETUP also for channels with different PID/PSM, which will not be added to the same ECRED_CONN_REQ in any case. Consequently, only one ECRED connection group can work at a time although it appears intended they would be separate for each PID/PSM combination. Fix by clearing FLAG_DEFER_SETUP only for the connections that could be added in the request. Retain test_bit(FLAG_DEFER_SETUP) before calling get_peer_pid as it may be NULL otherwise. Fixes: da49b602f7f7 ("Bluetooth: L2CAP: Use DEFER_SETUP to group ECRED connections") Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/l2cap_core.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 60833fa2835b..644e31160d55 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -1352,7 +1352,7 @@ static void l2cap_ecred_defer_connect(struct l2cap_chan *chan, void *data) if (chan == conn->chan) return; - if (!test_and_clear_bit(FLAG_DEFER_SETUP, &chan->flags)) + if (!test_bit(FLAG_DEFER_SETUP, &chan->flags)) return; pid = chan->ops->get_peer_pid(chan); @@ -1362,6 +1362,9 @@ static void l2cap_ecred_defer_connect(struct l2cap_chan *chan, void *data) chan->mode != L2CAP_MODE_EXT_FLOWCTL || chan->state != BT_CONNECT) return; + if (!test_and_clear_bit(FLAG_DEFER_SETUP, &chan->flags)) + return; + if (test_and_set_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags)) return; From 2deb76c21b81e42b3282224f7dd2046fe73fd1e0 Mon Sep 17 00:00:00 2001 From: Gongwei Li Date: Tue, 25 Aug 2026 10:01:45 +0800 Subject: [PATCH 580/641] Bluetooth: hci_mrvl: Fix wrong return value check of wait_on_bit_timeout() wait_on_bit_timeout() returns 0 if the bit was cleared, -EINTR if the process received a signal and the mode permitted wake up on that signal, or -EAGAIN if the timeout elapsed. It never returns 1. Hence the check "err == 1" in mrvl_load_firmware() is dead code: when the waiting task is interrupted by a signal (-EINTR), the code falls into the "else if (err)" branch and misreports it as "Firmware request timeout" with -ETIMEDOUT instead of propagating -EINTR. Fix this by testing for -EINTR so that an interrupted firmware load is properly detected and reported. Fixes: 162f812f23ba ("Bluetooth: hci_uart: Add Marvell support") Signed-off-by: Gongwei Li Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/hci_mrvl.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/bluetooth/hci_mrvl.c b/drivers/bluetooth/hci_mrvl.c index 516b8f74c434..5798a8db016e 100644 --- a/drivers/bluetooth/hci_mrvl.c +++ b/drivers/bluetooth/hci_mrvl.c @@ -307,9 +307,8 @@ static int mrvl_load_firmware(struct hci_dev *hdev, const char *name) err = wait_on_bit_timeout(&mrvl->flags, STATE_FW_REQ_PENDING, TASK_INTERRUPTIBLE, msecs_to_jiffies(2000)); - if (err == 1) { + if (err == -EINTR) { bt_dev_err(hdev, "Firmware load interrupted"); - err = -EINTR; break; } else if (err) { bt_dev_err(hdev, "Firmware request timeout"); From 93e257938aa67a6c957217db94091d9d9e5d403f Mon Sep 17 00:00:00 2001 From: Aaron Tomlin Date: Mon, 31 Aug 2026 14:15:53 -0400 Subject: [PATCH 581/641] tools/workqueue/wq_dump.py: Support backward compatibility for wq->attrs rename Commit 464e454e1cb4 ("workqueue: rename wq->unbound_attrs to wq->attrs") renamed wq->unbound_attrs to wq->attrs. When running wq_dump.py against older running kernels or vmcores where struct workqueue_struct still contains unbound_attrs, drgn raises an AttributeError. Add a wq_attrs() helper to allow wq_dump.py to inspect both older and newer kernel versions seamlessly. Fixes: 464e454e1cb4 ("workqueue: rename wq->unbound_attrs to wq->attrs") Signed-off-by: Aaron Tomlin Signed-off-by: Tejun Heo --- tools/workqueue/wq_dump.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tools/workqueue/wq_dump.py b/tools/workqueue/wq_dump.py index 31afc24ef17b..9313ebe0c525 100644 --- a/tools/workqueue/wq_dump.py +++ b/tools/workqueue/wq_dump.py @@ -78,6 +78,12 @@ def cpumask_str(cpumask): wq_type_len = 9 +def wq_attrs(wq): + try: + return wq.attrs + except AttributeError: + return wq.unbound_attrs + def wq_type_str(wq): if wq.flags & WQ_BH: return f'{"bh":{wq_type_len}}' @@ -85,7 +91,7 @@ def wq_type_str(wq): if wq.flags & WQ_ORDERED: return f'{"ordered":{wq_type_len}}' else: - if wq.attrs.affn_strict: + if wq_attrs(wq).affn_strict: return f'{"unbound,S":{wq_type_len}}' else: return f'{"unbound":{wq_type_len}}' @@ -206,7 +212,7 @@ for wq in list_for_each_entry('struct workqueue_struct', workqueues.address_of_( print(f'{wq.name.string_().decode():{WQ_NAME_LEN}}', end='') if wq.flags & WQ_UNBOUND: - print(f' {cpumask_str(wq.attrs.cpumask):{ucpus_len}}', end='') + print(f' {cpumask_str(wq_attrs(wq).cpumask):{ucpus_len}}', end='') else: print(f' {"":{ucpus_len}}', end='') From fa5acd038ea657ad5033713d6916214cbd349151 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 28 Aug 2026 14:07:47 -0500 Subject: [PATCH 582/641] net/iucv: fix the recvmsg window update iucv_sock_recvmsg() sends the HiperSockets-only AF_IUCV_FLAG_WIN without testing the transport, so on a classic z/VM socket iucv_send_ctrl() sizes the skb through a NULL iucv->hs_dev. SO_MSGLIMIT accepts 1, so msglimit / 2 is zero and one recvmsg() on its own socket is enough for an unprivileged process to take a spurious disconnect. It also calls iucv_send_ctrl() under spin_lock_bh(&message_q.lock), which allocates GFP_KERNEL inside a section the code treats as atomic. Sending outside that lock lets two recvmsg() reach afiucv_hs_send() at once, where msg_recv is sampled for the advertised window and subtracted after dev_queue_xmit() -- and sendmsg reaches that counter under lock_sock() while recvmsg holds no socket lock, so both can subtract the same value, the counter goes negative and the credit reaches the peer twice. Test the transport, claim the credit with atomic_xchg() after the last error exit and hand it back if the transmit fails, and send once the lock is dropped. Fixes: 3881ac441f64 ("af_iucv: add HiperSockets transport") Fixes: 238965b71b96 ("net/af_iucv: build proper skbs for HiperTransport") Cc: stable@vger.kernel.org Tested-by: Aswin Karuvally Signed-off-by: Bryam Vargas Reviewed-by: Alexandra Winter Link: https://patch.msgid.link/20260828-b4-disp-33fac0ed-v3-1-e6d061880ee0@proton.me Signed-off-by: Jakub Kicinski --- net/iucv/af_iucv.c | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/net/iucv/af_iucv.c b/net/iucv/af_iucv.c index 4e5cc9da6e06..db261ecd19af 100644 --- a/net/iucv/af_iucv.c +++ b/net/iucv/af_iucv.c @@ -210,12 +210,6 @@ static int afiucv_hs_send(struct iucv_message *imsg, struct sock *sock, phs_hdr->flags = flags; if (flags == AF_IUCV_FLAG_SYN) phs_hdr->window = iucv->msglimit; - else if ((flags == AF_IUCV_FLAG_WIN) || !flags) { - confirm_recv = atomic_read(&iucv->msg_recv); - phs_hdr->window = confirm_recv; - if (confirm_recv) - phs_hdr->flags = phs_hdr->flags | AF_IUCV_FLAG_WIN; - } memcpy(phs_hdr->destUserID, iucv->dst_user_id, 8); memcpy(phs_hdr->destAppName, iucv->dst_name, 8); memcpy(phs_hdr->srcUserID, iucv->src_user_id, 8); @@ -250,13 +244,22 @@ static int afiucv_hs_send(struct iucv_message *imsg, struct sock *sock, } skb->protocol = cpu_to_be16(ETH_P_AF_IUCV); + /* Claim the receive credit here, not while building the header: every + * way this frame can be dropped has now been ruled out, so the window + * is zeroed only for as long as the transmit itself takes. + */ + if (flags == AF_IUCV_FLAG_WIN || !flags) { + confirm_recv = atomic_xchg(&iucv->msg_recv, 0); + phs_hdr->window = confirm_recv; + if (confirm_recv) + phs_hdr->flags = phs_hdr->flags | AF_IUCV_FLAG_WIN; + } + atomic_inc(&iucv->skbs_in_xmit); err = dev_queue_xmit(skb); if (net_xmit_eval(err)) { atomic_dec(&iucv->skbs_in_xmit); - } else { - atomic_sub(confirm_recv, &iucv->msg_recv); - WARN_ON(atomic_read(&iucv->msg_recv) < 0); + atomic_add(confirm_recv, &iucv->msg_recv); } return net_xmit_eval(err); @@ -1241,6 +1244,7 @@ static int iucv_sock_recvmsg(struct socket *sock, struct msghdr *msg, struct iucv_sock *iucv = iucv_sk(sk); unsigned int copied, rlen; struct sk_buff *skb, *rskb, *cskb; + bool send_win = false; int err = 0; u32 offset; @@ -1331,16 +1335,20 @@ static int iucv_sock_recvmsg(struct socket *sock, struct msghdr *msg, if (skb_queue_empty(&iucv->backlog_skb_q)) { if (!list_empty(&iucv->message_q.list)) iucv_process_message_q(sk); - if (atomic_read(&iucv->msg_recv) >= - iucv->msglimit / 2) { - err = iucv_send_ctrl(sk, AF_IUCV_FLAG_WIN); - if (err) { - sk->sk_state = IUCV_DISCONN; - sk->sk_state_change(sk); - } - } + if (iucv->transport == AF_IUCV_TRANS_HIPER && + atomic_read(&iucv->msg_recv) >= + iucv->msglimit / 2) + send_win = true; } spin_unlock_bh(&iucv->message_q.lock); + + if (send_win) { + err = iucv_send_ctrl(sk, AF_IUCV_FLAG_WIN); + if (err) { + sk->sk_state = IUCV_DISCONN; + sk->sk_state_change(sk); + } + } } done: From 5443d9c4f55d46634b95432e1e8a40b824019bbb Mon Sep 17 00:00:00 2001 From: Selvamani Rajagopal Date: Mon, 24 Aug 2026 14:57:58 -0700 Subject: [PATCH 583/641] net: ethernet: oa_tc6: Protect skb pointer used by two different kernel instances Threaded IRQ uses waiting_tx_skb. Transmit path also uses this pointer without any mutual exclusion protection. As a result, it might leak skb buffer, particularly if threaded IRQ sets disable_traffic true after start_xmit already checked and found that disable_traffic being false, if they happen to run on different cores. On fatal error, where disable_traffic is set, transmit function drops the packet and return NETDEV_TX_OK. Due to this change, skb_linearize call is moved up to the beginning of the transmit function. Since skb buffer may be freed from different contexts, dev_kfree_skb_any is used to free skb buffer now, replacing one of the kfree_skb call. oa_tc6_exit disables the irq before setting disable_traffic true. Fixes: b542d13fab0f ("net: ethernet: oa_tc6: Interrupt is active low, level triggered.") Signed-off-by: Selvamani Rajagopal Link: https://patch.msgid.link/20260824-fix-race-condition-and-crash-v7-1-4323279b18f2@onsemi.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/oa_tc6.c | 111 +++++++++++++++++++++++----------- 1 file changed, 77 insertions(+), 34 deletions(-) diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c index 417c15d1ff42..2f45001be0f5 100644 --- a/drivers/net/ethernet/oa_tc6.c +++ b/drivers/net/ethernet/oa_tc6.c @@ -693,6 +693,26 @@ static int oa_tc6_enable_data_transfer(struct oa_tc6 *tc6) return oa_tc6_write_register(tc6, OA_TC6_REG_CONFIG0, value); } +/* Called when a frame that is meant to be transmitted, is dropped. */ +static void oa_tc6_drop_tx_skb(struct oa_tc6 *tc6, struct sk_buff *skb) +{ + if (skb) { + tc6->netdev->stats.tx_dropped++; + dev_kfree_skb_any(skb); + } +} + +static struct sk_buff *oa_tc6_detach_waiting_tx_skb(struct oa_tc6 *tc6) +{ + struct sk_buff *skb; + + lockdep_assert_held(&tc6->tx_skb_lock); + skb = tc6->waiting_tx_skb; + tc6->waiting_tx_skb = NULL; + + return skb; +} + static void oa_tc6_cleanup_ongoing_rx_skb(struct oa_tc6 *tc6) { if (tc6->rx_skb) { @@ -704,26 +724,30 @@ static void oa_tc6_cleanup_ongoing_rx_skb(struct oa_tc6 *tc6) static void oa_tc6_cleanup_ongoing_tx_skb(struct oa_tc6 *tc6) { - if (tc6->ongoing_tx_skb) { - tc6->netdev->stats.tx_dropped++; - kfree_skb(tc6->ongoing_tx_skb); - tc6->ongoing_tx_skb = NULL; - } + oa_tc6_drop_tx_skb(tc6, tc6->ongoing_tx_skb); + tc6->ongoing_tx_skb = NULL; } static void oa_tc6_cleanup_waiting_tx_skb(struct oa_tc6 *tc6) { - if (tc6->waiting_tx_skb) { - tc6->netdev->stats.tx_dropped++; - kfree_skb(tc6->waiting_tx_skb); - tc6->waiting_tx_skb = NULL; - } + struct sk_buff *skb; + + spin_lock_bh(&tc6->tx_skb_lock); + skb = oa_tc6_detach_waiting_tx_skb(tc6); + spin_unlock_bh(&tc6->tx_skb_lock); + + oa_tc6_drop_tx_skb(tc6, skb); +} + +static void oa_tc6_free_ongoing_skbs(struct oa_tc6 *tc6) +{ + oa_tc6_cleanup_ongoing_tx_skb(tc6); + oa_tc6_cleanup_ongoing_rx_skb(tc6); } static void oa_tc6_free_pending_skbs(struct oa_tc6 *tc6) { - oa_tc6_cleanup_ongoing_tx_skb(tc6); - oa_tc6_cleanup_ongoing_rx_skb(tc6); + oa_tc6_free_ongoing_skbs(tc6); oa_tc6_cleanup_waiting_tx_skb(tc6); } @@ -734,9 +758,15 @@ static void oa_tc6_free_pending_skbs(struct oa_tc6 *tc6) static void oa_tc6_disable_traffic(struct oa_tc6 *tc6) { u32 regval = OA_TC6_INT_MASK0_ALL_INTERRUPTS; + struct sk_buff *skb; + spin_lock_bh(&tc6->tx_skb_lock); tc6->disable_traffic = true; - oa_tc6_free_pending_skbs(tc6); + skb = oa_tc6_detach_waiting_tx_skb(tc6); + spin_unlock_bh(&tc6->tx_skb_lock); + + oa_tc6_drop_tx_skb(tc6, skb); + oa_tc6_free_ongoing_skbs(tc6); oa_tc6_write_register(tc6, OA_TC6_REG_INT_MASK0, regval); oa_tc6_read_register(tc6, OA_TC6_REG_STATUS0, ®val); oa_tc6_write_register(tc6, OA_TC6_REG_STATUS0, regval); @@ -1177,8 +1207,7 @@ static int oa_tc6_try_spi_transfer(struct oa_tc6 *tc6) if (ret == -EAGAIN) continue; - oa_tc6_cleanup_ongoing_tx_skb(tc6); - oa_tc6_cleanup_ongoing_rx_skb(tc6); + oa_tc6_free_ongoing_skbs(tc6); netdev_err(tc6->netdev, "Device error: %d\n", ret); return ret; } @@ -1200,15 +1229,20 @@ static irqreturn_t oa_tc6_macphy_threaded_irq(int irq, void *data) * no need to attempt spi transfer, once it fails. Pending skbs * are already freed. */ - if (!tc6->disable_traffic) { - while (tc6->int_flag || - (tc6->waiting_tx_skb && tc6->tx_credits)) { - ret = oa_tc6_try_spi_transfer(tc6); - if (ret) { - disable_irq_nosync(tc6->spi->irq); - oa_tc6_disable_traffic(tc6); - break; - } + spin_lock_bh(&tc6->tx_skb_lock); + if (tc6->disable_traffic) { + spin_unlock_bh(&tc6->tx_skb_lock); + return IRQ_HANDLED; + } + spin_unlock_bh(&tc6->tx_skb_lock); + + while (tc6->int_flag || + (tc6->waiting_tx_skb && tc6->tx_credits)) { + ret = oa_tc6_try_spi_transfer(tc6); + if (ret) { + disable_irq_nosync(tc6->spi->irq); + oa_tc6_disable_traffic(tc6); + break; } } @@ -1287,23 +1321,30 @@ EXPORT_SYMBOL_GPL(oa_tc6_zero_align_receive_frame_enable); * @tc6: oa_tc6 struct. * @skb: socket buffer in which the ethernet frame is stored. * - * Return: NETDEV_TX_OK if the transmit ethernet frame skb added in the tx_skb_q - * otherwise returns NETDEV_TX_BUSY. + * Return: NETDEV_TX_OK either on successful queueing of the packet for + * transmission, or on packet getting dropped. Packet can be dropped due to + * failure in linearizing the buffer or disable_traffic is set due to + * earlier fatal error. Returns NETDEV_TX_BUSY when there is no room + * to queue the packet. */ netdev_tx_t oa_tc6_start_xmit(struct oa_tc6 *tc6, struct sk_buff *skb) { - if (tc6->disable_traffic || tc6->waiting_tx_skb) { - netif_stop_queue(tc6->netdev); - return NETDEV_TX_BUSY; - } - if (skb_linearize(skb)) { - dev_kfree_skb_any(skb); - tc6->netdev->stats.tx_dropped++; + oa_tc6_drop_tx_skb(tc6, skb); return NETDEV_TX_OK; } spin_lock_bh(&tc6->tx_skb_lock); + if (tc6->waiting_tx_skb) { + netif_stop_queue(tc6->netdev); + spin_unlock_bh(&tc6->tx_skb_lock); + return NETDEV_TX_BUSY; + } + if (tc6->disable_traffic) { + spin_unlock_bh(&tc6->tx_skb_lock); + oa_tc6_drop_tx_skb(tc6, skb); + return NETDEV_TX_OK; + } tc6->waiting_tx_skb = skb; spin_unlock_bh(&tc6->tx_skb_lock); @@ -1462,8 +1503,10 @@ EXPORT_SYMBOL_GPL(oa_tc6_init); */ void oa_tc6_exit(struct oa_tc6 *tc6) { - tc6->disable_traffic = true; disable_irq(tc6->spi->irq); + spin_lock_bh(&tc6->tx_skb_lock); + tc6->disable_traffic = true; + spin_unlock_bh(&tc6->tx_skb_lock); oa_tc6_phy_exit(tc6); oa_tc6_free_pending_skbs(tc6); } From 172c974113bffe5723b80b1acac17593bb50513c Mon Sep 17 00:00:00 2001 From: Selvamani Rajagopal Date: Mon, 24 Aug 2026 14:57:59 -0700 Subject: [PATCH 584/641] net: ethernet: oa_tc6: Improve the error recovery When oversubscribed traffic causes lot of buffer overflow errors, probably due to loss of data chunks, driver fails to find a data chunk with end_valid bit set, before it runs out of sk buffer space. As a result, assert is seen during skb_put. Now, check is made if skb buffer has enough tailroom for the incoming data before accepting. If there is no room, current frame is abandoned and it will start looking for a data chunk with start_valid bit, that is a new frame. SK buffer allocation error is considered as recoverable error. rx_buf_overflow flag is too specific and no longer the only condition this flag is used for. Therefore it is renamed as wait_until_start_valid. This is more appropriate as this flag is used to look for the next data chunk with SV bit set, after failures like buffer overflow, buffer allocation failure, skb pointer validity besides buffer overflow error. Not writing to status0 if it reads 0. Fixes: d70a0d8f2f2d ("net: ethernet: oa_tc6: implement receive path to receive rx ethernet frames") Signed-off-by: Selvamani Rajagopal Link: https://patch.msgid.link/20260824-fix-race-condition-and-crash-v7-2-4323279b18f2@onsemi.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/oa_tc6.c | 143 +++++++++++++++++++++++++--------- 1 file changed, 108 insertions(+), 35 deletions(-) diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c index 2f45001be0f5..657b1c6119da 100644 --- a/drivers/net/ethernet/oa_tc6.c +++ b/drivers/net/ethernet/oa_tc6.c @@ -83,7 +83,7 @@ struct oa_tc6 { u16 spi_data_tx_buf_offset; u16 tx_credits; u8 rx_chunks_available; - bool rx_buf_overflow; + bool wait_until_start_valid; bool int_flag; bool disable_traffic; bool prot_ctrl; @@ -751,6 +751,12 @@ static void oa_tc6_free_pending_skbs(struct oa_tc6 *tc6) oa_tc6_cleanup_waiting_tx_skb(tc6); } +static void oa_tc6_look_for_new_frame(struct oa_tc6 *tc6) +{ + tc6->wait_until_start_valid = true; + oa_tc6_cleanup_ongoing_rx_skb(tc6); +} + /* If the failure is at SPI interface level, masking and clearing * the interrupt of the device won't work. Since SPI interrupt is * disabled, it should stop the repeated interrupts. @@ -785,6 +791,13 @@ static int oa_tc6_process_extended_status(struct oa_tc6 *tc6) return ret; } + /* This function is called for each chunk received in a given SPI + * transaction. In case, extended status bit is set in more than + * one chunk, skip the write, if status0 is already cleared. + */ + if (!value) + return 0; + /* Clear the error interrupts status */ ret = oa_tc6_write_register(tc6, OA_TC6_REG_STATUS0, value); if (ret) { @@ -794,8 +807,7 @@ static int oa_tc6_process_extended_status(struct oa_tc6 *tc6) } if (FIELD_GET(OA_TC6_STATUS0_RX_BUFFER_OVERFLOW_ERROR, value)) { - tc6->rx_buf_overflow = true; - oa_tc6_cleanup_ongoing_rx_skb(tc6); + oa_tc6_look_for_new_frame(tc6); net_err_ratelimited("%s: Receive buffer overflow error\n", tc6->netdev->name); return -EAGAIN; @@ -821,6 +833,8 @@ static int oa_tc6_process_extended_status(struct oa_tc6 *tc6) static int oa_tc6_process_rx_chunk_footer(struct oa_tc6 *tc6, u32 footer) { + int ret = 0; + /* Process rx chunk footer for the following, * 1. tx credits * 2. errors if any from MAC-PHY @@ -831,9 +845,11 @@ static int oa_tc6_process_rx_chunk_footer(struct oa_tc6 *tc6, u32 footer) footer); if (FIELD_GET(OA_TC6_DATA_FOOTER_EXTENDED_STS, footer)) { - int ret = oa_tc6_process_extended_status(tc6); - - if (ret) + ret = oa_tc6_process_extended_status(tc6); + /* EAGAIN error is recoverable. Move on to check + * HEADER and SYNC errors before returning. + */ + if (ret && ret != -EAGAIN) return ret; } @@ -851,7 +867,7 @@ static int oa_tc6_process_rx_chunk_footer(struct oa_tc6 *tc6, u32 footer) return -ENODEV; } - return 0; + return ret; } static void oa_tc6_submit_rx_skb(struct oa_tc6 *tc6) @@ -876,13 +892,35 @@ static void oa_tc6_submit_rx_skb(struct oa_tc6 *tc6) tc6->rx_skb = NULL; } -static void oa_tc6_update_rx_skb(struct oa_tc6 *tc6, u8 *payload, u8 length) +/* On oversubscribed traffic condition, particularly with overwhelming rx + * buffer overflow errors, there could be data chunk loss. If tail + length + * goes beyond end pointer, that is an indication that the data chunk with + * end_valid bit is lost. Time to look for a data chunk with start_valid bit. + * + * If rx_skb is NULL, it is time to start looking for data chunk with + * start_bit. + */ +static int oa_tc6_update_rx_skb(struct oa_tc6 *tc6, u8 *payload, u8 length) { + if (!tc6->rx_skb || + skb_tailroom(tc6->rx_skb) < length) { + oa_tc6_look_for_new_frame(tc6); + return -EAGAIN; + } + memcpy(skb_put(tc6->rx_skb, length), payload, length); + return 0; } +/* On overwhelming rx buffer overflow errors, due to data chunk loss, it is + * possible that we get two data chunks with start_valid bit set, without + * end_valid bit set in between. In this case, rx_skb would have a valid + * buffer pointer. We should release, if a valid pointer is found before + * allocating a new one. + */ static int oa_tc6_allocate_rx_skb(struct oa_tc6 *tc6) { + oa_tc6_cleanup_ongoing_rx_skb(tc6); tc6->rx_skb = netdev_alloc_skb_ip_align(tc6->netdev, tc6->netdev->mtu + ETH_HLEN + ETH_FCS_LEN); if (!tc6->rx_skb) { @@ -902,7 +940,9 @@ static int oa_tc6_prcs_complete_rx_frame(struct oa_tc6 *tc6, u8 *payload, if (ret) return ret; - oa_tc6_update_rx_skb(tc6, payload, size); + ret = oa_tc6_update_rx_skb(tc6, payload, size); + if (ret) + return ret; oa_tc6_submit_rx_skb(tc6); @@ -917,22 +957,24 @@ static int oa_tc6_prcs_rx_frame_start(struct oa_tc6 *tc6, u8 *payload, u16 size) if (ret) return ret; - oa_tc6_update_rx_skb(tc6, payload, size); - - return 0; + return oa_tc6_update_rx_skb(tc6, payload, size); } -static void oa_tc6_prcs_rx_frame_end(struct oa_tc6 *tc6, u8 *payload, u16 size) +static int oa_tc6_prcs_rx_frame_end(struct oa_tc6 *tc6, u8 *payload, u16 size) { - oa_tc6_update_rx_skb(tc6, payload, size); + int ret; - oa_tc6_submit_rx_skb(tc6); + ret = oa_tc6_update_rx_skb(tc6, payload, size); + if (!ret) + oa_tc6_submit_rx_skb(tc6); + return ret; } -static void oa_tc6_prcs_ongoing_rx_frame(struct oa_tc6 *tc6, u8 *payload, - u32 footer) +static int oa_tc6_prcs_ongoing_rx_frame(struct oa_tc6 *tc6, u8 *payload, + u32 footer) { - oa_tc6_update_rx_skb(tc6, payload, OA_TC6_CHUNK_PAYLOAD_SIZE); + return oa_tc6_update_rx_skb(tc6, payload, + OA_TC6_CHUNK_PAYLOAD_SIZE); } static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data, @@ -947,10 +989,10 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data, u16 size; /* Restart the new rx frame after receiving rx buffer overflow error */ - if (start_valid && tc6->rx_buf_overflow) - tc6->rx_buf_overflow = false; + if (start_valid && tc6->wait_until_start_valid) + tc6->wait_until_start_valid = false; - if (tc6->rx_buf_overflow) + if (tc6->wait_until_start_valid) return 0; /* Process the chunk with complete rx frame */ @@ -972,8 +1014,7 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data, /* Process the chunk with only rx frame end */ if (end_valid && !start_valid) { size = end_byte_offset + 1; - oa_tc6_prcs_rx_frame_end(tc6, data, size); - return 0; + return oa_tc6_prcs_rx_frame_end(tc6, data, size); } /* Process the chunk with previous rx frame end and next rx frame @@ -987,6 +1028,15 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data, if (tc6->rx_skb) { size = end_byte_offset + 1; oa_tc6_prcs_rx_frame_end(tc6, data, size); + + /* Return value from oa_tc6_prcs_rx_frame_end is not + * checked. If it returned an error, it is to make + * the code to look for new frame. At this stage, + * code below is going to process a new frame. So, + * error condition is set to false, in case it is + * set before proceeding. + */ + tc6->wait_until_start_valid = false; } size = OA_TC6_CHUNK_PAYLOAD_SIZE - start_byte_offset; return oa_tc6_prcs_rx_frame_start(tc6, @@ -995,9 +1045,7 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data, } /* Process the chunk with ongoing rx frame data */ - oa_tc6_prcs_ongoing_rx_frame(tc6, data, footer); - - return 0; + return oa_tc6_prcs_ongoing_rx_frame(tc6, data, footer); } static u32 oa_tc6_get_rx_chunk_footer(struct oa_tc6 *tc6, u16 footer_offset) @@ -1013,8 +1061,9 @@ static u32 oa_tc6_get_rx_chunk_footer(struct oa_tc6 *tc6, u16 footer_offset) static int oa_tc6_process_spi_data_rx_buf(struct oa_tc6 *tc6, u16 length) { u16 no_of_rx_chunks = length / OA_TC6_CHUNK_SIZE; + bool retry = false; + int ret = 0; u32 footer; - int ret; /* All the rx chunks in the receive SPI data buffer are examined here */ for (int i = 0; i < no_of_rx_chunks; i++) { @@ -1023,8 +1072,11 @@ static int oa_tc6_process_spi_data_rx_buf(struct oa_tc6 *tc6, u16 length) OA_TC6_CHUNK_PAYLOAD_SIZE); ret = oa_tc6_process_rx_chunk_footer(tc6, footer); - if (ret) - return ret; + if (ret) { + if (ret != -EAGAIN) + return ret; + retry = true; + } /* If there is a data valid chunks then process it for the * information needed to determine the validity and the location @@ -1036,12 +1088,35 @@ static int oa_tc6_process_spi_data_rx_buf(struct oa_tc6 *tc6, u16 length) ret = oa_tc6_prcs_rx_chunk_payload(tc6, payload, footer); - if (ret) - return ret; + if (ret) { + if (ret != -ENOMEM && ret != -EAGAIN) + return ret; + retry = true; + } } } - return 0; + /* Not bailing out on recoverable error codes, -EAGAIN and + * -ENOMEM. If subsequent loop iterations, if any, succeeds, + * error code would be overwritten. retry flag helps to + * make the caller to continue and retry. Since recovery + * action for -ENOMEM and -EAGAIN are same, we are returning + * one of the error codes, that is -EAGAIN. + * + * Successful recovery depends on how small the frames are, + * how many chunks, among the received chunks triggered the + * error, whether data is intact even with error conditions. + * As a result, there is no single, best method to recover + * most data when error conditions hit. We do our best by + * processing all the chunks with good "footer header" and + * "data valid" bit set. + */ + if (retry) { + ret = -EAGAIN; + oa_tc6_look_for_new_frame(tc6); + } + + return ret; } static __be32 oa_tc6_prepare_data_header(bool data_valid, bool start_valid, @@ -1203,10 +1278,8 @@ static int oa_tc6_try_spi_transfer(struct oa_tc6 *tc6) } ret = oa_tc6_process_spi_data_rx_buf(tc6, spi_len); - if (ret) { - if (ret == -EAGAIN) - continue; + if (ret && ret != -EAGAIN) { oa_tc6_free_ongoing_skbs(tc6); netdev_err(tc6->netdev, "Device error: %d\n", ret); return ret; From 349c366365876b7f67120827a0deb44899f59303 Mon Sep 17 00:00:00 2001 From: Selvamani Rajagopal Date: Mon, 24 Aug 2026 14:58:00 -0700 Subject: [PATCH 585/641] net: ethernet: oa_tc6: Disable tx queues on fatal error Previously, TX queue interface was stopped when disable_traffic flag was set, which would indicate fatal error. It is more appropriate to disable the queue as, unless driver is unloaded and reloaded, there is no recovery after disable_traffic is set. Queues may be re-enabled inadvertently by other layers. Intention of disable_traffic is only to stop the traffic from flowing on fatal error. Fixes: b542d13fab0f ("net: ethernet: oa_tc6: Interrupt is active low, level triggered.") Signed-off-by: Selvamani Rajagopal Link: https://patch.msgid.link/20260824-fix-race-condition-and-crash-v7-3-4323279b18f2@onsemi.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/oa_tc6.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c index 657b1c6119da..eea00b41fb8d 100644 --- a/drivers/net/ethernet/oa_tc6.c +++ b/drivers/net/ethernet/oa_tc6.c @@ -771,6 +771,10 @@ static void oa_tc6_disable_traffic(struct oa_tc6 *tc6) skb = oa_tc6_detach_waiting_tx_skb(tc6); spin_unlock_bh(&tc6->tx_skb_lock); + /* disable_traffic, when set, is a point of no return to + * working state. Keeping the TX queues disabled. + */ + netif_tx_disable(tc6->netdev); oa_tc6_drop_tx_skb(tc6, skb); oa_tc6_free_ongoing_skbs(tc6); oa_tc6_write_register(tc6, OA_TC6_REG_INT_MASK0, regval); From 3cc2aa96b97184abd6fc106aac626ddf14389813 Mon Sep 17 00:00:00 2001 From: Selvamani Rajagopal Date: Mon, 24 Aug 2026 14:58:01 -0700 Subject: [PATCH 586/641] net: ethernet: oa_tc6: Fix for the wrong data type Inadvertently bool data type is used where int is supposed to be used. This might turn a negative error code into true or false and sign of the return code would be lost. Fixes: 8f9bf857e43b ("net: ethernet: oa_tc6: implement internal PHY initialization") Signed-off-by: Selvamani Rajagopal Link: https://patch.msgid.link/20260824-fix-race-condition-and-crash-v7-4-4323279b18f2@onsemi.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/oa_tc6.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c index eea00b41fb8d..6fcc5f561d56 100644 --- a/drivers/net/ethernet/oa_tc6.c +++ b/drivers/net/ethernet/oa_tc6.c @@ -455,7 +455,7 @@ static int oa_tc6_mdiobus_read(struct mii_bus *bus, int addr, int regnum) { struct oa_tc6 *tc6 = bus->priv; u32 regval; - bool ret; + int ret; ret = oa_tc6_read_register(tc6, OA_TC6_PHY_STD_REG_ADDR_BASE | (regnum & OA_TC6_PHY_STD_REG_ADDR_MASK), From d7e7e98d23f42a92d9ab7e36302bd96bd9b33b5f Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Tue, 25 Aug 2026 04:10:51 -0400 Subject: [PATCH 587/641] net/sched: cls_u32: fix duplicate handle when node ID pool is exhausted MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gen_new_kid() falls back to returning max (htid | 0xFFF) when both idr_alloc_u32() ranges are full, instead of reporting an error. u32_change() trusts that value and inserts a new knode with a handle that is already live in the hash table, breaking handle uniqueness within the table's node ID space. The handle was never reserved in ht->handle_idr, so every later error path that does idr_remove(&ht->handle_idr, handle) removes the reservation of a different, live knode, which is then reused — one failed add compounds into further duplicates. The 4095 limit is per (table, bucket) — ht->handle_idr is per hash table and the range is derived from htid (bucketid), so a table with divisor 256 can legitimately hold 256*4095 knodes. The sibling helper gen_new_htid() has the same silent in-band failure: it returns 0 when the tp_c handle pool (1..0x7FF) is full, and u32_init() publishes the root hash table with handle 0 without checking. Two root tables with handle 0 alias in u32_lookup_ht(), allowing cross-tcf_proto knode add/lookup/delete. Add the same exhaustion check that the divisor path already has. Return an error so u32_change() fails with ENOSPC/ENOMEM when the node ID space is exhausted, and so u32_init() fails with -ENOMEM when the hash table ID space is exhausted. The extack message distinguishes pool exhaustion (-ENOSPC) from a transient allocation failure (-ENOMEM). Conditions to recreate the bug: - CONFIG_NET_SCHED=y, CONFIG_CLS_U32=y (or =m with module loaded) - Create a clsact qdisc on a device, then add 4095 u32 filters with auto-generated handles to fill the node ID space for the root hash table (single bucket). The 4096th auto-handle filter add triggers the duplicate handle (fh 800::fff reused). Reachable at Level 2 (unshare -Urn, namespace-local CAP_NET_ADMIN). - For gen_new_htid: create 2047 u32 proto entries on the same block to fill the tp_c handle pool, then create one more. The root table gets handle 0 and aliases with other handle-0 root tables. Fixes: 7801db8aec95 ("net_sched: avoid generating same handle for u32 filters") Reported-by: vega@nebusec.ai Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/20260825081052.133898-1-jhs@mojatatu.com Signed-off-by: Jakub Kicinski --- net/sched/cls_u32.c | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/net/sched/cls_u32.c b/net/sched/cls_u32.c index ac6d0fa5a40e..a3e65c8cf29e 100644 --- a/net/sched/cls_u32.c +++ b/net/sched/cls_u32.c @@ -370,6 +370,10 @@ static int u32_init(struct tcf_proto *tp) refcount_set(&root_ht->refcnt, 1); root_ht->handle = tp_c ? gen_new_htid(tp_c, root_ht) : id2handle(0); + if (root_ht->handle == 0) { + kfree(root_ht); + return -ENOMEM; + } root_ht->prio = tp->prio; root_ht->is_root = true; idr_init(&root_ht->handle_idr); @@ -695,21 +699,33 @@ out: return ret; } -static u32 gen_new_kid(struct tc_u_hnode *ht, u32 htid) +static u32 gen_new_kid(struct tc_u_hnode *ht, u32 htid, int *err) { u32 index = htid | 0x800; u32 max = htid | 0xFFF; + *err = 0; + if (idr_alloc_u32(&ht->handle_idr, NULL, &index, max, GFP_KERNEL)) { index = htid + 1; - if (idr_alloc_u32(&ht->handle_idr, NULL, &index, max, - GFP_KERNEL)) - index = max; + *err = idr_alloc_u32(&ht->handle_idr, NULL, &index, max, + GFP_KERNEL); + if (*err) + return 0; } return index; } +static int u32_kid_extack(int err, struct netlink_ext_ack *extack) +{ + if (err == -ENOSPC) + NL_SET_ERR_MSG_MOD(extack, "Hash table node ID pool exhausted"); + else + NL_SET_ERR_MSG_MOD(extack, "Failed to allocate node ID"); + return err; +} + static const struct nla_policy u32_policy[TCA_U32_MAX + 1] = { [TCA_U32_CLASSID] = { .type = NLA_U32 }, [TCA_U32_HASH] = { .type = NLA_U32 }, @@ -1079,7 +1095,9 @@ static int u32_change(struct net *net, struct sk_buff *in_skb, * handle which is used to uniquely identify the match entry. */ if (!TC_U32_NODE(handle)) { - handle = gen_new_kid(ht, htid); + handle = gen_new_kid(ht, htid, &err); + if (err) + return u32_kid_extack(err, extack); } else { handle = htid | TC_U32_NODE(handle); err = idr_alloc_u32(&ht->handle_idr, NULL, &handle, @@ -1091,7 +1109,9 @@ static int u32_change(struct net *net, struct sk_buff *in_skb, /* The user did not give us a handle; lets just generate one * from the table's pool of nodeids. */ - handle = gen_new_kid(ht, htid); + handle = gen_new_kid(ht, htid, &err); + if (err) + return u32_kid_extack(err, extack); } if (tb[TCA_U32_SEL] == NULL) { From 7b120a771943ffc3cbce787daecdd23eccb0505f Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Tue, 25 Aug 2026 04:10:52 -0400 Subject: [PATCH 588/641] selftests: tc-testing: add u32 node ID pool exhaustion test Add a tdc test case that fills the u32 node ID space with 4095 auto-generated handles, then attempts to add a 4096th. On the fixed kernel the 4096th filter is rejected with ENOSPC (exit 2). On the unfixed kernel it silently succeeds with a duplicate handle. The setup pipes the 4095 add commands directly into `tc -b -` inside a single bash -c (matching the existing test id 1234 pattern), avoiding any temp file. Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/20260825081052.133898-2-jhs@mojatatu.com Signed-off-by: Jakub Kicinski --- .../tc-testing/tc-tests/filters/u32.json | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tools/testing/selftests/tc-testing/tc-tests/filters/u32.json b/tools/testing/selftests/tc-testing/tc-tests/filters/u32.json index b2ca9d4e991b..e2b03f2b5e89 100644 --- a/tools/testing/selftests/tc-testing/tc-tests/filters/u32.json +++ b/tools/testing/selftests/tc-testing/tc-tests/filters/u32.json @@ -353,5 +353,28 @@ "teardown": [ "$TC qdisc del dev $DEV1 parent root drr" ] + }, + { + "id": "70fd", + "name": "Add u32 filter when node ID pool is exhausted (4096th filter rejected)", + "category": [ + "filter", + "u32" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DUMMY clsact", + "bash -c 'for i in {1..4095}; do echo filter add dev $DUMMY ingress prio 1 protocol ip u32 match u8 0 0 at 0; done | $TC -b -'" + ], + "cmdUnderTest": "$TC filter add dev $DUMMY ingress prio 1 protocol ip u32 match u8 0 0 at 0", + "expExitCode": "2", + "verifyCmd": "$TC -d filter show dev $DUMMY ingress", + "matchPattern": "fh 800::", + "matchCount": "4095", + "teardown": [ + "$TC qdisc del dev $DUMMY clsact" + ] } ] From dc0df5a0c62ccea1d0e08d39a4dc9064de81d411 Mon Sep 17 00:00:00 2001 From: Florian Schauer Date: Fri, 28 Aug 2026 08:08:22 +0200 Subject: [PATCH 589/641] page_pool: keep frag_offset aligned for odd-sized requests page_pool_alloc_frag_netmem() rounds the requested fragment size with size = ALIGN(size, dma_get_cache_alignment()); dma_get_cache_alignment() returns 1 unless the architecture defines ARCH_DMA_MINALIGN, which DMA-coherent architectures such as x86 do not. There the ALIGN() is a no-op and pool->frag_offset advances by the raw, unrounded size. A single caller asking for an odd size then leaves frag_offset misaligned for every fragment carved out of that page afterwards. The pool is shared, so the damage is not confined to the caller that caused it. The per-cpu system_page_pool used by generic XDP hits this. skb_pp_cow_data() allocates its fragments with the raw packet length: size = min_t(u32, len, PAGE_SIZE); truesize = size; page = page_pool_dev_alloc(pool, &page_off, &truesize); leaving frag_offset odd for whatever is carved out of that page next. Its own head allocation is already aligned -- SKB_HEAD_ALIGN(size) plus the XDP_PACKET_HEADROOM its callers pass -- so it is a later user of the shared pool that pays: page_pool_dev_alloc_va() returns a misaligned buffer, napi_build_skb() installs it as skb->head, and skb_shinfo(skb) == skb->head + skb->end is misaligned with it. skb_shinfo()->dataref is a 4-byte atomic_t at offset 0x20, so the atomic_inc() in __skb_clone() straddles a cache line. On x86 with split lock detection -- fatal for kernel split locks by default -- this panics the machine: Oops: Split lock detected RIP: 0010:skb_clone+0x154/0x1e0 Call Trace: raw_local_deliver+0x1ed/0x2c0 ip_protocol_deliver_rcu+0x54/0x1c0 ip_local_deliver_finish+0x85/0x100 ip_local_deliver+0x67/0x100 __netif_receive_skb_one_core+0x85/0xa0 process_backlog+0x87/0x130 Reproduced by attaching any generic-mode XDP program to loopback and opening a RAW IPPROTO_UDP socket, which makes raw_local_deliver() clone every locally delivered UDP packet; ordinary DNS traffic then triggers it, roughly once per 2500 clones. Observed on 6.12.101 and 7.1.8. Tracing page_pool_alloc_frag_netmem() over one such run shows the amplification -- two odd-sized requests, nine misaligned offsets: requested size & 7: 0: 17035 5: 1 7: 1 frag_offset & 7: 0: 17028 3: 1 4: 1 5: 1 6: 1 7: 5 and skb_pp_cow_data() returning heads that were aligned on entry: head 0xffff8f4c86aeac00 -> 0xffff8f4c53a9a9c4 (&7=4) head 0xffff8f4d6a8a42c0 -> 0xffff8f4c4f7b7a45 (&7=5) Round the fragment size up to at least the alignment struct skb_shared_info requires, so fragments are always suitably aligned for the objects callers build on them. Architectures needing a larger DMA alignment keep it. This also makes the remainder computed in page_pool_alloc_netmem(), *size = max_size - *offset; aligned, since max_size is a power of two -- which fixes the matching misalignment of skb->end. Verified with a controlled A/B under QEMU/KVM: same tree, same config, same compiler, same rootfs and identical traffic, differing only by this patch. A SEC("xdp.frags") XDP_PASS program on lo plus UDP datagrams larger than max_head_size drives skb_pp_cow_data()'s fragment loop, which passes raw packet lengths to the pool. Measured at the return of skb_pp_cow_data(): unpatched patched skb_pp_cow_data calls 40800 40800 misaligned skb->head 1120 0 dataref at line offset >60 80 0 The last row counts the accesses that actually fault: skb_shinfo()->dataref sits at head+end+0x20 and is a 4-byte atomic, so `lock incl` splits a 64-byte cache line only when that address lands at offset 61..63. All 80 occurrences were at offset 61; the panic reported above was at offset 62. Eliminating the misalignment removes every one of them. Same class of bug as commit 3bed3cc4156e ("net: Do not allocate page fragments that are not skb aligned"), which fixed the older netdev_alloc_frag()/napi_alloc_frag() allocators. Fixes: 53e0961da1c7 ("page_pool: add frag page recycling support in page pool") Cc: stable@vger.kernel.org Signed-off-by: Florian Schauer Acked-by: Jesper Dangaard Brouer Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260828060822.2628276-1-florian@schauer.to Signed-off-by: Jakub Kicinski --- net/core/page_pool.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/core/page_pool.c b/net/core/page_pool.c index 8f8956fb061b..08d7f35cf608 100644 --- a/net/core/page_pool.c +++ b/net/core/page_pool.c @@ -1073,7 +1073,8 @@ netmem_ref page_pool_alloc_frag_netmem(struct page_pool *pool, if (WARN_ON(size > max_size)) return 0; - size = ALIGN(size, dma_get_cache_alignment()); + size = ALIGN(size, max_t(unsigned int, dma_get_cache_alignment(), + __alignof__(struct skb_shared_info))); *offset = pool->frag_offset; if (netmem && *offset + size > max_size) { From dee44f41f206becb41c492899c1996cfd7f82a1b Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Wed, 26 Aug 2026 09:39:27 +0900 Subject: [PATCH 590/641] vsock/virtio: validate packet source for connected sockets virtio_transport_recv_pkt() looks up sockets first by the full source and destination tuple, then by destination only in the bound table. The fallback is needed for listening and connecting sockets, but sockets remain in the bound table after connect(), so it can also return a non-listening socket. The fallback does not validate the source address. In TCP_SYN_SENT, a RESPONSE from an unrelated source can transition the victim socket to TCP_ESTABLISHED while its stored remote address remains unchanged. Subsequent RW packets from that source are delivered through the same destination-only fallback. This was reproduced with capability-empty processes under different UIDs. The attacker discovered the target tuple through unprivileged AF_VSOCK sock_diag and caused the victim socket to read 16 attacker-chosen bytes; the intended peer-side socket read 0 of those 16 bytes. Add vsock_check_source() to validate the transport, source port and source CID against the peer stored in a non-listening socket. The local transport is the CID exception because its packets are generated internally with VMADDR_CID_LOCAL as their source, including connections using CID aliases. Use the helper after lock_sock() in the virtio receive path. Fixes: 06a8fc78367d ("VSOCK: Introduce virtio_vsock_common.ko") Closes: https://lore.kernel.org/netdev/20260813121236.2328599-1-4ncienth@gmail.com/ Cc: stable@vger.kernel.org Suggested-by: Stefano Garzarella Reviewed-by: Bobby Eshleman Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Link: https://patch.msgid.link/20260826003929.966160-2-4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- include/net/af_vsock.h | 3 +++ net/vmw_vsock/af_vsock.c | 32 +++++++++++++++++++++++++ net/vmw_vsock/virtio_transport_common.c | 3 ++- 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/include/net/af_vsock.h b/include/net/af_vsock.h index 3357ee62d10b..5549298c1ec6 100644 --- a/include/net/af_vsock.h +++ b/include/net/af_vsock.h @@ -229,6 +229,9 @@ struct sock *vsock_find_bound_socket_net(struct sockaddr_vm *addr, struct sock *vsock_find_connected_socket_net(struct sockaddr_vm *src, struct sockaddr_vm *dst, struct net *net); +bool vsock_check_source(const struct vsock_sock *vsk, + const struct vsock_transport *transport, + const struct sockaddr_vm *src); void vsock_remove_sock(struct vsock_sock *vsk); void vsock_for_each_connected_socket(struct vsock_transport *transport, void (*fn)(struct sock *sk)); diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c index a33b2a2d381d..f840498b58af 100644 --- a/net/vmw_vsock/af_vsock.c +++ b/net/vmw_vsock/af_vsock.c @@ -438,6 +438,38 @@ struct sock *vsock_find_connected_socket(struct sockaddr_vm *src, } EXPORT_SYMBOL_GPL(vsock_find_connected_socket); +/** + * vsock_check_source - validate a packet source against a socket peer + * @vsk: socket receiving the packet + * @transport: transport receiving the packet + * @src: source address from the packet + * + * Return: true if the packet arrived on the socket's assigned transport and + * its source matches the stored peer. Loopback packets are generated + * internally and always use the local CID as their source, including + * connections using a valid CID alias. + * + * The caller must hold the socket lock and must not call this for listening + * sockets, which accept packets from any source and have no assigned + * transport. + */ +bool vsock_check_source(const struct vsock_sock *vsk, + const struct vsock_transport *transport, + const struct sockaddr_vm *src) +{ + if (vsk->transport != transport) + return false; + + if (src->svm_port != vsk->remote_addr.svm_port) + return false; + + if (src->svm_cid == vsk->remote_addr.svm_cid) + return true; + + return transport->get_local_cid() == VMADDR_CID_LOCAL; +} +EXPORT_SYMBOL_GPL(vsock_check_source); + void vsock_remove_sock(struct vsock_sock *vsk) { /* Transport reassignment must not remove the binding. */ diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c index 88df82364f77..f225f53ed4ba 100644 --- a/net/vmw_vsock/virtio_transport_common.c +++ b/net/vmw_vsock/virtio_transport_common.c @@ -1836,7 +1836,8 @@ void virtio_transport_recv_pkt(struct virtio_transport *t, * lock_sock (note: listener sockets are not assigned to any transport) */ if (sock_flag(sk, SOCK_DONE) || - (sk->sk_state != TCP_LISTEN && vsk->transport != &t->transport)) { + (sk->sk_state != TCP_LISTEN && + !vsock_check_source(vsk, &t->transport, &src))) { (void)virtio_transport_reset_no_sock(t, skb, net); release_sock(sk); sock_put(sk); From ad9a7da3fa39c2d616ec0dd3cf6e30531d032fe7 Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Wed, 26 Aug 2026 09:39:28 +0900 Subject: [PATCH 591/641] vsock/vmci: validate packet source for connected sockets vmci_transport_recv_stream_cb() looks up sockets first by the full source and destination tuple, then by destination only in the bound table. The fallback can select a non-listening socket without checking whether the packet came from its stored peer. This was reproduced with two VMCI contexts. A RST from the context not stored in a TCP_SYN_SENT socket reset that socket after it was selected by the destination-only lookup. VMCI can process notification packets in bottom-half context when the socket is not owned by user context, or defer packets to a workqueue. Use vsock_check_source() after taking the socket lock in the bottom-half path, and recheck after lock_sock() in the workqueue path. Listening sockets continue to accept packets from any source. Reply with a RST addressed from the received packet before dropping a source that fails validation. This preserves the existing reset behavior for bound non-listening and concurrently closed sockets without directing the reset to a connected socket's stored peer. Fixes: d021c344051a ("VSOCK: Introduce VM Sockets") Reported-by: Sashiko Closes: https://lore.kernel.org/netdev/20260814121255.6B5001F000E9@smtp.kernel.org/ Cc: stable@vger.kernel.org Suggested-by: Stefano Garzarella Suggested-by: Paolo Abeni Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Reviewed-by: Vishnu Dasa Link: https://patch.msgid.link/20260826003929.966160-3-4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- net/vmw_vsock/vmci_transport.c | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/net/vmw_vsock/vmci_transport.c b/net/vmw_vsock/vmci_transport.c index 1c4ee039c166..1f186e8f8364 100644 --- a/net/vmw_vsock/vmci_transport.c +++ b/net/vmw_vsock/vmci_transport.c @@ -680,11 +680,13 @@ static int vmci_transport_recv_stream_cb(void *data, struct vmci_datagram *dg) struct vmci_transport_packet *pkt; struct vsock_sock *vsk; bool bh_process_pkt; + bool drop_pkt; int err; sk = NULL; err = VMCI_SUCCESS; bh_process_pkt = false; + drop_pkt = false; /* Ignore incoming packets from resources that aren't vsock * implementations. @@ -765,17 +767,29 @@ static int vmci_transport_recv_stream_cb(void *data, struct vmci_datagram *dg) bh_lock_sock(sk); if (!sock_owned_by_user(sk)) { - /* The local context ID may be out of date, update it. */ - vsk->local_addr.svm_cid = dst.svm_cid; + if (sk->sk_state != TCP_LISTEN && + !vsock_check_source(vsk, &vmci_transport, &src)) { + drop_pkt = true; + err = VMCI_ERROR_NO_ACCESS; + } else { + /* The local context ID may be out of date, update it. */ + vsk->local_addr.svm_cid = dst.svm_cid; - if (sk->sk_state == TCP_ESTABLISHED) - vmci_trans(vsk)->notify_ops->handle_notify_pkt( - sk, pkt, true, &dst, &src, - &bh_process_pkt); + if (sk->sk_state == TCP_ESTABLISHED) + vmci_trans(vsk)->notify_ops->handle_notify_pkt(sk, pkt, true, + &dst, &src, + &bh_process_pkt); + } } bh_unlock_sock(sk); + if (drop_pkt) { + if (vmci_transport_send_reset_bh(&dst, &src, pkt) < 0) + pr_err("unable to send reset\n"); + goto out; + } + if (!bh_process_pkt) { struct vmci_transport_recv_pkt_info *recv_pkt_info; @@ -900,6 +914,7 @@ static void vmci_transport_recv_pkt_work(struct work_struct *work) { struct vmci_transport_recv_pkt_info *recv_pkt_info; struct vmci_transport_packet *pkt; + struct sockaddr_vm src; struct sock *sk; recv_pkt_info = @@ -908,6 +923,12 @@ static void vmci_transport_recv_pkt_work(struct work_struct *work) pkt = &recv_pkt_info->pkt; lock_sock(sk); + vsock_addr_init(&src, pkt->dg.src.context, pkt->src_port); + if (sk->sk_state != TCP_LISTEN && + !vsock_check_source(vsock_sk(sk), &vmci_transport, &src)) { + vmci_transport_reply_reset(pkt); + goto out; + } /* The local context ID may be out of date. */ vsock_sk(sk)->local_addr.svm_cid = pkt->dg.dst.context; @@ -937,6 +958,7 @@ static void vmci_transport_recv_pkt_work(struct work_struct *work) break; } +out: release_sock(sk); kfree(recv_pkt_info); /* Release reference obtained in the stream callback when we fetched From fee10655709c5c597e8e9f722f3035d9ea31ff3a Mon Sep 17 00:00:00 2001 From: Aohan Mei Date: Wed, 26 Aug 2026 10:51:20 +0800 Subject: [PATCH 592/641] net/sched: cls_flower: validate mask pointer after nla_next() fl_set_enc_opt() iterates the key's nested tunnel-option attributes with nla_for_each_attr() while advancing a single mask pointer via nla_next() at the bottom of each loop, so the mask cursor is driven by the number of key attributes rather than by the mask's own attributes. The nla_ok() added by commit c96adff956191 ("cls_flower: call nla_ok() before nla_next()") only validates the mask pointer that was just consumed; the pointer produced by nla_next() is used by the next iteration (fl_set_geneve_opt() and siblings) without any validation. The mask's nested attributes are validated with NL_VALIDATE_LIBERAL, which merely warns on trailing bytes that do not form a complete attribute. A mask carrying one valid attribute plus 1-3 residue bytes (or a non-aligned attribute length making msk_depth negative) therefore reaches the next iteration with msk_depth != 0, so neither the !msk_depth check in fl_set_enc_opt() nor the !depth check in the per-type helpers fires. nla_type() then reads past the mask payload and nla_parse_nested_deprecated() iterates with an nla_len taken from those bytes, reading well beyond the mask attribute (KASAN: slab-out-of-bounds read in __nla_validate_parse from fl_change()). Validate the advanced mask pointer as well: when the mask is not legitimately exhausted (msk_depth != 0) and the new pointer fails nla_ok(), reject the filter with -EINVAL. An exactly exhausted mask still skips the check, preserving exact-match behaviour for the remaining key attributes. Fixes: c96adff95619 ("cls_flower: call nla_ok() before nla_next()") Reported-by: TencentOS Corvus AI Cc: stable@vger.kernel.org Signed-off-by: Aohan Mei Link: https://patch.msgid.link/20260826025123.62758-1-ljp1205831794@gmail.com Signed-off-by: Jakub Kicinski --- net/sched/cls_flower.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/sched/cls_flower.c b/net/sched/cls_flower.c index 0e275b58151c..1cefea571efd 100644 --- a/net/sched/cls_flower.c +++ b/net/sched/cls_flower.c @@ -1703,6 +1703,11 @@ static int fl_set_enc_opt(struct nlattr **tb, struct fl_flow_key *key, return -EINVAL; } nla_opt_msk = nla_next(nla_opt_msk, &msk_depth); + + if (msk_depth && !nla_ok(nla_opt_msk, msk_depth)) { + NL_SET_ERR_MSG(extack, "A mask attribute is invalid"); + return -EINVAL; + } } return 0; From 2987ee196c88dbde0463dc87d5fb209c684e34a2 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 27 Aug 2026 16:06:56 +0000 Subject: [PATCH 593/641] igmp: convert struct ip_sf_list to RCU Commit 23d2b94043ca ("igmp: Add ip_mc_list lock in ip_check_mc_rcu") added spin_lock_bh(&im->lock) to ip_check_mc_rcu() to prevent a use-after-free while iterating im->sources during concurrent deletions. However, ip_check_mc_rcu() is called from RCU read-side critical sections in packet receive and route lookup fast paths (e.g. __mkroute_output(), ip_route_input_rcu(), and __udp4_lib_rcv()). When igmpv3_send_cr() or igmpv3_send_report() holds &pmc->lock and calls add_grec() -> igmpv3_newpack() -> ip_route_output_ports(), an XFRM policy matching a multicast destination triggers xfrm_tmpl_resolve_one() -> xfrm4_get_saddr() -> __mkroute_output() -> ip_check_mc_rcu(). This attempts to acquire &im->lock while &pmc->lock is already held on the same CPU, triggering a lockdep recursive locking warning / deadlock. Fix this by converting IPv4 struct ip_sf_list to RCU, mirroring the IPv6 implementation in net/ipv6/mcast.c: 1. Add struct rcu_head to struct ip_sf_list and annotate sf_next, sources, and tomb as __rcu pointers. 2. Use rcu_assign_pointer() and kfree_rcu() for list updates and deletions. 3. Remove spin_lock_bh(&im->lock) from ip_check_mc_rcu() and traverse im->sources locklessly with for_each_psf_rcu(), reading and writing counter fields with READ_ONCE() and WRITE_ONCE(). Note: RCU conversion of /proc/net/mcfilter will be done in a separate patch. Fixes: 23d2b94043ca ("igmp: Add ip_mc_list lock in ip_check_mc_rcu") Reported-by: syzbot+3d99fb01bcd740f2fc1e@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=3d99fb01bcd740f2fc1e Signed-off-by: Eric Dumazet Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260827160656.903003-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- include/linux/igmp.h | 7 +- net/ipv4/igmp.c | 210 +++++++++++++++++++++++++++---------------- 2 files changed, 135 insertions(+), 82 deletions(-) diff --git a/include/linux/igmp.h b/include/linux/igmp.h index 3a2d35a9f307..a0cf0398519f 100644 --- a/include/linux/igmp.h +++ b/include/linux/igmp.h @@ -57,20 +57,21 @@ struct ip_mc_socklist { }; struct ip_sf_list { - struct ip_sf_list *sf_next; + struct ip_sf_list __rcu *sf_next; unsigned long sf_count[2]; /* include/exclude counts */ __be32 sf_inaddr; unsigned char sf_gsresp; /* include in g & s response? */ unsigned char sf_oldin; /* change state */ unsigned char sf_crcount; /* retrans. left to send */ + struct rcu_head rcu; }; struct ip_mc_list { struct in_device *interface; __be32 multiaddr; unsigned int sfmode; - struct ip_sf_list *sources; - struct ip_sf_list *tomb; + struct ip_sf_list __rcu *sources; + struct ip_sf_list __rcu *tomb; unsigned long sfcount[2]; union { struct ip_mc_list *next; diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c index b80b8a92f46e..d56355aca797 100644 --- a/net/ipv4/igmp.c +++ b/net/ipv4/igmp.c @@ -188,6 +188,10 @@ static void ip_ma_put(struct ip_mc_list *im) } } +#define pmc_dereference(e, pmc) \ + rcu_dereference_protected(e, lockdep_is_held(&(pmc)->lock) || \ + lockdep_is_held(&(pmc)->interface->mc_tomb_lock)) + #define for_each_pmc_rcu(in_dev, pmc) \ for (pmc = rcu_dereference(in_dev->mc_list); \ pmc != NULL; \ @@ -198,13 +202,28 @@ static void ip_ma_put(struct ip_mc_list *im) pmc != NULL; \ pmc = rtnl_dereference(pmc->next_rcu)) +#define for_each_psf_mclock(pmc, psf) \ + for (psf = pmc_dereference((pmc)->sources, pmc); \ + psf; \ + psf = pmc_dereference(psf->sf_next, pmc)) + +#define for_each_psf_rcu(im, psf) \ + for (psf = rcu_dereference((im)->sources); \ + psf; \ + psf = rcu_dereference(psf->sf_next)) + +#define for_each_psf_tomb(pmc, psf) \ + for (psf = pmc_dereference((pmc)->tomb, pmc); \ + psf; \ + psf = pmc_dereference(psf->sf_next, pmc)) + static void ip_sf_list_clear_all(struct ip_sf_list *psf) { struct ip_sf_list *next; while (psf) { - next = psf->sf_next; - kfree(psf); + next = rcu_dereference_protected(psf->sf_next, 1); + kfree_rcu(psf, rcu); psf = next; } } @@ -349,7 +368,7 @@ igmp_scount(struct ip_mc_list *pmc, int type, int gdeleted, int sdeleted) struct ip_sf_list *psf; int scount = 0; - for (psf = pmc->sources; psf; psf = psf->sf_next) { + for_each_psf_mclock(pmc, psf) { if (!is_in(pmc, psf, type, gdeleted, sdeleted)) continue; scount++; @@ -494,7 +513,8 @@ static struct sk_buff *add_grec(struct sk_buff *skb, struct ip_mc_list *pmc, struct net *net = dev_net(dev); struct igmpv3_report *pih; struct igmpv3_grec *pgr = NULL; - struct ip_sf_list *psf, *psf_next, *psf_prev, **psf_list; + struct ip_sf_list *psf, *psf_next, *psf_prev; + struct ip_sf_list __rcu **psf_list; int scount, stotal, first, isquery, truncate; unsigned int mtu; @@ -517,7 +537,7 @@ static struct sk_buff *add_grec(struct sk_buff *skb, struct ip_mc_list *pmc, psf_list = sdeleted ? &pmc->tomb : &pmc->sources; - if (!*psf_list) + if (!rcu_access_pointer(*psf_list)) goto empty_source; pih = skb ? igmpv3_report_hdr(skb) : NULL; @@ -533,10 +553,12 @@ static struct sk_buff *add_grec(struct sk_buff *skb, struct ip_mc_list *pmc, } first = 1; psf_prev = NULL; - for (psf = *psf_list; psf; psf = psf_next) { + for (psf = pmc_dereference(*psf_list, pmc); + psf; + psf = psf_next) { __be32 *psrc; - psf_next = psf->sf_next; + psf_next = pmc_dereference(psf->sf_next, pmc); if (!is_in(pmc, psf, type, gdeleted, sdeleted)) { psf_prev = psf; @@ -583,10 +605,12 @@ decrease_sf_crcount: psf->sf_crcount--; if ((sdeleted || gdeleted) && psf->sf_crcount == 0) { if (psf_prev) - psf_prev->sf_next = psf->sf_next; + rcu_assign_pointer(psf_prev->sf_next, + psf_next); else - *psf_list = psf->sf_next; - kfree(psf); + rcu_assign_pointer(*psf_list, + psf_next); + kfree_rcu(psf, rcu); continue; } } @@ -655,28 +679,29 @@ static int igmpv3_send_report(struct in_device *in_dev, struct ip_mc_list *pmc) /* * remove zero-count source records from a source filter list */ -static void igmpv3_clear_zeros(struct ip_sf_list **ppsf) +static void igmpv3_clear_zeros(struct ip_sf_list __rcu **ppsf) { struct ip_sf_list *psf_prev, *psf_next, *psf; psf_prev = NULL; - for (psf = *ppsf; psf; psf = psf_next) { - psf_next = psf->sf_next; + for (psf = rcu_dereference_protected(*ppsf, 1); psf; psf = psf_next) { + psf_next = rcu_dereference_protected(psf->sf_next, 1); if (psf->sf_crcount == 0) { if (psf_prev) - psf_prev->sf_next = psf->sf_next; + rcu_assign_pointer(psf_prev->sf_next, psf_next); else - *ppsf = psf->sf_next; - kfree(psf); - } else + rcu_assign_pointer(*ppsf, psf_next); + kfree_rcu(psf, rcu); + } else { psf_prev = psf; + } } } static void kfree_pmc(struct ip_mc_list *pmc) { - ip_sf_list_clear_all(pmc->sources); - ip_sf_list_clear_all(pmc->tomb); + ip_sf_list_clear_all(rcu_dereference_protected(pmc->sources, 1)); + ip_sf_list_clear_all(rcu_dereference_protected(pmc->tomb, 1)); kfree(pmc); } @@ -710,7 +735,8 @@ static void igmpv3_send_cr(struct in_device *in_dev) igmpv3_clear_zeros(&pmc->sources); } } - if (pmc->crcount == 0 && !pmc->tomb && !pmc->sources) { + if (pmc->crcount == 0 && !rcu_access_pointer(pmc->tomb) && + !rcu_access_pointer(pmc->sources)) { if (pmc_prev) pmc_prev->next = pmc_next; else @@ -896,7 +922,7 @@ static int igmp_xmarksources(struct ip_mc_list *pmc, int nsrcs, __be32 *srcs) int i, scount; scount = 0; - for (psf = pmc->sources; psf; psf = psf->sf_next) { + for_each_psf_mclock(pmc, psf) { if (scount == nsrcs) break; for (i = 0; i < nsrcs; i++) { @@ -927,7 +953,7 @@ static int igmp_marksources(struct ip_mc_list *pmc, int nsrcs, __be32 *srcs) /* mark INCLUDE-mode sources */ scount = 0; - for (psf = pmc->sources; psf; psf = psf->sf_next) { + for_each_psf_mclock(pmc, psf) { if (scount == nsrcs) break; for (i = 0; i < nsrcs; i++) @@ -1228,11 +1254,12 @@ static void igmpv3_add_delrec(struct in_device *in_dev, struct ip_mc_list *im, if (pmc->sfmode == MCAST_INCLUDE) { struct ip_sf_list *psf; + for_each_psf_mclock(im, psf) + psf->sf_crcount = pmc->crcount; pmc->tomb = im->tomb; pmc->sources = im->sources; - im->tomb = im->sources = NULL; - for (psf = pmc->sources; psf; psf = psf->sf_next) - psf->sf_crcount = pmc->crcount; + RCU_INIT_POINTER(im->tomb, NULL); + RCU_INIT_POINTER(im->sources, NULL); } spin_unlock_bh(&im->lock); @@ -1271,9 +1298,18 @@ static void igmpv3_del_delrec(struct in_device *in_dev, struct ip_mc_list *im) if (pmc) { im->interface = pmc->interface; if (im->sfmode == MCAST_INCLUDE) { - swap(im->tomb, pmc->tomb); - swap(im->sources, pmc->sources); - for (psf = im->sources; psf; psf = psf->sf_next) + struct ip_sf_list *sources, *tomb; + + tomb = rcu_replace_pointer(im->tomb, + rcu_dereference_protected(pmc->tomb, 1), + lockdep_is_held(&im->lock)); + rcu_assign_pointer(pmc->tomb, tomb); + + sources = rcu_replace_pointer(im->sources, + rcu_dereference_protected(pmc->sources, 1), + lockdep_is_held(&im->lock)); + rcu_assign_pointer(pmc->sources, sources); + for_each_psf_mclock(im, psf) psf->sf_crcount = in_dev->mr_qrv ?: READ_ONCE(net->ipv4.sysctl_igmp_qrv); } else { @@ -1310,8 +1346,8 @@ static void igmpv3_clear_delrec(struct in_device *in_dev) struct ip_sf_list *psf; spin_lock_bh(&pmc->lock); - psf = pmc->tomb; - pmc->tomb = NULL; + psf = pmc_dereference(pmc->tomb, pmc); + RCU_INIT_POINTER(pmc->tomb, NULL); spin_unlock_bh(&pmc->lock); ip_sf_list_clear_all(psf); } @@ -1990,7 +2026,7 @@ static int ip_mc_del1_src(struct ip_mc_list *pmc, int sfmode, int rv = 0; psf_prev = NULL; - for (psf = pmc->sources; psf; psf = psf->sf_next) { + for_each_psf_mclock(pmc, psf) { if (psf->sf_inaddr == *psfsrc) break; psf_prev = psf; @@ -1999,7 +2035,7 @@ static int ip_mc_del1_src(struct ip_mc_list *pmc, int sfmode, /* source filter not found, or count wrong => bug */ return -ESRCH; } - psf->sf_count[sfmode]--; + WRITE_ONCE(psf->sf_count[sfmode], psf->sf_count[sfmode] - 1); if (psf->sf_count[sfmode] == 0) { ip_rt_multicast_event(pmc->interface); } @@ -2011,19 +2047,28 @@ static int ip_mc_del1_src(struct ip_mc_list *pmc, int sfmode, /* no more filters for this source */ if (psf_prev) - psf_prev->sf_next = psf->sf_next; + rcu_assign_pointer(psf_prev->sf_next, + pmc_dereference(psf->sf_next, pmc)); else - pmc->sources = psf->sf_next; + rcu_assign_pointer(pmc->sources, + pmc_dereference(psf->sf_next, pmc)); #ifdef CONFIG_IP_MULTICAST if (psf->sf_oldin && !IGMP_V1_SEEN(in_dev) && !IGMP_V2_SEEN(in_dev)) { - psf->sf_crcount = in_dev->mr_qrv ?: READ_ONCE(net->ipv4.sysctl_igmp_qrv); - psf->sf_next = pmc->tomb; - pmc->tomb = psf; - rv = 1; - } else + struct ip_sf_list *dpsf = kmalloc_obj(*dpsf, GFP_ATOMIC); + + if (dpsf) { + *dpsf = *psf; + dpsf->sf_crcount = in_dev->mr_qrv ?: + READ_ONCE(net->ipv4.sysctl_igmp_qrv); + rcu_assign_pointer(dpsf->sf_next, + pmc_dereference(pmc->tomb, pmc)); + rcu_assign_pointer(pmc->tomb, dpsf); + rv = 1; + } + } #endif - kfree(psf); + kfree_rcu(psf, rcu); } return rv; } @@ -2060,7 +2105,7 @@ static int ip_mc_del_src(struct in_device *in_dev, __be32 *pmca, int sfmode, err = -EINVAL; if (!pmc->sfcount[sfmode]) goto out_unlock; - pmc->sfcount[sfmode]--; + WRITE_ONCE(pmc->sfcount[sfmode], pmc->sfcount[sfmode] - 1); } err = 0; for (i = 0; i < sfcount; i++) { @@ -2083,7 +2128,7 @@ static int ip_mc_del_src(struct in_device *in_dev, __be32 *pmca, int sfmode, #ifdef CONFIG_IP_MULTICAST pmc->crcount = in_dev->mr_qrv ?: READ_ONCE(net->ipv4.sysctl_igmp_qrv); WRITE_ONCE(in_dev->mr_ifc_count, pmc->crcount); - for (psf = pmc->sources; psf; psf = psf->sf_next) + for_each_psf_mclock(pmc, psf) psf->sf_crcount = 0; igmp_ifc_event(pmc->interface); } else if (sf_setstate(pmc) || changerec) { @@ -2104,7 +2149,7 @@ static int ip_mc_add1_src(struct ip_mc_list *pmc, int sfmode, struct ip_sf_list *psf, *psf_prev; psf_prev = NULL; - for (psf = pmc->sources; psf; psf = psf->sf_next) { + for_each_psf_mclock(pmc, psf) { if (psf->sf_inaddr == *psfsrc) break; psf_prev = psf; @@ -2114,12 +2159,12 @@ static int ip_mc_add1_src(struct ip_mc_list *pmc, int sfmode, if (!psf) return -ENOBUFS; psf->sf_inaddr = *psfsrc; - if (psf_prev) { - psf_prev->sf_next = psf; - } else - pmc->sources = psf; + if (psf_prev) + rcu_assign_pointer(psf_prev->sf_next, psf); + else + rcu_assign_pointer(pmc->sources, psf); } - psf->sf_count[sfmode]++; + WRITE_ONCE(psf->sf_count[sfmode], psf->sf_count[sfmode] + 1); if (psf->sf_count[sfmode] == 1) { ip_rt_multicast_event(pmc->interface); } @@ -2132,13 +2177,15 @@ static void sf_markstate(struct ip_mc_list *pmc) struct ip_sf_list *psf; int mca_xcount = pmc->sfcount[MCAST_EXCLUDE]; - for (psf = pmc->sources; psf; psf = psf->sf_next) + for_each_psf_mclock(pmc, psf) { if (pmc->sfcount[MCAST_EXCLUDE]) { psf->sf_oldin = mca_xcount == psf->sf_count[MCAST_EXCLUDE] && !psf->sf_count[MCAST_INCLUDE]; - } else + } else { psf->sf_oldin = psf->sf_count[MCAST_INCLUDE] != 0; + } + } } static int sf_setstate(struct ip_mc_list *pmc) @@ -2149,27 +2196,31 @@ static int sf_setstate(struct ip_mc_list *pmc) int new_in, rv; rv = 0; - for (psf = pmc->sources; psf; psf = psf->sf_next) { + for_each_psf_mclock(pmc, psf) { if (pmc->sfcount[MCAST_EXCLUDE]) { new_in = mca_xcount == psf->sf_count[MCAST_EXCLUDE] && !psf->sf_count[MCAST_INCLUDE]; - } else + } else { new_in = psf->sf_count[MCAST_INCLUDE] != 0; + } if (new_in) { if (!psf->sf_oldin) { struct ip_sf_list *prev = NULL; - for (dpsf = pmc->tomb; dpsf; dpsf = dpsf->sf_next) { + for_each_psf_tomb(pmc, dpsf) { if (dpsf->sf_inaddr == psf->sf_inaddr) break; prev = dpsf; } if (dpsf) { + struct ip_sf_list *dpsf_next; + + dpsf_next = pmc_dereference(dpsf->sf_next, pmc); if (prev) - prev->sf_next = dpsf->sf_next; + rcu_assign_pointer(prev->sf_next, dpsf_next); else - pmc->tomb = dpsf->sf_next; - kfree(dpsf); + rcu_assign_pointer(pmc->tomb, dpsf_next); + kfree_rcu(dpsf, rcu); } psf->sf_crcount = qrv; rv++; @@ -2181,17 +2232,19 @@ static int sf_setstate(struct ip_mc_list *pmc) * add or update "delete" records if an active filter * is now inactive */ - for (dpsf = pmc->tomb; dpsf; dpsf = dpsf->sf_next) + for_each_psf_tomb(pmc, dpsf) { if (dpsf->sf_inaddr == psf->sf_inaddr) break; + } if (!dpsf) { dpsf = kmalloc_obj(*dpsf, GFP_ATOMIC); if (!dpsf) continue; *dpsf = *psf; /* pmc->lock held by callers */ - dpsf->sf_next = pmc->tomb; - pmc->tomb = dpsf; + rcu_assign_pointer(dpsf->sf_next, + pmc_dereference(pmc->tomb, pmc)); + rcu_assign_pointer(pmc->tomb, dpsf); } dpsf->sf_crcount = qrv; rv++; @@ -2231,7 +2284,7 @@ static int ip_mc_add_src(struct in_device *in_dev, __be32 *pmca, int sfmode, #endif isexclude = pmc->sfmode == MCAST_EXCLUDE; if (!delta) - pmc->sfcount[sfmode]++; + WRITE_ONCE(pmc->sfcount[sfmode], pmc->sfcount[sfmode] + 1); err = 0; for (i = 0; i < sfcount; i++) { err = ip_mc_add1_src(pmc, sfmode, &psfsrc[i]); @@ -2242,7 +2295,7 @@ static int ip_mc_add_src(struct in_device *in_dev, __be32 *pmca, int sfmode, int j; if (!delta) - pmc->sfcount[sfmode]--; + WRITE_ONCE(pmc->sfcount[sfmode], pmc->sfcount[sfmode] - 1); for (j = 0; j < i; j++) (void) ip_mc_del1_src(pmc, sfmode, &psfsrc[j]); } else if (isexclude != (pmc->sfcount[MCAST_EXCLUDE] != 0)) { @@ -2262,7 +2315,7 @@ static int ip_mc_add_src(struct in_device *in_dev, __be32 *pmca, int sfmode, pmc->crcount = in_dev->mr_qrv ?: READ_ONCE(net->ipv4.sysctl_igmp_qrv); WRITE_ONCE(in_dev->mr_ifc_count, pmc->crcount); - for (psf = pmc->sources; psf; psf = psf->sf_next) + for_each_psf_mclock(pmc, psf) psf->sf_crcount = 0; igmp_ifc_event(in_dev); } else if (sf_setstate(pmc)) { @@ -2278,13 +2331,13 @@ static void ip_mc_clear_src(struct ip_mc_list *pmc) struct ip_sf_list *tomb, *sources; spin_lock_bh(&pmc->lock); - tomb = pmc->tomb; - pmc->tomb = NULL; - sources = pmc->sources; - pmc->sources = NULL; + tomb = pmc_dereference(pmc->tomb, pmc); + RCU_INIT_POINTER(pmc->tomb, NULL); + sources = pmc_dereference(pmc->sources, pmc); + RCU_INIT_POINTER(pmc->sources, NULL); pmc->sfmode = MCAST_EXCLUDE; - pmc->sfcount[MCAST_INCLUDE] = 0; - pmc->sfcount[MCAST_EXCLUDE] = 1; + WRITE_ONCE(pmc->sfcount[MCAST_INCLUDE], 0); + WRITE_ONCE(pmc->sfcount[MCAST_EXCLUDE], 1); spin_unlock_bh(&pmc->lock); ip_sf_list_clear_all(tomb); @@ -2866,20 +2919,19 @@ int ip_check_mc_rcu(struct in_device *in_dev, __be32 mc_addr, __be32 src_addr, u rv = 1; } else if (im) { if (src_addr) { - spin_lock_bh(&im->lock); - for (psf = im->sources; psf; psf = psf->sf_next) { + for_each_psf_rcu(im, psf) { if (psf->sf_inaddr == src_addr) break; } if (psf) - rv = psf->sf_count[MCAST_INCLUDE] || - psf->sf_count[MCAST_EXCLUDE] != - im->sfcount[MCAST_EXCLUDE]; + rv = READ_ONCE(psf->sf_count[MCAST_INCLUDE]) || + READ_ONCE(psf->sf_count[MCAST_EXCLUDE]) != + READ_ONCE(im->sfcount[MCAST_EXCLUDE]); else - rv = im->sfcount[MCAST_EXCLUDE] != 0; - spin_unlock_bh(&im->lock); - } else + rv = READ_ONCE(im->sfcount[MCAST_EXCLUDE]) != 0; + } else { rv = 1; /* unspecified source; tentatively allow */ + } } return rv; } @@ -3043,7 +3095,7 @@ static inline struct ip_sf_list *igmp_mcf_get_first(struct seq_file *seq) im = rcu_dereference(idev->mc_list); if (likely(im)) { spin_lock_bh(&im->lock); - psf = im->sources; + psf = pmc_dereference(im->sources, im); if (likely(psf)) { state->im = im; state->idev = idev; @@ -3059,7 +3111,7 @@ static struct ip_sf_list *igmp_mcf_get_next(struct seq_file *seq, struct ip_sf_l { struct igmp_mcf_iter_state *state = igmp_mcf_seq_private(seq); - psf = psf->sf_next; + psf = pmc_dereference(psf->sf_next, state->im); while (!psf) { spin_unlock_bh(&state->im->lock); state->im = state->im->next; @@ -3075,7 +3127,7 @@ static struct ip_sf_list *igmp_mcf_get_next(struct seq_file *seq, struct ip_sf_l state->im = rcu_dereference(state->idev->mc_list); } spin_lock_bh(&state->im->lock); - psf = state->im->sources; + psf = pmc_dereference(state->im->sources, state->im); } out: return psf; From 9feb069e5ed03582fbf6272539f1caa2a17dc6d5 Mon Sep 17 00:00:00 2001 From: Qingfang Deng Date: Fri, 28 Aug 2026 15:32:36 +0800 Subject: [PATCH 594/641] ppp: ppp_async: simplify tty disc_data access tty_ldisc_hangup() invokes the hangup callback while holding only a read lock on tty->ldisc_sem, so it can run concurrently with other line discipline callbacks. This currently forces async PPP to maintain separate lifetime protection around tty->disc_data. Line discipline close is called under the write lock during hangup processing. Remove the hangup callback and rely on close for teardown, as done for SLIP by commit 23c53269f2ba ("slip: remove slip_hangup() to fix use-after-free in slip_receive_buf()"). This serializes teardown with all other line discipline operations. disc_data_lock, refcount and completion are redundant with that serialization. Remove them and access tty->disc_data directly. This also eliminates a lockdep warning reported by syzbot. The warning does not indicate a real deadlock because the write side runs only in process context with hardirqs disabled. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: syzbot+8e808eb853386f575d86@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/0000000000002fbad30611e25849@google.com/ Signed-off-by: Qingfang Deng Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260828073245.126804-1-qingfang.deng@linux.dev Signed-off-by: Jakub Kicinski --- drivers/net/ppp/ppp_async.c | 82 ++++--------------------------------- 1 file changed, 7 insertions(+), 75 deletions(-) diff --git a/drivers/net/ppp/ppp_async.c b/drivers/net/ppp/ppp_async.c index 583426d06381..ea7fe9608ffd 100644 --- a/drivers/net/ppp/ppp_async.c +++ b/drivers/net/ppp/ppp_async.c @@ -63,8 +63,6 @@ struct asyncppp { struct tasklet_struct tsk; - refcount_t refcnt; - struct completion dead; struct ppp_channel chan; /* interface to generic ppp layer */ unsigned char obuf[OBUFSIZE]; }; @@ -114,38 +112,6 @@ static const struct ppp_channel_ops async_ops = { * Routines implementing the PPP line discipline. */ -/* - * We have a potential race on dereferencing tty->disc_data, - * because the tty layer provides no locking at all - thus one - * cpu could be running ppp_asynctty_receive while another - * calls ppp_asynctty_close, which zeroes tty->disc_data and - * frees the memory that ppp_asynctty_receive is using. The best - * way to fix this is to use a rwlock in the tty struct, but for now - * we use a single global rwlock for all ttys in ppp line discipline. - * - * FIXME: this is no longer true. The _close path for the ldisc is - * now guaranteed to be sane. - */ -static DEFINE_RWLOCK(disc_data_lock); - -static struct asyncppp *ap_get(struct tty_struct *tty) -{ - struct asyncppp *ap; - - read_lock(&disc_data_lock); - ap = tty->disc_data; - if (ap != NULL) - refcount_inc(&ap->refcnt); - read_unlock(&disc_data_lock); - return ap; -} - -static void ap_put(struct asyncppp *ap) -{ - if (refcount_dec_and_test(&ap->refcnt)) - complete(&ap->dead); -} - /* * Called when a tty is put into PPP line discipline. Called in process * context. @@ -180,9 +146,6 @@ ppp_asynctty_open(struct tty_struct *tty) skb_queue_head_init(&ap->rqueue); tasklet_setup(&ap->tsk, ppp_async_process); - refcount_set(&ap->refcnt, 1); - init_completion(&ap->dead); - ap->chan.private = ap; ap->chan.ops = &async_ops; ap->chan.mtu = PPP_MRU; @@ -203,34 +166,18 @@ ppp_asynctty_open(struct tty_struct *tty) } /* - * Called when the tty is put into another line discipline - * or it hangs up. We have to wait for any cpu currently - * executing in any of the other ppp_asynctty_* routines to - * finish before we can call ppp_unregister_channel and free - * the asyncppp struct. This routine must be called from - * process context, not interrupt or softirq context. + * Called when the tty is put into another line discipline or it hangs up. + * This call is serialized against other ldisc functions. */ static void ppp_asynctty_close(struct tty_struct *tty) { - struct asyncppp *ap; + struct asyncppp *ap = tty->disc_data; - write_lock_irq(&disc_data_lock); - ap = tty->disc_data; - tty->disc_data = NULL; - write_unlock_irq(&disc_data_lock); if (!ap) return; - /* - * We have now ensured that nobody can start using ap from now - * on, but we have to wait for all existing users to finish. - * Note that ppp_unregister_channel ensures that no calls to - * our channel ops (i.e. ppp_async_send/ioctl) are in progress - * by the time it returns. - */ - if (!refcount_dec_and_test(&ap->refcnt)) - wait_for_completion(&ap->dead); + tty->disc_data = NULL; tasklet_kill(&ap->tsk); ppp_unregister_channel(&ap->chan); @@ -240,17 +187,6 @@ ppp_asynctty_close(struct tty_struct *tty) kfree(ap); } -/* - * Called on tty hangup in process context. - * - * Wait for I/O to driver to complete and unregister PPP channel. - * This is already done by the close routine, so just call that. - */ -static void ppp_asynctty_hangup(struct tty_struct *tty) -{ - ppp_asynctty_close(tty); -} - /* * Read does nothing - no data is ever available this way. * Pppd reads and writes packets via /dev/ppp instead. @@ -281,7 +217,7 @@ ppp_asynctty_write(struct tty_struct *tty, struct file *file, const u8 *buf, static int ppp_asynctty_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg) { - struct asyncppp *ap = ap_get(tty); + struct asyncppp *ap = tty->disc_data; int err, val; int __user *p = (int __user *)arg; @@ -322,7 +258,6 @@ ppp_asynctty_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg) err = tty_mode_ioctl(tty, cmd, arg); } - ap_put(ap); return err; } @@ -331,7 +266,7 @@ static void ppp_asynctty_receive(struct tty_struct *tty, const u8 *buf, const u8 *cflags, size_t count) { - struct asyncppp *ap = ap_get(tty); + struct asyncppp *ap = tty->disc_data; unsigned long flags; if (!ap) @@ -341,21 +276,19 @@ ppp_asynctty_receive(struct tty_struct *tty, const u8 *buf, const u8 *cflags, spin_unlock_irqrestore(&ap->recv_lock, flags); if (!skb_queue_empty(&ap->rqueue)) tasklet_schedule(&ap->tsk); - ap_put(ap); tty_unthrottle(tty); } static void ppp_asynctty_wakeup(struct tty_struct *tty) { - struct asyncppp *ap = ap_get(tty); + struct asyncppp *ap = tty->disc_data; clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); if (!ap) return; set_bit(XMIT_WAKEUP, &ap->xmit_flags); tasklet_schedule(&ap->tsk); - ap_put(ap); } @@ -365,7 +298,6 @@ static struct tty_ldisc_ops ppp_ldisc = { .name = "ppp", .open = ppp_asynctty_open, .close = ppp_asynctty_close, - .hangup = ppp_asynctty_hangup, .read = ppp_asynctty_read, .write = ppp_asynctty_write, .ioctl = ppp_asynctty_ioctl, From d8d4d1cf40d541a5d7cc3b15d57e42d0815c7d53 Mon Sep 17 00:00:00 2001 From: Qingfang Deng Date: Fri, 28 Aug 2026 15:32:37 +0800 Subject: [PATCH 595/641] ppp: ppp_synctty: simplify tty disc_data access Apply the same simplification as the preceding ppp_async change. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: syzbot+b503105c2410c3433459@syzkaller.appspotmail.com Closes: https://syzbot.org/bug?extid=b503105c2410c3433459 Signed-off-by: Qingfang Deng Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260828073245.126804-2-qingfang.deng@linux.dev Signed-off-by: Jakub Kicinski --- drivers/net/ppp/ppp_synctty.c | 83 +++-------------------------------- 1 file changed, 7 insertions(+), 76 deletions(-) diff --git a/drivers/net/ppp/ppp_synctty.c b/drivers/net/ppp/ppp_synctty.c index 0b1bd1635c39..f87d43faeeab 100644 --- a/drivers/net/ppp/ppp_synctty.c +++ b/drivers/net/ppp/ppp_synctty.c @@ -38,11 +38,9 @@ #include #include #include -#include #include #include #include -#include #include #include @@ -67,8 +65,6 @@ struct syncppp { struct tasklet_struct tsk; - refcount_t refcnt; - struct completion dead_cmp; struct ppp_channel chan; /* interface to generic ppp layer */ }; @@ -116,37 +112,6 @@ ppp_print_buffer (const char *name, const __u8 *buf, int count) * Routines implementing the synchronous PPP line discipline. */ -/* - * We have a potential race on dereferencing tty->disc_data, - * because the tty layer provides no locking at all - thus one - * cpu could be running ppp_synctty_receive while another - * calls ppp_synctty_close, which zeroes tty->disc_data and - * frees the memory that ppp_synctty_receive is using. The best - * way to fix this is to use a rwlock in the tty struct, but for now - * we use a single global rwlock for all ttys in ppp line discipline. - * - * FIXME: Fixed in tty_io nowadays. - */ -static DEFINE_RWLOCK(disc_data_lock); - -static struct syncppp *sp_get(struct tty_struct *tty) -{ - struct syncppp *ap; - - read_lock(&disc_data_lock); - ap = tty->disc_data; - if (ap != NULL) - refcount_inc(&ap->refcnt); - read_unlock(&disc_data_lock); - return ap; -} - -static void sp_put(struct syncppp *ap) -{ - if (refcount_dec_and_test(&ap->refcnt)) - complete(&ap->dead_cmp); -} - /* * Called when a tty is put into sync-PPP line discipline. */ @@ -177,9 +142,6 @@ ppp_sync_open(struct tty_struct *tty) skb_queue_head_init(&ap->rqueue); tasklet_setup(&ap->tsk, ppp_sync_process); - refcount_set(&ap->refcnt, 1); - init_completion(&ap->dead_cmp); - ap->chan.private = ap; ap->chan.ops = &sync_ops; ap->chan.mtu = PPP_MRU; @@ -201,34 +163,18 @@ ppp_sync_open(struct tty_struct *tty) } /* - * Called when the tty is put into another line discipline - * or it hangs up. We have to wait for any cpu currently - * executing in any of the other ppp_synctty_* routines to - * finish before we can call ppp_unregister_channel and free - * the syncppp struct. This routine must be called from - * process context, not interrupt or softirq context. + * Called when the tty is put into another line discipline or it hangs up. + * This call is serialized against other ldisc functions. */ static void ppp_sync_close(struct tty_struct *tty) { - struct syncppp *ap; + struct syncppp *ap = tty->disc_data; - write_lock_irq(&disc_data_lock); - ap = tty->disc_data; - tty->disc_data = NULL; - write_unlock_irq(&disc_data_lock); if (!ap) return; - /* - * We have now ensured that nobody can start using ap from now - * on, but we have to wait for all existing users to finish. - * Note that ppp_unregister_channel ensures that no calls to - * our channel ops (i.e. ppp_sync_send/ioctl) are in progress - * by the time it returns. - */ - if (!refcount_dec_and_test(&ap->refcnt)) - wait_for_completion(&ap->dead_cmp); + tty->disc_data = NULL; tasklet_kill(&ap->tsk); ppp_unregister_channel(&ap->chan); @@ -237,17 +183,6 @@ ppp_sync_close(struct tty_struct *tty) kfree(ap); } -/* - * Called on tty hangup in process context. - * - * Wait for I/O to driver to complete and unregister PPP channel. - * This is already done by the close routine, so just call that. - */ -static void ppp_sync_hangup(struct tty_struct *tty) -{ - ppp_sync_close(tty); -} - /* * Read does nothing - no data is ever available this way. * Pppd reads and writes packets via /dev/ppp instead. @@ -273,7 +208,7 @@ ppp_sync_write(struct tty_struct *tty, struct file *file, const u8 *buf, static int ppp_synctty_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg) { - struct syncppp *ap = sp_get(tty); + struct syncppp *ap = tty->disc_data; int __user *p = (int __user *)arg; int err, val; @@ -314,7 +249,6 @@ ppp_synctty_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg) break; } - sp_put(ap); return err; } @@ -323,7 +257,7 @@ static void ppp_sync_receive(struct tty_struct *tty, const u8 *buf, const u8 *cflags, size_t count) { - struct syncppp *ap = sp_get(tty); + struct syncppp *ap = tty->disc_data; unsigned long flags; if (!ap) @@ -333,21 +267,19 @@ ppp_sync_receive(struct tty_struct *tty, const u8 *buf, const u8 *cflags, spin_unlock_irqrestore(&ap->recv_lock, flags); if (!skb_queue_empty(&ap->rqueue)) tasklet_schedule(&ap->tsk); - sp_put(ap); tty_unthrottle(tty); } static void ppp_sync_wakeup(struct tty_struct *tty) { - struct syncppp *ap = sp_get(tty); + struct syncppp *ap = tty->disc_data; clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); if (!ap) return; set_bit(XMIT_WAKEUP, &ap->xmit_flags); tasklet_schedule(&ap->tsk); - sp_put(ap); } @@ -357,7 +289,6 @@ static struct tty_ldisc_ops ppp_sync_ldisc = { .name = "pppsync", .open = ppp_sync_open, .close = ppp_sync_close, - .hangup = ppp_sync_hangup, .read = ppp_sync_read, .write = ppp_sync_write, .ioctl = ppp_synctty_ioctl, From 93b49239840b91313adbd77b8b52993eff2d08c1 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Fri, 28 Aug 2026 08:45:27 +0000 Subject: [PATCH 596/641] ipv6: mcast: fix RCU list diversion in ip6_mc_del1_src() When removing a source filter whose count reaches zero, ip6_mc_del1_src() unlinks psf from pmc->mca_sources. If the filter was previously active, the code moved psf directly into pmc->mca_tomb by updating psf->sf_next. Because pmc->mca_sources is traversed locklessly under RCU (e.g. by ipv6_chk_mcast_addr()), mutating psf->sf_next before a grace period elapses diverts concurrent readers to the tombstone list. Consequently, readers miss remaining active sources in pmc->mca_sources and improperly examine deleted tombstone entries. Fix this by allocating a new tombstone node for pmc->mca_tomb (as done in sf_setstate()) and retiring the original psf via kfree_rcu(). Fixes: 4b200e398953 ("mld: convert ip6_sf_list to RCU") Signed-off-by: Eric Dumazet Cc: Taehee Yoo Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260828084531.1826790-2-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/ipv6/mcast.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c index aaba4c2aae23..ec7fac511c8d 100644 --- a/net/ipv6/mcast.c +++ b/net/ipv6/mcast.c @@ -2351,14 +2351,18 @@ static int ip6_mc_del1_src(struct ifmcaddr6 *pmc, int sfmode, if (psf->sf_oldin && !(pmc->mca_flags & MAF_NOREPORT) && !mld_in_v1_mode(idev)) { - psf->sf_crcount = idev->mc_qrv; - rcu_assign_pointer(psf->sf_next, - mc_dereference(pmc->mca_tomb, idev)); - rcu_assign_pointer(pmc->mca_tomb, psf); - rv = 1; - } else { - kfree_rcu(psf, rcu); + struct ip6_sf_list *dpsf = kmalloc_obj(*dpsf); + + if (dpsf) { + *dpsf = *psf; + dpsf->sf_crcount = idev->mc_qrv; + rcu_assign_pointer(dpsf->sf_next, + mc_dereference(pmc->mca_tomb, idev)); + rcu_assign_pointer(pmc->mca_tomb, dpsf); + rv = 1; + } } + kfree_rcu(psf, rcu); } return rv; } From c073d1b070f171d206b19c98d71739a97f15b3f1 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Fri, 28 Aug 2026 08:45:28 +0000 Subject: [PATCH 597/641] ipv6: mcast: use copy-on-write RCU updates in ip6_mc_source() pmc->sflist is read locklessly under rcu_read_lock() by inet6_mc_check() during packet reception in the UDP and RAW multicast receive paths. ip6_mc_source() mutated psl->sl_addr and psl->sl_count in-place when adding or removing a source filter. Additionally, when expanding the filter buffer, newpsl was published via rcu_assign_pointer() before writing the new source into the array. Because 16-byte struct in6_addr writes are not atomic and array shifting is not synchronized with RCU readers, concurrent readers in inet6_mc_check() could read torn IPv6 addresses or observe duplicated/missed source entries. Fix this by switching ip6_mc_source() to copy-on-write RCU updates: allocate and fully populate newpsl before publishing it via rcu_assign_pointer(), and reclaim the old filter via kfree_rcu(), matching ip6_mc_msfilter(). Also remove the now unused IP6_SFBLOCK macro. Fixes: 882ba1f73c06 ("mld: convert ipv6_mc_socklist->sflist to RCU") Signed-off-by: Eric Dumazet Cc: Taehee Yoo Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260828084531.1826790-3-edumazet@google.com Signed-off-by: Jakub Kicinski --- include/net/if_inet6.h | 2 - net/ipv6/mcast.c | 98 ++++++++++++++++++++++++------------------ 2 files changed, 56 insertions(+), 44 deletions(-) diff --git a/include/net/if_inet6.h b/include/net/if_inet6.h index 238ad3349456..795fb41b45f5 100644 --- a/include/net/if_inet6.h +++ b/include/net/if_inet6.h @@ -88,8 +88,6 @@ struct ip6_sf_socklist { struct in6_addr sl_addr[] __counted_by(sl_max); }; -#define IP6_SFBLOCK 10 /* allocate this many at once */ - struct ipv6_mc_socklist { struct in6_addr addr; int ifindex; diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c index ec7fac511c8d..66f5858e5fea 100644 --- a/net/ipv6/mcast.c +++ b/net/ipv6/mcast.c @@ -355,12 +355,12 @@ int ip6_mc_source(int add, int omode, struct sock *sk, { struct ipv6_pinfo *inet6 = inet6_sk(sk); struct in6_addr *source, *group; + struct ip6_sf_socklist *newpsl, *psl; struct net *net = sock_net(sk); struct ipv6_mc_socklist *pmc; - struct ip6_sf_socklist *psl; struct inet6_dev *idev; int leavegroup = 0; - int i, j, rv; + int i, j; int err; source = &((struct sockaddr_in6 *)&pgsr->gsr_source)->sin6_addr; @@ -409,13 +409,11 @@ int ip6_mc_source(int add, int omode, struct sock *sk, if (!add) { if (!psl) goto done; /* err = -EADDRNOTAVAIL */ - rv = !0; for (i = 0; i < psl->sl_count; i++) { - rv = !ipv6_addr_equal(&psl->sl_addr[i], source); - if (rv == 0) + if (ipv6_addr_equal(&psl->sl_addr[i], source)) break; } - if (rv) /* source not found */ + if (i == psl->sl_count) /* source not found */ goto done; /* err = -EADDRNOTAVAIL */ /* special case - (INCLUDE, empty) == LEAVE_GROUP */ @@ -424,58 +422,74 @@ int ip6_mc_source(int add, int omode, struct sock *sk, goto done; } + atomic_sub(struct_size(psl, sl_addr, psl->sl_max), + &sk->sk_omem_alloc); + + if (psl->sl_count == 1) { + newpsl = NULL; + } else { + newpsl = sock_kmalloc(sk, struct_size(newpsl, sl_addr, + psl->sl_count - 1), + GFP_KERNEL); + if (!newpsl) { + atomic_add(struct_size(psl, sl_addr, psl->sl_max), + &sk->sk_omem_alloc); + err = -ENOBUFS; + goto done; + } + newpsl->sl_max = psl->sl_count - 1; + newpsl->sl_count = psl->sl_count - 1; + for (j = 0; j < i; j++) + newpsl->sl_addr[j] = psl->sl_addr[j]; + for (j = i + 1; j < psl->sl_count; j++) + newpsl->sl_addr[j - 1] = psl->sl_addr[j]; + } + /* update the interface filter */ ip6_mc_del_src(idev, group, omode, 1, source, 1); - for (j = i+1; j < psl->sl_count; j++) - psl->sl_addr[j-1] = psl->sl_addr[j]; - psl->sl_count--; + rcu_assign_pointer(pmc->sflist, newpsl); + kfree_rcu(psl, rcu); err = 0; goto done; } /* else, add a new source to the filter */ - if (psl && psl->sl_count >= sysctl_mld_max_msf) { + if (psl && psl->sl_count >= READ_ONCE(sysctl_mld_max_msf)) { err = -ENOBUFS; goto done; } - if (!psl || psl->sl_count == psl->sl_max) { - struct ip6_sf_socklist *newpsl; - int count = IP6_SFBLOCK; + if (psl) { + for (i = 0; i < psl->sl_count; i++) { + if (ipv6_addr_equal(&psl->sl_addr[i], source)) + goto done; /* err = -EADDRNOTAVAIL */ + } + } - if (psl) - count += psl->sl_max; - newpsl = sock_kmalloc(sk, struct_size(newpsl, sl_addr, count), - GFP_KERNEL); - if (!newpsl) { - err = -ENOBUFS; - goto done; - } - newpsl->sl_max = count; - newpsl->sl_count = count - IP6_SFBLOCK; - if (psl) { - for (i = 0; i < psl->sl_count; i++) - newpsl->sl_addr[i] = psl->sl_addr[i]; - atomic_sub(struct_size(psl, sl_addr, psl->sl_max), - &sk->sk_omem_alloc); - } - rcu_assign_pointer(pmc->sflist, newpsl); - kfree_rcu(psl, rcu); - psl = newpsl; + i = psl ? psl->sl_count + 1 : 1; + newpsl = sock_kmalloc(sk, struct_size(newpsl, sl_addr, i), + GFP_KERNEL); + if (!newpsl) { + err = -ENOBUFS; + goto done; } - rv = 1; /* > 0 for insert logic below if sl_count is 0 */ - for (i = 0; i < psl->sl_count; i++) { - rv = !ipv6_addr_equal(&psl->sl_addr[i], source); - if (rv == 0) /* There is an error in the address. */ - goto done; + newpsl->sl_max = i; + newpsl->sl_count = i; + if (psl) { + for (j = 0; j < psl->sl_count; j++) + newpsl->sl_addr[j] = psl->sl_addr[j]; } - for (j = psl->sl_count-1; j >= i; j--) - psl->sl_addr[j+1] = psl->sl_addr[j]; - psl->sl_addr[i] = *source; - psl->sl_count++; - err = 0; + newpsl->sl_addr[i - 1] = *source; + /* update the interface list */ ip6_mc_add_src(idev, group, omode, 1, source, 1); + + if (psl) + atomic_sub(struct_size(psl, sl_addr, psl->sl_max), + &sk->sk_omem_alloc); + rcu_assign_pointer(pmc->sflist, newpsl); + kfree_rcu(psl, rcu); + err = 0; done: mutex_unlock(&idev->mc_lock); in6_dev_put(idev); From 75fa9caeb8aaba19c2463dee0b0a1e09d39c04af Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Fri, 28 Aug 2026 08:45:29 +0000 Subject: [PATCH 598/641] ipv6: mcast: fix delay calculation in igmp6_join_group() When joining a multicast group, if a report work is already pending (e.g. scheduled by a query or a previous join), igmp6_join_group() cancels the delayed work and recalculates the delay: if (cancel_delayed_work(&ma->mca_work)) { refcount_dec(&ma->mca_refcnt); delay = ma->mca_work.timer.expires - jiffies; } Unlike igmp6_group_queried(), igmp6_join_group() did not check if delay >= interval. This leads to two issues: 1. If the timer has already expired (timer.expires <= jiffies), the stale expiry is reused by mod_delayed_work(), causing the second unsolicited report to fire on the very next tick without a randomized delay. 2. If the timer was originally armed by a query with a large maximum response delay, delay could exceed unsolicited_report_interval(ma->idev). Fix this by initializing delay to unsolicited_report_interval(ma->idev) and re-randomizing it with get_random_u32_below(interval) when delay >= interval, mirroring the logic in igmp6_group_queried(). Fixes: 2d9a93b4902b ("mld: convert from timer to delayed work") Signed-off-by: Eric Dumazet Cc: Taehee Yoo Reviewed-by: Ido Schimmel Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Link: https://patch.msgid.link/20260828084531.1826790-4-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/ipv6/mcast.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c index 66f5858e5fea..4423b90dc9ab 100644 --- a/net/ipv6/mcast.c +++ b/net/ipv6/mcast.c @@ -2639,7 +2639,7 @@ static void ip6_mc_clear_src(struct ifmcaddr6 *pmc) static void igmp6_join_group(struct ifmcaddr6 *ma) { - unsigned long delay; + unsigned long delay, interval; mc_assert_locked(ma->idev); @@ -2648,13 +2648,17 @@ static void igmp6_join_group(struct ifmcaddr6 *ma) igmp6_send(&ma->mca_addr, ma->idev->dev, ICMPV6_MGM_REPORT); - delay = get_random_u32_below(unsolicited_report_interval(ma->idev)); + interval = unsolicited_report_interval(ma->idev); + delay = interval; if (cancel_delayed_work(&ma->mca_work)) { refcount_dec(&ma->mca_refcnt); delay = ma->mca_work.timer.expires - jiffies; } + if (delay >= interval) + delay = get_random_u32_below(interval); + if (!mod_delayed_work(mld_wq, &ma->mca_work, delay)) refcount_inc(&ma->mca_refcnt); WRITE_ONCE(ma->mca_flags, ma->mca_flags | From 0c8f56c583c3250408367880c98e4d6fbc929315 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Fri, 28 Aug 2026 08:45:30 +0000 Subject: [PATCH 599/641] ipv6: mcast: use rcu_assign_pointer() for __rcu list updates Several places in net/ipv6/mcast.c update RCU-protected lists (np->ipv6_mc_list, idev->mc_list, idev->mc_tomb) using direct pointer assignments instead of rcu_assign_pointer(): 1. In __ipv6_dev_mc_dec(), unlinking a group from idev->mc_list did: *map = ma->next; without rcu_assign_pointer() while concurrent readers traverse idev->mc_list locklessly under rcu_read_lock(). 2. In ipv6_sock_mc_drop() and __ipv6_sock_mc_close(), unlinking a group from np->ipv6_mc_list directly assigned *lnk = mc_lst->next and np->ipv6_mc_list = mc_lst->next without rcu_assign_pointer(), racing with lockless readers in inet6_mc_check(). 3. In __ipv6_sock_mc_join(), mc_lst->next was initialized to np->ipv6_mc_list via raw assignment before publishing mc_lst. 4. In mld_del_delrec() and __ipv6_dev_mc_inc(), __rcu source pointers passed into rcu_assign_pointer() lacked explicit dereference helpers. Fix these by consistently using rcu_assign_pointer() along with mc_dereference() / sock_dereference(). Fixes: 456b61bca8ee ("ipv6: mcast: RCU conversion") Fixes: 88e2ca308094 ("mld: convert ifmcaddr6 to RCU") Signed-off-by: Eric Dumazet Cc: Taehee Yoo Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260828084531.1826790-5-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/ipv6/mcast.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c index 4423b90dc9ab..2290457eb8d3 100644 --- a/net/ipv6/mcast.c +++ b/net/ipv6/mcast.c @@ -240,7 +240,8 @@ static int __ipv6_sock_mc_join(struct sock *sk, int ifindex, return err; } - mc_lst->next = np->ipv6_mc_list; + rcu_assign_pointer(mc_lst->next, + sock_dereference(np->ipv6_mc_list, sk)); rcu_assign_pointer(np->ipv6_mc_list, mc_lst); return 0; @@ -300,7 +301,8 @@ int ipv6_sock_mc_drop(struct sock *sk, int ifindex, const struct in6_addr *addr) lnk = &mc_lst->next) { if ((ifindex == 0 || mc_lst->ifindex == ifindex) && ipv6_addr_equal(&mc_lst->addr, addr)) { - *lnk = mc_lst->next; + rcu_assign_pointer(*lnk, + sock_dereference(mc_lst->next, sk)); __ipv6_sock_mc_drop(sk, mc_lst); return 0; } @@ -333,7 +335,8 @@ void __ipv6_sock_mc_close(struct sock *sk) struct ipv6_mc_socklist *mc_lst; while ((mc_lst = sock_dereference(np->ipv6_mc_list, sk)) != NULL) { - np->ipv6_mc_list = mc_lst->next; + rcu_assign_pointer(np->ipv6_mc_list, + sock_dereference(mc_lst->next, sk)); __ipv6_sock_mc_drop(sk, mc_lst); } } @@ -798,9 +801,11 @@ static void mld_del_delrec(struct inet6_dev *idev, struct ifmcaddr6 *im) if (!pmc) return; if (pmc_prev) - rcu_assign_pointer(pmc_prev->next, pmc->next); + rcu_assign_pointer(pmc_prev->next, + mc_dereference(pmc->next, idev)); else - rcu_assign_pointer(idev->mc_tomb, pmc->next); + rcu_assign_pointer(idev->mc_tomb, + mc_dereference(pmc->next, idev)); im->idev = pmc->idev; if (im->mca_sfmode == MCAST_INCLUDE) { @@ -980,7 +985,7 @@ static int __ipv6_dev_mc_inc(struct net_device *dev, return -ENOMEM; } - rcu_assign_pointer(mc->next, idev->mc_list); + rcu_assign_pointer(mc->next, mc_dereference(idev->mc_list, idev)); rcu_assign_pointer(idev->mc_list, mc); mld_del_delrec(idev, mc); @@ -1014,7 +1019,8 @@ int __ipv6_dev_mc_dec(struct inet6_dev *idev, const struct in6_addr *addr) WRITE_ONCE(ma->mca_users, new_users); if (new_users == 0) { - *map = ma->next; + rcu_assign_pointer(*map, + mc_dereference(ma->next, idev)); igmp6_group_dropped(ma); inet6_ifmcaddr_notify(idev->dev, ma, From b4cf4a092a7bdaa62acca39c28f386b6d1674968 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Fri, 28 Aug 2026 08:45:31 +0000 Subject: [PATCH 600/641] ipv6: mcast: use jiffies_delta_to_clock_t() in igmp6_mc_seq_show() If a multicast group timer has expired but the delayed work has not yet run to clear MAF_TIMER_RUNNING, expires - jiffies produces a negative value. Because unsigned arithmetic was used with jiffies_to_clock_t(), expires - jiffies underflows to a huge value and reports invalid timer durations in /proc/net/igmp6. Use jiffies_delta_to_clock_t() with a signed long delta to properly cap expired deltas to 0, matching IPv4 igmp_mc_seq_show() and commit a399a8053164 ("time: jiffies_delta_to_clock_t() helper to the rescue"). Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Eric Dumazet Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260828084531.1826790-6-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/ipv6/mcast.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c index 2290457eb8d3..ecef55f26189 100644 --- a/net/ipv6/mcast.c +++ b/net/ipv6/mcast.c @@ -3029,7 +3029,7 @@ static int igmp6_mc_seq_show(struct seq_file *seq, void *v) struct ifmcaddr6 *im = (struct ifmcaddr6 *)v; struct igmp6_mc_iter_state *state = igmp6_mc_seq_private(seq); unsigned int mca_flags = READ_ONCE(im->mca_flags); - unsigned long expires = READ_ONCE(im->mca_work.timer.expires); + long delta = READ_ONCE(im->mca_work.timer.expires) - jiffies; seq_printf(seq, "%-4d %-15s %pi6 %5d %08X %ld\n", @@ -3037,7 +3037,7 @@ static int igmp6_mc_seq_show(struct seq_file *seq, void *v) &im->mca_addr, READ_ONCE(im->mca_users), mca_flags, (mca_flags & MAF_TIMER_RUNNING) ? - jiffies_to_clock_t(expires - jiffies) : 0); + jiffies_delta_to_clock_t(delta) : 0); return 0; } From 97cc84dad1d7f68a36b71b69b361d88482707673 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Fri, 28 Aug 2026 10:37:31 +0000 Subject: [PATCH 601/641] ip6_gre: check tunnel info before xmit in ip6gre_tunnel_xmit Shuangpeng Bai reported a KASAN slab-use-after-free in ip6gre_tunnel_xmit(). The precise KASAN bug was caused by ip6_tnl_xmit() consuming the skb during headroom expansion and returning an error, while ip6gre_tunnel_xmit() still held the stale pointer and called skb_tunnel_info_txcheck(skb) at tx_err. That specific bug was fixed by commit 87f21b59ddc6 ("ip6_tunnel: use skb_cow_head() in ip6_tnl_xmit()"). However, calling skb_tunnel_info_txcheck(skb) at the tx_err label after the transmission attempt remains problematic: Downstream helpers like ip6_tnl_xmit() call skb_scrub_packet(), which drops the skb's metadata_dst before transmission. If an error occurs later during transmit, inspecting skb at tx_err sees a scrubbed dst and misclassifies tx_errors vs tx_dropped. Commit e5f7e211b6aa ("ip6gre: avoid tx_error when sending MLD/DAD on external tunnels") already handled this correctly in ip6erspan_tunnel_xmit() by checking and caching tun_info before transmit. Align ip6gre_tunnel_xmit() with ip6erspan_tunnel_xmit() by caching tun_info before xmit and checking it at tx_err. Fixes: e5f7e211b6aa ("ip6gre: avoid tx_error when sending MLD/DAD on external tunnels") Reported-by: Shuangpeng Bai Closes: https://lore.kernel.org/netdev/20260819062224.3197349-1-shuangpeng.kernel@gmail.com/ Cc: Davide Caratti Signed-off-by: Eric Dumazet Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260828103731.1951815-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/ipv6/ip6_gre.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c index 69c51f1a5bf0..8ebda0b6a78b 100644 --- a/net/ipv6/ip6_gre.c +++ b/net/ipv6/ip6_gre.c @@ -878,6 +878,7 @@ static int ip6gre_xmit_other(struct sk_buff *skb, struct net_device *dev) static netdev_tx_t ip6gre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev) { + struct ip_tunnel_info *tun_info = NULL; struct ip6_tnl *t = netdev_priv(dev); __be16 payload_protocol; int ret; @@ -888,6 +889,9 @@ static netdev_tx_t ip6gre_tunnel_xmit(struct sk_buff *skb, if (!ip6_tnl_xmit_ctl(t, &t->parms.laddr, &t->parms.raddr)) goto tx_err; + if (t->parms.collect_md) + tun_info = skb_tunnel_info_txcheck(skb); + payload_protocol = skb_protocol(skb, true); switch (payload_protocol) { case htons(ETH_P_IP): @@ -907,7 +911,7 @@ static netdev_tx_t ip6gre_tunnel_xmit(struct sk_buff *skb, return NETDEV_TX_OK; tx_err: - if (!t->parms.collect_md || !IS_ERR(skb_tunnel_info_txcheck(skb))) + if (!IS_ERR(tun_info)) DEV_STATS_INC(dev, tx_errors); DEV_STATS_INC(dev, tx_dropped); kfree_skb(skb); From cd51b74bdd0b75aedf255dc67306a16dd057f7ee Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Fri, 28 Aug 2026 22:23:41 +0300 Subject: [PATCH 602/641] ipv6: Fix redirect exception creation for UDP/RAW sockets When an ICMP Redirect Message is matched to a socket, both IPv4 and IPv6 verify that the source IP of the ICMP packet is the current gateway for the quoted packet. Both also pass the socket's bound device as the expected nexthop device. The difference is that IPv4 treats "oif=0" as "any", whereas IPv6 always requires an exact match (see ip6_redirect_nh_match()), since the gateway address is usually a link-local address. Therefore, when an IPv6 UDP/RAW socket is not bound to a device, the above verification fails and an exception is not created. This also happens when the socket is bound to a VRF, as l3mdev_update_flow() resets the oif to 0. Fix this by passing the ifindex of the ingress device as the expected nexthop device. This is consistent with the existing callers of ip6_redirect(). Note that for ICMPv6 Redirect Message packets the VRF driver does not reset skb->dev to the VRF device, so skb->dev is correct, even when it is a VRF port. Fixes: b55b76b22144 ("ipv6:introduce function to find route for redirect") Cc: stable@vger.kernel.org Reviewed-by: Eric Dumazet Reviewed-by: David Ahern Signed-off-by: Ido Schimmel Link: https://patch.msgid.link/20260828192344.2596928-2-idosch@nvidia.com Signed-off-by: Jakub Kicinski --- net/ipv6/route.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ipv6/route.c b/net/ipv6/route.c index 6a40c5074543..9658939511e0 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -3255,7 +3255,7 @@ void ip6_redirect_no_header(struct sk_buff *skb, struct net *net, int oif) void ip6_sk_redirect(struct sk_buff *skb, struct sock *sk) { - ip6_redirect(skb, sock_net(sk), sk->sk_bound_dev_if, + ip6_redirect(skb, sock_net(sk), skb->dev->ifindex, READ_ONCE(sk->sk_mark), sk_uid(sk)); } From 4c3499f79f8c7e8561266bcc220a18538cec0458 Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Fri, 28 Aug 2026 22:23:42 +0300 Subject: [PATCH 603/641] ipv4: udp: Create exceptions before socket matching Currently, when ICMP Fragmentation Needed and Redirect Message packets are locally delivered and quote a UDP packet, a FIB nexthop exception (FNHE) is only created if the kernel can match the UDP packet to an existing socket. This behavior allows off-path attackers to conduct a side-channel attack on the FNHE cache in order to discover the ephemeral port used by a connected UDP socket. Commit 6457378fe796 ("ipv4: use siphash instead of Jenkins in fnhe_hashfun()") and commit 67d6d681e15b ("ipv4: make exception cache less predictible") tried to mitigate such attacks by making it harder for attackers to discover hash collisions in the FNHE cache and by randomizing the number of exceptions a hash bucket can hold, respectively. Unfortunately, both of the mitigations can be bypassed. Instead, mitigate such attacks by always creating a FNHE, even before trying to find a matching socket. Do that by calling ipv4_update_pmtu() and ipv4_redirect(), the helpers used when the quoted packet did not originate from a socket. This means that guesses (right or wrong) from an off-path attacker will always result in a FNHE being created or updated in the cache that the attacker can observe. Pass an oif of 0, in a similar fashion to icmp_err(). This is also the oif used by the socket path for sockets that are not bound to a device. Note that this does not allow attackers to create FNHEs that they could not create before, as both helpers can already be reached with little to no validation. For example, by sending an ICMP error that quotes an ICMP Echo Reply or one that quotes a UDP source port that matches a wildcard socket. Also note that in the good case (matched socket) the above scheme comes at the cost of an extra route lookup, as the no socket helpers perform their own lookup before the one performed by ipv4_sk_update_pmtu() / ipv4_sk_redirect(). When the two resolve to different nexthops, it also results in two exceptions being created for the same destination IP. One in the FNHE cache of the nexthop resolved by the no socket helpers and another in the FNHE cache of the nexthop used by the socket. Fixes: 4895c771c7f0 ("ipv4: Add FIB nexthop exceptions.") Cc: stable@vger.kernel.org Reported-by: Amit Klein Reported-by: Noam Caspi Signed-off-by: Ido Schimmel Reviewed-by: David Ahern Link: https://patch.msgid.link/20260828192344.2596928-3-idosch@nvidia.com Signed-off-by: Jakub Kicinski --- net/ipv4/udp.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c index 6ff5670bf6ed..bb8cfc62cb00 100644 --- a/net/ipv4/udp.c +++ b/net/ipv4/udp.c @@ -900,6 +900,15 @@ out: return sk; } +static void udp_err_update_exception(struct net *net, struct sk_buff *skb, + int type, int code, u32 info) +{ + if (type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED) + ipv4_update_pmtu(skb, net, info, 0, IPPROTO_UDP); + else if (type == ICMP_REDIRECT) + ipv4_redirect(skb, net, 0, IPPROTO_UDP); +} + /* * This routine is called by the ICMP module when it gets some * sort of error condition. If err < 0 then the socket should @@ -923,6 +932,8 @@ int udp_err(struct sk_buff *skb, u32 info) int harderr; int err; + udp_err_update_exception(net, skb, type, code, info); + uh = (struct udphdr *)(skb->data + (iph->ihl << 2)); sk = __udp4_lib_lookup(net, iph->daddr, uh->dest, iph->saddr, uh->source, skb->dev->ifindex, From ac76cab50e899a7346408b8d3c3a4192c2eefb9f Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Fri, 28 Aug 2026 22:23:43 +0300 Subject: [PATCH 604/641] ipv6: udp: Create exceptions before socket matching Currently, when ICMPv6 Packet Too Big and Redirect Message packets are locally delivered and quote a UDP packet, an exception is only created in the IPv6 exception cache if the kernel can match the UDP packet to an existing socket. This behavior allows off-path attackers to conduct a side-channel attack on the exception cache in order to discover the ephemeral port used by a connected UDP socket. Commit 4785305c05b2 ("ipv6: use siphash in rt6_exception_hash()") and commit a00df2caffed ("ipv6: make exception cache less predictible") tried to mitigate such attacks by making it harder for attackers to discover hash collisions in the exception cache and by randomizing the number of exceptions a hash bucket can hold, respectively. Unfortunately, both of the mitigations can be bypassed. Instead, mitigate such attacks by always creating an exception, even before trying to find a matching socket. Do that by calling ip6_update_pmtu() and ip6_redirect(), the helpers used when the quoted packet did not originate from a socket. This means that guesses (right or wrong) from an off-path attacker will always result in an exception being created or updated in the cache that the attacker can observe. Pass the ifindex of the ingress device and the default uid, in a similar fashion to icmpv6_err(). Unlike IPv4, an oif of 0 would not match any nexthop in ip6_redirect_nh_match() and no exception would be created in response to a Redirect Message. Note that this does not allow attackers to create exceptions that they could not create before, as both helpers can already be reached with little to no validation. For example, by sending an ICMPv6 error that quotes an ICMPv6 Echo Reply or one that quotes a UDP source port that matches a wildcard socket. Also note that in the good case (matched socket) the above scheme comes at the cost of an extra route lookup, as the no socket helpers perform their own lookup before the one performed by ip6_sk_update_pmtu() / ip6_sk_redirect(). When the two resolve to different nexthops, it also results in two exceptions being created for the same destination IP. One in the exception cache of the nexthop resolved by the no socket helpers and another in the exception cache of the nexthop used by the socket. Fixes: 2b760fcf5cfb ("ipv6: hook up exception table to store dst cache") Cc: stable@vger.kernel.org Reported-by: Amit Klein Reported-by: Noam Caspi Signed-off-by: Ido Schimmel Reviewed-by: David Ahern Link: https://patch.msgid.link/20260828192344.2596928-4-idosch@nvidia.com Signed-off-by: Jakub Kicinski --- net/ipv6/udp.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c index fd875908ac0c..93478d1ad576 100644 --- a/net/ipv6/udp.c +++ b/net/ipv6/udp.c @@ -690,6 +690,17 @@ out: return sk; } +static void udpv6_err_update_exception(struct net *net, struct sk_buff *skb, + u8 type, __be32 info) +{ + if (type == ICMPV6_PKT_TOOBIG) + ip6_update_pmtu(skb, net, info, skb->dev->ifindex, 0, + sock_net_uid(net, NULL)); + else if (type == NDISC_REDIRECT) + ip6_redirect(skb, net, skb->dev->ifindex, 0, + sock_net_uid(net, NULL)); +} + static int udpv6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, u8 type, u8 code, int offset, __be32 info) { @@ -703,6 +714,8 @@ static int udpv6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, int harderr; int err; + udpv6_err_update_exception(net, skb, type, info); + daddr = seg6_get_daddr(skb, opt) ? : &hdr->daddr; saddr = &hdr->saddr; sk = __udp6_lib_lookup(net, daddr, uh->dest, saddr, uh->source, From c923c14942b164cfc2c1efa4e6324214f2fc248a Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Fri, 28 Aug 2026 22:23:44 +0300 Subject: [PATCH 605/641] selftests: net: Add exception cache tests Add a test for the IPv4 and IPv6 exception caches, covering the exceptions that are created in response to ICMP errors quoting a UDP packet. The topology consists of a host (h1) that reaches a remote host (h2) via a router (r1), with a second router (r2) attached to the segment shared by h1 and r1. UDP packets are injected using a packet socket, so that an ICMP error quoting them is only matched to a socket when one was opened separately with the same source port. PMTU errors are provoked by lowering the MTU of the far end of the path and redirects by pointing r1's route towards h2 back over the segment it received the packet from. The following is tested for both address families and for both PMTU and redirect exceptions: * An error that is not matched to a socket creates an exception that carries the new MTU or gateway. * An error that is matched to a socket creates the same exception. The PMTU tests further verify that a lower PMTU replaces the one stored in the exception whereas a higher one does not, and that a socket which disabled PMTU discovery using IP{,V6}_PMTUDISC_OMIT gets the same exception as the other cases. Without "ipv4: udp: Create exceptions before socket matching" and "ipv6: udp: Create exceptions before socket matching", the tests that do not open a socket fail: # ./exception_cache.sh TEST: IPv4: PMTU: exception without a matching socket [FAIL] No socket: exception does not carry an MTU of 1400 TEST: IPv6: PMTU: exception without a matching socket [FAIL] No socket: exception does not carry an MTU of 1400 TEST: IPv4: PMTU: exception with a matching socket [ OK ] TEST: IPv6: PMTU: exception with a matching socket [ OK ] TEST: IPv4: PMTU: exception with a socket ignoring it [FAIL] PMTU discovery disabled: exception does not carry an MTU of 1400 TEST: IPv6: PMTU: exception with a socket ignoring it [FAIL] PMTU discovery disabled: exception does not carry an MTU of 1400 TEST: IPv4: Redirect: exception without a matching socket [FAIL] No socket: exception does not carry the new gateway TEST: IPv6: Redirect: exception without a matching socket [FAIL] No socket: exception does not carry the new gateway TEST: IPv4: Redirect: exception with a matching socket [ OK ] TEST: IPv6: Redirect: exception with a matching socket [ OK ] Signed-off-by: Ido Schimmel Link: https://patch.msgid.link/20260828192344.2596928-5-idosch@nvidia.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/Makefile | 1 + .../testing/selftests/net/exception_cache.sh | 521 ++++++++++++++++++ 2 files changed, 522 insertions(+) create mode 100755 tools/testing/selftests/net/exception_cache.sh diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile index 0f5c178bc224..517c09d60bef 100644 --- a/tools/testing/selftests/net/Makefile +++ b/tools/testing/selftests/net/Makefile @@ -28,6 +28,7 @@ TEST_PROGS := \ double_udp_encap.sh \ drop_monitor_tests.sh \ ecmp_rehash.sh \ + exception_cache.sh \ fcnal-ipv4.sh \ fcnal-ipv6.sh \ fcnal-other.sh \ diff --git a/tools/testing/selftests/net/exception_cache.sh b/tools/testing/selftests/net/exception_cache.sh new file mode 100755 index 000000000000..8d3eed5c532a --- /dev/null +++ b/tools/testing/selftests/net/exception_cache.sh @@ -0,0 +1,521 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Test that the state of the route exception cache after an ICMP error is +# processed does not depend on whether the quoted packet was matched to a +# socket. Otherwise, an off-path attacker can probe the cache to discover the +# ephemeral port used by a connected UDP socket. +# +# When the quoted packet is not matched to a socket, the same exception is +# created as when it is matched, so that neither its presence nor its contents +# reveal the result of socket matching. +# +# +----+ +# +---------| r1 | +# | +----+ +# +----+ +--------+ | .1 +# | h1 |---| bridge | | 198.51.100.0/30 +# +----+ +--------+ | 2001:db8:2::/64 +# .1 | | .2 +# | +----+ +----+ +# +---------| r2 |-----------| h2 | +# .2 .3 +----+ .1 .2 +----+ +# 203.0.113.0/24 +# 2001:db8:3::/64 +# 192.0.2.0/24 +# 2001:db8:1::/64 +# +# Traffic from h1 to h2 is routed via r1, which reaches h2's network via r2 +# over the point-to-point link. The MTU of the r2 - h2 link is lowered so that +# r2 emits ICMP errors towards h1. +# +# For the redirect tests r1's route to h2's network is replaced with one via r2 +# on the shared segment, so that r1 forwards the packet back to the segment it +# arrived from and emits a redirect towards h1. +# +# The packets that provoke the ICMP errors are injected with a packet socket so +# that no socket is ever associated with them. A socket is created separately, +# with socat, when a test needs the ICMP error to be matched. + +# shellcheck disable=SC1091,SC2034,SC2154,SC2329 +source lib.sh + +require_command jq +require_command mausezahn +require_command nstat +require_command socat + +ALL_TESTS=" + pmtu_no_socket_ipv4 + pmtu_no_socket_ipv6 + pmtu_socket_ipv4 + pmtu_socket_ipv6 + pmtu_omit_ipv4 + pmtu_omit_ipv6 + redirect_no_socket_ipv4 + redirect_no_socket_ipv6 + redirect_socket_ipv4 + redirect_socket_ipv6 +" + +# Shared segment. +H1_ADDR4=192.0.2.1 +R1_ADDR4=192.0.2.2 +R2_ADDR4=192.0.2.3 +H1_ADDR6=2001:db8:1::1 +R1_ADDR6=2001:db8:1::2 +R2_ADDR6=2001:db8:1::3 + +# r1 - r2 link. +R2_R1_ADDR4=198.51.100.2 +R2_R1_ADDR6=2001:db8:2::2 + +# r2 - h2 link. +H2_ADDR4=203.0.113.2 +H2_NET4=203.0.113.0/24 +H2_ADDR6=2001:db8:3::2 +H2_NET6=2001:db8:3::/64 + +SPORT=12345 +DPORT=54321 + +# The MTU of the shared segment and of the r1 - r2 link. Large enough for the +# injected packets to reach r2 intact. +SEGMENT_MTU=2000 +# Size of the injected packets. The PMTU tests need a size that exceeds every +# MTU used for the r2 - h2 link, so that r2 responds with an ICMP error. The +# redirect tests need a size that does not, otherwise r2 would respond with an +# ICMP error in addition to the redirect emitted by r1. +PMTU_PACKET_SIZE=1800 +REDIRECT_PACKET_SIZE=100 + +# The MTUs used for the r2 - h2 link. All of them must be at least +# IPV6_MIN_MTU, otherwise IPv6 silently ignores the error instead of creating +# an exception. +MTU_MID=1400 +MTU_LOW=1300 + +# Values for the IP{,V6}_MTU_DISCOVER socket option. +PMTUDISC_DONT=0 +PMTUDISC_OMIT=5 + +SOCAT_PID= + +linklocal_get() +{ + local ns=$1; shift + local dev=$1; shift + + ip -n "$ns" -j -6 addr show dev "$dev" | \ + jq -r '.[]["addr_info"][] | select(.scope == "link") | .local' +} + +linklocal_exists() +{ + local ns=$1; shift + local dev=$1; shift + + [ -n "$(linklocal_get "$ns" "$dev")" ] +} + +family_vars_set() +{ + local family=$1; shift + + FAMILY=$family + + if [ "$family" -eq 4 ]; then + H1_ADDR=$H1_ADDR4 + H2_ADDR=$H2_ADDR4 + MZ_FAMILY_OPT=() + # Without the Don't Fragment bit set r2 fragments the packet + # instead of reporting the MTU of the next hop. + MZ_IP_OPTS="df," + SOCAT_DST="UDP4-CONNECT:$H2_ADDR4:$DPORT" + SOCAT_BIND="bind=$H1_ADDR4:$SPORT" + SOCAT_PMTUDISC="ip-mtu-discover" + else + H1_ADDR=$H1_ADDR6 + H2_ADDR=$H2_ADDR6 + MZ_FAMILY_OPT=(-6) + MZ_IP_OPTS= + SOCAT_DST="UDP6-CONNECT:[$H2_ADDR6]:$DPORT" + SOCAT_BIND="bind=[$H1_ADDR6]:$SPORT" + SOCAT_PMTUDISC="ipv6-mtu-discover" + fi +} + +topology_setup() +{ + local ns + + setup_ns h1 r1 r2 h2 sw + defer cleanup_all_ns + + # Link-local addresses are generated from the MAC address and read + # back during setup, so request that generation mode explicitly and + # make the addresses available as soon as the devices are brought up. + for ns in "$h1" "$r1" "$r2" "$h2" "$sw"; do + ip netns exec "$ns" sysctl -qw \ + net.ipv6.conf.default.addr_gen_mode=0 \ + net.ipv6.conf.default.accept_dad=0 \ + net.ipv6.conf.all.accept_dad=0 + done + + ip -n "$sw" link add name br0 type bridge + ip -n "$sw" link set dev br0 mtu "$SEGMENT_MTU" up + + ip -n "$h1" link add name eth0 mtu "$SEGMENT_MTU" type veth \ + peer name swp1 mtu "$SEGMENT_MTU" netns "$sw" + ip -n "$r1" link add name eth0 mtu "$SEGMENT_MTU" type veth \ + peer name swp2 mtu "$SEGMENT_MTU" netns "$sw" + ip -n "$r2" link add name eth0 mtu "$SEGMENT_MTU" type veth \ + peer name swp3 mtu "$SEGMENT_MTU" netns "$sw" + ip -n "$r1" link add name eth1 mtu "$SEGMENT_MTU" type veth \ + peer name eth1 mtu "$SEGMENT_MTU" netns "$r2" + ip -n "$r2" link add name eth2 type veth peer name eth0 netns "$h2" + + ip -n "$sw" link set dev swp1 master br0 up + ip -n "$sw" link set dev swp2 master br0 up + ip -n "$sw" link set dev swp3 master br0 up + + ip -n "$h1" link set dev eth0 up + ip -n "$r1" link set dev eth0 up + ip -n "$r1" link set dev eth1 up + ip -n "$r2" link set dev eth0 up + ip -n "$r2" link set dev eth1 up + ip -n "$r2" link set dev eth2 up + ip -n "$h2" link set dev eth0 up + + ip -n "$h1" address add "$H1_ADDR4/24" dev eth0 + ip -n "$r1" address add "$R1_ADDR4/24" dev eth0 + ip -n "$r2" address add "$R2_ADDR4/24" dev eth0 + ip -n "$r1" address add 198.51.100.1/30 dev eth1 + ip -n "$r2" address add "$R2_R1_ADDR4/30" dev eth1 + ip -n "$r2" address add 203.0.113.1/24 dev eth2 + ip -n "$h2" address add "$H2_ADDR4/24" dev eth0 + + ip -n "$h1" -6 address add "$H1_ADDR6/64" dev eth0 nodad + ip -n "$r1" -6 address add "$R1_ADDR6/64" dev eth0 nodad + ip -n "$r2" -6 address add "$R2_ADDR6/64" dev eth0 nodad + ip -n "$r1" -6 address add 2001:db8:2::1/64 dev eth1 nodad + ip -n "$r2" -6 address add "$R2_R1_ADDR6/64" dev eth1 nodad + ip -n "$r2" -6 address add 2001:db8:3::1/64 dev eth2 nodad + ip -n "$h2" -6 address add "$H2_ADDR6/64" dev eth0 nodad + + ip netns exec "$r1" sysctl -qw net.ipv4.ip_forward=1 + ip netns exec "$r1" sysctl -qw net.ipv4.conf.all.send_redirects=1 + ip netns exec "$r1" sysctl -qw net.ipv6.conf.all.forwarding=1 + ip netns exec "$r2" sysctl -qw net.ipv4.ip_forward=1 + ip netns exec "$r2" sysctl -qw net.ipv6.conf.all.forwarding=1 + + ip netns exec "$h1" sysctl -qw net.ipv4.conf.all.accept_redirects=1 + ip netns exec "$h1" sysctl -qw net.ipv4.conf.eth0.accept_redirects=1 + ip netns exec "$h1" sysctl -qw net.ipv6.conf.all.accept_redirects=1 + ip netns exec "$h1" sysctl -qw net.ipv6.conf.eth0.accept_redirects=1 + + slowwait 5 linklocal_exists "$r1" eth0 + check_err $? "r1: link-local address was not generated" + slowwait 5 linklocal_exists "$r2" eth0 + check_err $? "r2: link-local address was not generated" + + R1_LLADDR=$(linklocal_get "$r1" eth0) + R2_LLADDR=$(linklocal_get "$r2" eth0) + R1_MAC=$(ip -n "$r1" -j link show dev eth0 | jq -r '.[]["address"]') + R2_MAC=$(ip -n "$r2" -j link show dev eth0 | jq -r '.[]["address"]') + + ip -n "$h1" route add "$H2_NET4" via "$R1_ADDR4" dev eth0 + ip -n "$h1" -6 route add "$H2_NET6" via "$R1_LLADDR" dev eth0 + ip -n "$r1" route add "$H2_NET4" via "$R2_R1_ADDR4" dev eth1 + ip -n "$r1" -6 route add "$H2_NET6" via "$R2_R1_ADDR6" dev eth1 + ip -n "$h2" route add default via 203.0.113.1 dev eth0 + ip -n "$h2" -6 route add default via 2001:db8:3::1 dev eth0 + + far_mtu_set "$MTU_MID" +} + +# Make r1 forward towards h2's network over the segment it receives the packet +# from, so that it emits a redirect towards h1. +redirect_route_set() +{ + ip -n "$r1" route replace "$H2_NET4" via "$R2_ADDR4" dev eth0 + ip -n "$r1" -6 route replace "$H2_NET6" via "$R2_LLADDR" dev eth0 + + # __ip_do_redirect() only creates an exception if the new gateway is + # already a valid neighbour. Otherwise it merely triggers address + # resolution. IPv6 resolves the target itself, in rt6_do_redirect(). + ip -n "$h1" neigh replace "$R2_ADDR4" lladdr "$R2_MAC" dev eth0 \ + nud permanent +} + +far_mtu_set() +{ + local mtu=$1; shift + + ip -n "$r2" link set dev eth2 mtu "$mtu" + ip -n "$h2" link set dev eth0 mtu "$mtu" +} + +socket_is_open() +{ + ip netns exec "$h1" ss -uHn "sport = :$SPORT" | grep -q . +} + +socket_start() +{ + # Disable PMTU discovery by default so that ICMP errors are not + # reported to the socket. Otherwise socat would exit when the first one + # arrives and later packets in the same test would not be matched to a + # socket. The exception is still created, as ip{,6}_sk_accept_pmtu() + # only rejects IP{,V6}_PMTUDISC_{INTERFACE,OMIT}. + local pmtudisc=${1:-$PMTUDISC_DONT} + + # Send socat's diagnostics to /dev/null. It reports the ICMP errors + # that reach the socket, which is exactly what the tests provoke. + ip netns exec "$h1" socat -u -lf/dev/null \ + "$SOCAT_DST,$SOCAT_BIND,$SOCAT_PMTUDISC=$pmtudisc" \ + OPEN:/dev/null,wronly=1 & + SOCAT_PID=$! + defer socket_stop + + slowwait 5 socket_is_open + check_err $? "socket did not open" +} + +socket_stop() +{ + [ -z "$SOCAT_PID" ] && return 0 + + kill "$SOCAT_PID" &> /dev/null + wait "$SOCAT_PID" 2> /dev/null + SOCAT_PID= +} + +# Inject a packet towards h2 with a packet socket. No socket is associated with +# it, so an ICMP error quoting it is matched to a socket only if one was +# created separately with the same source port. +packet_send() +{ + local size=$1; shift + + ip netns exec "$h1" mausezahn "${MZ_FAMILY_OPT[@]}" eth0 \ + -a own -b "$R1_MAC" -A "$H1_ADDR" -B "$H2_ADDR" \ + -t udp "${MZ_IP_OPTS}sp=$SPORT,dp=$DPORT" \ + -p "$size" -c 1 -q +} + +exception_show() +{ + if [ "$FAMILY" -eq 4 ]; then + # IPv4 exceptions without a bound route are not dumped, but + # "route get" reports the exception and binds a route to it. + ip -n "$h1" route get "$H2_ADDR" + else + # IPv6 does not report a cache indication in "route get" + # output, so dump the exceptions instead. + ip -n "$h1" -6 route show cache | grep -F "$H2_ADDR" || true + fi +} + +exception_mtu_get() +{ + exception_show | grep -o "mtu [0-9]*" | cut -d ' ' -f 2 +} + +exception_gw_get() +{ + exception_show | grep -o "via [0-9a-f.:]*" | cut -d ' ' -f 2 +} + +exception_mtu_check() +{ + local expected=$1; shift + + [ "$(exception_mtu_get)" = "$expected" ] +} + +icmp_errors_get() +{ + local ctr=IcmpInDestUnreachs + + [ "$FAMILY" -eq 6 ] && ctr=Icmp6InPktTooBigs + + ip netns exec "$h1" nstat -asz "$ctr" | \ + awk -v ctr="$ctr" '$1 == ctr { print $2 }' +} + +exception_pmtu_check() +{ + local mtu=$1; shift + local desc=$1; shift + + busywait "$BUSYWAIT_TIMEOUT" exception_mtu_check "$mtu" + check_err $? "$desc: exception does not carry an MTU of $mtu" +} + +pmtu_no_socket() +{ + local family=$1; shift + + RET=0 + family_vars_set "$family" + topology_setup + + packet_send "$PMTU_PACKET_SIZE" + exception_pmtu_check "$MTU_MID" "No socket" + + log_test "IPv$family: PMTU: exception without a matching socket" +} + +pmtu_no_socket_ipv4() +{ + pmtu_no_socket 4 +} + +pmtu_no_socket_ipv6() +{ + pmtu_no_socket 6 +} + +pmtu_socket() +{ + local family=$1; shift + local t0 + + RET=0 + family_vars_set "$family" + topology_setup + socket_start + + packet_send "$PMTU_PACKET_SIZE" + exception_pmtu_check "$MTU_MID" "Matching socket" + + # A lower PMTU replaces the one currently stored in the exception. + far_mtu_set "$MTU_LOW" + packet_send "$PMTU_PACKET_SIZE" + exception_pmtu_check "$MTU_LOW" "Lower PMTU" + + # A higher PMTU is ignored, so the exception is left as it is. Wait + # for the error to be received, as otherwise the check below would + # pass even if it never was. + far_mtu_set "$MTU_MID" + t0=$(icmp_errors_get) + packet_send "$PMTU_PACKET_SIZE" + busywait "$BUSYWAIT_TIMEOUT" until_counter_is ">= $((t0 + 1))" \ + icmp_errors_get > /dev/null + check_err $? "Higher PMTU: ICMP error was not received" + + exception_mtu_check "$MTU_LOW" + check_err $? "Higher PMTU: exception does not carry an MTU of $MTU_LOW" + + log_test "IPv$family: PMTU: exception with a matching socket" +} + +pmtu_socket_ipv4() +{ + pmtu_socket 4 +} + +pmtu_socket_ipv6() +{ + pmtu_socket 6 +} + +pmtu_omit() +{ + local family=$1; shift + + RET=0 + family_vars_set "$family" + topology_setup + socket_start "$PMTUDISC_OMIT" + + packet_send "$PMTU_PACKET_SIZE" + exception_pmtu_check "$MTU_MID" "PMTU discovery disabled" + + log_test "IPv$family: PMTU: exception with a socket ignoring it" +} + +pmtu_omit_ipv4() +{ + pmtu_omit 4 +} + +pmtu_omit_ipv6() +{ + pmtu_omit 6 +} + +exception_gw_check() +{ + local expected=$1; shift + + [ -n "$expected" ] && [ "$(exception_gw_get)" = "$expected" ] +} + +redirect_gw_new() +{ + if [ "$FAMILY" -eq 4 ]; then + echo "$R2_ADDR4" + else + echo "$R2_LLADDR" + fi +} + +redirect_no_socket() +{ + local family=$1; shift + + RET=0 + family_vars_set "$family" + topology_setup + redirect_route_set + + packet_send "$REDIRECT_PACKET_SIZE" + busywait "$BUSYWAIT_TIMEOUT" exception_gw_check "$(redirect_gw_new)" + check_err $? "No socket: exception does not carry the new gateway" + + log_test "IPv$family: Redirect: exception without a matching socket" +} + +redirect_no_socket_ipv4() +{ + redirect_no_socket 4 +} + +redirect_no_socket_ipv6() +{ + redirect_no_socket 6 +} + +redirect_socket() +{ + local family=$1; shift + + RET=0 + family_vars_set "$family" + topology_setup + redirect_route_set + socket_start + + packet_send "$REDIRECT_PACKET_SIZE" + busywait "$BUSYWAIT_TIMEOUT" exception_gw_check "$(redirect_gw_new)" + check_err $? "Matching socket: exception does not carry the new gateway" + + log_test "IPv$family: Redirect: exception with a matching socket" +} + +redirect_socket_ipv4() +{ + redirect_socket 4 +} + +redirect_socket_ipv6() +{ + redirect_socket 6 +} + +trap defer_scopes_cleanup EXIT +tests_run + +exit "$EXIT_STATUS" From b3b76e9f4f2476f1135b2ba7743a821db4a0df4b Mon Sep 17 00:00:00 2001 From: Tung Nguyen Date: Thu, 27 Aug 2026 18:13:46 +0700 Subject: [PATCH 606/641] tipc: fix NULL deref in tipc_named_node_up() on empty publication list User-space applications can bind a large number of service addresses to one or more sockets. Each binding of a local-scope service address inserts one entry (publication) into the TIPC name table. If the number of these publications exceeds TIPC_MAX_PUBL (65535), protocol service types (such as node state and link state) are no longer inserted into the name table. This causes two issues: 1. User-space applications subscribing to node or link up/down events stop receiving notifications. 2. A NULL pointer dereference can occur: BUG: kernel NULL pointer dereference, address: 00000000000000d0 ... CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Not tainted 7.2.0-rc4-default+ #5 PREEMPT(full) ... RIP: 0010:tipc_named_node_up (./include/linux/skbuff.h:2251 net/tipc/name_distr.c:195 net/tipc/name_distr.c:221) ... Call Trace: tipc_node_write_unlock (net/tipc/node.c:428) tipc_rcv (net/tipc/node.c:934 net/tipc/node.c:2189) tipc_udp_recv (net/tipc/udp_media.c:389) Thread 1 (tipc_net_finalize) | Thread 2 (named_distribute) -----------------------------|----------------------------- | ... | list_for_each_entry(publ, pls, binding_node) { | ... | __skb_queue_tail(list, skb); | ... | } | ... | hdr = buf_msg(skb_peek_tail(list)); ... | tipc_nametbl_publish(); | If 'tipc_nametbl_publish()' (Thread 1) fails because the number of local publications reaches TIPC_MAX_PUBL, list (Thread 2) will be empty. As a result, NULL is passed to 'buf_msg()', leading to a NULL pointer dereference. Fix these issues by allowing protocol service types (node state, link state, and topology server) to be inserted into the name table unconditionally. This ensures that users subscribing to these types always receive notifications. In addition, the maximum number of local user publications is reduced to (TIPC_MAX_PUBL - 1). This ensures that the maximum bulk size calculated in tipc_link_set_queue_limits() remains valid. Fixes: a5e7ac5ce134 ("tipc: fix regression bug where node events are not being generated") Reported-by: Xiang Mei Tested-by: Weiming Shi Signed-off-by: Tung Nguyen Link: https://patch.msgid.link/20260827111418.164957-1-tung.quang.nguyen@est.tech Signed-off-by: Jakub Kicinski --- net/tipc/name_table.c | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/net/tipc/name_table.c b/net/tipc/name_table.c index 253c72d1366e..6fda36ab1766 100644 --- a/net/tipc/name_table.c +++ b/net/tipc/name_table.c @@ -763,21 +763,40 @@ struct publication *tipc_nametbl_publish(struct net *net, struct tipc_uaddr *ua, struct tipc_socket_addr *sk, u32 key) { struct name_table *nt = tipc_name_table(net); + u32 max_user_pub = TIPC_MAX_PUBL - 1; struct tipc_net *tn = tipc_net(net); struct publication *p = NULL; struct sk_buff *skb = NULL; + bool protocol_type = false; u32 rc_dests; - spin_lock_bh(&tn->nametbl_lock); + if (ua->sr.type == TIPC_NODE_STATE || ua->sr.type == TIPC_LINK_STATE || + ua->sr.type == TIPC_TOP_SRV) + protocol_type = true; - if (nt->local_publ_count >= TIPC_MAX_PUBL) { - pr_warn("Bind failed, max limit %u reached\n", TIPC_MAX_PUBL); + spin_lock_bh(&tn->nametbl_lock); + if (protocol_type) + goto insert; + + /* Reserve one entry for node state service type because it has cluster + * scope and it is distributed in bulk. So, the maximum number of user's + * publications is (TIPC_MAX_PUBL - 1). + */ + if (nt->local_publ_count >= max_user_pub) { + pr_warn("Bind failed, max limit %u reached\n", max_user_pub); goto exit; } +insert: p = tipc_nametbl_insert_publ(net, ua, sk, key); if (p) { - nt->local_publ_count++; + /* Not count node state, link state and topology server types + * so that maximum nt->local_publ_count does not prevent + * protocol service types from being inserted into the name + * table. + */ + if (!protocol_type) + nt->local_publ_count++; skb = tipc_named_publish(net, p); } rc_dests = nt->rc_dests; @@ -810,7 +829,10 @@ void tipc_nametbl_withdraw(struct net *net, struct tipc_uaddr *ua, p = tipc_nametbl_remove_publ(net, ua, sk, key); if (p) { - nt->local_publ_count--; + if (p->sr.type != TIPC_NODE_STATE && + p->sr.type != TIPC_LINK_STATE && + p->sr.type != TIPC_TOP_SRV) + nt->local_publ_count--; skb = tipc_named_withdraw(net, p); list_del_init(&p->binding_sock); kfree_rcu(p, rcu); From 81c600c26302a27852ed8b19c5f2f647ea3555c9 Mon Sep 17 00:00:00 2001 From: David Laight Date: Sat, 29 Aug 2026 12:58:12 +0100 Subject: [PATCH 607/641] tipc: Dont send random pad bytes in RESET/ACTIVATE messages The interface name is passed in a fixed length (TIPC_MAX_IF_NAME) buffer. Replace the strcpy(data, l->if_name) with memcpy() so that the pad bytes are actually written (l->if_name[] is zero padded) rather than sending random bytes from the skb to the remote system. Replace two other strcpy() with strscpy(). Fixes: e74a386d70c7 ("tipc: remove pre-allocated message header in link struct") Signed-off-by: David Laight Reviewed-by: Tung Nguyen Link: https://patch.msgid.link/20260829115813.188600-1-david.laight.linux@gmail.com Signed-off-by: Jakub Kicinski --- net/tipc/link.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/net/tipc/link.c b/net/tipc/link.c index 49dfc098d89b..6427c69f8929 100644 --- a/net/tipc/link.c +++ b/net/tipc/link.c @@ -504,7 +504,7 @@ bool tipc_link_create(struct net *net, char *if_name, int bearer_id, snprintf(l->name, sizeof(l->name), "%s:%s-%s:unknown", self_str, if_name, peer_str); - strcpy(l->if_name, if_name); + strscpy(l->if_name, if_name); l->addr = peer; l->peer_caps = peer_caps; l->net = net; @@ -574,7 +574,7 @@ bool tipc_link_bc_create(struct net *net, u32 ownnode, u32 peer, u8 *peer_id, snprintf(l->name, sizeof(l->name), "%s:%s", tipc_bclink_name, peer_str); } else { - strcpy(l->name, tipc_bclink_name); + strscpy(l->name, tipc_bclink_name); } trace_tipc_link_reset(l, TIPC_DUMP_ALL, "bclink created!"); tipc_link_reset(l); @@ -1898,7 +1898,7 @@ static void tipc_link_build_proto_msg(struct tipc_link *l, int mtyp, bool probe, msg_set_dest_session(hdr, l->peer_session); } msg_set_max_pkt(hdr, l->advertised_mtu); - strcpy(data, l->if_name); + memcpy(data, l->if_name, TIPC_MAX_IF_NAME); msg_set_size(hdr, INT_H_SIZE + TIPC_MAX_IF_NAME); skb_trim(skb, INT_H_SIZE + TIPC_MAX_IF_NAME); } From 975b5b067f525a1b1338c4a3bee1c46545801518 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Fri, 28 Aug 2026 14:17:27 +0000 Subject: [PATCH 608/641] ipv6: sr: restore network header before routing and forwarding ipv6_srh_rcv() runs with skb->data at the Segment Routing Header (SRH) while skb_network_header() points at the IPv6 header. When segments_left > 0, ipv6_srh_rcv() previously restored the skb->data position by pushing sizeof(struct ipv6hdr), assuming the SRH immediately followed the fixed IPv6 header. If another extension header (such as a Hop-by-Hop options header) precedes the SRH, skb_network_offset() remained negative. This led to two problems: 1. During ip6_route_input(), fib6_rules_early_flow_dissect() invokes __skb_flow_dissect() which passes the negative skb_network_offset() to flow dissection, breaking BPF and C flow dissector logic. 2. If forwarded via ip6_forward() or redirected via act_mirred, downstream handlers (like sch_fragment() or neighbour output) pass the negative offset as an unsigned length, triggering OOB memcpy or buffer overflows. Fix this by pushing -skb_network_offset(skb) before routing, ensuring skb_network_offset(skb) is 0 for route lookup / flow dissection as well as downstream forwarding. On the loopback path, pull skb_transport_offset(skb) to restore skb->data to the SRH before looping back. Fixes: 1ababeba4a21 ("ipv6: implement dataplane support for rthdr type 4 (Segment Routing Header)") Reported-by: TencentOS Corvus AI Reported-by: Jun Yang Reported-by: Fourie Zhang Closes: https://lore.kernel.org/netdev/20260817104128.22681-1-juny24602@gmail.com/ Closes: https://lore.kernel.org/netdev/20260827092345.2301937-1-fouriezhang@tencent.com/ Signed-off-by: Eric Dumazet Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260828141727.2372570-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/ipv6/exthdrs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c index 51941ad656a3..09a4552f7f08 100644 --- a/net/ipv6/exthdrs.c +++ b/net/ipv6/exthdrs.c @@ -445,7 +445,7 @@ looped_back: hdr->segments_left--; addr = hdr->segments + hdr->segments_left; - skb_push(skb, sizeof(struct ipv6hdr)); + skb_push(skb, -skb_network_offset(skb)); if (skb->ip_summed == CHECKSUM_COMPLETE) seg6_update_csum(skb); @@ -469,7 +469,7 @@ looped_back: } ipv6_hdr(skb)->hop_limit--; - skb_pull(skb, sizeof(struct ipv6hdr)); + skb_pull(skb, skb_transport_offset(skb)); goto looped_back; } From 545b63503c696c4ce0663b3fcd37f41169aec1eb Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Mon, 31 Aug 2026 00:16:17 +0900 Subject: [PATCH 609/641] net: ntb_netdev: Fix statistics races ntb_netdev updates shared net_device stats from per-QP RX and TX callbacks. Once multiple queues are enabled, concurrent updates can be lost. Use per-CPU tstats for packet and byte counters and DEV_STATS_INC() for less frequent drop and error counters. Callbacks can run synchronously in the xmit path or asynchronously from a tasklet or the memcpy kthread. Pin TX updates against migration in the kthread path. Use the IRQ-safe u64_stats helpers because netpoll can invoke the synchronous path with IRQs disabled. Let the core manage tstats while keeping transport teardown after unregister_netdev(), outside RTNL. RCU lets unregister wait for TX completions already updating stats, while later completions only consume the skb and skip accounting and queue wake. Fixes: 24d9e73c7e00 ("net: ntb_netdev: Support ethtool channels for multi-queue") Cc: stable@vger.kernel.org Suggested-by: Jakub Kicinski Signed-off-by: Koichiro Den Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260830151617.3546585-1-den@valinux.co.jp Signed-off-by: Jakub Kicinski --- drivers/net/ntb_netdev.c | 47 +++++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/drivers/net/ntb_netdev.c b/drivers/net/ntb_netdev.c index 9c171697e762..2c04be6d61a8 100644 --- a/drivers/net/ntb_netdev.c +++ b/drivers/net/ntb_netdev.c @@ -127,8 +127,10 @@ static void ntb_netdev_rx_handler(struct ntb_transport_qp *qp, void *qp_data, { struct ntb_netdev_queue *q = qp_data; struct ntb_netdev *dev = q->ntdev; + struct pcpu_sw_netstats *tstats; struct sk_buff *skb, *new_skb; struct net_device *ndev; + unsigned long flags; int rc; ndev = dev->ndev; @@ -139,17 +141,20 @@ static void ntb_netdev_rx_handler(struct ntb_transport_qp *qp, void *qp_data, netdev_dbg(ndev, "%s: %d byte payload received\n", __func__, len); if (len < 0) { - ndev->stats.rx_errors++; - ndev->stats.rx_length_errors++; + DEV_STATS_INC(ndev, rx_errors); + DEV_STATS_INC(ndev, rx_length_errors); goto enqueue_again; } - ndev->stats.rx_packets++; - ndev->stats.rx_bytes += len; + tstats = this_cpu_ptr(ndev->tstats); + flags = u64_stats_update_begin_irqsave(&tstats->syncp); + u64_stats_inc(&tstats->rx_packets); + u64_stats_add(&tstats->rx_bytes, len); + u64_stats_update_end_irqrestore(&tstats->syncp, flags); new_skb = netdev_alloc_skb(ndev, ndev->mtu + ETH_HLEN); if (!new_skb) { - ndev->stats.rx_dropped++; + DEV_STATS_INC(ndev, rx_dropped); goto enqueue_again; } @@ -166,8 +171,8 @@ enqueue_again: rc = ntb_transport_rx_enqueue(qp, skb, skb->data, ndev->mtu + ETH_HLEN); if (rc) { dev_kfree_skb_any(skb); - ndev->stats.rx_errors++; - ndev->stats.rx_fifo_errors++; + DEV_STATS_INC(ndev, rx_errors); + DEV_STATS_INC(ndev, rx_fifo_errors); } } @@ -210,25 +215,39 @@ static void ntb_netdev_tx_handler(struct ntb_transport_qp *qp, void *qp_data, { struct ntb_netdev_queue *q = qp_data; struct ntb_netdev *dev = q->ntdev; + struct pcpu_sw_netstats *tstats; struct net_device *ndev; struct sk_buff *skb; + unsigned long flags; + bool registered; ndev = dev->ndev; skb = data; if (!skb || !ndev) return; + rcu_read_lock(); + registered = READ_ONCE(ndev->reg_state) == NETREG_REGISTERED; + if (!registered) + goto free_skb; + if (len > 0) { - ndev->stats.tx_packets++; - ndev->stats.tx_bytes += skb->len; + /* The memcpy kthread can migrate, so pin the per-CPU update. */ + tstats = get_cpu_ptr(ndev->tstats); + flags = u64_stats_update_begin_irqsave(&tstats->syncp); + u64_stats_inc(&tstats->tx_packets); + u64_stats_add(&tstats->tx_bytes, skb->len); + u64_stats_update_end_irqrestore(&tstats->syncp, flags); + put_cpu_ptr(ndev->tstats); } else { - ndev->stats.tx_errors++; - ndev->stats.tx_aborted_errors++; + DEV_STATS_INC(ndev, tx_errors); + DEV_STATS_INC(ndev, tx_aborted_errors); } +free_skb: dev_kfree_skb_any(skb); - if (ntb_transport_tx_free_entry(qp) >= tx_start) { + if (registered && ntb_transport_tx_free_entry(qp) >= tx_start) { /* Make sure anybody stopping the queue after this sees the new * value of ntb_transport_tx_free_entry() */ @@ -237,6 +256,7 @@ static void ntb_netdev_tx_handler(struct ntb_transport_qp *qp, void *qp_data, ntb_transport_link_query(q->qp)) netif_wake_subqueue(ndev, q->qid); } + rcu_read_unlock(); } static const struct ntb_queue_handlers ntb_netdev_handlers = { @@ -277,7 +297,7 @@ static netdev_tx_t ntb_netdev_start_xmit(struct sk_buff *skb, drop: dev_kfree_skb_any(skb); - ndev->stats.tx_dropped++; + DEV_STATS_INC(ndev, tx_dropped); return NETDEV_TX_OK; } @@ -647,6 +667,7 @@ static int ntb_netdev_probe(struct device *client_dev) } ndev->features = NETIF_F_HIGHDMA; + ndev->pcpu_stat_type = NETDEV_PCPU_STAT_TSTATS; ndev->priv_flags |= IFF_LIVE_ADDR_CHANGE; From 73e594c19b4f815d8343461cec7074c4713bbde7 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sun, 30 Aug 2026 18:09:12 +0000 Subject: [PATCH 610/641] af_packet: Don't cast tpacket_hdr.tp_len to int in tpacket_parse_header(). syzbot reported BUG() in sock_sendmsg_nosec(). [0] The problem is that tpacket_parse_header() casts user-provided tpacket_hdr.tp_len, which is u32, to int. If the length is larger than INT_MAX, the following condition in tpacket_parse_header() passes, if (unlikely(tp_len > size_max)) and any negative value can be returned to the caller, up to sock_sendmsg_nosec(). The repro set tpacket_hdr.tp_len to 0xfffffdef, which is cast to -EIOCBQUEUED (-529), triggering BUG() in sock_sendmsg_nosec(). *(uint64_t*)0x200000000008 = 0xfffffdef; ... syscall(__NR_write, /*fd=*/r[0], /*buf=*/0x200000000000ul, /*count=*/1ul); Let's define the local tp_len as u32 in tpacket_parse_header(). [0]: kernel BUG at net/socket.c:803! Oops: invalid opcode: 0000 [#1] SMP KASAN PTI CPU: 0 UID: 0 PID: 5628 Comm: syz-executor176 Not tainted syzkaller #0 PREEMPT(full) Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 07/24/2026 RIP: 0010:sock_sendmsg_nosec+0x145/0x180 net/socket.c:803 Code: 06 67 48 0f b9 3a eb 95 e8 e8 3a 22 f8 48 89 df 4c 89 f6 4c 89 e2 4d 89 fb 2e e8 32 a5 5c 16 e9 51 ff ff ff e8 cc 3a 22 f8 90 <0f> 0b e8 c4 3a 22 f8 48 83 c3 18 48 89 d8 48 c1 e8 03 42 80 3c 28 RSP: 0018:ffffc90003aefb48 EFLAGS: 00010293 RAX: ffffffff89a578d4 RBX: ffff8880764c67c0 RCX: ffff88807fb23e80 RDX: 0000000000000000 RSI: 00000000fffffdef RDI: 00000000fffffdef RBP: 00000000fffffdef R08: ffffc90003aef747 R09: 1ffff9200075dee8 R10: dffffc0000000000 R11: fffff5200075dee9 R12: 0000000000000001 R13: dffffc0000000000 R14: ffffc90003aefbc0 R15: ffffffff8aac4310 FS: 000055559101b400(0000) GS:ffff888124ce0000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 0000200000000210 CR3: 0000000073dca000 CR4: 00000000003526f0 Call Trace: __sock_sendmsg net/socket.c:815 [inline] sock_write_iter+0x2de/0x3e0 net/socket.c:1266 new_sync_write fs/read_write.c:595 [inline] vfs_write+0x612/0xba0 fs/read_write.c:687 ksys_write+0x150/0x270 fs/read_write.c:739 do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline] do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7f173130ecb9 Code: c0 79 93 eb d5 48 8d 7c 1d 00 eb 99 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 d8 ff ff ff f7 d8 64 89 01 48 RSP: 002b:00007ffd67e44248 EFLAGS: 00000246 ORIG_RAX: 0000000000000001 RAX: ffffffffffffffda RBX: 0000200000000000 RCX: 00007f173130ecb9 RDX: 0000000000000001 RSI: 0000200000000000 RDI: 0000000000000003 RBP: 0000000000000001 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000246 R12: 00007ffd67e44388 R13: 0000000000000002 R14: 00002000000000c0 R15: 0000000000000002 Fixes: 69e3c75f4d54 ("net: TX_RING and packet mmap") Reported-by: syzbot+73df3f89e1e13089e466@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6a946ffa.1d9ded08.62e62.0123.GAE@google.com/ Signed-off-by: Kuniyuki Iwashima Reviewed-by: Eric Dumazet Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260830180915.260225-1-kuniyu@google.com Signed-off-by: Paolo Abeni --- net/packet/af_packet.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c index b22cda322136..76bde7906d49 100644 --- a/net/packet/af_packet.c +++ b/net/packet/af_packet.c @@ -2675,7 +2675,8 @@ static int tpacket_parse_header(struct packet_sock *po, void *frame, int size_max, void **data) { union tpacket_uhdr ph; - int tp_len, off; + u32 tp_len; + int off; ph.raw = frame; @@ -2695,7 +2696,7 @@ static int tpacket_parse_header(struct packet_sock *po, void *frame, break; } if (unlikely(tp_len > size_max)) { - pr_err("packet size is too long (%d > %d)\n", tp_len, size_max); + pr_err("packet size is too long (%u > %d)\n", tp_len, size_max); return -EMSGSIZE; } From c037915f80c4db47f7d061d68e703ffd551b1a34 Mon Sep 17 00:00:00 2001 From: Kaiwen Shi Date: Sun, 30 Aug 2026 07:05:51 +0800 Subject: [PATCH 611/641] mac802154: fix data race and NULL deref on local->assoc_dev local->assoc_dev is shared between the association path and the association-response worker without common synchronization. mac802154_perform_association() stores the coordinator pointer and waits for a response. Its timeout and error paths clear the pointer and return to mac802154_associate(), which may then free the coordinator object. Meanwhile, mac802154_rx_mac_cmd_worker() may observe the associating bit and enter mac802154_process_association_resp(), which dereferences assoc_dev. The worker's bit test and the handler's pointer dereference are not atomic with respect to cleanup. Cleanup can clear assoc_dev between them, causing a NULL dereference, or free the coordinator while the response handler still uses the pointer. The recorded result is exposed to the same window. assoc_status and assoc_addr are written by the handler but read by the association path while the associating bit is still set, so a second response for the same request - a malicious one, for instance - can replace them between those reads and leave the caller with an incoherent status and address pair. The response handler only needs the coordinator extended address. Replace assoc_dev with a cached address, removing the pointer lifetime dependency. Protect the cached address and the associating bit with a dedicated spinlock. A READ_ONCE()/WRITE_ONCE() pair would not guarantee an atomic __le64 access on all 32-bit architectures. wpan_dev->association_lock cannot be reused here: nl802154_associate() holds it across rdev_associate(), hence for the whole of mac802154_perform_association() including the wait for the response. A response handler taking that lock would only get it once the association has already given up. Reset the completion, publish the cached address, and set the associating bit while holding the lock. The response handler takes the lock, rechecks the bit and the cached address, records the response, clears the bit, and only then completes the waiter. Thus cleanup cannot pass the handler between its state check and completion, and the cached 64-bit value cannot tear. The handler clears the bit before completing, not the woken waiter: otherwise complete() is issued under the lock and a second (e.g. malicious) response can reacquire it before the waiter and replace the result. So a wait that returns success implies the bit is already clear, and the success and negative-response paths return directly. The transmit-error and timeout paths still clear it under assoc_lock, which serializes any racing response against the cleanup while the call returns the error it already selected. Both paths snapshot assoc_status and assoc_addr under the same lock. Both users run in process context, so a plain spinlock is sufficient. The lock is not held while waiting for the completion. Suggested-by: Miquel Raynal Suggested-by: Xuanqiang Luo Fixes: fefd19807fe9 ("mac802154: Handle associating") Cc: stable@vger.kernel.org Signed-off-by: Kaiwen Shi Reviewed-by: Xuanqiang Luo Reviewed-by: Miquel Raynal Link: https://patch.msgid.link/20260829230551.1787432-1-skwkevin@mail.ustc.edu.cn Signed-off-by: Paolo Abeni --- net/mac802154/ieee802154_i.h | 7 ++++- net/mac802154/main.c | 1 + net/mac802154/scan.c | 51 +++++++++++++++++++++++++++--------- 3 files changed, 45 insertions(+), 14 deletions(-) diff --git a/net/mac802154/ieee802154_i.h b/net/mac802154/ieee802154_i.h index 8f2bff268392..c53aa293a222 100644 --- a/net/mac802154/ieee802154_i.h +++ b/net/mac802154/ieee802154_i.h @@ -76,7 +76,12 @@ struct ieee802154_local { struct work_struct rx_mac_cmd_work; /* Association */ - struct ieee802154_pan_device *assoc_dev; + /* assoc_lock protects assoc_dev_extended_addr, assoc_addr, + * assoc_status, the assoc_done reinit/complete pairing and the + * IEEE802154_IS_ASSOCIATING bit in @ongoing. + */ + spinlock_t assoc_lock; + __le64 assoc_dev_extended_addr; struct completion assoc_done; __le16 assoc_addr; u8 assoc_status; diff --git a/net/mac802154/main.c b/net/mac802154/main.c index ea1efef3572a..63e89bd586e3 100644 --- a/net/mac802154/main.c +++ b/net/mac802154/main.c @@ -104,6 +104,7 @@ ieee802154_alloc_hw(size_t priv_data_len, const struct ieee802154_ops *ops) INIT_WORK(&local->rx_mac_cmd_work, mac802154_rx_mac_cmd_worker); init_completion(&local->assoc_done); + spin_lock_init(&local->assoc_lock); /* init supported flags with 802.15.4 default ranges */ phy->supported.max_minbe = 8; diff --git a/net/mac802154/scan.c b/net/mac802154/scan.c index 005338f89b75..dd156c01ac49 100644 --- a/net/mac802154/scan.c +++ b/net/mac802154/scan.c @@ -536,7 +536,9 @@ int mac802154_perform_association(struct ieee802154_sub_if_data *sdata, struct ieee802154_association_req_frame frame = {}; struct ieee802154_local *local = sdata->local; struct wpan_dev *wpan_dev = &sdata->wpan_dev; + __le16 resp_short_addr; struct sk_buff *skb; + u8 resp_status; int ret; frame.mhr.fc.type = IEEE802154_FC_TYPE_MAC_CMD; @@ -578,9 +580,11 @@ int mac802154_perform_association(struct ieee802154_sub_if_data *sdata, return ret; } - local->assoc_dev = coord; + spin_lock(&local->assoc_lock); reinit_completion(&local->assoc_done); + local->assoc_dev_extended_addr = coord->extended_addr; set_bit(IEEE802154_IS_ASSOCIATING, &local->ongoing); + spin_unlock(&local->assoc_lock); ret = ieee802154_mlme_tx_one_locked(local, sdata, skb); if (ret) { @@ -599,25 +603,37 @@ int mac802154_perform_association(struct ieee802154_sub_if_data *sdata, goto clear_assoc; } - if (local->assoc_status != IEEE802154_ASSOCIATION_SUCCESSFUL) { - if (local->assoc_status == IEEE802154_PAN_AT_CAPACITY) + /* The association is complete: mac802154_process_association_resp() + * cleared the associating bit before waking us, so a second (e.g. + * malicious) ASSOC RESP can no longer pass the recheck and overwrite + * the result. Snapshot assoc_status/assoc_addr under the lock. + */ + spin_lock(&local->assoc_lock); + resp_status = local->assoc_status; + resp_short_addr = local->assoc_addr; + spin_unlock(&local->assoc_lock); + + if (resp_status != IEEE802154_ASSOCIATION_SUCCESSFUL) { + if (resp_status == IEEE802154_PAN_AT_CAPACITY) ret = -ERANGE; else ret = -EPERM; dev_warn(&sdata->dev->dev, "Negative ASSOC RESP received from %8phC: %s\n", &ceaddr, - local->assoc_status == IEEE802154_PAN_AT_CAPACITY ? + resp_status == IEEE802154_PAN_AT_CAPACITY ? "PAN at capacity" : "access denied"); - goto clear_assoc; + return ret; } - ret = 0; - *short_addr = local->assoc_addr; + *short_addr = resp_short_addr; + + return 0; clear_assoc: + spin_lock(&local->assoc_lock); clear_bit(IEEE802154_IS_ASSOCIATING, &local->ongoing); - local->assoc_dev = NULL; + spin_unlock(&local->assoc_lock); return ret; } @@ -639,19 +655,28 @@ int mac802154_process_association_resp(struct ieee802154_sub_if_data *sdata, dest->mode != IEEE802154_EXTENDED_ADDRESSING)) return -EINVAL; - if (unlikely(dest->extended_addr != wpan_dev->extended_addr || - src->extended_addr != local->assoc_dev->extended_addr)) + spin_lock(&local->assoc_lock); + if (unlikely(!test_bit(IEEE802154_IS_ASSOCIATING, &local->ongoing) || + dest->extended_addr != wpan_dev->extended_addr || + src->extended_addr != local->assoc_dev_extended_addr)) { + spin_unlock(&local->assoc_lock); return -ENODEV; + } memcpy(&resp_pl, skb->data, sizeof(resp_pl)); local->assoc_addr = resp_pl.short_addr; local->assoc_status = resp_pl.status; + /* Clear the associating bit before waking the waiter: once the result + * is saved, any subsequent (e.g. malicious) ASSOC RESP must fail the + * test_bit() recheck above and can no longer overwrite the result. + */ + clear_bit(IEEE802154_IS_ASSOCIATING, &local->ongoing); + complete(&local->assoc_done); + spin_unlock(&local->assoc_lock); dev_dbg(&skb->dev->dev, "ASSOC RESP 0x%x received from %8phC, getting short address %04x\n", - local->assoc_status, &deaddr, local->assoc_addr); - - complete(&local->assoc_done); + resp_pl.status, &deaddr, resp_pl.short_addr); return 0; } From bc93419130bb70fabf6561e197054caae85c160c Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Mon, 31 Aug 2026 08:10:27 +0000 Subject: [PATCH 612/641] net: bonding: annotate lockless writes with WRITE_ONCE() Several fields in bonding are read locklessly using READ_ONCE() (or ACCESS_ONCE() previously) but have corresponding writes that do not use WRITE_ONCE(). Add WRITE_ONCE() annotations to: - bond->send_peer_notif decrements in bond_peer_notify_may_events() and reset in bond_close(). - bond->slave_cnt increments and decrements in bond_enslave() and __bond_release_one(). - bond->recv_probe updates in bond_open(), bond_option_arp_interval_set() and rlb_initialize(). - slaves->count decrement in bond_skip_slave(). Fixes: 4d97480b1806 ("bonding: use local function pointer of bond->recv_probe in bond_handle_frame") Fixes: 9a72c2da690d ("bonding: fix div by zero while enslaving and transmitting") Fixes: ee6377147409 ("bonding: Simplify the xmit function for modes that use xmit_hash") Fixes: 429208aab9db ("net: bonding: add the READ_ONCE/WRITE_ONCE for outside lock accessing") Signed-off-by: Eric Dumazet Cc: Jay Vosburgh Reviewed-by: Xuanqiang Luo Reviewed-by: Hangbin Liu Link: https://patch.msgid.link/20260831081027.3209554-1-edumazet@google.com Signed-off-by: Paolo Abeni --- drivers/net/bonding/bond_alb.c | 2 +- drivers/net/bonding/bond_main.c | 14 +++++++------- drivers/net/bonding/bond_options.c | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/net/bonding/bond_alb.c b/drivers/net/bonding/bond_alb.c index 839f7482dc18..d2fb67a47cf9 100644 --- a/drivers/net/bonding/bond_alb.c +++ b/drivers/net/bonding/bond_alb.c @@ -875,7 +875,7 @@ static int rlb_initialize(struct bonding *bond) spin_unlock_bh(&bond->mode_lock); /* register to receive ARPs */ - bond->recv_probe = rlb_arp_recv; + WRITE_ONCE(bond->recv_probe, rlb_arp_recv); return 0; } diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c index ef9eb0c53c66..947d92a669b6 100644 --- a/drivers/net/bonding/bond_main.c +++ b/drivers/net/bonding/bond_main.c @@ -1245,7 +1245,7 @@ static void bond_peer_notify_may_events(struct bonding *bond, bool force) } if (notified || force) - bond->send_peer_notif--; + WRITE_ONCE(bond->send_peer_notif, bond->send_peer_notif - 1); } /** @@ -2284,7 +2284,7 @@ skip_mac_set: } } - bond->slave_cnt++; + WRITE_ONCE(bond->slave_cnt, bond->slave_cnt + 1); netdev_compute_master_upper_features(bond->dev, true); bond_set_carrier(bond); @@ -2533,7 +2533,7 @@ static int __bond_release_one(struct net_device *bond_dev, unblock_netpoll_tx(); synchronize_rcu(); - bond->slave_cnt--; + WRITE_ONCE(bond->slave_cnt, bond->slave_cnt - 1); if (!bond_has_slaves(bond)) { call_netdevice_notifiers(NETDEV_CHANGEADDR, bond->dev); @@ -4385,13 +4385,13 @@ static int bond_open(struct net_device *bond_dev) if (bond->params.arp_interval) { /* arp interval, in milliseconds. */ queue_delayed_work(bond->wq, &bond->arp_work, 0); - bond->recv_probe = bond_rcv_validate; + WRITE_ONCE(bond->recv_probe, bond_rcv_validate); } if (BOND_MODE(bond) == BOND_MODE_8023AD) { queue_delayed_work(bond->wq, &bond->ad_work, 0); /* register to receive LACPDUs */ - bond->recv_probe = bond_3ad_lacpdu_recv; + WRITE_ONCE(bond->recv_probe, bond_3ad_lacpdu_recv); bond_3ad_initiate_agg_selection(bond, 1); bond_for_each_slave(bond, slave, iter) @@ -4413,7 +4413,7 @@ static int bond_close(struct net_device *bond_dev) struct slave *slave; bond_work_cancel_all(bond); - bond->send_peer_notif = 0; + WRITE_ONCE(bond->send_peer_notif, 0); WRITE_ONCE(bond->recv_probe, NULL); /* Wait for any in-flight RX handlers */ @@ -5118,7 +5118,7 @@ static void bond_skip_slave(struct bond_up_slave *slaves, if (skipslave == slaves->arr[idx]) { slaves->arr[idx] = slaves->arr[slaves->count - 1]; - slaves->count--; + WRITE_ONCE(slaves->count, slaves->count - 1); break; } } diff --git a/drivers/net/bonding/bond_options.c b/drivers/net/bonding/bond_options.c index 36b8d89387ee..9efadeff6a22 100644 --- a/drivers/net/bonding/bond_options.c +++ b/drivers/net/bonding/bond_options.c @@ -1147,11 +1147,11 @@ static int bond_option_arp_interval_set(struct bonding *bond, */ if (!newval->value) { if (bond->params.arp_validate) - bond->recv_probe = NULL; + WRITE_ONCE(bond->recv_probe, NULL); cancel_delayed_work_sync(&bond->arp_work); } else { /* arp_validate can be set only in active-backup mode */ - bond->recv_probe = bond_rcv_validate; + WRITE_ONCE(bond->recv_probe, bond_rcv_validate); cancel_delayed_work_sync(&bond->mii_work); queue_delayed_work(bond->wq, &bond->arp_work, 0); } From 148845aa1921d95ef5dc851c76e6a284f6657df6 Mon Sep 17 00:00:00 2001 From: Ben Cressey Date: Wed, 26 Aug 2026 00:25:33 +0000 Subject: [PATCH 613/641] dm-crypt: fix a tiny race condition in crypt_dec_pending crypt_dec_pending reads io->error before calling atomic_dec_and_test. Another context, for example crypt_endio called from an interrupt, may set io->error and drop its reference between the read and the decrement. crypt_dec_pending then drops the last reference and completes the bio with the stale status - so a read that failed and was never decrypted, or a write that failed, is reported as successful. The read was placed before the decrement by commit b35f8caa0890 ("dm crypt: wait for endio to complete before destruction"), because that commit freed dm_crypt_io before calling bio_endio. This is no longer the case, dm_crypt_io lives in the per-bio data now. Read io->error after atomic_dec_and_test instead. atomic_dec_and_test is fully ordered, so no additional barrier is needed. Fixes: b35f8caa0890 ("dm crypt: wait for endio to complete before destruction") Cc: stable@vger.kernel.org Reviewed-by: Jose Fernandez (Anthropic) Signed-off-by: Ben Cressey Assisted-by: Claude:unspecified Signed-off-by: Mikulas Patocka --- drivers/md/dm-crypt.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/md/dm-crypt.c b/drivers/md/dm-crypt.c index 608b617fb817..9e170de50ad3 100644 --- a/drivers/md/dm-crypt.c +++ b/drivers/md/dm-crypt.c @@ -1745,7 +1745,6 @@ static void crypt_dec_pending(struct dm_crypt_io *io) { struct crypt_config *cc = io->cc; struct bio *base_bio = io->base_bio; - blk_status_t error = io->error; if (!atomic_dec_and_test(&io->io_pending)) return; @@ -1767,7 +1766,7 @@ static void crypt_dec_pending(struct dm_crypt_io *io) else kfree(io->integrity_metadata); - base_bio->bi_status = error; + base_bio->bi_status = io->error; bio_endio(base_bio); } From bc9781c0247de107876f32929f1637db93a42b34 Mon Sep 17 00:00:00 2001 From: Benjamin Marzinski Date: Tue, 25 Aug 2026 15:22:26 -0400 Subject: [PATCH 614/641] dm cache: fix issue with background work locking dm cache used a rw_semaphore for background_work_lock. Write locks on rw_semaphores have strict owner semantics, but there was no guarantee that the process that locked background_work_lock was the same process that unlocked it. This can be easily seen using a kernel compiled with CONFIG_DEBUG_RWSEMS. Given a dm cache device , run: 'dmsetup suspend && dmsetup resume '. This will trigger a kernel warning: DEBUG_RWSEMS_WARN_ON((rwsem_owner(sem) != current) && !rwsem_test_oflags(sem, RWSEM_NONSPINNABLE)) triggered by cache_resume(). To fix this, switch from a rw_semaphore to a spinlock and a wait queue. dm cache already has a wait queue and associated counter, migration_wait and nr_allocated_migrations, that was getting woken up when background work was getting completed, but wasn't actually used by anything. This is replaced by the background_work queue and counter. Fixes: b29d4986d0da ("dm cache: significant rework to leverage dm-bio-prison-v2") Cc: stable@vger.kernel.org Signed-off-by: Benjamin Marzinski Reviewed-by: Matthew Sakai Reviewed-by: Ming-Hung Tsai Signed-off-by: Mikulas Patocka --- drivers/md/dm-cache-target.c | 55 ++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/drivers/md/dm-cache-target.c b/drivers/md/dm-cache-target.c index 33dbc71b730f..d8d9c63d2a67 100644 --- a/drivers/md/dm-cache-target.c +++ b/drivers/md/dm-cache-target.c @@ -340,8 +340,6 @@ struct cache { struct list_head invalidation_requests; sector_t migration_threshold; - wait_queue_head_t migration_wait; - atomic_t nr_allocated_migrations; /* * The number of in flight migrations that are performing @@ -397,7 +395,11 @@ struct cache { bool loaded_mappings:1; bool loaded_discards:1; - struct rw_semaphore background_work_lock; + /* background work management */ + bool background_work_allowed; + unsigned background_work_nr; + spinlock_t background_work_lock; + wait_queue_head_t background_work_wait; struct batcher committer; struct work_struct commit_ws; @@ -488,19 +490,13 @@ static struct dm_cache_migration *alloc_migration(struct cache *cache) memset(mg, 0, sizeof(*mg)); mg->cache = cache; - atomic_inc(&cache->nr_allocated_migrations); return mg; } static void free_migration(struct dm_cache_migration *mg) { - struct cache *cache = mg->cache; - - if (atomic_dec_and_test(&cache->nr_allocated_migrations)) - wake_up(&cache->migration_wait); - - mempool_free(mg, &cache->migration_pool); + mempool_free(mg, &mg->cache->migration_pool); } /*----------------------------------------------------------------*/ @@ -1030,34 +1026,39 @@ static void calc_discard_block_range(struct cache *cache, struct bio *bio, static void prevent_background_work(struct cache *cache) { - lockdep_off(); - down_write(&cache->background_work_lock); - lockdep_on(); + spin_lock_irq(&cache->background_work_lock); + cache->background_work_allowed = false; + wait_event_lock_irq(cache->background_work_wait, + cache->background_work_nr == 0, + cache->background_work_lock); + spin_unlock_irq(&cache->background_work_lock); } static void allow_background_work(struct cache *cache) { - lockdep_off(); - up_write(&cache->background_work_lock); - lockdep_on(); + spin_lock_irq(&cache->background_work_lock); + cache->background_work_allowed = true; + spin_unlock_irq(&cache->background_work_lock); } static bool background_work_begin(struct cache *cache) { bool r; - lockdep_off(); - r = down_read_trylock(&cache->background_work_lock); - lockdep_on(); - + spin_lock_irq(&cache->background_work_lock); + r = cache->background_work_allowed; + if (r) + cache->background_work_nr++; + spin_unlock_irq(&cache->background_work_lock); return r; } static void background_work_end(struct cache *cache) { - lockdep_off(); - up_read(&cache->background_work_lock); - lockdep_on(); + spin_lock_irq(&cache->background_work_lock); + if (--cache->background_work_nr == 0) + wake_up(&cache->background_work_wait); + spin_unlock_irq(&cache->background_work_lock); } /*----------------------------------------------------------------*/ @@ -2507,9 +2508,7 @@ static int cache_create(struct cache_args *ca, struct cache **result) spin_lock_init(&cache->lock); bio_list_init(&cache->deferred_bios); - atomic_set(&cache->nr_allocated_migrations, 0); atomic_set(&cache->nr_io_migrations, 0); - init_waitqueue_head(&cache->migration_wait); r = -ENOMEM; atomic_set(&cache->nr_dirty, 0); @@ -2592,8 +2591,10 @@ static int cache_create(struct cache_args *ca, struct cache **result) issue_op, cache, cache->wq); dm_iot_init(&cache->tracker); - init_rwsem(&cache->background_work_lock); - prevent_background_work(cache); + init_waitqueue_head(&cache->background_work_wait); + spin_lock_init(&cache->background_work_lock); + cache->background_work_allowed = false; + cache->background_work_nr = 0; *result = cache; return 0; From b2fd92f016e9d692fd3c8c08d0ee014e9212279d Mon Sep 17 00:00:00 2001 From: Chen Cheng Date: Mon, 24 Aug 2026 19:34:49 +0800 Subject: [PATCH 615/641] dm-integrity: require stable writes for internal hash modes dm-integrity direct, bitmap and inline internal-hash modes compute integrity tags from the pages carried by the write bio. The lower data write also uses those pages, so the tag and the data write depend on the same memory contents staying unchanged while writeback is in flight. Without stable writes, a buffered writer can modify a writeback folio after dm-integrity has submitted the data bio and before the lower device has consumed the data. After a crash, this can leave data from the later contents with a tag calculated from the earlier contents, causing permanent checksum failures on read. Set BLK_FEAT_STABLE_WRITES for internal-hash D, B and I modes so filesystems wait for writeback folios to become stable before modifying them again. Journal mode is left unchanged because it copies data into the journal before computing and persisting the tag. Tested using dm-delay over a virtio-blk test disk, dm-integrity internal_hash:crc32c and no-journal ext4. The D and B reproducers both failed with checksum errors before this change and completed with READ_RC=0 and zero mismatches after it. Fixes: 7eada909bfd7 ("dm: add integrity target") Cc: stable@vger.kernel.org Reported-by: Sun Yangkai Link: https://github.com/chencheng-fnnas/reproducer/blob/main/dm-integrity-writeback-race.py Signed-off-by: Chen Cheng Signed-off-by: Mikulas Patocka --- drivers/md/dm-integrity.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/md/dm-integrity.c b/drivers/md/dm-integrity.c index c50feaa98bf9..0370d7d7ce72 100644 --- a/drivers/md/dm-integrity.c +++ b/drivers/md/dm-integrity.c @@ -4130,6 +4130,10 @@ static void dm_integrity_io_hints(struct dm_target *ti, struct queue_limits *lim limits->dma_alignment = limits->logical_block_size - 1; limits->discard_granularity = ic->sectors_per_block << SECTOR_SHIFT; + if (ic->internal_hash && + (ic->mode == 'D' || ic->mode == 'B' || ic->mode == 'I')) + limits->features |= BLK_FEAT_STABLE_WRITES; + if (!ic->internal_hash) { struct blk_integrity *bi = &limits->integrity; From 59e6f919d77d72ec79cbf171256f2f7819737580 Mon Sep 17 00:00:00 2001 From: Ben Cressey Date: Thu, 20 Aug 2026 21:44:57 +0000 Subject: [PATCH 616/641] dm-integrity: fix buffer overflow with keyed discard Since commit 68c5c42567bc ("dm-integrity: replace forgeable discard filler with a keyed sector marker"), integrity_metadata computes a checksum for every discarded block into the "checksums" buffer. integrity_sector_checksum always writes the whole digest. So if the tag size is smaller than the digest size, the checksum of the last block that fits into the buffer is written past the end of it. For example, with hmac(sha256) and tag size 16, a 4MiB discard writes 16 bytes past the kmalloc'ed page. Fix this by subtracting extra_space from the buffer size when computing max_blocks, like we do for writes. Fixes: 68c5c42567bc ("dm-integrity: replace forgeable discard filler with a keyed sector marker") Reviewed-by: Jose Fernandez (Anthropic) Signed-off-by: Ben Cressey Assisted-by: Claude:unspecified Signed-off-by: Mikulas Patocka --- drivers/md/dm-integrity.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/md/dm-integrity.c b/drivers/md/dm-integrity.c index 0370d7d7ce72..d4fe85d61d33 100644 --- a/drivers/md/dm-integrity.c +++ b/drivers/md/dm-integrity.c @@ -1980,7 +1980,7 @@ static void integrity_metadata(struct work_struct *w) if (unlikely(dio->op == REQ_OP_DISCARD)) { unsigned int bi_size = dio->bio_details.bi_iter.bi_size; unsigned int max_size = likely(checksums != checksums_onstack) ? PAGE_SIZE : HASH_MAX_DIGESTSIZE; - unsigned int max_blocks = max_size / ic->tag_size; + unsigned int max_blocks = (max_size - extra_space) / ic->tag_size; sector_t sector = dio->range.logical_sector; if (!ic->discard_keyed) From 18d80c77b4c7dd20699e81cedfbbff4e9d198f28 Mon Sep 17 00:00:00 2001 From: Ben Cressey Date: Thu, 20 Aug 2026 21:44:58 +0000 Subject: [PATCH 617/641] dm-integrity: fix infinite loop on discard with large tag size When integrity_metadata handles a discard, it fills a buffer with DISCARD_FILLER and writes it over the tags, max_blocks blocks at a time. If the kmalloc fails, the buffer is the on-stack array checksums_onstack and max_size is set to HASH_MAX_DIGESTSIZE. So if the tag size is larger than HASH_MAX_DIGESTSIZE, max_blocks is zero, bi_size is never decremented and the loop never terminates. Fix this by using sizeof(checksums_onstack) as max_size. The array has MAX_TAG_SIZE bytes since commit b93b6643e9b5 ("dm integrity: fix a crash with unusually large tag size"), so max_blocks is at least 1. Fixes: 84597a44a9d8 ("dm integrity: add optional discard support") Cc: stable@vger.kernel.org Reviewed-by: Jose Fernandez (Anthropic) Signed-off-by: Ben Cressey Assisted-by: Claude:unspecified Signed-off-by: Mikulas Patocka --- drivers/md/dm-integrity.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/md/dm-integrity.c b/drivers/md/dm-integrity.c index d4fe85d61d33..5327d7c6a71c 100644 --- a/drivers/md/dm-integrity.c +++ b/drivers/md/dm-integrity.c @@ -1979,7 +1979,7 @@ static void integrity_metadata(struct work_struct *w) if (unlikely(dio->op == REQ_OP_DISCARD)) { unsigned int bi_size = dio->bio_details.bi_iter.bi_size; - unsigned int max_size = likely(checksums != checksums_onstack) ? PAGE_SIZE : HASH_MAX_DIGESTSIZE; + unsigned int max_size = likely(checksums != checksums_onstack) ? PAGE_SIZE : sizeof(checksums_onstack); unsigned int max_blocks = (max_size - extra_space) / ic->tag_size; sector_t sector = dio->range.logical_sector; From 1d2929d0850fff683b8aff051275945e65f082c8 Mon Sep 17 00:00:00 2001 From: Norbert Szetei Date: Sat, 29 Aug 2026 18:56:18 +0200 Subject: [PATCH 618/641] net: psp: do not inherit the Rx association on clone sk->psp_assoc sits past sk_dontcopy_end, so sock_copy() copies it into every socket accepted from a listener without taking a reference, while inet_sock_destruct() puts for every inet socket. psp_twsk_init() does refcount_inc() for the timewait socket, so a child closing through TIME_WAIT cancels its own put and leaves the association with one reference and N timewait sockets holding the same pointer. Closing the listener frees it, and the timewait timers then put freed memory. Rejecting the association on a listening socket is not sufficient: a socket can acquire one while established and then be turned back into a listener, because tcp_disconnect() leaves sk->psp_assoc in place. BUG: KASAN: slab-use-after-free in psp_twsk_assoc_free+0x6f/0xf0 Write of size 4 at addr ffff888110f9255c by task swapper/7/0 psp_twsk_assoc_free+0x6f/0xf0 inet_twsk_put+0xda/0x1b0 call_timer_fn+0x53/0x2e0 __run_timers+0x764/0xa80 Freed by task 99: kfree+0x1a7/0x500 process_one_work+0x7ec/0x1100 An association carries a per-connection SPI and key, so a child must not inherit the parent's. Clear it on clone. Fixes: 6b46ca260e22 ("net: psp: add socket security association code") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: Norbert Szetei Reviewed-by: Daniel Zahka Link: https://patch.msgid.link/BC10EB92-ABB3-41B2-AB16-266BEEBE18C0@doyensec.com Signed-off-by: Paolo Abeni --- net/core/sock.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/core/sock.c b/net/core/sock.c index 1ad41904db25..fa60b7494c58 100644 --- a/net/core/sock.c +++ b/net/core/sock.c @@ -2494,6 +2494,9 @@ struct sock *sk_clone(const struct sock *sk, const gfp_t priority, #ifdef CONFIG_BPF_SYSCALL RCU_INIT_POINTER(newsk->sk_bpf_storage, NULL); #endif +#if IS_ENABLED(CONFIG_INET_PSP) + RCU_INIT_POINTER(newsk->psp_assoc, NULL); +#endif /* SANITY */ if (likely(newsk->sk_net_refcnt)) { From 2ccb8878c149443c6acf628b438c9c942c20abb2 Mon Sep 17 00:00:00 2001 From: Ming-Hung Tsai Date: Tue, 18 Aug 2026 18:05:47 +0800 Subject: [PATCH 619/641] dm cache: fix demotion stats in passthrough mode The demotion counter is incremented per incoming write bio before the invalidation begins, causing the demotion count to exceed the actual number of cached blocks when multiple bios target the same cached block. Additionally, the counter is incremented unconditionally regardless of invalidation failure. Reproduce steps: 1. Create a cache device consisting of 512 cache entries modprobe brd rd_size=262144 dmsetup create cmeta --table "0 8192 linear /dev/ram0 0" dmsetup create cdata --table "0 65536 linear /dev/ram0 8192" dmsetup create corig --table "0 65536 linear /dev/ram0 262144" dd if=/dev/zero of=/dev/mapper/cmeta bs=4k count=1 oflag=direct dmsetup create cache --table "0 65536 cache /dev/mapper/cmeta \ /dev/mapper/cdata /dev/mapper/corig 128 2 metadata2 writethrough smq 0" 2. Populate the cache, and record the number of cached blocks fio --name=populate --filename=/dev/mapper/cache --rw=randwrite --bs=4k \ --direct=1 --ioengine=libaio --iodepth=32 --io_size=2048m nr_cached=$(dmsetup status cache | awk '{split($7, a, "/"); print a[1]}') 3. Reload the cache into passthrough mode dmsetup suspend cache dmsetup reload cache --table "0 65536 cache /dev/mapper/cmeta \ /dev/mapper/cdata /dev/mapper/corig 128 2 metadata2 passthrough smq 0" dmsetup resume cache 4. Write to the passthrough cache with multiple jobs to trigger multiple bios hitting the same cached block. fio --filename=/dev/mapper/cache --name=test --rw=write --bs=4k \ --direct=1 --ioengine=libaio --iodepth=32 --numjobs=4 5. Check if demoted matches cached block count. These numbers should match but may differ due to overcounting per bio. nr_demoted=$(dmsetup status cache | awk '{print $12}') echo "$nr_cached, $nr_demoted" Fix by moving the demotion counter increment into invalidate_complete(), gated on the success flag. Reported-by: Ben Marzinski Fixes: b29d4986d0da ("dm cache: significant rework to leverage dm-bio-prison-v2") Cc: stable@vger.kernel.org Signed-off-by: Ming-Hung Tsai Reviewed-by: Benjamin Marzinski Signed-off-by: Mikulas Patocka --- drivers/md/dm-cache-target.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/md/dm-cache-target.c b/drivers/md/dm-cache-target.c index d8d9c63d2a67..1a5072425c4a 100644 --- a/drivers/md/dm-cache-target.c +++ b/drivers/md/dm-cache-target.c @@ -1463,6 +1463,9 @@ static void invalidate_complete(struct dm_cache_migration *mg, bool success) struct bio_list bios; struct cache *cache = mg->cache; + if (success) + atomic_inc(&cache->stats.demotion); + bio_list_init(&bios); if (mg->cell) { if (dm_cell_unlock_v2(cache->prison, mg->cell, &bios)) @@ -1734,7 +1737,6 @@ static int map_bio(struct cache *cache, struct bio *bio, dm_oblock_t block, if (passthrough_mode(cache)) { if (bio_data_dir(bio) == WRITE) { bio_drop_shared_lock(cache, bio); - atomic_inc(&cache->stats.demotion); invalidate_start(cache, cblock, block, bio); return DM_MAPIO_SUBMITTED; } else From 2625480a1bf79c62ffb09aafdf61778e682da492 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 1 Sep 2026 23:50:02 +0100 Subject: [PATCH 620/641] hardening: Default randstruct off with rust for better allmodconfig support Currently randstruct does not support rust so we have Kconfig dependencies which prevent rust being enabled when randstruct is. Unfortunately this prevents rust being enabled in allmodconfig, our standard coverage build. randstruct gets turned on by default, then the dependency on !RANDSTRUCT causes rust to get disabled. Work around this by disabling randstruct by default if we have a usable rust toolchain and rust support for the architecture, circular dependencies prevent us directly depending on !RUST. This means we might end up with a configuration that disables both rust and randstruct but hopefully it's more likely go give the expected result. Signed-off-by: Mark Brown Acked-by: Miguel Ojeda Link: https://patch.msgid.link/20260901-rust-reverse-randstruct-dep-v4-1-3bfa19efe1fa@kernel.org Signed-off-by: Kees Cook --- security/Kconfig.hardening | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/Kconfig.hardening b/security/Kconfig.hardening index 6923036e1a2f..81c81ad983ad 100644 --- a/security/Kconfig.hardening +++ b/security/Kconfig.hardening @@ -278,7 +278,7 @@ config CC_HAS_RANDSTRUCT choice prompt "Randomize layout of sensitive kernel structures" - default RANDSTRUCT_FULL if COMPILE_TEST && (GCC_PLUGINS || CC_HAS_RANDSTRUCT) + default RANDSTRUCT_FULL if !(RUST_IS_AVAILABLE && HAVE_RUST) && COMPILE_TEST && (GCC_PLUGINS || CC_HAS_RANDSTRUCT) default RANDSTRUCT_NONE help If you enable this, the layouts of structures that are entirely From b264d8422779d69febce914efc47a92a85cc382c Mon Sep 17 00:00:00 2001 From: Aswin Karuvally Date: Thu, 27 Aug 2026 08:34:08 +0200 Subject: [PATCH 621/641] s390/ctcm: Prevent XID null dereference The mpc_validate_xid() function sets grp->saved_xid2->xid2_flag2 to 0x40 to signal XID validation error. If peer XID is NULL or r/w channel pairing mismatch happens, grp->saved_xid2 is never initialized. An attempt to set the flag in such case leads to NULL dereference. Fix this by using the always available priv->xid->xid2_flag2 instead of grp->saved_xid2->xid2_flag2 for validation errors. Fixes: 293d984f0e36 ("ctcm: infrastructure for replaced ctc driver") Cc: stable@vger.kernel.org Signed-off-by: Aswin Karuvally Link: https://patch.msgid.link/20260827063408.2168914-1-aswin@linux.ibm.com Signed-off-by: Jakub Kicinski --- drivers/s390/net/ctcm_mpc.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/s390/net/ctcm_mpc.c b/drivers/s390/net/ctcm_mpc.c index 08e36685e578..61c88fe853c5 100644 --- a/drivers/s390/net/ctcm_mpc.c +++ b/drivers/s390/net/ctcm_mpc.c @@ -826,7 +826,7 @@ static void mpc_action_go_ready(fsm_instance *fsm, int event, void *arg) fsm_deltimer(&grp->timer); - if (grp->saved_xid2->xid2_flag2 == 0x40) { + if (priv->xid->xid2_flag2 == 0x40) { priv->xid->xid2_flag2 = 0x00; if (grp->estconnfunc) { grp->estconnfunc(grp->port_num, 1, @@ -1636,7 +1636,6 @@ done: "The XID used in the MPC protocol is not valid, " "rc = %d\n", rc); priv->xid->xid2_flag2 = 0x40; - grp->saved_xid2->xid2_flag2 = 0x40; } return rc; From 70f3995830d3f1e79faa14eb0605914f778feca9 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Mon, 31 Aug 2026 19:46:26 +0000 Subject: [PATCH 622/641] bonding: alb: fix uninitialized transport header access in alb_determine_nd() alb_determine_nd() uses icmp6_hdr(skb) to inspect ICMPv6 headers. However, in xmit paths (e.g. packets sent via AF_PACKET / raw sockets or forwarded packets), skb->transport_header is not guaranteed to be initialized. While pskb_network_may_pull() ensures the packet data is linear starting from the network header, it does not set or adjust the transport header offset. Dereferencing icmp6_hdr(skb) can therefore access out-of-bounds memory. Fetch the icmp6hdr directly after ipv6hdr following pskb_network_may_pull(), and reload ipv6hdr in case pskb_may_pull() reallocated skb->head. Also remove the unused bond argument from alb_determine_nd(). Fixes: 0da8aa00bfcf ("net: bonding: Add support for IPV6 ns/na to balance-alb/balance-tlb mode") Signed-off-by: Eric Dumazet Reviewed-by: Joe Damato Link: https://patch.msgid.link/20260831194626.119371-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- drivers/net/bonding/bond_alb.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/net/bonding/bond_alb.c b/drivers/net/bonding/bond_alb.c index d2fb67a47cf9..654f051d0023 100644 --- a/drivers/net/bonding/bond_alb.c +++ b/drivers/net/bonding/bond_alb.c @@ -1281,10 +1281,10 @@ unwind: } /* determine if the packet is NA or NS */ -static bool alb_determine_nd(struct sk_buff *skb, struct bonding *bond) +static bool alb_determine_nd(struct sk_buff *skb) { - struct ipv6hdr *ip6hdr; - struct icmp6hdr *hdr; + const struct ipv6hdr *ip6hdr; + const struct icmp6hdr *hdr; if (!pskb_network_may_pull(skb, sizeof(*ip6hdr))) return true; @@ -1296,7 +1296,8 @@ static bool alb_determine_nd(struct sk_buff *skb, struct bonding *bond) if (!pskb_network_may_pull(skb, sizeof(*ip6hdr) + sizeof(*hdr))) return true; - hdr = icmp6_hdr(skb); + ip6hdr = ipv6_hdr(skb); + hdr = (const struct icmp6hdr *)(ip6hdr + 1); return hdr->icmp6_type == NDISC_NEIGHBOUR_ADVERTISEMENT || hdr->icmp6_type == NDISC_NEIGHBOUR_SOLICITATION; } @@ -1381,7 +1382,7 @@ struct slave *bond_xmit_tlb_slave_get(struct bonding *bond, if (!is_multicast_ether_addr(eth_data->h_dest)) { switch (skb->protocol) { case htons(ETH_P_IPV6): - if (alb_determine_nd(skb, bond)) + if (alb_determine_nd(skb)) break; fallthrough; case htons(ETH_P_IP): @@ -1467,7 +1468,7 @@ struct slave *bond_xmit_alb_slave_get(struct bonding *bond, break; } - if (alb_determine_nd(skb, bond)) { + if (alb_determine_nd(skb)) { do_tx_balance = false; break; } From f695390ea63941a9e412bf1f3afe65ab245fc681 Mon Sep 17 00:00:00 2001 From: Sunil Goutham Date: Fri, 28 Aug 2026 14:49:45 +0530 Subject: [PATCH 623/641] octeontx2-af: Fix limiting SRIOV VF count logic When RVU PF0/AF's VFs are SDP instead of LBK, limiting the VF count based on the LBK channel count is incorrect. Apply LBK channel-based VF limits only when the VF device ID matches the LBK RVU AFVF device. Fixes: 9bd6caf33567 ("octeontx2-af: Enable sriov on AF to create VFs") Signed-off-by: Sunil Goutham Signed-off-by: Nitin Shetty J Signed-off-by: David S. Miller --- .../net/ethernet/marvell/octeontx2/af/rvu.c | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu.c index 74c041ab5280..937b085582b5 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu.c @@ -3468,6 +3468,8 @@ err: return ret; } +#define PCI_DEVID_OCTEONTX2_RVU_AFVF 0xA0F8 + static int rvu_enable_sriov(struct rvu *rvu) { struct pci_dev *pdev = rvu->pdev; @@ -3486,24 +3488,27 @@ static int rvu_enable_sriov(struct rvu *rvu) return 0; pci_read_config_word(pdev, pos + PCI_SRIOV_VF_DID, &rvu->vf_devid); - chans = rvu_get_num_lbk_chans(); - if (chans < 0) - return chans; - vfs = pci_sriov_get_totalvfs(pdev); - - /* Limit VFs in case we have more VFs than LBK channels available. */ - if (vfs > chans) - vfs = chans; - if (!vfs) return 0; - /* LBK channel number 63 is used for switching packets between - * CGX mapped VFs. Hence limit LBK pairs till 62 only. - */ - if (vfs > 62) - vfs = 62; + if (rvu->vf_devid == PCI_DEVID_OCTEONTX2_RVU_AFVF) { + chans = rvu_get_num_lbk_chans(); + if (chans < 0) + return chans; + + /* The last LBK channel is reserved for switching packets between + * CGX mapped VFs. Also, since LBK VFs work in pairs, limit VF + * count to available LBK channels minus 2. + */ + vfs = min(vfs, chans - 2); + + if (vfs <= 0) { + dev_warn(&pdev->dev, + "Skipping SRIOV enablement, not enough LBK channels available\n"); + return 0; + } + } /* Save VFs number for reference in VF interrupts handlers. * Since interrupts might start arriving during SRIOV enablement From 544d85de4dc22c01badfd8cefa59829ce35c4858 Mon Sep 17 00:00:00 2001 From: Chris Lew Date: Thu, 27 Aug 2026 17:48:46 +0530 Subject: [PATCH 624/641] net: qrtr: Send HELLO message on endpoint register HELLO is currently handled entirely by the name server (NS): it is sent once as a broadcast when the NS initializes, and again as a reply whenever the NS receives an inbound HELLO from a remote. Some remote QRTR endpoints (e.g. an external WLAN chipset attached over MHI) operate in a slave role: they only ever send a HELLO in response to one they receive, and never initiate. Since the host cannot tell in advance which remotes behave this way, if the host also only replies, both sides wait on the other to speak first and no HELLO is ever exchanged, stalling further communication. To fix this: - Transfer HELLO handshake ownership to the core layer. A HELLO is now sent once, per endpoint, at registration time. - Schedule a delayed work item on endpoint registration to send a HELLO once the name server is bound. The work reschedules itself with a 100ms backoff if the name server socket is not yet bound or if allocating the control packet fails, so a transient startup condition does not abandon the handshake permanently. - Enforce HELLO-first ordering by dropping non-HELLO packets and returning -EAGAIN until the HELLO is confirmed sent, using bool hello_sent guarded by ep_lock to make the gate check atomic with xmit(). - Skip nodes with nid == QRTR_EP_NID_AUTO in bcast_enqueue(), to avoid broadcasting control packets with QRTR_EP_NID_AUTO as the destination node ID. - Remove say_hello() from the name server's ctrl_cmd_hello() handler and from qrtr_ns_init(); the core layer is now the sole sender of the outbound HELLO. This removes the NS's reply-on-receive behaviour without a replacement. Signed-off-by: Chris Lew Co-developed-by: Deepak Kumar Singh Signed-off-by: Deepak Kumar Singh Co-developed-by: Pranav Mahesh Phansalkar Signed-off-by: Pranav Mahesh Phansalkar Signed-off-by: David S. Miller --- net/qrtr/af_qrtr.c | 66 ++++++++++++++++++++++++++++++++++++++++++++-- net/qrtr/ns.c | 35 +----------------------- 2 files changed, 65 insertions(+), 36 deletions(-) diff --git a/net/qrtr/af_qrtr.c b/net/qrtr/af_qrtr.c index a30fa56e6aa3..78347c937af7 100644 --- a/net/qrtr/af_qrtr.c +++ b/net/qrtr/af_qrtr.c @@ -9,6 +9,7 @@ #include /* For TIOCINQ/OUTQ */ #include #include +#include #include @@ -120,8 +121,10 @@ static DEFINE_XARRAY_ALLOC(qrtr_ports); * @nid: node id * @qrtr_tx_flow: xarray of qrtr_tx_flow, keyed by node << 32 | port * @qrtr_tx_lock: lock for qrtr_tx_flow inserts + * @hello_sent: hello packet send successful * @rx_queue: receive queue * @item: list item for broadcast list + * @say_hello: delayed work for sending hello packet */ struct qrtr_node { struct mutex ep_lock; @@ -132,8 +135,11 @@ struct qrtr_node { struct xarray qrtr_tx_flow; struct mutex qrtr_tx_lock; /* for qrtr_tx_flow */ + bool hello_sent; + struct sk_buff_head rx_queue; struct list_head item; + struct delayed_work say_hello; }; /** @@ -187,6 +193,8 @@ static void __qrtr_node_release(struct kref *kref) list_del(&node->item); mutex_unlock(&qrtr_node_lock); + cancel_delayed_work_sync(&node->say_hello); + skb_queue_purge(&node->rx_queue); /* Free tx flow counters */ @@ -341,6 +349,14 @@ static int qrtr_node_enqueue(struct qrtr_node *node, struct sk_buff *skb, size_t len = skb->len; int rc, confirm_rx; + mutex_lock(&node->ep_lock); + if (!node->hello_sent && type != QRTR_TYPE_HELLO) { + mutex_unlock(&node->ep_lock); + kfree_skb(skb); + return -EAGAIN; + } + mutex_unlock(&node->ep_lock); + confirm_rx = qrtr_tx_wait(node, to->sq_node, to->sq_port, type); if (confirm_rx < 0) { kfree_skb(skb); @@ -353,7 +369,7 @@ static int qrtr_node_enqueue(struct qrtr_node *node, struct sk_buff *skb, hdr->src_node_id = cpu_to_le32(from->sq_node); hdr->src_port_id = cpu_to_le32(from->sq_port); if (to->sq_port == QRTR_PORT_CTRL) { - hdr->dst_node_id = cpu_to_le32(node->nid); + hdr->dst_node_id = cpu_to_le32(READ_ONCE(node->nid)); hdr->dst_port_id = cpu_to_le32(QRTR_PORT_CTRL); } else { hdr->dst_node_id = cpu_to_le32(to->sq_node); @@ -372,6 +388,8 @@ static int qrtr_node_enqueue(struct qrtr_node *node, struct sk_buff *skb, rc = node->ep->xmit(node->ep, skb); else kfree_skb(skb); + if (!rc && type == QRTR_TYPE_HELLO) + node->hello_sent = true; mutex_unlock(&node->ep_lock); } /* Need to ensure that a subsequent message carries the otherwise lost @@ -379,6 +397,9 @@ static int qrtr_node_enqueue(struct qrtr_node *node, struct sk_buff *skb, if (rc && confirm_rx) qrtr_tx_flow_failed(node, to->sq_node, to->sq_port); + if (rc == -EAGAIN && type == QRTR_TYPE_HELLO) + schedule_delayed_work(&node->say_hello, msecs_to_jiffies(100)); + return rc; } @@ -416,7 +437,7 @@ static void qrtr_node_assign(struct qrtr_node *node, unsigned int nid) spin_lock_irqsave(&qrtr_nodes_lock, flags); radix_tree_insert(&qrtr_nodes, nid, node); if (node->nid == QRTR_EP_NID_AUTO) - node->nid = nid; + WRITE_ONCE(node->nid, nid); spin_unlock_irqrestore(&qrtr_nodes_lock, flags); } @@ -570,6 +591,38 @@ static struct sk_buff *qrtr_alloc_ctrl_packet(struct qrtr_ctrl_pkt **pkt, return skb; } +static void qrtr_hello_work(struct work_struct *work) +{ + struct sockaddr_qrtr from = {AF_QIPCRTR, 0, QRTR_PORT_CTRL}; + struct sockaddr_qrtr to = {AF_QIPCRTR, 0, QRTR_PORT_CTRL}; + struct qrtr_ctrl_pkt *pkt; + struct qrtr_node *node; + struct qrtr_sock *ctrl; + struct sk_buff *skb; + + node = container_of(to_delayed_work(work), struct qrtr_node, say_hello); + + /* NS must be bound before we can send; retry with backoff if not ready */ + ctrl = qrtr_port_lookup(QRTR_PORT_CTRL); + if (!ctrl) { + schedule_delayed_work(&node->say_hello, msecs_to_jiffies(100)); + return; + } + + skb = qrtr_alloc_ctrl_packet(&pkt, GFP_KERNEL); + if (!skb) { + qrtr_port_put(ctrl); + schedule_delayed_work(&node->say_hello, msecs_to_jiffies(100)); + return; + } + + pkt->cmd = cpu_to_le32(QRTR_TYPE_HELLO); + from.sq_node = qrtr_local_nid; + to.sq_node = node->nid; + qrtr_node_enqueue(node, skb, QRTR_TYPE_HELLO, &from, &to); + qrtr_port_put(ctrl); +} + /** * qrtr_endpoint_register() - register a new endpoint * @ep: endpoint to register @@ -595,6 +648,9 @@ int qrtr_endpoint_register(struct qrtr_endpoint *ep, unsigned int nid) node->nid = QRTR_EP_NID_AUTO; node->ep = ep; + node->hello_sent = false; + INIT_DELAYED_WORK(&node->say_hello, qrtr_hello_work); + xa_init(&node->qrtr_tx_flow); mutex_init(&node->qrtr_tx_lock); @@ -605,6 +661,9 @@ int qrtr_endpoint_register(struct qrtr_endpoint *ep, unsigned int nid) mutex_unlock(&qrtr_node_lock); ep->node = node; + /* Initiate HELLO handshake from the core layer */ + schedule_delayed_work(&node->say_hello, 0); + return 0; } EXPORT_SYMBOL_GPL(qrtr_endpoint_register); @@ -879,6 +938,9 @@ static int qrtr_bcast_enqueue(struct qrtr_node *node, struct sk_buff *skb, mutex_lock(&qrtr_node_lock); list_for_each_entry(node, &qrtr_all_nodes, item) { + /* Skip nodes with no assigned node ID yet. */ + if (READ_ONCE(node->nid) == QRTR_EP_NID_AUTO) + continue; skbn = pskb_copy(skb, GFP_KERNEL); if (!skbn) break; diff --git a/net/qrtr/ns.c b/net/qrtr/ns.c index c5e7e01db249..bcb090ee79d4 100644 --- a/net/qrtr/ns.c +++ b/net/qrtr/ns.c @@ -212,6 +212,7 @@ static void lookup_notify(struct sockaddr_qrtr *to, struct qrtr_server *srv, pr_err("failed to send lookup notification\n"); } +/* Announce the list of servers registered on the local node */ static int announce_servers(struct sockaddr_qrtr *sq) { struct qrtr_server *srv; @@ -326,38 +327,8 @@ static int server_del(struct qrtr_node *node, unsigned int port, bool bcast) return 0; } -static int say_hello(struct sockaddr_qrtr *dest) -{ - struct qrtr_ctrl_pkt pkt; - struct msghdr msg = { }; - struct kvec iv; - int ret; - - iv.iov_base = &pkt; - iv.iov_len = sizeof(pkt); - - memset(&pkt, 0, sizeof(pkt)); - pkt.cmd = cpu_to_le32(QRTR_TYPE_HELLO); - - msg.msg_name = (struct sockaddr *)dest; - msg.msg_namelen = sizeof(*dest); - - ret = kernel_sendmsg(qrtr_ns.sock, &msg, &iv, 1, sizeof(pkt)); - if (ret < 0) - pr_err("failed to send hello msg\n"); - - return ret; -} - -/* Announce the list of servers registered on the local node */ static int ctrl_cmd_hello(struct sockaddr_qrtr *sq) { - int ret; - - ret = say_hello(sq); - if (ret < 0) - return ret; - return announce_servers(sq); } @@ -774,10 +745,6 @@ int qrtr_ns_init(void) qrtr_ns.bcast_sq.sq_node = QRTR_NODE_BCAST; qrtr_ns.bcast_sq.sq_port = QRTR_PORT_CTRL; - ret = say_hello(&qrtr_ns.bcast_sq); - if (ret < 0) - goto err_wq; - /* As the qrtr ns socket owner and creator is the same module, we have * to decrease the qrtr module reference count to guarantee that it * remains zero after the ns socket is created, otherwise, executing From 7d4d4f3b668d708d94f62ecdd33ac330a6fd8a84 Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Wed, 2 Sep 2026 16:36:26 +0200 Subject: [PATCH 625/641] dm-integrity: fix NULL pointer dereference when the 'R' flag is used If the dm-integrity device has the SB_FLAG_DIRTY_BITMAP flag set and the user activates the device in the 'R' mode, a crash in dm_integrity_resume happens because the function attempts to read the journal containing the bitmap. This patch makes dm-integrity skip any writes to the device in dm_integrity_resume if the device is activated in the 'R' mode. Signed-off-by: Mikulas Patocka Fixes: 468dfca38b1a ("dm integrity: add a bitmap mode") Cc: stable@vger.kernel.org --- drivers/md/dm-integrity.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/md/dm-integrity.c b/drivers/md/dm-integrity.c index 5327d7c6a71c..92970e12267a 100644 --- a/drivers/md/dm-integrity.c +++ b/drivers/md/dm-integrity.c @@ -3875,6 +3875,10 @@ static void dm_integrity_resume(struct dm_target *ti) r = sync_rw_sb(ic, REQ_OP_READ); if (r) dm_integrity_io_error(ic, "reading superblock", r); + + if (ic->mode == 'R') + goto skip_writes; + if ((ic->sb->flags & flags) != flags) { ic->sb->flags |= flags; r = sync_rw_sb(ic, REQ_OP_WRITE | REQ_FUA); @@ -3984,6 +3988,7 @@ static void dm_integrity_resume(struct dm_target *ti) } } +skip_writes: ic->reboot_notifier.notifier_call = dm_integrity_reboot; ic->reboot_notifier.next = NULL; ic->reboot_notifier.priority = INT_MAX - 1; /* be notified after md and before hardware drivers */ From 7ac81e2d2240f2c57bd073b0733e0b2abca38e82 Mon Sep 17 00:00:00 2001 From: Genjian Zhang Date: Fri, 7 Aug 2026 23:57:15 +0800 Subject: [PATCH 626/641] dm-ebs: fix incorrect device offset check in ebs_ctr() is a backing-device sector offset; ti->len is the virtual target length. Comparing them rejects valid tables, e.g.: dmsetup create ebs0 --table "0 1048576 ebs /dev/sda 2097152 1 8" -> ebs: Invalid device offset sector (-EINVAL) Drop the check. Bounds against the backing device are already enforced later by device_area_is_invalid() via ebs_iterate_devices(). Cc: stable@vger.kernel.org Fixes: d3c7b35c20d6 ("dm: add emulated block size target") Signed-off-by: Genjian Zhang Signed-off-by: Mikulas Patocka --- drivers/md/dm-ebs-target.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/md/dm-ebs-target.c b/drivers/md/dm-ebs-target.c index 1e52bde48b91..5d67c6c19d4b 100644 --- a/drivers/md/dm-ebs-target.c +++ b/drivers/md/dm-ebs-target.c @@ -265,8 +265,7 @@ static int ebs_ctr(struct dm_target *ti, unsigned int argc, char **argv) r = -EINVAL; if (sscanf(argv[1], "%llu%c", &tmp, &dummy) != 1 || - tmp != (sector_t)tmp || - (sector_t)tmp >= ti->len) { + tmp != (sector_t)tmp) { ti->error = "Invalid device offset sector"; goto bad; } From af602c7aa5fedc9be3043244017aef4f26c96b70 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Mon, 31 Aug 2026 20:30:42 +0000 Subject: [PATCH 627/641] bonding: do not clear curr_active_slave prematurely when releasing all slaves When releasing all slaves during bond destruction (all == true), __bond_release_one() unconditionally clears bond->curr_active_slave to NULL in every iteration. If a backup slave is released before the active slave, bond_alb_deinit_slave() triggers rlb_teach_disabled_mac_on_primary(), which increments the active slave dev promiscuity counter and sets bond_info->primary_is_promisc = 1. Because bond->curr_active_slave was prematurely cleared to NULL when releasing the backup slave, the subsequent iteration releasing the active slave evaluates oldcurrent as NULL, so bond_change_active_slave(bond, NULL) is skipped. Consequently, bond_alb_handle_active_change() is never called to decrement the promiscuity counter, permanently leaking promiscuous mode on the physical device after bond teardown. When oldcurrent == slave, bond_change_active_slave(bond, NULL) already sets bond->curr_active_slave to NULL. We only need to avoid selecting a new active slave when all == true. Replace the if (all) branch with if (!all && oldcurrent == slave). Fixes: 0896341a44bf ("bonding: fix bond_release_all inconsistencies") Signed-off-by: Eric Dumazet Acked-by: Jay Vosburgh Reviewed-by: Nikolay Aleksandrov Link: https://patch.msgid.link/20260831203042.164466-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- drivers/net/bonding/bond_main.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c index 947d92a669b6..a9bff7663eec 100644 --- a/drivers/net/bonding/bond_main.c +++ b/drivers/net/bonding/bond_main.c @@ -2517,9 +2517,7 @@ static int __bond_release_one(struct net_device *bond_dev, bond_alb_deinit_slave(bond, slave); } - if (all) { - RCU_INIT_POINTER(bond->curr_active_slave, NULL); - } else if (oldcurrent == slave) { + if (!all && oldcurrent == slave) { /* Note that we hold RTNL over this sequence, so there * is no concern that another slave add/remove event * will interfere. From 5a3f7a683aee56e1f15c9d53041f3236767eaae7 Mon Sep 17 00:00:00 2001 From: Jun Yang Date: Mon, 31 Aug 2026 19:13:04 +0800 Subject: [PATCH 628/641] net: bridge: mcast: don't truncate the port group walk on teardown __br_multicast_disable_port_ctx() and br_multicast_del_port() walk port->mglist with hlist_for_each_entry_safe(). However, br_multicast_find_del_pg() can also delete other entries from the same list through br_multicast_fwd_src_remove() or __fwd_del_star_excl(). If such an entry is the iterator's saved next node, hlist_del_init() clears its ->next and terminates the walk early. The reproducer triggers this in both teardown walks, leaving port groups in the bridge mdb with a dangling ->key.port after del_nbp() frees the port: BUG: KASAN: slab-use-after-free in __mdb_fill_info+0x1191/0x1320 __mdb_fill_info+0x1191/0x1320 br_mdb_dump+0x594/0xe40 rtnl_mdb_dump+0x1cf/0x5d0 Use hlist_del_init_rcu() to unlink the group while preserving ->next. br_multicast_del_pg() and the teardown walks run under br->multicast_lock. The GC worker must acquire the same lock before detaching the group for destruction, so the node remains alive while the walk uses the preserved pointer. Preserving ->next means a walk can now reach a group that an earlier iteration already deleted as a side effect. That group is off mp->ports, so br_multicast_find_del_pg() would fall through its port scan and hit the trailing WARN_ON(1). Skip such groups at the top of that helper: a port group is put on port->mglist when it is created and only unlinked when it is deleted, so hlist_unhashed() identifies exactly this case. Fixes: b08123684bd5 ("net: bridge: mcast: install S,G entries automatically based on reports") Cc: stable@vger.kernel.org Suggested-by: Nikolay Aleksandrov Reported-by: TencentOS Corvus AI Signed-off-by: Jun Yang Reviewed-by: Nikolay Aleksandrov Link: https://patch.msgid.link/20260831111330.199543-1-junvyyang@tencent.com Signed-off-by: Jakub Kicinski --- net/bridge/br_multicast.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c index 3ef5d8bbf552..3e9b10f8abf1 100644 --- a/net/bridge/br_multicast.c +++ b/net/bridge/br_multicast.c @@ -808,7 +808,11 @@ void br_multicast_del_pg(struct net_bridge_mdb_entry *mp, struct hlist_node *tmp; rcu_assign_pointer(*pp, pg->next); - hlist_del_init(&pg->mglist); + /* Keep ->next (held under multicast_lock, freed later by the GC work): + * a port->mglist teardown walk may have latched this node as its next, + * and deleting other groups of the same port must not truncate it. + */ + hlist_del_init_rcu(&pg->mglist); br_multicast_eht_clean_sets(pg); hlist_for_each_entry_safe(ent, tmp, &pg->src_list, node) br_multicast_del_group_src(ent, false); @@ -835,6 +839,13 @@ static void br_multicast_find_del_pg(struct net_bridge *br, struct net_bridge_mdb_entry *mp; struct net_bridge_port_group *p; + /* A teardown walk over port->mglist can reach a group that an earlier + * iteration already deleted as a side effect. It is off mp->ports by + * now, so skip it instead of falling through to the WARN_ON() below. + */ + if (hlist_unhashed(&pg->mglist)) + return; + mp = br_mdb_ip_get(br, &pg->key.addr); if (WARN_ON(!mp)) return; From debac3a20dec524a59625cf10fa2f18571127824 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Tue, 1 Sep 2026 00:55:44 +0000 Subject: [PATCH 629/641] net: Remove conflicting altnames for dying netns in __dev_change_net_namespace(). syzbot reported the warning in cfg80211_pernet_exit(). [0] The repro does the following: 1. create two device in root netns and non-root netns 2. assign the same altname for the two devices 3. remove the non-root netns Since commit 7663d522099e ("net: check for altname conflicts when changing netdev's netns"), cfg80211_switch_netns() and cfg802154_switch_netns() fail if init_net has a device with the conflicting altname. default_device_exit_net() had the same issue and commit d09486a04f5d ("net: fix removing a namespace with conflicting altnames") fixed it. cfg80211_pernet_exit() and cfg802154_pernet_exit() need the same fix. Let's generalise the fix by removing conflicting altnames for dying netns in __dev_change_net_namespace(). [0]: cfg80211_switch_netns(rdev, &init_net) WARNING: net/wireless/core.c:1871 at cfg80211_pernet_exit+0xd5/0x120 net/wireless/core.c:1871, CPU#1: kworker/u8:9/1160 Modules linked in: CPU: 1 UID: 0 PID: 1160 Comm: kworker/u8:9 Not tainted syzkaller #0 PREEMPT(full) Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 07/24/2026 Workqueue: netns cleanup_net RIP: 0010:cfg80211_pernet_exit+0xd5/0x120 net/wireless/core.c:1871 Code: e8 03 42 80 3c 20 00 74 08 4c 89 f7 e8 b4 ef 0e f7 4d 8b 36 49 81 fe 20 10 4a 90 74 12 e8 03 3d 9f f6 eb 85 e8 fc 3c 9f f6 90 <0f> 0b 90 eb cc e8 f1 3c 9f f6 eb 05 e8 ea 3c 9f f6 5b 41 5c 41 5e RSP: 0018:ffffc900057a78f0 EFLAGS: 00010293 RAX: ffffffff8b287154 RBX: ffff88807ba72780 RCX: ffff8880213e8000 RDX: 0000000000000000 RSI: 00000000ffffffef RDI: 0000000000000000 RBP: 00000000ffffffef R08: ffffffff9024cc67 R09: 0000000000000000 R10: fffff52000af4eb0 R11: fffffbfff204998d R12: dffffc0000000000 R13: ffffffff904a1080 R14: ffff888144ed0008 R15: ffff888144ed0e20 FS: 0000000000000000(0000) GS:ffff888124de6000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00005642de0a8a70 CR3: 000000007a40c000 CR4: 00000000003526f0 Call Trace: ops_exit_list net/core/net_namespace.c:200 [inline] ops_undo_list+0x43d/0x8d0 net/core/net_namespace.c:253 cleanup_net+0x572/0x810 net/core/net_namespace.c:706 process_one_work kernel/workqueue.c:3387 [inline] process_scheduled_works+0xc3d/0x1630 kernel/workqueue.c:3470 worker_thread+0xa47/0xfb0 kernel/workqueue.c:3551 kthread+0x38b/0x480 kernel/kthread.c:436 ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245 Fixes: 36fbf1e52bd3 ("net: rtnetlink: add linkprop commands to add and delete alternative ifnames") Reported-by: syzbot+74f338e09f1ef3ee6457@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/6a96219e.04428c52.29b18.0001.GAE@google.com/T/ Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260901005550.2042357-1-kuniyu@google.com Signed-off-by: Jakub Kicinski --- net/core/dev.c | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/net/core/dev.c b/net/core/dev.c index 38336858c168..290e0f099e6b 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -12703,7 +12703,7 @@ int __dev_change_net_namespace(struct net_device *dev, struct net *net, const char *pat, int new_ifindex, struct netlink_ext_ack *extack) { - struct netdev_name_node *name_node; + struct netdev_name_node *name_node, *tmp; struct net *net_old = dev_net(dev); char new_name[IFNAMSIZ] = {}; int err, new_nsid; @@ -12749,13 +12749,19 @@ int __dev_change_net_namespace(struct net_device *dev, struct net *net, } /* Check that none of the altnames conflicts. */ err = -EEXIST; - netdev_for_each_altname(dev, name_node) { - if (netdev_name_in_use(net, name_node->name)) { - NL_SET_ERR_MSG_FMT(extack, - "An interface with the altname %s exists in the target netns", - name_node->name); - goto out; + netdev_for_each_altname_safe(dev, name_node, tmp) { + if (!netdev_name_in_use(net, name_node->name)) + continue; + + if (!check_net(net_old)) { + __netdev_name_node_alt_destroy(name_node); + continue; } + + NL_SET_ERR_MSG_FMT(extack, + "An interface with the altname %s exists in the target netns", + name_node->name); + goto out; } /* Check that new_ifindex isn't used yet. */ @@ -13210,7 +13216,6 @@ static struct pernet_operations __net_initdata netdev_net_ops = { static void __net_exit default_device_exit_net(struct net *net) { - struct netdev_name_node *name_node, *tmp; struct net_device *dev, *aux; /* * Push all migratable network devices back to the @@ -13234,10 +13239,6 @@ static void __net_exit default_device_exit_net(struct net *net) if (netdev_name_in_use(&init_net, fb_name)) snprintf(fb_name, IFNAMSIZ, "dev%%d"); - netdev_for_each_altname_safe(dev, name_node, tmp) - if (netdev_name_in_use(&init_net, name_node->name)) - __netdev_name_node_alt_destroy(name_node); - err = dev_change_net_namespace(dev, &init_net, fb_name); if (err) { pr_emerg("%s: failed to move %s to init_net: %d\n", From d85f521a9afb786b1d95bbcb218d3afdf3fe73ab Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Mon, 31 Aug 2026 13:31:28 +0200 Subject: [PATCH 630/641] net: macb: exclude software FCS from TX byte statistics Frames for which macb_pad_and_fcs() supplies the FCS have four FCS bytes appended, and TX completion then accounts the grown skb->len. tx_bytes is defined to exclude the FCS, so these frames are reported four bytes too large. Track only the number of FCS bytes appended in software, 0 or ETH_FCS_LEN, and subtract that from skb->len at completion. skb->len already reflects the padded length by then, so there is nothing else to store. macb_pad_and_fcs() already returns 0 on every non-error path. Return the FCS length from there instead, rather than recomputing the same check in the caller. BQL stays on the padded skb->len that netdev_tx_sent_queue() saw. Fixes: 653e92a9175e ("net: macb: add support for padding and fcs computation") Signed-off-by: Nicolai Buchwitz Link: https://patch.msgid.link/20260831113128.1678674-1-nb@tipi-net.de Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cadence/macb.h | 3 +++ drivers/net/ethernet/cadence/macb_main.c | 21 +++++++++++++-------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/drivers/net/ethernet/cadence/macb.h b/drivers/net/ethernet/cadence/macb.h index 1e1f52285a39..d6931c41f39d 100644 --- a/drivers/net/ethernet/cadence/macb.h +++ b/drivers/net/ethernet/cadence/macb.h @@ -968,6 +968,8 @@ struct macb_dma_desc_ptp { * of the frame * @mapping: DMA address of the skb's fragment buffer * @size: size of the DMA mapped buffer + * @fcs_len: FCS bytes appended in software, 0 or ETH_FCS_LEN, only + * set for the last buffer of the frame * @mapped_as_page: true when buffer was mapped with skb_frag_dma_map(), * false when buffer was mapped with dma_map_single() */ @@ -975,6 +977,7 @@ struct macb_tx_skb { struct sk_buff *skb; dma_addr_t mapping; size_t size; + u8 fcs_len; bool mapped_as_page; }; diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index 76ee4f506033..b1939da4c95a 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -1322,8 +1322,8 @@ static void macb_tx_error_task(struct work_struct *work) bp->netdev->stats.tx_packets++; queue->stats.tx_packets++; packets++; - bp->netdev->stats.tx_bytes += skb->len; - queue->stats.tx_bytes += skb->len; + bp->netdev->stats.tx_bytes += skb->len - tx_skb->fcs_len; + queue->stats.tx_bytes += skb->len - tx_skb->fcs_len; bytes += skb->len; } } else { @@ -1450,8 +1450,8 @@ static int macb_tx_complete(struct macb_queue *queue, int budget) skb->data); bp->netdev->stats.tx_packets++; queue->stats.tx_packets++; - bp->netdev->stats.tx_bytes += skb->len; - queue->stats.tx_bytes += skb->len; + bp->netdev->stats.tx_bytes += skb->len - tx_skb->fcs_len; + queue->stats.tx_bytes += skb->len - tx_skb->fcs_len; packets++; bytes += skb->len; } @@ -2199,7 +2199,8 @@ static void macb_poll_controller(struct net_device *netdev) static unsigned int macb_tx_map(struct macb *bp, struct macb_queue *queue, struct sk_buff *skb, - unsigned int hdrlen) + unsigned int hdrlen, + u8 fcs_len) { unsigned int f, nr_frags = skb_shinfo(skb)->nr_frags; unsigned int len, i, tx_head = queue->tx_head; @@ -2284,6 +2285,7 @@ static unsigned int macb_tx_map(struct macb *bp, /* This is the last buffer of the frame: save socket buffer */ tx_skb->skb = skb; + tx_skb->fcs_len = fcs_len; /* Update TX ring: update buffer descriptors in reverse order * to avoid race condition @@ -2417,6 +2419,7 @@ static inline int macb_clear_csum(struct sk_buff *skb) return 0; } +/* Returns a negative errno, or the FCS bytes appended (0 or ETH_FCS_LEN). */ static int macb_pad_and_fcs(struct sk_buff **skb, struct net_device *netdev) { bool cloned = skb_cloned(*skb) || skb_header_cloned(*skb) || @@ -2465,7 +2468,7 @@ add_fcs: skb_put_u8(*skb, (fcs >> 16) & 0xff); skb_put_u8(*skb, (fcs >> 24) & 0xff); - return 0; + return ETH_FCS_LEN; } static netdev_tx_t macb_start_xmit(struct sk_buff *skb, @@ -2478,6 +2481,7 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, netdev_tx_t ret = NETDEV_TX_OK; unsigned int hdrlen; unsigned long flags; + int fcs_len; bool is_lso; if (macb_clear_csum(skb)) { @@ -2485,7 +2489,8 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, return ret; } - if (macb_pad_and_fcs(&skb, netdev)) { + fcs_len = macb_pad_and_fcs(&skb, netdev); + if (fcs_len < 0) { dev_kfree_skb_any(skb); return ret; } @@ -2548,7 +2553,7 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, } /* Map socket buffer for DMA transfer */ - if (macb_tx_map(bp, queue, skb, hdrlen)) { + if (macb_tx_map(bp, queue, skb, hdrlen, fcs_len)) { dev_kfree_skb_any(skb); goto unlock; } From 08710f033e3e35704e45adf8a95b5043ece34899 Mon Sep 17 00:00:00 2001 From: Ian Lin Date: Mon, 31 Aug 2026 16:41:24 +0800 Subject: [PATCH 631/641] net: usb: qmi_wwan: add Compal EXM-G1x support The Compal EXM-G1x is a Qualcomm SDX12-based LTE modem. Add support for its QMI WWAN interface 8 using the DTR quirk. Tested on a Compal EXM-G1x modem. Signed-off-by: Ian Lin Link: https://patch.msgid.link/20260831084124.65074-1-jisayme@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/usb/qmi_wwan.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/usb/qmi_wwan.c b/drivers/net/usb/qmi_wwan.c index 8178a8758cd3..fdfdcf24ddcf 100644 --- a/drivers/net/usb/qmi_wwan.c +++ b/drivers/net/usb/qmi_wwan.c @@ -1446,6 +1446,7 @@ static const struct usb_device_id products[] = { {QMI_QUIRK_SET_DTR(0x2c7c, 0x0316, 3)}, /* Quectel RG255C */ {QMI_QUIRK_SET_DTR(0x2cb7, 0x0104, 4)}, /* Fibocom NL678 series */ {QMI_QUIRK_SET_DTR(0x2cb7, 0x0112, 0)}, /* Fibocom FG132 */ + {QMI_QUIRK_SET_DTR(0x04b7, 0x8217, 8)}, /* Compal EXM-G1x */ {QMI_FIXED_INTF(0x0489, 0xe0b4, 0)}, /* Foxconn T77W968 LTE */ {QMI_FIXED_INTF(0x0489, 0xe0b5, 0)}, /* Foxconn T77W968 LTE with eSIM support*/ {QMI_FIXED_INTF(0x2692, 0x9025, 4)}, /* Cellient MPL200 (rebranded Qualcomm 05c6:9025) */ From 6d0c8b7073913011459cf968cbbadd341e166bc3 Mon Sep 17 00:00:00 2001 From: Allison Henderson Date: Fri, 28 Aug 2026 15:39:15 -0700 Subject: [PATCH 632/641] net/rds: use wq_has_sleeper() in release_in_xmit() release_in_xmit() clears RDS_IN_XMIT with clear_bit_unlock() and then checks waitqueue_active() to decide whether anyone needs waking. clear_bit_unlock() is only a release operation: it orders the critical section before the bit clear, but does not order the subsequent plain load of the wait queue head after it. The waiter side does the mirror image - it adds itself to the wait queue and then tests the bit. That is the classic store-buffering pattern: the releasing CPU can read the wait queue as empty while the waiting CPU still reads the bit as set, so the sleeper is never woken. The waiters are rds_conn_shutdown() and rds_tcp_reset_callbacks(), both in uninterruptible wait_event() with no timeout. A lost wake-up strands the shutdown worker on its single-threaded workqueue until some other sender releases the bit again - and on a connection that is being torn down precisely because it failed, there may never be another sender. The barrier used to be there: release_in_xmit() did clear_bit() followed by smp_mb__after_atomic() until commit 1422f28826d2 ("rds: introduce acquire/release ordering in acquire/release_in_xmit()") folded both into clear_bit_unlock(), which strengthened the lock hand-off but silently dropped the full barrier the wake-up check depends on. The refill counterpart, release_refill() in net/rds/ib_recv.c, still carries its smp_mb__after_atomic() for exactly this reason. Use wq_has_sleeper(), which is waitqueue_active() preceded by the required full barrier. Fixes: 1422f28826d2 ("rds: introduce acquire/release ordering in acquire/release_in_xmit()") Signed-off-by: Allison Henderson Link: https://patch.msgid.link/20260828223921.202913-2-achender@kernel.org Signed-off-by: Jakub Kicinski --- net/rds/send.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/net/rds/send.c b/net/rds/send.c index 15a1b97f13e7..8aad185e4b1a 100644 --- a/net/rds/send.c +++ b/net/rds/send.c @@ -114,8 +114,13 @@ static void release_in_xmit(struct rds_conn_path *cp) * hot path and finding waiters is very rare. We don't want to walk * the system-wide hashed waitqueue buckets in the fast path only to * almost never find waiters. + * + * wq_has_sleeper() supplies the full barrier that orders the wait + * queue read after the bit clear; clear_bit_unlock() alone is only + * a release and would let this check read a stale empty queue, + * losing the wake-up. */ - if (waitqueue_active(&cp->cp_waitq)) + if (wq_has_sleeper(&cp->cp_waitq)) wake_up_all(&cp->cp_waitq); } From 17c4476dbb9c3bfd34193a6c22f2c3da8747134a Mon Sep 17 00:00:00 2001 From: Allison Henderson Date: Fri, 28 Aug 2026 15:39:16 -0700 Subject: [PATCH 633/641] net/rds: use clear_bit_unlock() in release_refill() release_refill() drops the RDS_RECV_REFILL bit with a plain clear_bit(). clear_bit() has no ordering semantics, and the smp_mb__after_atomic() that follows it sits on the wrong side for a lock release: it orders the clear against the waitqueue_active() load below it, but does nothing to order the refill critical section's ring and descriptor stores before the clear itself. That matters once connection teardown owns RDS_RECV_REFILL as a lock across the transport shutdown and path reset, rather than sampling it clear, which "net/rds: acquire the fastpath locks in rds_conn_shutdown()" later in this series arranges: on a weakly ordered architecture the teardown can win the bit and start the shutdown and reset while some of the refill's stores are not yet visible to it. The same gap existed under the sample-based scheme - a waiter that saw the bit clear had no guarantee it also observed the refill's stores - but taking the bit as a lock makes the missing release pairing load-bearing. Switch to clear_bit_unlock(), which orders the critical section before the release, and replace the open-coded barrier-plus-waitqueue_active() with wq_has_sleeper(), whose internal full barrier keeps the store-buffering guarantee between clearing the bit and checking for sleepers. This mirrors what "net/rds: use wq_has_sleeper() in release_in_xmit()" does for RDS_IN_XMIT. The fast-path acquire side, acquire_refill(), uses test_and_set_bit(), a full-barrier RMW that pairs with this release. The teardown at this point in the series still samples the bit, so on its own this change is release-side hardening; the shutdown-conversion patch named above makes the teardown acquire the bit with the same RMW, completing the pairing at the end of the series. Fixes: 73ce4317bf98 ("RDS: make sure we post recv buffers") Signed-off-by: Allison Henderson Link: https://patch.msgid.link/20260828223921.202913-3-achender@kernel.org Signed-off-by: Jakub Kicinski --- net/rds/ib_recv.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/net/rds/ib_recv.c b/net/rds/ib_recv.c index 357128d34a54..a6983861eec7 100644 --- a/net/rds/ib_recv.c +++ b/net/rds/ib_recv.c @@ -363,15 +363,14 @@ static int acquire_refill(struct rds_connection *conn) static void release_refill(struct rds_connection *conn) { - clear_bit(RDS_RECV_REFILL, &conn->c_flags); - smp_mb__after_atomic(); + clear_bit_unlock(RDS_RECV_REFILL, &conn->c_flags); /* We don't use wait_on_bit()/wake_up_bit() because our waking is in a * hot path and finding waiters is very rare. We don't want to walk * the system-wide hashed waitqueue buckets in the fast path only to * almost never find waiters. */ - if (waitqueue_active(&conn->c_waitq)) + if (wq_has_sleeper(&conn->c_waitq)) wake_up_all(&conn->c_waitq); } From 103c4b13c4f50322910078d1c02f29334a574122 Mon Sep 17 00:00:00 2001 From: Allison Henderson Date: Fri, 28 Aug 2026 15:39:17 -0700 Subject: [PATCH 634/641] net/rds: clear cp_flags bits individually in rds_conn_path_reset() rds_conn_path_reset() wipes the whole flag word with a plain cp->cp_flags = 0 store. Every other accessor of that word uses atomic bitops, and some of them can run concurrently with the reset: RDS_LL_SEND_FULL is set from rds_send_xmit() and cleared from the transport completion paths, neither of which holds anything that excludes the shutdown worker. A plain store racing an atomic read-modify-write on the same word is a data race, and whichever side loses has its update silently discarded. Clear the two bits the reset is actually responsible for instead. RDS_IN_XMIT and RDS_RECV_REFILL need no store at all here: they belong to the caller, rds_conn_shutdown(), which waits for both to be clear before calling the transport shutdown and this reset. This also gives every bit in cp_flags a single well-defined writer discipline, which the following patches rely on when they turn RDS_IN_XMIT and RDS_RECV_REFILL into bit locks held across the teardown: a blanket store mid-teardown would destroy lock ownership that an atomic clear preserves. Oracle UEK carries the same conversion ("net/rds: Preserve essential connection state flags"), motivated by its asynchronous shutdown state machine, whose progress and destroy flags must survive the reset. UEK's variant also clears RDS_IN_XMIT and RDS_RECV_REFILL because there the reset runs as the final step of a teardown that owns both bits, making those clears its unlock. Upstream that release belongs in rds_conn_shutdown(): once a later patch in this series turns the two bits into locks held across the teardown, ending ownership needs release semantics and a wake-up that a plain clear inside the reset would not provide. Based on Oracle UEK commit "net/rds: Preserve essential connection state flags" by Gerd Rausch. Fixes: 00e0f34c6166 ("RDS: Connection handling") Signed-off-by: Allison Henderson Link: https://patch.msgid.link/20260828223921.202913-4-achender@kernel.org Signed-off-by: Jakub Kicinski --- net/rds/connection.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/net/rds/connection.c b/net/rds/connection.c index 7c8ab8e973e1..46ac72088f84 100644 --- a/net/rds/connection.c +++ b/net/rds/connection.c @@ -120,7 +120,15 @@ static void rds_conn_path_reset(struct rds_conn_path *cp) rds_stats_inc(s_conn_reset); rds_send_path_reset(cp); - cp->cp_flags = 0; + + /* Clear the bits the reset is responsible for individually: a + * blanket cp_flags = 0 is a plain store that can clobber a + * concurrent atomic read-modify-write on the same word. + * RDS_IN_XMIT and RDS_RECV_REFILL belong to the caller, + * rds_conn_shutdown(), and are left alone here. + */ + clear_bit(RDS_LL_SEND_FULL, &cp->cp_flags); + clear_bit(RDS_RECONNECT_PENDING, &cp->cp_flags); /* Do not clear next_rx_seq here, else we cannot distinguish * retransmitted packets from new packets, and will hand all From e8e60d74fec49ccae2aea9b04a6eb162feb8d9af Mon Sep 17 00:00:00 2001 From: Gerd Rausch Date: Fri, 28 Aug 2026 15:39:18 -0700 Subject: [PATCH 635/641] net/rds: tcp: don't force RDS_CONN_RESETTING over a concurrent shutdown rds_tcp_reset_callbacks() resolves a duelling SYN by storing RDS_CONN_RESETTING into cp_state unconditionally. Nothing serializes that store against the shutdown path: rds_tcp_accept_one() checks for RDS_CONN_CONNECTING or RDS_CONN_ERROR under t_conn_path_lock, but neither rds_conn_path_drop(), which forces RDS_CONN_ERROR, nor rds_conn_shutdown(), which moves the path to RDS_CONN_DISCONNECTING under cp_cm_lock, takes that lock. The store can therefore land on top of a shutdown that is already in progress, or that gets queued right after the accept-side check. When it does, the shutdown worker's final DISCONNECTING -> DOWN transition fails and the path goes through rds_conn_path_error() and a second drop/shutdown cycle instead of a clean reconnect, tearing down the socket the accept path has just installed. Before commit ad22d24be635 ("net/rds: No shortcut out of RDS_CONN_ERROR") a path found in RDS_CONN_RESETTING even made rds_conn_shutdown() bail out altogether. Make the transition conditional: move CONNECTING -> RESETTING (or stay in RESETTING from an earlier duel), and drop the path in any other state. The drop has side effects of its own: it replaces the shutdown's RDS_CONN_DISCONNECTING (or RDS_CONN_ERROR) with RDS_CONN_ERROR and queues one more cp_down_w run. The difference is that rds_conn_shutdown() accepts RDS_CONN_ERROR in its final transition to RDS_CONN_DOWN, so the shutdown in flight completes normally instead of through rds_conn_path_error(); the extra down-work pass then finds the path already down and falls through to the reconnect check, or catches a reconnect that has already started and restarts it. The accept path still installs the new socket, rds_connect_path_complete() then fails its RESETTING -> UP transition and drops it: the raced socket ends up torn down as it does today. The comment at that call site, which promised that rds_connect_path_complete() marks the path RDS_CONN_UP, is updated to name this outcome as well. The state can change again between the failed transitions and the drop. That is inherent to rds_conn_path_drop(), which the socket state-change callbacks also call unconditionally, and costs at most one extra drop/reconnect cycle. Based on Oracle UEK commit "net/rds: Don't force state RDS_CONN_RESETTING" by Gerd Rausch. Fixes: 9c79440e2c5e ("RDS: TCP: fix race windows in send-path quiescence by rds_tcp_accept_one()") Signed-off-by: Gerd Rausch [achender: port to net-next: use the two-argument rds_conn_path_transition()/rds_conn_path_drop() and rewrite the changelog for the upstream shutdown path] Signed-off-by: Allison Henderson Link: https://patch.msgid.link/20260828223921.202913-5-achender@kernel.org Signed-off-by: Jakub Kicinski --- net/rds/tcp.c | 17 +++++++++++++++-- net/rds/tcp_listen.c | 6 +++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/net/rds/tcp.c b/net/rds/tcp.c index b263634ac750..ad14217867a4 100644 --- a/net/rds/tcp.c +++ b/net/rds/tcp.c @@ -150,9 +150,22 @@ void rds_tcp_reset_callbacks(struct socket *sock, * end up deadlocking with tcp_sendmsg(), and the RDS_IN_XMIT * would not get set. As a result, we set c_state to * RDS_CONN_RESETTTING, to ensure that rds_tcp_state_change - * cannot mark rds_conn_path_up() in the window before lock_sock() + * cannot mark rds_conn_path_up() in the window before lock_sock(). + * + * Only make that transition if the path is still connecting + * (or already resetting from an earlier duel). A path in any + * other state - typically RDS_CONN_DISCONNECTING or + * RDS_CONN_ERROR with a shutdown in flight - is dropped + * instead. That still replaces its state, with RDS_CONN_ERROR, + * and queues one more shutdown pass, but rds_conn_shutdown() + * accepts RDS_CONN_ERROR in its final transition to + * RDS_CONN_DOWN, so the shutdown in flight completes normally. */ - atomic_set(&cp->cp_state, RDS_CONN_RESETTING); + if (!rds_conn_path_transition(cp, RDS_CONN_CONNECTING, + RDS_CONN_RESETTING) && + !rds_conn_path_transition(cp, RDS_CONN_RESETTING, + RDS_CONN_RESETTING)) + rds_conn_path_drop(cp, 0); wait_event(cp->cp_waitq, !test_bit(RDS_IN_XMIT, &cp->cp_flags)); /* reset receive side state for rds_tcp_data_recv() for osock */ cancel_delayed_work_sync(&cp->cp_send_w); diff --git a/net/rds/tcp_listen.c b/net/rds/tcp_listen.c index a3db9b057084..13fa60c1985b 100644 --- a/net/rds/tcp_listen.c +++ b/net/rds/tcp_listen.c @@ -295,7 +295,11 @@ int rds_tcp_accept_one(struct rds_tcp_net *rtn) if (rs_tcp->t_sock) { /* Duelling SYN has been handled in rds_tcp_accept_one() */ rds_tcp_reset_callbacks(new_sock, cp); - /* rds_connect_path_complete() marks RDS_CONN_UP */ + /* rds_connect_path_complete() marks RDS_CONN_UP, or, + * if a concurrent shutdown won the duel, drops the + * path again and the pass that drop queues reaps the + * socket installed above. + */ rds_connect_path_complete(cp, RDS_CONN_RESETTING); } else { rds_tcp_set_callbacks(new_sock, cp); From 02c5f9dc2efd823e061954d564ce00bacd1bebeb Mon Sep 17 00:00:00 2001 From: Allison Henderson Date: Fri, 28 Aug 2026 15:39:19 -0700 Subject: [PATCH 636/641] net/rds: acquire RDS_IN_XMIT in rds_tcp_reset_callbacks() rds_tcp_reset_callbacks() quiesces the transmit path by setting the path state to RDS_CONN_RESETTING and then waiting for RDS_IN_XMIT to be sampled clear before swapping the underlying socket and calling rds_send_path_reset(). Sampling the bit clear is not the same as owning it: rds_send_xmit() can re-acquire RDS_IN_XMIT right after the wait_event() returns. Its state recheck after taking the lock is a store-buffering pattern (the resetter writes the state and reads the bit, the sender writes the bit and reads the state) and acquire_in_xmit() is only an acquire operation, so on weakly ordered architectures both sides can miss each other's write and the transmit path then runs concurrently with rds_send_path_reset() rewriting cp_xmit_* state - which is exactly what the comment above rds_send_path_reset() tells its callers to prevent. Take the lock instead, hold it across the socket swap and rds_send_path_reset(), and release it with a wake-up at the end. The lock-ordering constraint documented above the wait still holds: the lock is acquired before lock_sock(), so a sender inside tcp_sendmsg() can never be waited on while we hold the socket lock. Two details of the old code go away with the same change: - t_sock is now read only after the lock is acquired. The old code cached it before waiting; the teardown in rds_conn_shutdown() releases that socket and clears t_sock, so a pointer cached before the wait can be stale by the time the accept path resumes. Reading it under RDS_IN_XMIT is what makes the exclusion complete once the teardown owns the same lock, which the next patch arranges; until then the teardown still only samples the bit, and the two paths remain as exposed to each other as they are today. - The old !osock early path called rds_send_path_reset() with no serialization at all. It now runs under the lock like the normal path. The conditional RDS_CONN_RESETTING transition of the previous patch happens before the socket check either way: a path found without a socket is either still connecting (its reconnect worker blocked on t_conn_path_lock) and legitimately goes RESETTING -> UP on the new socket, or it has been torn down meanwhile and is dropped. The in-function comment describing the old wait-based quiesce is rewritten to describe the lock-based one, and the stale block comment above the function (which still described a return value and an incomplete list of t_sock writers) is refreshed to name all four writers - the connect, accept, teardown and swap paths - and what serializes each of them. Fixes: 335b48d980f6 ("RDS: TCP: Add/use rds_tcp_reset_callbacks to reset tcp socket safely") Signed-off-by: Allison Henderson Link: https://patch.msgid.link/20260828223921.202913-6-achender@kernel.org Signed-off-by: Jakub Kicinski --- net/rds/tcp.c | 70 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 25 deletions(-) diff --git a/net/rds/tcp.c b/net/rds/tcp.c index ad14217867a4..f4c83e368390 100644 --- a/net/rds/tcp.c +++ b/net/rds/tcp.c @@ -115,42 +115,48 @@ void rds_tcp_restore_callbacks(struct socket *sock, } /* - * rds_tcp_reset_callbacks() switches the to the new sock and - * returns the existing tc->t_sock. + * rds_tcp_reset_callbacks() switches a path to a new socket and + * releases the old one it finds in tc->t_sock, resolving a duelling + * SYN. * - * The only functions that set tc->t_sock are rds_tcp_set_callbacks - * and rds_tcp_reset_callbacks. Send and receive trust that - * it is set. The absence of RDS_CONN_UP bit protects those paths - * from being called while it isn't set. + * tc->t_sock is set by rds_tcp_set_callbacks() and cleared by + * rds_tcp_restore_callbacks(). Four paths write it: the active + * connect in rds_tcp_conn_path_connect(), which sets it and clears it + * again on failure; the accept path in rds_tcp_accept_one(), which + * sets it for a path with no socket yet; the teardown in + * rds_tcp_conn_path_shutdown(), which clears it; and the swap done + * here, which does both. The connect and accept paths are serialized + * against each other by t_conn_path_lock. Send and receive trust + * that it is set: the absence of RDS_CONN_UP protects those paths + * from being called while it isn't, and the swap done here runs under + * RDS_IN_XMIT so that it cannot interleave with a sender already + * inside rds_send_xmit(). */ void rds_tcp_reset_callbacks(struct socket *sock, struct rds_conn_path *cp) { struct rds_tcp_connection *tc = cp->cp_transport_data; - struct socket *osock = tc->t_sock; - - if (!osock) - goto newsock; + struct socket *osock; /* Need to resolve a duelling SYN between peers. * We have an outstanding SYN to this peer, which may * potentially have transitioned to the RDS_CONN_UP state, * so we must quiesce any send threads before resetting - * cp_transport_data. We quiesce these threads by setting - * cp_state to something other than RDS_CONN_UP, and then - * waiting for any existing threads in rds_send_xmit to - * complete release_in_xmit(). (Subsequent threads entering - * rds_send_xmit() will bail on !rds_conn_up(). + * cp_transport_data. Setting cp_state to something other + * than RDS_CONN_UP stops new senders, and owning RDS_IN_XMIT + * excludes any thread already inside rds_send_xmit() for the + * whole socket swap and the rds_send_path_reset() below. * - * However an incoming syn-ack at this point would end up - * marking the conn as RDS_CONN_UP, and would again permit - * rds_send_xmi() threads through, so ideally we would - * synchronize on RDS_CONN_UP after lock_sock(), but cannot - * do that: waiting on !RDS_IN_XMIT after lock_sock() may - * end up deadlocking with tcp_sendmsg(), and the RDS_IN_XMIT - * would not get set. As a result, we set c_state to - * RDS_CONN_RESETTTING, to ensure that rds_tcp_state_change - * cannot mark rds_conn_path_up() in the window before lock_sock(). + * An incoming syn-ack at this point would end up marking the + * conn as RDS_CONN_UP, and would again permit rds_send_xmit() + * threads through, so ideally we would synchronize on + * RDS_CONN_UP after lock_sock(), but cannot do that: acquiring + * RDS_IN_XMIT after lock_sock() may end up deadlocking with + * tcp_sendmsg(), which takes the socket lock while holding + * RDS_IN_XMIT. As a result, we set c_state to + * RDS_CONN_RESETTING, to ensure that rds_tcp_state_change + * cannot mark rds_conn_path_up() in the window before + * lock_sock(). * * Only make that transition if the path is still connecting * (or already resetting from an earlier duel). A path in any @@ -166,7 +172,18 @@ void rds_tcp_reset_callbacks(struct socket *sock, !rds_conn_path_transition(cp, RDS_CONN_RESETTING, RDS_CONN_RESETTING)) rds_conn_path_drop(cp, 0); - wait_event(cp->cp_waitq, !test_bit(RDS_IN_XMIT, &cp->cp_flags)); + wait_event(cp->cp_waitq, + !test_and_set_bit_lock(RDS_IN_XMIT, &cp->cp_flags)); + + /* Read t_sock only while owning RDS_IN_XMIT, never before the + * wait: the teardown in rds_conn_shutdown() releases the old + * socket and clears t_sock, so a pointer sampled earlier can + * be stale by the time we wake up. + */ + osock = tc->t_sock; + if (!osock) + goto newsock; + /* reset receive side state for rds_tcp_data_recv() for osock */ cancel_delayed_work_sync(&cp->cp_send_w); cancel_delayed_work_sync(&cp->cp_recv_w); @@ -185,6 +202,9 @@ newsock: lock_sock(sock->sk); rds_tcp_set_callbacks(sock, cp); release_sock(sock->sk); + + clear_bit_unlock(RDS_IN_XMIT, &cp->cp_flags); + wake_up_all(&cp->cp_waitq); } /* Add tc to rds_tcp_tc_list and set tc->t_sock. See comments From 813f3582ac7ae9f60f917937d54660e0952d5f2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Bugge?= Date: Fri, 28 Aug 2026 15:39:20 -0700 Subject: [PATCH 637/641] net/rds: acquire the fastpath locks in rds_conn_shutdown() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rds_conn_shutdown() quiesces the transmit and receive-refill paths by waiting for RDS_IN_XMIT and RDS_RECV_REFILL to be sampled clear, and then runs the transport shutdown and rds_conn_path_reset(). Sampling the bits clear is not the same as owning them: the moment after the wait_event() returns, rds_send_xmit() can re-acquire RDS_IN_XMIT (or rds_ib_recv_refill() can re-acquire RDS_RECV_REFILL) and run concurrently with the teardown. The sender does recheck the connection state after taking the lock, but that recheck is a classic store-buffering pattern: teardown writes the state and reads the bit while the sender writes the bit and reads the state. acquire_in_xmit() is only an acquire operation, so on weakly ordered architectures both sides can miss each other's write, and the transmit path then runs while the transport zeroes its rings (e.g. rds_ib_ring_init()) and rds_send_path_reset() rewrites the transmit state under it. Oracle UEK fixed the same class of crashes - a 14-year tail of BUG_ON()s in rds_ib_sub_signaled(), unexpected op-codes and NULL dereferences in rds_ib_send_cqe_handler() during failover testing - by making the teardown path *acquire* the fastpath bit locks instead of testing them ("rds: Make sure transmit path and connection tear-down does not run concurrently"). Ownership of a single word is decided by RMW atomicity, so no cross-variable ordering is needed. Do the same here: take both locks before calling the transport shutdown, hold them across rds_conn_path_reset(), and release them explicitly with a wake-up afterwards. Both are released with clear_bit_unlock(), so that the ring re-initialization done by the transport shutdown and the transmit state rewritten by rds_send_path_reset() are ordered before either bit is seen clear by the next acquire_in_xmit() or acquire_refill(). The fastpath users of these bits - rds_send_xmit() and rds_ib_recv_refill() - are trylock style and back off while teardown owns the locks, so no new lock dependency is introduced for them. rds_tcp_reset_callbacks() is different: since the previous patch it acquires RDS_IN_XMIT as well, and it blocks doing so, so its wait now spans the teardown instead of at most one send batch. That waiter runs from rds_tcp_accept_one() on the single-threaded krdsd workqueue and holds rds_tcp_accept_lock and t_conn_path_lock while it waits, so a duelling SYN accepted while its path is being torn down parks accept processing for the duration of the teardown - for TCP bounded by the (up to 5 s) drain loop in rds_tcp_conn_path_shutdown(). An IB path's drain in rds_ib_conn_path_shutdown() has no round cap, but no blocking waiter either: rds_tcp_reset_callbacks() is the only blocking acquirer of these bits and waits only on its own TCP path, and the fastpaths are trylock-and-back-off on both transports, so a long IB drain lengthens only that path's own quiesce. The window is narrow: the accept-side state check has to pass before the teardown moves the path to RDS_CONN_DISCONNECTING. Because krdsd is a single global workqueue, everything else queued there - accept processing for other connections and network namespaces, and the flush_workqueue(rds_wq) in rds_tcp_listen_stop() during namespace teardown - waits behind the parked accept worker for that time. It cannot deadlock, although the waits do point at each other: the teardown blocks until the bit's holder releases it, and the holder may be that krdsd accept worker. The holder finishes without needing anything the teardown owns: the sync cancels rds_tcp_reset_callbacks() issues target cp_send_w and cp_recv_w on the path's ordered cp_wq, whose only execution slot is occupied by the blocked cp_down_w itself, so they are pending at most and cancel without flushing - a reliance on cp_wq being ordered that is now noted next to those cancels (on the allocation-failure fallback where a path shares rds_wq, the work items simply serialize). Nor is the blocking wait itself new: rds_tcp_reset_callbacks() has waited on RDS_IN_XMIT from the krdsd work item since commit 335b48d980f6 ("RDS: TCP: Add/use rds_tcp_reset_callbacks to reset tcp socket safely"); this patch stretches its worst case from a sender's batch to the teardown's drain. The alternative to parking is the accept path racing the teardown, which is what these patches close; making the teardown itself non-blocking is a separate item. One observable side effect: the SENDING flag reported by rds-info has always mirrored RDS_IN_XMIT, so it now also covers the window where teardown owns the bit. The comments that describe the old sample-based handshake or name rds_send_xmit() as the only other holder of these bits - in rds_send_xmit(), above rds_conn_path_reset(), in rds_ib_recv_refill() and in rds_tcp_reset_callbacks() - are updated to match. For anyone backporting this patch standalone: it depends on "net/rds: clear cp_flags bits individually in rds_conn_path_reset()" and "net/rds: acquire RDS_IN_XMIT in rds_tcp_reset_callbacks()" earlier in this series. Without the former, the blanket cp_flags clear in rds_conn_path_reset() would drop both held bits in the middle of the teardown; without the latter, rds_tcp_reset_callbacks() would still sample t_sock without owning RDS_IN_XMIT. "net/rds: use clear_bit_unlock() in release_refill()" is needed for the refill side's release to pair with the acquire added here, and the follow-up "net/rds: don't let rds_conn_shutdown() consume a concurrent drop" completes the teardown-state handling for the waiter this patch parks; a backport should carry all four. Fixes: 0f4b1c7e89e6 ("rds: fix rds_send_xmit() serialization") Signed-off-by: Håkon Bugge [achender: reimplement for net-next shutdown path: acquire the existing RDS_IN_XMIT/RDS_RECV_REFILL bit locks in rds_conn_shutdown() and release after teardown; update comments and commit message] Signed-off-by: Allison Henderson Link: https://patch.msgid.link/20260828223921.202913-7-achender@kernel.org Signed-off-by: Jakub Kicinski --- net/rds/connection.c | 40 ++++++++++++++++++++++++++++++++-------- net/rds/ib_recv.c | 4 +++- net/rds/send.c | 7 +++++-- net/rds/tcp.c | 19 +++++++++++++++---- 4 files changed, 55 insertions(+), 15 deletions(-) diff --git a/net/rds/connection.c b/net/rds/connection.c index 46ac72088f84..fbbac55a0e81 100644 --- a/net/rds/connection.c +++ b/net/rds/connection.c @@ -106,10 +106,12 @@ static struct rds_connection *rds_conn_lookup(struct net *net, } /* - * This is called by transports as they're bringing down a connection. - * It clears partial message state so that the transport can start sending - * and receiving over this connection again in the future. It is up to - * the transport to have serialized this call with its send and recv. + * This is called by rds_conn_shutdown() once the transport has brought + * a path down. It clears partial message state so that the transport + * can start sending and receiving over this path again in the future. + * The caller owns RDS_IN_XMIT and RDS_RECV_REFILL across this call, + * which is what serializes it against the send and receive-refill + * paths. */ static void rds_conn_path_reset(struct rds_conn_path *cp) { @@ -124,8 +126,9 @@ static void rds_conn_path_reset(struct rds_conn_path *cp) /* Clear the bits the reset is responsible for individually: a * blanket cp_flags = 0 is a plain store that can clobber a * concurrent atomic read-modify-write on the same word. - * RDS_IN_XMIT and RDS_RECV_REFILL belong to the caller, - * rds_conn_shutdown(), and are left alone here. + * RDS_IN_XMIT and RDS_RECV_REFILL are held as locks by the + * caller, rds_conn_shutdown(), which releases them once the + * teardown is complete. */ clear_bit(RDS_LL_SEND_FULL, &cp->cp_flags); clear_bit(RDS_RECONNECT_PENDING, &cp->cp_flags); @@ -414,14 +417,35 @@ void rds_conn_shutdown(struct rds_conn_path *cp) } mutex_unlock(&cp->cp_cm_lock); + /* Quiesce the transmit and receive-refill paths by + * acquiring their bit locks, not merely waiting for + * them to be released: with a plain wait, either path + * can re-take its lock the instant after we sample it + * clear and then run concurrently with the transport + * shutdown and the path reset below. Holding both + * locks across the teardown makes that structurally + * impossible. + */ wait_event(cp->cp_waitq, - !test_bit(RDS_IN_XMIT, &cp->cp_flags)); + !test_and_set_bit_lock(RDS_IN_XMIT, &cp->cp_flags)); wait_event(cp->cp_waitq, - !test_bit(RDS_RECV_REFILL, &cp->cp_flags)); + !test_and_set_bit(RDS_RECV_REFILL, &cp->cp_flags)); conn->c_trans->conn_path_shutdown(cp); rds_conn_path_reset(cp); + /* Release the two locks and wake any waiter (e.g. + * rds_tcp_reset_callbacks()) that blocked on them while + * we held them. The unlock orders the transport's ring + * re-initialization and the path reset above before + * either bit is seen clear. rds_conn_path_reset() leaves + * both bits alone: ownership ends here, not inside the + * reset. + */ + clear_bit_unlock(RDS_IN_XMIT, &cp->cp_flags); + clear_bit_unlock(RDS_RECV_REFILL, &cp->cp_flags); + wake_up_all(&cp->cp_waitq); + if (!rds_conn_path_transition(cp, RDS_CONN_DISCONNECTING, RDS_CONN_DOWN) && !rds_conn_path_transition(cp, RDS_CONN_ERROR, diff --git a/net/rds/ib_recv.c b/net/rds/ib_recv.c index a6983861eec7..bd6cb3ffaa57 100644 --- a/net/rds/ib_recv.c +++ b/net/rds/ib_recv.c @@ -391,7 +391,9 @@ void rds_ib_recv_refill(struct rds_connection *conn, int prefill, gfp_t gfp) /* the goal here is to just make sure that someone, somewhere * is posting buffers. If we can't get the refill lock, - * let them do their thing + * let them do their thing. The holder may also be + * rds_conn_shutdown() tearing the path down, in which case + * there is nothing to post. */ if (!acquire_refill(conn)) return; diff --git a/net/rds/send.c b/net/rds/send.c index 8aad185e4b1a..1afa981e5c06 100644 --- a/net/rds/send.c +++ b/net/rds/send.c @@ -244,8 +244,11 @@ restart: WRITE_ONCE(cp->cp_send_gen, send_gen); /* - * rds_conn_shutdown() sets the conn state and then tests RDS_IN_XMIT, - * we do the opposite to avoid races. + * rds_conn_shutdown() sets the conn state and then acquires + * RDS_IN_XMIT; we take the lock first and then check the state. + * Ownership is decided by the atomic RMW on the cp_flags word: + * if the teardown won the bit we back off here, and if we won + * it the teardown waits until we release it. */ if (!rds_conn_path_up(cp)) { release_in_xmit(cp); diff --git a/net/rds/tcp.c b/net/rds/tcp.c index f4c83e368390..69c6d3145b5a 100644 --- a/net/rds/tcp.c +++ b/net/rds/tcp.c @@ -144,8 +144,10 @@ void rds_tcp_reset_callbacks(struct socket *sock, * so we must quiesce any send threads before resetting * cp_transport_data. Setting cp_state to something other * than RDS_CONN_UP stops new senders, and owning RDS_IN_XMIT - * excludes any thread already inside rds_send_xmit() for the - * whole socket swap and the rds_send_path_reset() below. + * excludes any thread already inside rds_send_xmit() - or a + * teardown in rds_conn_shutdown(), which holds the same lock + * for the duration of the transport shutdown - for the whole + * socket swap and the rds_send_path_reset() below. * * An incoming syn-ack at this point would end up marking the * conn as RDS_CONN_UP, and would again permit rds_send_xmit() @@ -178,13 +180,22 @@ void rds_tcp_reset_callbacks(struct socket *sock, /* Read t_sock only while owning RDS_IN_XMIT, never before the * wait: the teardown in rds_conn_shutdown() releases the old * socket and clears t_sock, so a pointer sampled earlier can - * be stale by the time we wake up. + * be stale by the time we wake up. The teardown holds the + * same lock while it does so, so what we read here cannot + * change under us until we release it. */ osock = tc->t_sock; if (!osock) goto newsock; - /* reset receive side state for rds_tcp_data_recv() for osock */ + /* reset receive side state for rds_tcp_data_recv() for osock. + * + * The sync cancels while owning RDS_IN_XMIT rely on cp_wq + * being ordered: a teardown blocked on the bit occupies + * cp_wq's only execution slot, so cp_send_w and cp_recv_w are + * pending at most and the cancels never flush. Nothing here + * may flush or wait on cp_wq itself. + */ cancel_delayed_work_sync(&cp->cp_send_w); cancel_delayed_work_sync(&cp->cp_recv_w); lock_sock(osock->sk); From 260c6308fe2e19ad519389d44d582e292aecc3af Mon Sep 17 00:00:00 2001 From: Allison Henderson Date: Fri, 28 Aug 2026 15:39:21 -0700 Subject: [PATCH 638/641] net/rds: don't let rds_conn_shutdown() consume a concurrent drop rds_conn_shutdown() finishes by moving the path from RDS_CONN_DISCONNECTING to RDS_CONN_DOWN, and also accepts RDS_CONN_ERROR as the starting state of that final transition, so that a FIN processed in softirq context during the teardown does not derail the shutdown into a noisy error path. But consuming that RDS_CONN_ERROR also consumes the shutdown pass that came with it: rds_conn_path_drop() sets RDS_CONN_ERROR and then queues cp_down_w, and a pass that starts on a path already in RDS_CONN_DOWN is a no-op. For the FIN case that is harmless - the socket the FIN arrived on is the very socket the teardown just released. It is not harmless for a dropper that attached something to the path first. rds_tcp_accept_one() is such a dropper. Its path claim in rds_tcp_accept_one_path() transitions RDS_CONN_DOWN -> RDS_CONN_CONNECTING, and a concurrent drop - a FIN on a previous socket in softirq context, an administrative reset - can put the path into RDS_CONN_ERROR between that claim and the state check that follows, which accepts RDS_CONN_ERROR. The accept then installs the freshly accepted socket with rds_tcp_set_callbacks() while the queued teardown - which sampled tc->t_sock before this socket existed - is still running. rds_connect_path_complete() fails its transition to RDS_CONN_UP and drops the path again, queueing the pass that should reap the socket it just installed. If the in-flight shutdown's final transition consumes that drop's RDS_CONN_ERROR, the queued pass finds the path in RDS_CONN_DOWN and does nothing. The installed socket is never torn down: it sits established with its callbacks armed and its rds_tcp_connection on rds_tcp_tc_list, the peer sees a connection that nothing ever reads, and the path is wedged in RDS_CONN_DOWN until some later event drops it again. Reproduced with widened race windows as an ever-growing receive queue on a socket owned by a path stuck in RDS_CONN_DOWN, with the peer's send path wedged behind it. Make the final transition only DISCONNECTING -> DOWN. If it fails because the path is in RDS_CONN_ERROR, a drop raced the teardown: cancel the reconnect timer and clear RDS_RECONNECT_PENDING - the one piece of the skipped tail that must not be left behind - and return, letting the pass the drop queued finish the job: it tears down whatever attached to the path in the meantime, completes the transition to RDS_CONN_DOWN, and re-arms the reconnect from its own tail. The timer quiesce in that branch matters because the racing drop does not always queue that pass: rds_conn_path_drop() returns without queueing when a destroy is pending - exactly the situation during a netns teardown or module unload, when a FIN on the dying socket is processed while rds_conn_path_destroy() flushes cp_down_w. If the flushed pass is the one that takes this return, no later pass exists, and rds_conn_path_destroy() would find cp_conn_w still armed (WARN_ON) and then free a path whose reconnect timer can still fire. With the cancel in the branch, every exit of a shutdown pass leaves the timer quiesced no matter which pass completes the transition. The FIN case keeps making progress, one pass later and still without noisy logging. Any other state keeps today's rds_conn_path_error() handling; no current cp_state writer can leave a DISCONNECTING path in anything but RDS_CONN_ERROR (every other writer is a cmpxchg from a non-DISCONNECTING state), so that branch is defensive. On kernels without the preceding patches the same hazard exists with the sample-based quiesce; the fix applies there equally. Fixes: e97656d03ca0 ("rds: tcp: allow progress of rds_conn_shutdown if the rds_connection is marked ERROR by an intervening FIN") Signed-off-by: Allison Henderson Link: https://patch.msgid.link/20260828223921.202913-8-achender@kernel.org Signed-off-by: Jakub Kicinski --- net/rds/connection.c | 43 ++++++++++++++++++++++++++++++++----------- net/rds/tcp.c | 9 ++++++--- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/net/rds/connection.c b/net/rds/connection.c index fbbac55a0e81..b6c4beb50eaf 100644 --- a/net/rds/connection.c +++ b/net/rds/connection.c @@ -447,19 +447,40 @@ void rds_conn_shutdown(struct rds_conn_path *cp) wake_up_all(&cp->cp_waitq); if (!rds_conn_path_transition(cp, RDS_CONN_DISCONNECTING, - RDS_CONN_DOWN) && - !rds_conn_path_transition(cp, RDS_CONN_ERROR, RDS_CONN_DOWN)) { - /* This can happen - eg when we're in the middle of tearing - * down the connection, and someone unloads the rds module. - * Quite reproducible with loopback connections. - * Mostly harmless. + /* The path was dropped again while we tore it + * down: by a socket state-change callback in + * irq context on receipt of a FIN, or by an + * accept that claimed the path just before a + * drop put it back to RDS_CONN_ERROR and then + * installed a fresh socket on it. Unless a + * pending destroy suppressed it, the drop also + * queued another shutdown pass, and that pass + * must run, because it is what tears down + * whatever attached to the path after the + * transport shutdown above sampled its state. + * Consuming the RDS_CONN_ERROR here would turn + * that pass into a no-op: leave the state + * alone, and let the pass finish the job. * - * Note that this also happens with rds-tcp because - * we could have triggered rds_conn_path_drop in irq - * mode from rds_tcp_state change on the receipt of - * a FIN, thus we need to recheck for RDS_CONN_ERROR - * here. + * Quiesce the reconnect timer before bailing + * out, though. When a pending destroy did + * suppress the queue, no later pass runs, and + * rds_conn_path_destroy() is about to flush + * cp_down_w and free the path: it must not + * find cp_conn_w still armed. A successor + * pass, when there is one, re-arms the + * reconnect from its own tail. + */ + cancel_delayed_work_sync(&cp->cp_conn_w); + clear_bit(RDS_RECONNECT_PENDING, &cp->cp_flags); + + if (rds_conn_path_state(cp) == RDS_CONN_ERROR) + return; + /* No current cp_state writer leaves a + * DISCONNECTING path in any state but + * RDS_CONN_ERROR; report loudly if one ever + * does. */ rds_conn_path_error(cp, "%s: failed to transition " "to state DOWN, current state " diff --git a/net/rds/tcp.c b/net/rds/tcp.c index 69c6d3145b5a..774a71f88d37 100644 --- a/net/rds/tcp.c +++ b/net/rds/tcp.c @@ -165,9 +165,12 @@ void rds_tcp_reset_callbacks(struct socket *sock, * other state - typically RDS_CONN_DISCONNECTING or * RDS_CONN_ERROR with a shutdown in flight - is dropped * instead. That still replaces its state, with RDS_CONN_ERROR, - * and queues one more shutdown pass, but rds_conn_shutdown() - * accepts RDS_CONN_ERROR in its final transition to - * RDS_CONN_DOWN, so the shutdown in flight completes normally. + * and, unless a pending destroy is about to reap the whole + * connection anyway, queues one more shutdown pass. A shutdown + * already in flight leaves that RDS_CONN_ERROR alone when it + * finishes; the queued pass then completes the transition to + * RDS_CONN_DOWN and tears down anything that attached to the + * path in the meantime. */ if (!rds_conn_path_transition(cp, RDS_CONN_CONNECTING, RDS_CONN_RESETTING) && From 7db28abbea0f7dc1ec4fdfdc149db5fbd9e4c994 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Sun, 30 Aug 2026 14:28:27 +0200 Subject: [PATCH 639/641] net: airoha: enable RX_DONE interrupt for RX queue 31 RX queue 31 has always been allocated and filled by airoha_qdma_init_rx() since RX_DONE_INT_MASK spans queues 0-31, but none of the RX_IRQ* _BANK_PIN_MASK values covered BIT(31). As a consequence the RX_DONE interrupt for queue 31 was never enabled, airoha_qdma_rx_process() never ran on that queue and its buffers were never reaped. Route RX queue 31's RX_DONE interrupt to IRQ bank 1 so that the queue is drained and its buffers returned to the page pool. Fixes: f252493e1835 ("net: airoha: Enable multiple IRQ lines support in airoha_eth driver.") Signed-off-by: Lorenzo Bianconi Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260830-airoha-rxdone-rxq31-v1-1-830a91503f2f@kernel.org Signed-off-by: Paolo Abeni --- drivers/net/ethernet/airoha/airoha_eth.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h index fa9a8edce22f..8277c1c87bb3 100644 --- a/drivers/net/ethernet/airoha/airoha_eth.h +++ b/drivers/net/ethernet/airoha/airoha_eth.h @@ -538,7 +538,7 @@ struct airoha_wdma_info { /* RX queue to IRQ mapping: BIT(q) in IRQ(n) */ #define RX_IRQ0_BANK_PIN_MASK 0x839f -#define RX_IRQ1_BANK_PIN_MASK 0x7fe00000 +#define RX_IRQ1_BANK_PIN_MASK 0xffe00000 #define RX_IRQ2_BANK_PIN_MASK 0x20 #define RX_IRQ3_BANK_PIN_MASK 0x40 #define RX_IRQ_BANK_PIN_MASK(_n) \ From 6b8fed2675fb75d23e6cf2b7e49c94926e884b34 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Mon, 31 Aug 2026 19:06:38 +0200 Subject: [PATCH 640/641] net: stmmac: reconfigure RX packet parser table in stmmac_hw_setup() after reset The core software reset issued in stmmac_init_dma_engine() during ndo_open() callback clears the MTL RX packet parser registers, but stmmac_rxp_config() is only invoked from the cls_u32 add/delete paths. After an ifdown/ifup cycle the hardware therefore runs with the default all-pass table while priv->tc_entries still reports the filters as installed. Re-apply the RX packet parser table from priv->tc_entries in stmmac_hw_setup(), right after the software reset, so the filters are restored when the interface is brought up again. Fixes: 4dbbe8dde848 ("net: stmmac: Add support for U32 TC filter using Flexible RX Parser") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260831-stmmac_tc_cls32_reconfigure-v1-1-21cb459e64ae@oss.qualcomm.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index d576059c04df..24656b35350b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -3676,6 +3676,14 @@ static int stmmac_hw_setup(struct net_device *dev) /* Initialize MTL*/ stmmac_mtl_configuration(priv); + /* Apply the RX packet parser table */ + if (priv->tc_entries) { + ret = stmmac_rxp_config(priv, priv->hw->pcsr, priv->tc_entries, + priv->tc_entries_max); + if (ret) + return ret; + } + /* Initialize Safety Features */ stmmac_safety_feat_configuration(priv); From 66817a9794263cd2a5dc4e99bf8e5fcc5ff7181e Mon Sep 17 00:00:00 2001 From: HW He Date: Tue, 1 Sep 2026 16:23:12 +0800 Subject: [PATCH 641/641] net: gro: Fix nesting of TCP GSO SKBs in skb_gro_receive_list() Fraglist GRO and hardware GRO can create an fraglist of HW-GRO packets. This cannot be segmented back into the original form on TCP tethering scenario. Avoid constructing such a GSO packet, by flushing an already built fraglist GRO packet if a hardware GRO packet arrives. Scenario (Tethering/Forwarding): 1.Driver submits a single TCP packet, P1. P1 is kept in the gro_list as the first packet. 2. The driver submits a TCP GSO skb, P2. P2 has already aggregated multiple TCP packets by HW_GRO, and its non-linear data is stored in frags[]. 3. P1 and P2 match the GRO rules, and since there is no local socket, they are aggregated by skb_gro_receive_list(). The resulting skb, P3, has a frag_list entry that still contains frags[]: P3: [ Linear Data ] -> frag_list -> [ Linear Data ] [ frag[1] ] [ frag[2] ] ... 4. Later, tcp4_gso_segment() or tcp6_gso_segment() calls skb_segment_list() to segment P3. However, skb_segment_list() only segments the entries in frag_list. It does not segment the frags[] inside P2, so P3 is not restored to the original packets, which leads to IP fragmentation or packet drop in the following path. Check skb_is_gso(skb) and current GRO method, make sure fraglist GRO applies to consecutive non-GSO skb, others adopt regular GRO path. Fixes: 8d95dc474f85 ("net: add code for TCP fraglist GRO") Signed-off-by: Zhaoping Shu Signed-off-by: HW He Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260901082312.14596-1-zhaoping.shu@mediatek.com Signed-off-by: Paolo Abeni --- net/ipv4/tcp_offload.c | 22 ++++++++++++++++------ net/ipv6/tcpv6_offload.c | 15 +++++++++++++-- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/net/ipv4/tcp_offload.c b/net/ipv4/tcp_offload.c index 3b1fdcd3cb29..e74d99ca9fac 100644 --- a/net/ipv4/tcp_offload.c +++ b/net/ipv4/tcp_offload.c @@ -332,6 +332,7 @@ struct sk_buff *tcp_gro_receive(struct list_head *head, struct sk_buff *skb, flush |= skb->ip_summed != p->ip_summed; flush |= skb->csum_level != p->csum_level; flush |= NAPI_GRO_CB(p)->count >= 64; + flush |= NAPI_GRO_CB(p)->is_flist != NAPI_GRO_CB(skb)->is_flist; skb_set_network_header(skb, skb_gro_receive_network_offset(skb)); if (flush || skb_gro_receive_list(p, skb)) @@ -395,12 +396,20 @@ static void tcp4_check_fraglist_gro(struct list_head *head, struct sk_buff *skb, struct net *net; int iif, sdif; - if (likely(!(skb->dev->features & NETIF_F_GRO_FRAGLIST))) - return; - p = tcp_gro_lookup(head, th); if (p) { - NAPI_GRO_CB(skb)->is_flist = NAPI_GRO_CB(p)->is_flist; + /* flist GRO applies to consecutive non-GSO skbs */ + if (!skb_is_gso(skb) || !NAPI_GRO_CB(p)->is_flist) { + NAPI_GRO_CB(skb)->is_flist = NAPI_GRO_CB(p)->is_flist; + return; + } + + /* Fall back to the regular GRO path */ + if (NAPI_GRO_CB(p)->count == 1) + NAPI_GRO_CB(p)->is_flist = 0; + + NAPI_GRO_CB(skb)->is_flist = 0; + return; } @@ -410,7 +419,7 @@ static void tcp4_check_fraglist_gro(struct list_head *head, struct sk_buff *skb, sk = __inet_lookup_established(net, iph->saddr, th->source, iph->daddr, ntohs(th->dest), iif, sdif); - NAPI_GRO_CB(skb)->is_flist = !sk; + NAPI_GRO_CB(skb)->is_flist = !sk && !skb_is_gso(skb); if (sk) sock_gen_put(sk); } @@ -430,7 +439,8 @@ struct sk_buff *tcp4_gro_receive(struct list_head *head, struct sk_buff *skb) if (!th) goto flush; - tcp4_check_fraglist_gro(head, skb, th); + if (unlikely(skb->dev->features & NETIF_F_GRO_FRAGLIST)) + tcp4_check_fraglist_gro(head, skb, th); return tcp_gro_receive(head, skb, th); diff --git a/net/ipv6/tcpv6_offload.c b/net/ipv6/tcpv6_offload.c index f2a659cd6183..eec3778855eb 100644 --- a/net/ipv6/tcpv6_offload.c +++ b/net/ipv6/tcpv6_offload.c @@ -26,7 +26,18 @@ static void tcp6_check_fraglist_gro(struct list_head *head, struct sk_buff *skb, p = tcp_gro_lookup(head, th); if (p) { - NAPI_GRO_CB(skb)->is_flist = NAPI_GRO_CB(p)->is_flist; + /* flist GRO applies to consecutive non-GSO skbs */ + if (!skb_is_gso(skb) || !NAPI_GRO_CB(p)->is_flist) { + NAPI_GRO_CB(skb)->is_flist = NAPI_GRO_CB(p)->is_flist; + return; + } + + /* Fall back to the regular GRO path */ + if (NAPI_GRO_CB(p)->count == 1) + NAPI_GRO_CB(p)->is_flist = 0; + + NAPI_GRO_CB(skb)->is_flist = 0; + return; } @@ -36,7 +47,7 @@ static void tcp6_check_fraglist_gro(struct list_head *head, struct sk_buff *skb, sk = __inet6_lookup_established(net, &hdr->saddr, th->source, &hdr->daddr, ntohs(th->dest), iif, sdif); - NAPI_GRO_CB(skb)->is_flist = !sk; + NAPI_GRO_CB(skb)->is_flist = !sk && !skb_is_gso(skb); if (sk) sock_gen_put(sk); #endif /* IS_ENABLED(CONFIG_IPV6) */