From da142f3d373a6ddaca0119615a8db2175ddc4121 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Fri, 5 Dec 2025 15:26:55 -0800 Subject: KVM: Remove subtle "struct kvm_stats_desc" pseudo-overlay Remove KVM's internal pseudo-overlay of kvm_stats_desc, which subtly aliases the flexible name[] in the uAPI definition with a fixed-size array of the same name. The unusual embedded structure results in compiler warnings due to -Wflex-array-member-not-at-end, and also necessitates an extra level of dereferencing in KVM. To avoid the "overlay", define the uAPI structure to have a fixed-size name when building for the kernel. Opportunistically clean up the indentation for the stats macros, and replace spaces with tabs. No functional change intended. Reported-by: Gustavo A. R. Silva Closes: https://lore.kernel.org/all/aPfNKRpLfhmhYqfP@kspp Acked-by: Marc Zyngier Acked-by: Christian Borntraeger [..] Acked-by: Anup Patel Reviewed-by: Bibo Mao Acked-by: Gustavo A. R. Silva Link: https://patch.msgid.link/20251205232655.445294-1-seanjc@google.com Signed-off-by: Sean Christopherson --- include/linux/kvm_host.h | 83 ++++++++++++++++++++---------------------------- include/uapi/linux/kvm.h | 8 +++++ 2 files changed, 43 insertions(+), 48 deletions(-) (limited to 'include') diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h index d93f75b05ae2..7428d9949382 100644 --- a/include/linux/kvm_host.h +++ b/include/linux/kvm_host.h @@ -1927,56 +1927,43 @@ enum kvm_stat_kind { struct kvm_stat_data { struct kvm *kvm; - const struct _kvm_stats_desc *desc; + const struct kvm_stats_desc *desc; enum kvm_stat_kind kind; }; -struct _kvm_stats_desc { - struct kvm_stats_desc desc; - char name[KVM_STATS_NAME_SIZE]; -}; - -#define STATS_DESC_COMMON(type, unit, base, exp, sz, bsz) \ - .flags = type | unit | base | \ - BUILD_BUG_ON_ZERO(type & ~KVM_STATS_TYPE_MASK) | \ - BUILD_BUG_ON_ZERO(unit & ~KVM_STATS_UNIT_MASK) | \ - BUILD_BUG_ON_ZERO(base & ~KVM_STATS_BASE_MASK), \ - .exponent = exp, \ - .size = sz, \ +#define STATS_DESC_COMMON(type, unit, base, exp, sz, bsz) \ + .flags = type | unit | base | \ + BUILD_BUG_ON_ZERO(type & ~KVM_STATS_TYPE_MASK) | \ + BUILD_BUG_ON_ZERO(unit & ~KVM_STATS_UNIT_MASK) | \ + BUILD_BUG_ON_ZERO(base & ~KVM_STATS_BASE_MASK), \ + .exponent = exp, \ + .size = sz, \ .bucket_size = bsz -#define VM_GENERIC_STATS_DESC(stat, type, unit, base, exp, sz, bsz) \ - { \ - { \ - STATS_DESC_COMMON(type, unit, base, exp, sz, bsz), \ - .offset = offsetof(struct kvm_vm_stat, generic.stat) \ - }, \ - .name = #stat, \ - } -#define VCPU_GENERIC_STATS_DESC(stat, type, unit, base, exp, sz, bsz) \ - { \ - { \ - STATS_DESC_COMMON(type, unit, base, exp, sz, bsz), \ - .offset = offsetof(struct kvm_vcpu_stat, generic.stat) \ - }, \ - .name = #stat, \ - } -#define VM_STATS_DESC(stat, type, unit, base, exp, sz, bsz) \ - { \ - { \ - STATS_DESC_COMMON(type, unit, base, exp, sz, bsz), \ - .offset = offsetof(struct kvm_vm_stat, stat) \ - }, \ - .name = #stat, \ - } -#define VCPU_STATS_DESC(stat, type, unit, base, exp, sz, bsz) \ - { \ - { \ - STATS_DESC_COMMON(type, unit, base, exp, sz, bsz), \ - .offset = offsetof(struct kvm_vcpu_stat, stat) \ - }, \ - .name = #stat, \ - } +#define VM_GENERIC_STATS_DESC(stat, type, unit, base, exp, sz, bsz) \ +{ \ + STATS_DESC_COMMON(type, unit, base, exp, sz, bsz), \ + .offset = offsetof(struct kvm_vm_stat, generic.stat), \ + .name = #stat, \ +} +#define VCPU_GENERIC_STATS_DESC(stat, type, unit, base, exp, sz, bsz) \ +{ \ + STATS_DESC_COMMON(type, unit, base, exp, sz, bsz), \ + .offset = offsetof(struct kvm_vcpu_stat, generic.stat), \ + .name = #stat, \ +} +#define VM_STATS_DESC(stat, type, unit, base, exp, sz, bsz) \ +{ \ + STATS_DESC_COMMON(type, unit, base, exp, sz, bsz), \ + .offset = offsetof(struct kvm_vm_stat, stat), \ + .name = #stat, \ +} +#define VCPU_STATS_DESC(stat, type, unit, base, exp, sz, bsz) \ +{ \ + STATS_DESC_COMMON(type, unit, base, exp, sz, bsz), \ + .offset = offsetof(struct kvm_vcpu_stat, stat), \ + .name = #stat, \ +} /* SCOPE: VM, VM_GENERIC, VCPU, VCPU_GENERIC */ #define STATS_DESC(SCOPE, stat, type, unit, base, exp, sz, bsz) \ SCOPE##_STATS_DESC(stat, type, unit, base, exp, sz, bsz) @@ -2053,7 +2040,7 @@ struct _kvm_stats_desc { STATS_DESC_IBOOLEAN(VCPU_GENERIC, blocking) ssize_t kvm_stats_read(char *id, const struct kvm_stats_header *header, - const struct _kvm_stats_desc *desc, + const struct kvm_stats_desc *desc, void *stats, size_t size_stats, char __user *user_buffer, size_t size, loff_t *offset); @@ -2098,9 +2085,9 @@ static inline void kvm_stats_log_hist_update(u64 *data, size_t size, u64 value) extern const struct kvm_stats_header kvm_vm_stats_header; -extern const struct _kvm_stats_desc kvm_vm_stats_desc[]; +extern const struct kvm_stats_desc kvm_vm_stats_desc[]; extern const struct kvm_stats_header kvm_vcpu_stats_header; -extern const struct _kvm_stats_desc kvm_vcpu_stats_desc[]; +extern const struct kvm_stats_desc kvm_vcpu_stats_desc[]; #ifdef CONFIG_KVM_GENERIC_MMU_NOTIFIER static inline int mmu_invalidate_retry(struct kvm *kvm, unsigned long mmu_seq) diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h index dddb781b0507..76bd54848b11 100644 --- a/include/uapi/linux/kvm.h +++ b/include/uapi/linux/kvm.h @@ -14,6 +14,10 @@ #include #include +#ifdef __KERNEL__ +#include +#endif + #define KVM_API_VERSION 12 /* @@ -1579,7 +1583,11 @@ struct kvm_stats_desc { __u16 size; __u32 offset; __u32 bucket_size; +#ifdef __KERNEL__ + char name[KVM_STATS_NAME_SIZE]; +#else char name[]; +#endif }; #define KVM_GET_STATS_FD _IO(KVMIO, 0xce) -- cgit v1.2.3 From 4ced4cf5c9d172d91f181df3accdf949d3761aab Mon Sep 17 00:00:00 2001 From: Andrei Vagin Date: Tue, 17 Feb 2026 18:01:05 +0000 Subject: binfmt_elf_fdpic: fix AUXV size calculation for ELF_HWCAP3 and ELF_HWCAP4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 4e6e8c2b757f ("binfmt_elf: Wire up AT_HWCAP3 at AT_HWCAP4") added support for AT_HWCAP3 and AT_HWCAP4, but it missed updating the AUX vector size calculation in create_elf_fdpic_tables() and AT_VECTOR_SIZE_BASE in include/linux/auxvec.h. Similar to the fix for AT_HWCAP2 in commit c6a09e342f8e ("binfmt_elf_fdpic: fix AUXV size calculation when ELF_HWCAP2 is defined"), this omission leads to a mismatch between the reserved space and the actual number of AUX entries, eventually triggering a kernel BUG_ON(csp != sp). Fix this by incrementing nitems when ELF_HWCAP3 or ELF_HWCAP4 are defined and updating AT_VECTOR_SIZE_BASE. Cc: Mark Brown Cc: Max Filippov Reviewed-by: Michal Koutný Reviewed-by: Mark Brown Reviewed-by: Cyrill Gorcunov Reviewed-by: Alexander Mikhalitsyn Fixes: 4e6e8c2b757f ("binfmt_elf: Wire up AT_HWCAP3 at AT_HWCAP4") Signed-off-by: Andrei Vagin Link: https://patch.msgid.link/20260217180108.1420024-2-avagin@google.com Signed-off-by: Kees Cook --- fs/binfmt_elf_fdpic.c | 6 ++++++ include/linux/auxvec.h | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/fs/binfmt_elf_fdpic.c b/fs/binfmt_elf_fdpic.c index 95b1d0852135..95b65aab7daa 100644 --- a/fs/binfmt_elf_fdpic.c +++ b/fs/binfmt_elf_fdpic.c @@ -595,6 +595,12 @@ static int create_elf_fdpic_tables(struct linux_binprm *bprm, #ifdef ELF_HWCAP2 nitems++; #endif +#ifdef ELF_HWCAP3 + nitems++; +#endif +#ifdef ELF_HWCAP4 + nitems++; +#endif csp = sp; sp -= nitems * 2 * sizeof(unsigned long); diff --git a/include/linux/auxvec.h b/include/linux/auxvec.h index 407f7005e6d6..8bcb9b726262 100644 --- a/include/linux/auxvec.h +++ b/include/linux/auxvec.h @@ -4,6 +4,6 @@ #include -#define AT_VECTOR_SIZE_BASE 22 /* NEW_AUX_ENT entries in auxiliary table */ +#define AT_VECTOR_SIZE_BASE 24 /* NEW_AUX_ENT entries in auxiliary table */ /* number of "#define AT_.*" above, minus {AT_NULL, AT_IGNORE, AT_NOTELF} */ #endif /* _LINUX_AUXVEC_H */ -- cgit v1.2.3 From 622d68772ddf07573cf88e833afe8ba6c70ac748 Mon Sep 17 00:00:00 2001 From: "Anirudh Rayabharam (Microsoft)" Date: Wed, 25 Feb 2026 12:44:03 +0000 Subject: mshv: add arm64 support for doorbell & intercept SINTs On x86, the HYPERVISOR_CALLBACK_VECTOR is used to receive synthetic interrupts (SINTs) from the hypervisor for doorbells and intercepts. There is no such vector reserved for arm64. On arm64, the hypervisor exposes a synthetic register that can be read to find the INTID that should be used for SINTs. This INTID is in the PPI range. To better unify the code paths, introduce mshv_sint_vector_init() that either reads the synthetic register and obtains the INTID (arm64) or just uses HYPERVISOR_CALLBACK_VECTOR as the interrupt vector (x86). Reviewed-by: Michael Kelley Reviewed-by: Stanislav Kinsburskii Signed-off-by: Anirudh Rayabharam (Microsoft) Signed-off-by: Wei Liu --- drivers/hv/mshv_synic.c | 119 ++++++++++++++++++++++++++++++++++++++++---- include/hyperv/hvgdk_mini.h | 2 + 2 files changed, 111 insertions(+), 10 deletions(-) (limited to 'include') diff --git a/drivers/hv/mshv_synic.c b/drivers/hv/mshv_synic.c index 617e8c02e365..43f1bcbbf2d3 100644 --- a/drivers/hv/mshv_synic.c +++ b/drivers/hv/mshv_synic.c @@ -10,17 +10,21 @@ #include #include #include +#include #include #include #include #include #include +#include #include "mshv_eventfd.h" #include "mshv.h" static int synic_cpuhp_online; static struct hv_synic_pages __percpu *synic_pages; +static int mshv_sint_vector = -1; /* hwirq for the SynIC SINTs */ +static int mshv_sint_irq = -1; /* Linux IRQ for mshv_sint_vector */ static u32 synic_event_ring_get_queued_port(u32 sint_index) { @@ -442,9 +446,7 @@ void mshv_isr(void) if (msg->header.message_flags.msg_pending) hv_set_non_nested_msr(HV_MSR_EOM, 0); -#ifdef HYPERVISOR_CALLBACK_VECTOR - add_interrupt_randomness(HYPERVISOR_CALLBACK_VECTOR); -#endif + add_interrupt_randomness(mshv_sint_vector); } else { pr_warn_once("%s: unknown message type 0x%x\n", __func__, msg->header.message_type); @@ -456,9 +458,7 @@ static int mshv_synic_cpu_init(unsigned int cpu) union hv_synic_simp simp; union hv_synic_siefp siefp; union hv_synic_sirbp sirbp; -#ifdef HYPERVISOR_CALLBACK_VECTOR union hv_synic_sint sint; -#endif union hv_synic_scontrol sctrl; struct hv_synic_pages *spages = this_cpu_ptr(synic_pages); struct hv_message_page **msg_page = &spages->hyp_synic_message_page; @@ -501,10 +501,12 @@ static int mshv_synic_cpu_init(unsigned int cpu) hv_set_non_nested_msr(HV_MSR_SIRBP, sirbp.as_uint64); -#ifdef HYPERVISOR_CALLBACK_VECTOR + if (mshv_sint_irq != -1) + enable_percpu_irq(mshv_sint_irq, 0); + /* Enable intercepts */ sint.as_uint64 = 0; - sint.vector = HYPERVISOR_CALLBACK_VECTOR; + sint.vector = mshv_sint_vector; sint.masked = false; sint.auto_eoi = hv_recommend_using_aeoi(); hv_set_non_nested_msr(HV_MSR_SINT0 + HV_SYNIC_INTERCEPTION_SINT_INDEX, @@ -512,13 +514,12 @@ static int mshv_synic_cpu_init(unsigned int cpu) /* Doorbell SINT */ sint.as_uint64 = 0; - sint.vector = HYPERVISOR_CALLBACK_VECTOR; + sint.vector = mshv_sint_vector; sint.masked = false; sint.as_intercept = 1; sint.auto_eoi = hv_recommend_using_aeoi(); hv_set_non_nested_msr(HV_MSR_SINT0 + HV_SYNIC_DOORBELL_SINT_INDEX, sint.as_uint64); -#endif /* Enable global synic bit */ sctrl.as_uint64 = hv_get_non_nested_msr(HV_MSR_SCONTROL); @@ -573,6 +574,9 @@ static int mshv_synic_cpu_exit(unsigned int cpu) hv_set_non_nested_msr(HV_MSR_SINT0 + HV_SYNIC_DOORBELL_SINT_INDEX, sint.as_uint64); + if (mshv_sint_irq != -1) + disable_percpu_irq(mshv_sint_irq); + /* Disable Synic's event ring page */ sirbp.as_uint64 = hv_get_non_nested_msr(HV_MSR_SIRBP); sirbp.sirbp_enabled = false; @@ -683,14 +687,106 @@ static struct notifier_block mshv_synic_reboot_nb = { .notifier_call = mshv_synic_reboot_notify, }; +#ifndef HYPERVISOR_CALLBACK_VECTOR +static DEFINE_PER_CPU(long, mshv_evt); + +static irqreturn_t mshv_percpu_isr(int irq, void *dev_id) +{ + mshv_isr(); + return IRQ_HANDLED; +} + +#ifdef CONFIG_ACPI +static int __init mshv_acpi_setup_sint_irq(void) +{ + return acpi_register_gsi(NULL, mshv_sint_vector, ACPI_EDGE_SENSITIVE, + ACPI_ACTIVE_HIGH); +} + +static void mshv_acpi_cleanup_sint_irq(void) +{ + acpi_unregister_gsi(mshv_sint_vector); +} +#else +static int __init mshv_acpi_setup_sint_irq(void) +{ + return -ENODEV; +} + +static void mshv_acpi_cleanup_sint_irq(void) +{ +} +#endif + +static int __init mshv_sint_vector_setup(void) +{ + int ret; + struct hv_register_assoc reg = { + .name = HV_ARM64_REGISTER_SINT_RESERVED_INTERRUPT_ID, + }; + union hv_input_vtl input_vtl = { 0 }; + + if (acpi_disabled) + return -ENODEV; + + ret = hv_call_get_vp_registers(HV_VP_INDEX_SELF, HV_PARTITION_ID_SELF, + 1, input_vtl, ®); + if (ret || !reg.value.reg64) + return -ENODEV; + + mshv_sint_vector = reg.value.reg64; + ret = mshv_acpi_setup_sint_irq(); + if (ret < 0) { + pr_err("Failed to setup IRQ for MSHV SINT vector %d: %d\n", + mshv_sint_vector, ret); + goto out_fail; + } + + mshv_sint_irq = ret; + + ret = request_percpu_irq(mshv_sint_irq, mshv_percpu_isr, "MSHV", + &mshv_evt); + if (ret) + goto out_unregister; + + return 0; + +out_unregister: + mshv_acpi_cleanup_sint_irq(); +out_fail: + return ret; +} + +static void mshv_sint_vector_cleanup(void) +{ + free_percpu_irq(mshv_sint_irq, &mshv_evt); + mshv_acpi_cleanup_sint_irq(); +} +#else /* !HYPERVISOR_CALLBACK_VECTOR */ +static int __init mshv_sint_vector_setup(void) +{ + mshv_sint_vector = HYPERVISOR_CALLBACK_VECTOR; + return 0; +} + +static void mshv_sint_vector_cleanup(void) +{ +} +#endif /* HYPERVISOR_CALLBACK_VECTOR */ + int __init mshv_synic_init(struct device *dev) { int ret = 0; + ret = mshv_sint_vector_setup(); + if (ret) + return ret; + synic_pages = alloc_percpu(struct hv_synic_pages); if (!synic_pages) { dev_err(dev, "Failed to allocate percpu synic page\n"); - return -ENOMEM; + ret = -ENOMEM; + goto sint_vector_cleanup; } ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "mshv_synic", @@ -713,6 +809,8 @@ remove_cpuhp_state: cpuhp_remove_state(synic_cpuhp_online); free_synic_pages: free_percpu(synic_pages); +sint_vector_cleanup: + mshv_sint_vector_cleanup(); return ret; } @@ -721,4 +819,5 @@ void mshv_synic_exit(void) unregister_reboot_notifier(&mshv_synic_reboot_nb); cpuhp_remove_state(synic_cpuhp_online); free_percpu(synic_pages); + mshv_sint_vector_cleanup(); } diff --git a/include/hyperv/hvgdk_mini.h b/include/hyperv/hvgdk_mini.h index 056ef7b6b360..8bb3dd71c5b4 100644 --- a/include/hyperv/hvgdk_mini.h +++ b/include/hyperv/hvgdk_mini.h @@ -1121,6 +1121,8 @@ enum hv_register_name { HV_X64_REGISTER_MSR_MTRR_FIX4KF8000 = 0x0008007A, HV_X64_REGISTER_REG_PAGE = 0x0009001C, +#elif defined(CONFIG_ARM64) + HV_ARM64_REGISTER_SINT_RESERVED_INTERRUPT_ID = 0x00070001, #endif }; -- cgit v1.2.3 From 8a9ebe8c3ca4c5bdad8f010656f4c2155da589fd Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sat, 28 Feb 2026 17:48:22 -0800 Subject: gpio: timberdale: repair kernel-doc comments Use a ':' after struct member names to avoid kernel-doc warnings: Warning: include/linux/timb_gpio.h:22 struct member 'gpio_base' not described in 'timbgpio_platform_data' Warning: include/linux/timb_gpio.h:22 struct member 'nr_pins' not described in 'timbgpio_platform_data' Warning: include/linux/timb_gpio.h:22 struct member 'irq_base' not described in 'timbgpio_platform_data' Signed-off-by: Randy Dunlap Link: https://patch.msgid.link/20260301014822.3133268-1-rdunlap@infradead.org Signed-off-by: Bartosz Golaszewski --- include/linux/timb_gpio.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/include/linux/timb_gpio.h b/include/linux/timb_gpio.h index 3faf5a6bb13e..74f5e73bf6db 100644 --- a/include/linux/timb_gpio.h +++ b/include/linux/timb_gpio.h @@ -9,10 +9,10 @@ /** * struct timbgpio_platform_data - Platform data of the Timberdale GPIO driver - * @gpio_base The number of the first GPIO pin, set to -1 for + * @gpio_base: The number of the first GPIO pin, set to -1 for * dynamic number allocation. - * @nr_pins Number of pins that is supported by the hardware (1-32) - * @irq_base If IRQ is supported by the hardware, this is the base + * @nr_pins: Number of pins that is supported by the hardware (1-32) + * @irq_base: If IRQ is supported by the hardware, this is the base * number of IRQ:s. One IRQ per pin will be used. Set to * -1 if IRQ:s is not supported. */ -- cgit v1.2.3 From 189645ba9cd9c1eed45151aacaae4347c1eb86a7 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sat, 28 Feb 2026 17:48:11 -0800 Subject: gpio: nomadik: repair some kernel-doc comments Avoid these kernel-doc warnings by: - adding short descriptions for enums - using correct (matching) struct names in kernel-doc short descriptions - using the correct struct member name for @nfunctions Warning: include/linux/gpio/gpio-nomadik.h:116 missing initial short description on line: * enum prcm_gpiocr_reg_index Warning: include/linux/gpio/gpio-nomadik.h:125 missing initial short description on line: * enum prcm_gpiocr_altcx_index Warning: include/linux/gpio/gpio-nomadik.h:146 expecting prototype for struct prcm_gpio_altcx. Prototype was for struct prcm_gpiocr_altcx instead Warning: include/linux/gpio/gpio-nomadik.h:156 expecting prototype for struct prcm_gpio_altcx_pin_desc. Prototype was for struct prcm_gpiocr_altcx_pin_desc instead Warning: include/linux/gpio/gpio-nomadik.h:212 struct member 'nfunctions' not described in 'nmk_pinctrl_soc_data' Signed-off-by: Randy Dunlap Link: https://patch.msgid.link/20260301014811.3133250-1-rdunlap@infradead.org Signed-off-by: Bartosz Golaszewski --- include/linux/gpio/gpio-nomadik.h | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'include') diff --git a/include/linux/gpio/gpio-nomadik.h b/include/linux/gpio/gpio-nomadik.h index 592a774a53cd..8061b9826361 100644 --- a/include/linux/gpio/gpio-nomadik.h +++ b/include/linux/gpio/gpio-nomadik.h @@ -114,8 +114,7 @@ struct nmk_gpio_chip { } /** - * enum prcm_gpiocr_reg_index - * Used to reference an PRCM GPIOCR register address. + * enum prcm_gpiocr_reg_index - Used to reference a PRCM GPIOCR register address. */ enum prcm_gpiocr_reg_index { PRCM_IDX_GPIOCR1, @@ -123,8 +122,7 @@ enum prcm_gpiocr_reg_index { PRCM_IDX_GPIOCR3 }; /** - * enum prcm_gpiocr_altcx_index - * Used to reference an Other alternate-C function. + * enum prcm_gpiocr_altcx_index - Used to reference an Other alternate-C function. */ enum prcm_gpiocr_altcx_index { PRCM_IDX_GPIOCR_ALTC1, @@ -135,7 +133,7 @@ enum prcm_gpiocr_altcx_index { }; /** - * struct prcm_gpio_altcx - Other alternate-C function + * struct prcm_gpiocr_altcx - Other alternate-C function * @used: other alternate-C function availability * @reg_index: PRCM GPIOCR register index used to control the function * @control_bit: PRCM GPIOCR bit used to control the function @@ -147,7 +145,7 @@ struct prcm_gpiocr_altcx { } __packed; /** - * struct prcm_gpio_altcx_pin_desc - Other alternate-C pin + * struct prcm_gpiocr_altcx_pin_desc - Other alternate-C pin * @pin: The pin number * @altcx: array of other alternate-C[1-4] functions */ @@ -193,7 +191,7 @@ struct nmk_pingroup { * numbering. * @npins: The number of entries in @pins. * @functions: The functions supported on this SoC. - * @nfunction: The number of entries in @functions. + * @nfunctions: The number of entries in @functions. * @groups: An array describing all pin groups the pin SoC supports. * @ngroups: The number of entries in @groups. * @altcx_pins: The pins that support Other alternate-C function on this SoC -- cgit v1.2.3 From 56bd57e7b161f75535df91b229b0b2c64c6e5581 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Sat, 28 Feb 2026 14:02:22 -0600 Subject: iio: add IIO_DECLARE_QUATERNION() macro Add a new IIO_DECLARE_QUATERNION() macro that is used to declare the field in an IIO buffer struct that contains a quaternion vector. Quaternions are currently the only IIO data type that uses the .repeat feature of struct iio_scan_type. This has an implicit rule that the element in the buffer must be aligned to the entire size of the repeated element. This macro will make that requirement explicit. Since this is the only user, we just call the macro IIO_DECLARE_QUATERNION() instead of something more generic. Signed-off-by: David Lechner Reviewed-by: Andy Shevchenko Cc: Signed-off-by: Jonathan Cameron --- include/linux/iio/iio.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'include') diff --git a/include/linux/iio/iio.h b/include/linux/iio/iio.h index a9ecff191bd9..2c91b7659ce9 100644 --- a/include/linux/iio/iio.h +++ b/include/linux/iio/iio.h @@ -931,6 +931,18 @@ static inline void *iio_device_get_drvdata(const struct iio_dev *indio_dev) #define IIO_DECLARE_DMA_BUFFER_WITH_TS(type, name, count) \ __IIO_DECLARE_BUFFER_WITH_TS(type, name, count) __aligned(IIO_DMA_MINALIGN) +/** + * IIO_DECLARE_QUATERNION() - Declare a quaternion element + * @type: element type of the individual vectors + * @name: identifier name + * + * Quaternions are a vector composed of 4 elements (W, X, Y, Z). Use this macro + * to declare a quaternion element in a struct to ensure proper alignment in + * an IIO buffer. + */ +#define IIO_DECLARE_QUATERNION(type, name) \ + type name[4] __aligned(sizeof(type) * 4) + struct iio_dev *iio_device_alloc(struct device *parent, int sizeof_priv); /* The information at the returned address is guaranteed to be cacheline aligned */ -- cgit v1.2.3 From 2d28ed588f8d7d0d41b0a4fad7f0d05e4bbf1797 Mon Sep 17 00:00:00 2001 From: Axel Rasmussen Date: Tue, 24 Feb 2026 16:24:34 -0800 Subject: Revert "ptdesc: remove references to folios from __pagetable_ctor() and pagetable_dtor()" This change swapped out mod_node_page_state for lruvec_stat_add_folio. But, these two APIs are not interchangeable: the lruvec version also increments memcg stats, in addition to "global" pgdat stats. So after this change, the "pagetables" memcg stat in memory.stat always yields "0", which is a userspace visible regression. I tried to look for a refactor where we add a variant of lruvec_stat_mod_folio which takes a pgdat and a memcg instead of a folio, to try to adhere to the spirit of the original patch. But at the end of the day this just means we have to call folio_memcg(ptdesc_folio(ptdesc)) anyway, which doesn't really accomplish much. This regression is visible in master as well as 6.18 stable, so CC stable too. Link: https://lkml.kernel.org/r/20260225002434.2953895-1-axelrasmussen@google.com Fixes: f0c92726e89f ("ptdesc: remove references to folios from __pagetable_ctor() and pagetable_dtor()") Signed-off-by: Axel Rasmussen Acked-by: Shakeel Butt Acked-by: Johannes Weiner Reviewed-by: Vishal Moola (Oracle) Cc: David Hildenbrand Cc: Liam Howlett Cc: Lorenzo Stoakes Cc: Matthew Wilcox (Oracle) Cc: Michal Hocko Cc: Mike Rapoport Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Roman Gushchin Cc: Muchun Song Cc: Signed-off-by: Andrew Morton --- include/linux/mm.h | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) (limited to 'include') diff --git a/include/linux/mm.h b/include/linux/mm.h index 5be3d8a8f806..abb4963c1f06 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -3514,26 +3514,21 @@ static inline bool ptlock_init(struct ptdesc *ptdesc) { return true; } static inline void ptlock_free(struct ptdesc *ptdesc) {} #endif /* defined(CONFIG_SPLIT_PTE_PTLOCKS) */ -static inline unsigned long ptdesc_nr_pages(const struct ptdesc *ptdesc) -{ - return compound_nr(ptdesc_page(ptdesc)); -} - static inline void __pagetable_ctor(struct ptdesc *ptdesc) { - pg_data_t *pgdat = NODE_DATA(memdesc_nid(ptdesc->pt_flags)); + struct folio *folio = ptdesc_folio(ptdesc); - __SetPageTable(ptdesc_page(ptdesc)); - mod_node_page_state(pgdat, NR_PAGETABLE, ptdesc_nr_pages(ptdesc)); + __folio_set_pgtable(folio); + lruvec_stat_add_folio(folio, NR_PAGETABLE); } static inline void pagetable_dtor(struct ptdesc *ptdesc) { - pg_data_t *pgdat = NODE_DATA(memdesc_nid(ptdesc->pt_flags)); + struct folio *folio = ptdesc_folio(ptdesc); ptlock_free(ptdesc); - __ClearPageTable(ptdesc_page(ptdesc)); - mod_node_page_state(pgdat, NR_PAGETABLE, -ptdesc_nr_pages(ptdesc)); + __folio_clear_pgtable(folio); + lruvec_stat_sub_folio(folio, NR_PAGETABLE); } static inline void pagetable_dtor_free(struct ptdesc *ptdesc) -- cgit v1.2.3 From 7392f8e4ea632622b2cd2086675ba022db238b3a Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sun, 1 Mar 2026 16:52:29 -0800 Subject: uaccess: correct kernel-doc parameter format Use the correct kernel-doc function parameter format to avoid kernel-doc warnings: Warning: include/linux/uaccess.h:814 function parameter 'uptr' not described in 'scoped_user_rw_access_size' Warning: include/linux/uaccess.h:826 function parameter 'uptr' not described in 'scoped_user_rw_access' Link: https://lkml.kernel.org/r/20260302005229.3471955-1-rdunlap@infradead.org Signed-off-by: Randy Dunlap Reviewed-by: Andrew Morton Signed-off-by: Andrew Morton --- include/linux/uaccess.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/linux/uaccess.h b/include/linux/uaccess.h index 1f3804245c06..001cfef21b61 100644 --- a/include/linux/uaccess.h +++ b/include/linux/uaccess.h @@ -806,7 +806,7 @@ for (bool done = false; !done; done = true) \ /** * scoped_user_rw_access_size - Start a scoped user read/write access with given size - * @uptr Pointer to the user space address to read from and write to + * @uptr: Pointer to the user space address to read from and write to * @size: Size of the access starting from @uptr * @elbl: Error label to goto when the access region is rejected * @@ -817,7 +817,7 @@ for (bool done = false; !done; done = true) \ /** * scoped_user_rw_access - Start a scoped user read/write access - * @uptr Pointer to the user space address to read from and write to + * @uptr: Pointer to the user space address to read from and write to * @elbl: Error label to goto when the access region is rejected * * The size of the access starting from @uptr is determined via sizeof(*@uptr)). -- cgit v1.2.3 From 599b4e290c8766b19378d85d4310c6ec8f90ade4 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sun, 1 Mar 2026 16:52:22 -0800 Subject: mm/mmu_notifier: clean up mmu_notifier.h kernel-doc Eliminate kernel-doc warnings in mmu_notifier.h: - add a missing struct short description - use the correct format for function parameters - add missing function return comment sections Warning: include/linux/mmu_notifier.h:236 missing initial short description on line: * struct mmu_interval_notifier_ops Warning: include/linux/mmu_notifier.h:325 function parameter 'interval_sub' not described in 'mmu_interval_set_seq' Warning: include/linux/mmu_notifier.h:325 function parameter 'cur_seq' not described in 'mmu_interval_set_seq' Warning: include/linux/mmu_notifier.h:346 function parameter 'interval_sub' not described in 'mmu_interval_read_retry' Warning: include/linux/mmu_notifier.h:346 function parameter 'seq' not described in 'mmu_interval_read_retry' Warning: include/linux/mmu_notifier.h:346 No description found for return value of 'mmu_interval_read_retry' Warning: include/linux/mmu_notifier.h:370 function parameter 'interval_sub' not described in 'mmu_interval_check_retry' Warning: include/linux/mmu_notifier.h:370 function parameter 'seq' not described in 'mmu_interval_check_retry' Warning: include/linux/mmu_notifier.h:370 No description found for return value of 'mmu_interval_check_retry' Link: https://lkml.kernel.org/r/20260302005222.3470783-1-rdunlap@infradead.org Signed-off-by: Randy Dunlap Reviewed-by: Jason Gunthorpe Cc: David Hildenbrand Cc: "Liam R. Howlett" Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Randy Dunlap Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- include/linux/mmu_notifier.h | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) (limited to 'include') diff --git a/include/linux/mmu_notifier.h b/include/linux/mmu_notifier.h index 07a2bbaf86e9..8450e18a87c2 100644 --- a/include/linux/mmu_notifier.h +++ b/include/linux/mmu_notifier.h @@ -234,7 +234,7 @@ struct mmu_notifier { }; /** - * struct mmu_interval_notifier_ops + * struct mmu_interval_notifier_ops - callback for range notification * @invalidate: Upon return the caller must stop using any SPTEs within this * range. This function can sleep. Return false only if sleeping * was required but mmu_notifier_range_blockable(range) is false. @@ -309,8 +309,8 @@ void mmu_interval_notifier_remove(struct mmu_interval_notifier *interval_sub); /** * mmu_interval_set_seq - Save the invalidation sequence - * @interval_sub - The subscription passed to invalidate - * @cur_seq - The cur_seq passed to the invalidate() callback + * @interval_sub: The subscription passed to invalidate + * @cur_seq: The cur_seq passed to the invalidate() callback * * This must be called unconditionally from the invalidate callback of a * struct mmu_interval_notifier_ops under the same lock that is used to call @@ -329,8 +329,8 @@ mmu_interval_set_seq(struct mmu_interval_notifier *interval_sub, /** * mmu_interval_read_retry - End a read side critical section against a VA range - * interval_sub: The subscription - * seq: The return of the paired mmu_interval_read_begin() + * @interval_sub: The subscription + * @seq: The return of the paired mmu_interval_read_begin() * * This MUST be called under a user provided lock that is also held * unconditionally by op->invalidate() when it calls mmu_interval_set_seq(). @@ -338,7 +338,7 @@ mmu_interval_set_seq(struct mmu_interval_notifier *interval_sub, * Each call should be paired with a single mmu_interval_read_begin() and * should be used to conclude the read side. * - * Returns true if an invalidation collided with this critical section, and + * Returns: true if an invalidation collided with this critical section, and * the caller should retry. */ static inline bool @@ -350,20 +350,21 @@ mmu_interval_read_retry(struct mmu_interval_notifier *interval_sub, /** * mmu_interval_check_retry - Test if a collision has occurred - * interval_sub: The subscription - * seq: The return of the matching mmu_interval_read_begin() + * @interval_sub: The subscription + * @seq: The return of the matching mmu_interval_read_begin() * * This can be used in the critical section between mmu_interval_read_begin() - * and mmu_interval_read_retry(). A return of true indicates an invalidation - * has collided with this critical region and a future - * mmu_interval_read_retry() will return true. - * - * False is not reliable and only suggests a collision may not have - * occurred. It can be called many times and does not have to hold the user - * provided lock. + * and mmu_interval_read_retry(). * * This call can be used as part of loops and other expensive operations to * expedite a retry. + * It can be called many times and does not have to hold the user + * provided lock. + * + * Returns: true indicates an invalidation has collided with this critical + * region and a future mmu_interval_read_retry() will return true. + * False is not reliable and only suggests a collision may not have + * occurred. */ static inline bool mmu_interval_check_retry(struct mmu_interval_notifier *interval_sub, -- cgit v1.2.3 From 55f854dd5bdd8e19b936a00ef1f8d776ac32c7b0 Mon Sep 17 00:00:00 2001 From: Laurent Vivier Date: Wed, 4 Mar 2026 14:43:38 +0100 Subject: qmi_wwan: allow max_mtu above hard_mtu to control rx_urb_size Commit c7159e960f14 ("usbnet: limit max_mtu based on device's hard_mtu") capped net->max_mtu to the device's hard_mtu in usbnet_probe(). While this correctly prevents oversized packets on standard USB network devices, it breaks the qmi_wwan driver. qmi_wwan relies on userspace (e.g. ModemManager) setting a large MTU on the wwan0 interface to configure rx_urb_size via usbnet_change_mtu(). QMI modems negotiate USB transfer sizes of 16,383 or 32,767 bytes, and the USB receive buffers must be sized accordingly. With max_mtu capped to hard_mtu (~1500 bytes), userspace can no longer raise the MTU, the receive buffers remain small, and download speeds drop from >300 Mbps to ~0.8 Mbps. Introduce a FLAG_NOMAXMTU driver flag that allows individual usbnet drivers to opt out of the max_mtu cap. Set this flag in qmi_wwan's driver_info structures to restore the previous behavior for QMI devices, while keeping the safety fix in place for all other usbnet drivers. Fixes: c7159e960f14 ("usbnet: limit max_mtu based on device's hard_mtu") Cc: stable@vger.kernel.org Link: https://lore.kernel.org/lkml/CAPh3n803k8JcBPV5qEzUB-oKzWkAs-D5CU7z=Vd_nLRCr5ZqQg@mail.gmail.com/ Reported-by: Koen Vandeputte Tested-by: Daniele Palmas Signed-off-by: Laurent Vivier Link: https://patch.msgid.link/20260304134338.1785002-1-lvivier@redhat.com Signed-off-by: Jakub Kicinski --- drivers/net/usb/qmi_wwan.c | 4 ++-- drivers/net/usb/usbnet.c | 7 ++++--- include/linux/usb/usbnet.h | 1 + 3 files changed, 7 insertions(+), 5 deletions(-) (limited to 'include') diff --git a/drivers/net/usb/qmi_wwan.c b/drivers/net/usb/qmi_wwan.c index 3a4985b582cb..05acac10cd2b 100644 --- a/drivers/net/usb/qmi_wwan.c +++ b/drivers/net/usb/qmi_wwan.c @@ -928,7 +928,7 @@ err: static const struct driver_info qmi_wwan_info = { .description = "WWAN/QMI device", - .flags = FLAG_WWAN | FLAG_SEND_ZLP, + .flags = FLAG_WWAN | FLAG_NOMAXMTU | FLAG_SEND_ZLP, .bind = qmi_wwan_bind, .unbind = qmi_wwan_unbind, .manage_power = qmi_wwan_manage_power, @@ -937,7 +937,7 @@ static const struct driver_info qmi_wwan_info = { static const struct driver_info qmi_wwan_info_quirk_dtr = { .description = "WWAN/QMI device", - .flags = FLAG_WWAN | FLAG_SEND_ZLP, + .flags = FLAG_WWAN | FLAG_NOMAXMTU | FLAG_SEND_ZLP, .bind = qmi_wwan_bind, .unbind = qmi_wwan_unbind, .manage_power = qmi_wwan_manage_power, diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c index ed86ba87ca4e..b72ba0803392 100644 --- a/drivers/net/usb/usbnet.c +++ b/drivers/net/usb/usbnet.c @@ -1829,11 +1829,12 @@ usbnet_probe(struct usb_interface *udev, const struct usb_device_id *prod) if ((dev->driver_info->flags & FLAG_NOARP) != 0) net->flags |= IFF_NOARP; - if (net->max_mtu > (dev->hard_mtu - net->hard_header_len)) + if ((dev->driver_info->flags & FLAG_NOMAXMTU) == 0 && + net->max_mtu > (dev->hard_mtu - net->hard_header_len)) net->max_mtu = dev->hard_mtu - net->hard_header_len; - if (net->mtu > net->max_mtu) - net->mtu = net->max_mtu; + if (net->mtu > (dev->hard_mtu - net->hard_header_len)) + net->mtu = dev->hard_mtu - net->hard_header_len; } else if (!info->in || !info->out) status = usbnet_get_endpoints(dev, udev); diff --git a/include/linux/usb/usbnet.h b/include/linux/usb/usbnet.h index b0e84896e6ac..bbf799ccf3b3 100644 --- a/include/linux/usb/usbnet.h +++ b/include/linux/usb/usbnet.h @@ -132,6 +132,7 @@ struct driver_info { #define FLAG_MULTI_PACKET 0x2000 #define FLAG_RX_ASSEMBLE 0x4000 /* rx packets may span >1 frames */ #define FLAG_NOARP 0x8000 /* device can't do ARP */ +#define FLAG_NOMAXMTU 0x10000 /* allow max_mtu above hard_mtu */ /* init device ... can sleep, or cause probe() failure */ int (*bind)(struct usbnet *, struct usb_interface *); -- cgit v1.2.3 From 6f1a9140ecda3baba3d945b9a6155af4268aafc4 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Sat, 7 Mar 2026 00:01:34 +0800 Subject: net: add xmit recursion limit to tunnel xmit functions Tunnel xmit functions (iptunnel_xmit, ip6tunnel_xmit) lack their own recursion limit. When a bond device in broadcast mode has GRE tap interfaces as slaves, and those GRE tunnels route back through the bond, multicast/broadcast traffic triggers infinite recursion between bond_xmit_broadcast() and ip_tunnel_xmit()/ip6_tnl_xmit(), causing kernel stack overflow. The existing XMIT_RECURSION_LIMIT (8) in the no-qdisc path is not sufficient because tunnel recursion involves route lookups and full IP output, consuming much more stack per level. Use a lower limit of 4 (IP_TUNNEL_RECURSION_LIMIT) to prevent overflow. Add recursion detection using dev_xmit_recursion helpers directly in iptunnel_xmit() and ip6tunnel_xmit() to cover all IPv4/IPv6 tunnel paths including UDP encapsulated tunnels (VXLAN, Geneve, etc.). Move dev_xmit_recursion helpers from net/core/dev.h to public header include/linux/netdevice.h so they can be used by tunnel code. BUG: KASAN: stack-out-of-bounds in blake2s.constprop.0+0xe7/0x160 Write of size 32 at addr ffff88810033fed0 by task kworker/0:1/11 Workqueue: mld mld_ifc_work Call Trace: __build_flow_key.constprop.0 (net/ipv4/route.c:515) ip_rt_update_pmtu (net/ipv4/route.c:1073) iptunnel_xmit (net/ipv4/ip_tunnel_core.c:84) ip_tunnel_xmit (net/ipv4/ip_tunnel.c:847) gre_tap_xmit (net/ipv4/ip_gre.c:779) dev_hard_start_xmit (net/core/dev.c:3887) sch_direct_xmit (net/sched/sch_generic.c:347) __dev_queue_xmit (net/core/dev.c:4802) bond_dev_queue_xmit (drivers/net/bonding/bond_main.c:312) bond_xmit_broadcast (drivers/net/bonding/bond_main.c:5279) bond_start_xmit (drivers/net/bonding/bond_main.c:5530) dev_hard_start_xmit (net/core/dev.c:3887) __dev_queue_xmit (net/core/dev.c:4841) ip_finish_output2 (net/ipv4/ip_output.c:237) ip_output (net/ipv4/ip_output.c:438) iptunnel_xmit (net/ipv4/ip_tunnel_core.c:86) gre_tap_xmit (net/ipv4/ip_gre.c:779) dev_hard_start_xmit (net/core/dev.c:3887) sch_direct_xmit (net/sched/sch_generic.c:347) __dev_queue_xmit (net/core/dev.c:4802) bond_dev_queue_xmit (drivers/net/bonding/bond_main.c:312) bond_xmit_broadcast (drivers/net/bonding/bond_main.c:5279) bond_start_xmit (drivers/net/bonding/bond_main.c:5530) dev_hard_start_xmit (net/core/dev.c:3887) __dev_queue_xmit (net/core/dev.c:4841) ip_finish_output2 (net/ipv4/ip_output.c:237) ip_output (net/ipv4/ip_output.c:438) iptunnel_xmit (net/ipv4/ip_tunnel_core.c:86) ip_tunnel_xmit (net/ipv4/ip_tunnel.c:847) gre_tap_xmit (net/ipv4/ip_gre.c:779) dev_hard_start_xmit (net/core/dev.c:3887) sch_direct_xmit (net/sched/sch_generic.c:347) __dev_queue_xmit (net/core/dev.c:4802) bond_dev_queue_xmit (drivers/net/bonding/bond_main.c:312) bond_xmit_broadcast (drivers/net/bonding/bond_main.c:5279) bond_start_xmit (drivers/net/bonding/bond_main.c:5530) dev_hard_start_xmit (net/core/dev.c:3887) __dev_queue_xmit (net/core/dev.c:4841) mld_sendpack mld_ifc_work process_one_work worker_thread Fixes: 745e20f1b626 ("net: add a recursion limit in xmit path") Reported-by: Xiang Mei Signed-off-by: Weiming Shi Link: https://patch.msgid.link/20260306160133.3852900-2-bestswngs@gmail.com Signed-off-by: Paolo Abeni --- include/linux/netdevice.h | 32 ++++++++++++++++++++++++++++++++ include/net/ip6_tunnel.h | 12 ++++++++++++ include/net/ip_tunnels.h | 7 +++++++ net/core/dev.h | 35 ----------------------------------- net/ipv4/ip_tunnel_core.c | 13 +++++++++++++ 5 files changed, 64 insertions(+), 35 deletions(-) (limited to 'include') diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 67e25f6d15a4..ae269a2e7f4d 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -3576,17 +3576,49 @@ struct page_pool_bh { }; DECLARE_PER_CPU(struct page_pool_bh, system_page_pool); +#define XMIT_RECURSION_LIMIT 8 + #ifndef CONFIG_PREEMPT_RT static inline int dev_recursion_level(void) { return this_cpu_read(softnet_data.xmit.recursion); } + +static inline bool dev_xmit_recursion(void) +{ + return unlikely(__this_cpu_read(softnet_data.xmit.recursion) > + XMIT_RECURSION_LIMIT); +} + +static inline void dev_xmit_recursion_inc(void) +{ + __this_cpu_inc(softnet_data.xmit.recursion); +} + +static inline void dev_xmit_recursion_dec(void) +{ + __this_cpu_dec(softnet_data.xmit.recursion); +} #else static inline int dev_recursion_level(void) { return current->net_xmit.recursion; } +static inline bool dev_xmit_recursion(void) +{ + return unlikely(current->net_xmit.recursion > XMIT_RECURSION_LIMIT); +} + +static inline void dev_xmit_recursion_inc(void) +{ + current->net_xmit.recursion++; +} + +static inline void dev_xmit_recursion_dec(void) +{ + current->net_xmit.recursion--; +} #endif void __netif_schedule(struct Qdisc *q); diff --git a/include/net/ip6_tunnel.h b/include/net/ip6_tunnel.h index 120db2865811..1253cbb4b0a4 100644 --- a/include/net/ip6_tunnel.h +++ b/include/net/ip6_tunnel.h @@ -156,6 +156,16 @@ static inline void ip6tunnel_xmit(struct sock *sk, struct sk_buff *skb, { int pkt_len, err; + if (dev_recursion_level() > IP_TUNNEL_RECURSION_LIMIT) { + net_crit_ratelimited("Dead loop on virtual device %s, fix it urgently!\n", + dev->name); + DEV_STATS_INC(dev, tx_errors); + kfree_skb(skb); + return; + } + + dev_xmit_recursion_inc(); + memset(skb->cb, 0, sizeof(struct inet6_skb_parm)); IP6CB(skb)->flags = ip6cb_flags; pkt_len = skb->len - skb_inner_network_offset(skb); @@ -166,6 +176,8 @@ static inline void ip6tunnel_xmit(struct sock *sk, struct sk_buff *skb, pkt_len = -1; iptunnel_xmit_stats(dev, pkt_len); } + + dev_xmit_recursion_dec(); } #endif #endif diff --git a/include/net/ip_tunnels.h b/include/net/ip_tunnels.h index 4021e6a73e32..80662f812080 100644 --- a/include/net/ip_tunnels.h +++ b/include/net/ip_tunnels.h @@ -27,6 +27,13 @@ #include #endif +/* Recursion limit for tunnel xmit to detect routing loops. + * Unlike XMIT_RECURSION_LIMIT (8) used in the no-qdisc path, tunnel + * recursion involves route lookups and full IP output, consuming much + * more stack per level, so a lower limit is needed. + */ +#define IP_TUNNEL_RECURSION_LIMIT 4 + /* Keep error state on tunnel for 30 sec */ #define IPTUNNEL_ERR_TIMEO (30*HZ) diff --git a/net/core/dev.h b/net/core/dev.h index 98793a738f43..781619e76b3e 100644 --- a/net/core/dev.h +++ b/net/core/dev.h @@ -366,41 +366,6 @@ static inline void napi_assert_will_not_race(const struct napi_struct *napi) void kick_defer_list_purge(unsigned int cpu); -#define XMIT_RECURSION_LIMIT 8 - -#ifndef CONFIG_PREEMPT_RT -static inline bool dev_xmit_recursion(void) -{ - return unlikely(__this_cpu_read(softnet_data.xmit.recursion) > - XMIT_RECURSION_LIMIT); -} - -static inline void dev_xmit_recursion_inc(void) -{ - __this_cpu_inc(softnet_data.xmit.recursion); -} - -static inline void dev_xmit_recursion_dec(void) -{ - __this_cpu_dec(softnet_data.xmit.recursion); -} -#else -static inline bool dev_xmit_recursion(void) -{ - return unlikely(current->net_xmit.recursion > XMIT_RECURSION_LIMIT); -} - -static inline void dev_xmit_recursion_inc(void) -{ - current->net_xmit.recursion++; -} - -static inline void dev_xmit_recursion_dec(void) -{ - current->net_xmit.recursion--; -} -#endif - int dev_set_hwtstamp_phylib(struct net_device *dev, struct kernel_hwtstamp_config *cfg, struct netlink_ext_ack *extack); diff --git a/net/ipv4/ip_tunnel_core.c b/net/ipv4/ip_tunnel_core.c index 2e61ac137128..b1b6bf949f65 100644 --- a/net/ipv4/ip_tunnel_core.c +++ b/net/ipv4/ip_tunnel_core.c @@ -58,6 +58,17 @@ void iptunnel_xmit(struct sock *sk, struct rtable *rt, struct sk_buff *skb, struct iphdr *iph; int err; + if (dev_recursion_level() > IP_TUNNEL_RECURSION_LIMIT) { + net_crit_ratelimited("Dead loop on virtual device %s, fix it urgently!\n", + dev->name); + DEV_STATS_INC(dev, tx_errors); + ip_rt_put(rt); + kfree_skb(skb); + return; + } + + dev_xmit_recursion_inc(); + skb_scrub_packet(skb, xnet); skb_clear_hash_if_not_l4(skb); @@ -88,6 +99,8 @@ void iptunnel_xmit(struct sock *sk, struct rtable *rt, struct sk_buff *skb, pkt_len = 0; iptunnel_xmit_stats(dev, pkt_len); } + + dev_xmit_recursion_dec(); } EXPORT_SYMBOL_GPL(iptunnel_xmit); -- cgit v1.2.3 From fa655a9ca73f7df32b8ca4d14ce11742f9578288 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Tue, 3 Mar 2026 22:31:01 +0100 Subject: nvme: Annotate struct nvme_dhchap_key with __counted_by Add the __counted_by() compiler attribute to the flexible array member 'key' to improve access bounds-checking via CONFIG_UBSAN_BOUNDS and CONFIG_FORTIFY_SOURCE. Reviewed-by: Christoph Hellwig Signed-off-by: Thorsten Blum Signed-off-by: Keith Busch --- include/linux/nvme-auth.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include') diff --git a/include/linux/nvme-auth.h b/include/linux/nvme-auth.h index 60e069a6757f..e75c29c51464 100644 --- a/include/linux/nvme-auth.h +++ b/include/linux/nvme-auth.h @@ -11,7 +11,7 @@ struct nvme_dhchap_key { size_t len; u8 hash; - u8 key[]; + u8 key[] __counted_by(len); }; u32 nvme_auth_get_seqnum(void); -- cgit v1.2.3 From 22fd7f7fed2ae3702f90d1985c326354e86b9c75 Mon Sep 17 00:00:00 2001 From: Muhammad Amirul Asyraf Mohamad Jamian Date: Thu, 5 Mar 2026 01:31:51 -0800 Subject: firmware: stratix10-svc: Add Multi SVC clients support In the current implementation, SVC client drivers such as socfpga-hwmon, intel_fcs, stratix10-soc, stratix10-rsu each send an SMC command that triggers a single thread in the stratix10-svc driver. Upon receiving a callback, the initiating client driver sends a stratix10-svc-done signal, terminating the thread without waiting for other pending SMC commands to complete. This leads to a timeout issue in the firmware SVC mailbox service when multiple client drivers send SMC commands concurrently. To resolve this issue, a dedicated thread is now created per channel. The stratix10-svc driver will support up to the number of channels defined by SVC_NUM_CHANNEL. Thread synchronization is handled using a mutex to prevent simultaneous issuance of SMC commands by multiple threads. SVC_NUM_DATA_IN_FIFO is reduced from 32 to 8, since each channel now has its own dedicated FIFO and the SDM processes commands one at a time. 8 entries per channel is sufficient while keeping the total aggregate capacity the same (4 channels x 8 = 32 entries). Additionally, a thread task is now validated before invoking kthread_stop when the user aborts, ensuring safe termination. Timeout values have also been adjusted to accommodate the increased load from concurrent client driver activity. Fixes: 7ca5ce896524 ("firmware: add Intel Stratix10 service layer driver") Cc: stable@vger.kernel.org Signed-off-by: Ang Tien Sung Signed-off-by: Fong, Yan Kei Signed-off-by: Muhammad Amirul Asyraf Mohamad Jamian Link: https://lore.kernel.org/all/20260305093151.2678-1-muhammad.amirul.asyraf.mohamad.jamian@altera.com Signed-off-by: Dinh Nguyen --- drivers/firmware/stratix10-svc.c | 228 ++++++++++++--------- .../linux/firmware/intel/stratix10-svc-client.h | 8 +- 2 files changed, 130 insertions(+), 106 deletions(-) (limited to 'include') diff --git a/drivers/firmware/stratix10-svc.c b/drivers/firmware/stratix10-svc.c index 6f5c298582ab..e9e35d67ef96 100644 --- a/drivers/firmware/stratix10-svc.c +++ b/drivers/firmware/stratix10-svc.c @@ -37,15 +37,14 @@ * service layer will return error to FPGA manager when timeout occurs, * timeout is set to 30 seconds (30 * 1000) at Intel Stratix10 SoC. */ -#define SVC_NUM_DATA_IN_FIFO 32 +#define SVC_NUM_DATA_IN_FIFO 8 #define SVC_NUM_CHANNEL 4 -#define FPGA_CONFIG_DATA_CLAIM_TIMEOUT_MS 200 +#define FPGA_CONFIG_DATA_CLAIM_TIMEOUT_MS 2000 #define FPGA_CONFIG_STATUS_TIMEOUT_SEC 30 #define BYTE_TO_WORD_SIZE 4 /* stratix10 service layer clients */ #define STRATIX10_RSU "stratix10-rsu" -#define INTEL_FCS "intel-fcs" /* Maximum number of SDM client IDs. */ #define MAX_SDM_CLIENT_IDS 16 @@ -105,11 +104,9 @@ struct stratix10_svc_chan; /** * struct stratix10_svc - svc private data * @stratix10_svc_rsu: pointer to stratix10 RSU device - * @intel_svc_fcs: pointer to the FCS device */ struct stratix10_svc { struct platform_device *stratix10_svc_rsu; - struct platform_device *intel_svc_fcs; }; /** @@ -251,12 +248,10 @@ struct stratix10_async_ctrl { * @num_active_client: number of active service client * @node: list management * @genpool: memory pool pointing to the memory region - * @task: pointer to the thread task which handles SMC or HVC call - * @svc_fifo: a queue for storing service message data * @complete_status: state for completion - * @svc_fifo_lock: protect access to service message data queue * @invoke_fn: function to issue secure monitor call or hypervisor call * @svc: manages the list of client svc drivers + * @sdm_lock: only allows a single command single response to SDM * @actrl: async control structure * * This struct is used to create communication channels for service clients, to @@ -269,12 +264,10 @@ struct stratix10_svc_controller { int num_active_client; struct list_head node; struct gen_pool *genpool; - struct task_struct *task; - struct kfifo svc_fifo; struct completion complete_status; - spinlock_t svc_fifo_lock; svc_invoke_fn *invoke_fn; struct stratix10_svc *svc; + struct mutex sdm_lock; struct stratix10_async_ctrl actrl; }; @@ -283,6 +276,9 @@ struct stratix10_svc_controller { * @ctrl: pointer to service controller which is the provider of this channel * @scl: pointer to service client which owns the channel * @name: service client name associated with the channel + * @task: pointer to the thread task which handles SMC or HVC call + * @svc_fifo: a queue for storing service message data (separate fifo for every channel) + * @svc_fifo_lock: protect access to service message data queue (locking pending fifo) * @lock: protect access to the channel * @async_chan: reference to asynchronous channel object for this channel * @@ -293,6 +289,9 @@ struct stratix10_svc_chan { struct stratix10_svc_controller *ctrl; struct stratix10_svc_client *scl; char *name; + struct task_struct *task; + struct kfifo svc_fifo; + spinlock_t svc_fifo_lock; spinlock_t lock; struct stratix10_async_chan *async_chan; }; @@ -527,10 +526,10 @@ static void svc_thread_recv_status_ok(struct stratix10_svc_data *p_data, */ static int svc_normal_to_secure_thread(void *data) { - struct stratix10_svc_controller - *ctrl = (struct stratix10_svc_controller *)data; - struct stratix10_svc_data *pdata; - struct stratix10_svc_cb_data *cbdata; + struct stratix10_svc_chan *chan = (struct stratix10_svc_chan *)data; + struct stratix10_svc_controller *ctrl = chan->ctrl; + struct stratix10_svc_data *pdata = NULL; + struct stratix10_svc_cb_data *cbdata = NULL; struct arm_smccc_res res; unsigned long a0, a1, a2, a3, a4, a5, a6, a7; int ret_fifo = 0; @@ -555,12 +554,12 @@ static int svc_normal_to_secure_thread(void *data) a6 = 0; a7 = 0; - pr_debug("smc_hvc_shm_thread is running\n"); + pr_debug("%s: %s: Thread is running!\n", __func__, chan->name); while (!kthread_should_stop()) { - ret_fifo = kfifo_out_spinlocked(&ctrl->svc_fifo, + ret_fifo = kfifo_out_spinlocked(&chan->svc_fifo, pdata, sizeof(*pdata), - &ctrl->svc_fifo_lock); + &chan->svc_fifo_lock); if (!ret_fifo) continue; @@ -569,9 +568,25 @@ static int svc_normal_to_secure_thread(void *data) (unsigned int)pdata->paddr, pdata->command, (unsigned int)pdata->size); + /* SDM can only process one command at a time */ + pr_debug("%s: %s: Thread is waiting for mutex!\n", + __func__, chan->name); + if (mutex_lock_interruptible(&ctrl->sdm_lock)) { + /* item already dequeued; notify client to unblock it */ + cbdata->status = BIT(SVC_STATUS_ERROR); + cbdata->kaddr1 = NULL; + cbdata->kaddr2 = NULL; + cbdata->kaddr3 = NULL; + if (pdata->chan->scl) + pdata->chan->scl->receive_cb(pdata->chan->scl, + cbdata); + break; + } + switch (pdata->command) { case COMMAND_RECONFIG_DATA_CLAIM: svc_thread_cmd_data_claim(ctrl, pdata, cbdata); + mutex_unlock(&ctrl->sdm_lock); continue; case COMMAND_RECONFIG: a0 = INTEL_SIP_SMC_FPGA_CONFIG_START; @@ -700,10 +715,11 @@ static int svc_normal_to_secure_thread(void *data) break; default: pr_warn("it shouldn't happen\n"); - break; + mutex_unlock(&ctrl->sdm_lock); + continue; } - pr_debug("%s: before SMC call -- a0=0x%016x a1=0x%016x", - __func__, + pr_debug("%s: %s: before SMC call -- a0=0x%016x a1=0x%016x", + __func__, chan->name, (unsigned int)a0, (unsigned int)a1); pr_debug(" a2=0x%016x\n", (unsigned int)a2); @@ -712,8 +728,8 @@ static int svc_normal_to_secure_thread(void *data) pr_debug(" a5=0x%016x\n", (unsigned int)a5); ctrl->invoke_fn(a0, a1, a2, a3, a4, a5, a6, a7, &res); - pr_debug("%s: after SMC call -- res.a0=0x%016x", - __func__, (unsigned int)res.a0); + pr_debug("%s: %s: after SMC call -- res.a0=0x%016x", + __func__, chan->name, (unsigned int)res.a0); pr_debug(" res.a1=0x%016x, res.a2=0x%016x", (unsigned int)res.a1, (unsigned int)res.a2); pr_debug(" res.a3=0x%016x\n", (unsigned int)res.a3); @@ -728,6 +744,7 @@ static int svc_normal_to_secure_thread(void *data) cbdata->kaddr2 = NULL; cbdata->kaddr3 = NULL; pdata->chan->scl->receive_cb(pdata->chan->scl, cbdata); + mutex_unlock(&ctrl->sdm_lock); continue; } @@ -801,6 +818,8 @@ static int svc_normal_to_secure_thread(void *data) break; } + + mutex_unlock(&ctrl->sdm_lock); } kfree(cbdata); @@ -1696,22 +1715,33 @@ int stratix10_svc_send(struct stratix10_svc_chan *chan, void *msg) if (!p_data) return -ENOMEM; - /* first client will create kernel thread */ - if (!chan->ctrl->task) { - chan->ctrl->task = - kthread_run_on_cpu(svc_normal_to_secure_thread, - (void *)chan->ctrl, - cpu, "svc_smc_hvc_thread"); - if (IS_ERR(chan->ctrl->task)) { + /* first caller creates the per-channel kthread */ + if (!chan->task) { + struct task_struct *task; + + task = kthread_run_on_cpu(svc_normal_to_secure_thread, + (void *)chan, + cpu, "svc_smc_hvc_thread"); + if (IS_ERR(task)) { dev_err(chan->ctrl->dev, "failed to create svc_smc_hvc_thread\n"); kfree(p_data); return -EINVAL; } + + spin_lock(&chan->lock); + if (chan->task) { + /* another caller won the race; discard our thread */ + spin_unlock(&chan->lock); + kthread_stop(task); + } else { + chan->task = task; + spin_unlock(&chan->lock); + } } - pr_debug("%s: sent P-va=%p, P-com=%x, P-size=%u\n", __func__, - p_msg->payload, p_msg->command, + pr_debug("%s: %s: sent P-va=%p, P-com=%x, P-size=%u\n", __func__, + chan->name, p_msg->payload, p_msg->command, (unsigned int)p_msg->payload_length); if (list_empty(&svc_data_mem)) { @@ -1747,12 +1777,16 @@ int stratix10_svc_send(struct stratix10_svc_chan *chan, void *msg) p_data->arg[2] = p_msg->arg[2]; p_data->size = p_msg->payload_length; p_data->chan = chan; - pr_debug("%s: put to FIFO pa=0x%016x, cmd=%x, size=%u\n", __func__, - (unsigned int)p_data->paddr, p_data->command, - (unsigned int)p_data->size); - ret = kfifo_in_spinlocked(&chan->ctrl->svc_fifo, p_data, + pr_debug("%s: %s: put to FIFO pa=0x%016x, cmd=%x, size=%u\n", + __func__, + chan->name, + (unsigned int)p_data->paddr, + p_data->command, + (unsigned int)p_data->size); + + ret = kfifo_in_spinlocked(&chan->svc_fifo, p_data, sizeof(*p_data), - &chan->ctrl->svc_fifo_lock); + &chan->svc_fifo_lock); kfree(p_data); @@ -1773,11 +1807,12 @@ EXPORT_SYMBOL_GPL(stratix10_svc_send); */ void stratix10_svc_done(struct stratix10_svc_chan *chan) { - /* stop thread when thread is running AND only one active client */ - if (chan->ctrl->task && chan->ctrl->num_active_client <= 1) { - pr_debug("svc_smc_hvc_shm_thread is stopped\n"); - kthread_stop(chan->ctrl->task); - chan->ctrl->task = NULL; + /* stop thread when thread is running */ + if (chan->task) { + pr_debug("%s: %s: svc_smc_hvc_shm_thread is stopping\n", + __func__, chan->name); + kthread_stop(chan->task); + chan->task = NULL; } } EXPORT_SYMBOL_GPL(stratix10_svc_done); @@ -1817,8 +1852,8 @@ void *stratix10_svc_allocate_memory(struct stratix10_svc_chan *chan, pmem->paddr = pa; pmem->size = s; list_add_tail(&pmem->node, &svc_data_mem); - pr_debug("%s: va=%p, pa=0x%016x\n", __func__, - pmem->vaddr, (unsigned int)pmem->paddr); + pr_debug("%s: %s: va=%p, pa=0x%016x\n", __func__, + chan->name, pmem->vaddr, (unsigned int)pmem->paddr); return (void *)va; } @@ -1855,6 +1890,13 @@ static const struct of_device_id stratix10_svc_drv_match[] = { {}, }; +static const char * const chan_names[SVC_NUM_CHANNEL] = { + SVC_CLIENT_FPGA, + SVC_CLIENT_RSU, + SVC_CLIENT_FCS, + SVC_CLIENT_HWMON +}; + static int stratix10_svc_drv_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -1862,11 +1904,11 @@ static int stratix10_svc_drv_probe(struct platform_device *pdev) struct stratix10_svc_chan *chans; struct gen_pool *genpool; struct stratix10_svc_sh_memory *sh_memory; - struct stratix10_svc *svc; + struct stratix10_svc *svc = NULL; svc_invoke_fn *invoke_fn; size_t fifo_size; - int ret; + int ret, i = 0; /* get SMC or HVC function */ invoke_fn = get_invoke_func(dev); @@ -1905,8 +1947,8 @@ static int stratix10_svc_drv_probe(struct platform_device *pdev) controller->num_active_client = 0; controller->chans = chans; controller->genpool = genpool; - controller->task = NULL; controller->invoke_fn = invoke_fn; + INIT_LIST_HEAD(&controller->node); init_completion(&controller->complete_status); ret = stratix10_svc_async_init(controller); @@ -1917,32 +1959,20 @@ static int stratix10_svc_drv_probe(struct platform_device *pdev) } fifo_size = sizeof(struct stratix10_svc_data) * SVC_NUM_DATA_IN_FIFO; - ret = kfifo_alloc(&controller->svc_fifo, fifo_size, GFP_KERNEL); - if (ret) { - dev_err(dev, "failed to allocate FIFO\n"); - goto err_async_exit; - } - spin_lock_init(&controller->svc_fifo_lock); - - chans[0].scl = NULL; - chans[0].ctrl = controller; - chans[0].name = SVC_CLIENT_FPGA; - spin_lock_init(&chans[0].lock); + mutex_init(&controller->sdm_lock); - chans[1].scl = NULL; - chans[1].ctrl = controller; - chans[1].name = SVC_CLIENT_RSU; - spin_lock_init(&chans[1].lock); - - chans[2].scl = NULL; - chans[2].ctrl = controller; - chans[2].name = SVC_CLIENT_FCS; - spin_lock_init(&chans[2].lock); - - chans[3].scl = NULL; - chans[3].ctrl = controller; - chans[3].name = SVC_CLIENT_HWMON; - spin_lock_init(&chans[3].lock); + for (i = 0; i < SVC_NUM_CHANNEL; i++) { + chans[i].scl = NULL; + chans[i].ctrl = controller; + chans[i].name = (char *)chan_names[i]; + spin_lock_init(&chans[i].lock); + ret = kfifo_alloc(&chans[i].svc_fifo, fifo_size, GFP_KERNEL); + if (ret) { + dev_err(dev, "failed to allocate FIFO %d\n", i); + goto err_free_fifos; + } + spin_lock_init(&chans[i].svc_fifo_lock); + } list_add_tail(&controller->node, &svc_ctrl); platform_set_drvdata(pdev, controller); @@ -1951,7 +1981,7 @@ static int stratix10_svc_drv_probe(struct platform_device *pdev) svc = devm_kzalloc(dev, sizeof(*svc), GFP_KERNEL); if (!svc) { ret = -ENOMEM; - goto err_free_kfifo; + goto err_free_fifos; } controller->svc = svc; @@ -1959,51 +1989,43 @@ static int stratix10_svc_drv_probe(struct platform_device *pdev) if (!svc->stratix10_svc_rsu) { dev_err(dev, "failed to allocate %s device\n", STRATIX10_RSU); ret = -ENOMEM; - goto err_free_kfifo; + goto err_free_fifos; } ret = platform_device_add(svc->stratix10_svc_rsu); - if (ret) { - platform_device_put(svc->stratix10_svc_rsu); - goto err_free_kfifo; - } - - svc->intel_svc_fcs = platform_device_alloc(INTEL_FCS, 1); - if (!svc->intel_svc_fcs) { - dev_err(dev, "failed to allocate %s device\n", INTEL_FCS); - ret = -ENOMEM; - goto err_unregister_rsu_dev; - } - - ret = platform_device_add(svc->intel_svc_fcs); - if (ret) { - platform_device_put(svc->intel_svc_fcs); - goto err_unregister_rsu_dev; - } + if (ret) + goto err_put_device; ret = of_platform_default_populate(dev_of_node(dev), NULL, dev); if (ret) - goto err_unregister_fcs_dev; + goto err_unregister_rsu_dev; pr_info("Intel Service Layer Driver Initialized\n"); return 0; -err_unregister_fcs_dev: - platform_device_unregister(svc->intel_svc_fcs); err_unregister_rsu_dev: platform_device_unregister(svc->stratix10_svc_rsu); -err_free_kfifo: - kfifo_free(&controller->svc_fifo); -err_async_exit: + goto err_free_fifos; +err_put_device: + platform_device_put(svc->stratix10_svc_rsu); +err_free_fifos: + /* only remove from list if list_add_tail() was reached */ + if (!list_empty(&controller->node)) + list_del(&controller->node); + /* free only the FIFOs that were successfully allocated */ + while (i--) + kfifo_free(&chans[i].svc_fifo); stratix10_svc_async_exit(controller); err_destroy_pool: gen_pool_destroy(genpool); + return ret; } static void stratix10_svc_drv_remove(struct platform_device *pdev) { + int i; struct stratix10_svc_controller *ctrl = platform_get_drvdata(pdev); struct stratix10_svc *svc = ctrl->svc; @@ -2011,14 +2033,16 @@ static void stratix10_svc_drv_remove(struct platform_device *pdev) of_platform_depopulate(ctrl->dev); - platform_device_unregister(svc->intel_svc_fcs); platform_device_unregister(svc->stratix10_svc_rsu); - kfifo_free(&ctrl->svc_fifo); - if (ctrl->task) { - kthread_stop(ctrl->task); - ctrl->task = NULL; + for (i = 0; i < SVC_NUM_CHANNEL; i++) { + if (ctrl->chans[i].task) { + kthread_stop(ctrl->chans[i].task); + ctrl->chans[i].task = NULL; + } + kfifo_free(&ctrl->chans[i].svc_fifo); } + if (ctrl->genpool) gen_pool_destroy(ctrl->genpool); list_del(&ctrl->node); diff --git a/include/linux/firmware/intel/stratix10-svc-client.h b/include/linux/firmware/intel/stratix10-svc-client.h index d290060f4c73..91013161e9db 100644 --- a/include/linux/firmware/intel/stratix10-svc-client.h +++ b/include/linux/firmware/intel/stratix10-svc-client.h @@ -68,12 +68,12 @@ * timeout value used in Stratix10 FPGA manager driver. * timeout value used in RSU driver */ -#define SVC_RECONFIG_REQUEST_TIMEOUT_MS 300 -#define SVC_RECONFIG_BUFFER_TIMEOUT_MS 720 -#define SVC_RSU_REQUEST_TIMEOUT_MS 300 +#define SVC_RECONFIG_REQUEST_TIMEOUT_MS 5000 +#define SVC_RECONFIG_BUFFER_TIMEOUT_MS 5000 +#define SVC_RSU_REQUEST_TIMEOUT_MS 2000 #define SVC_FCS_REQUEST_TIMEOUT_MS 2000 #define SVC_COMPLETED_TIMEOUT_MS 30000 -#define SVC_HWMON_REQUEST_TIMEOUT_MS 300 +#define SVC_HWMON_REQUEST_TIMEOUT_MS 2000 struct stratix10_svc_chan; -- cgit v1.2.3 From 6ffd853b0b10e1e292cef0bfd0997986471254de Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sun, 1 Mar 2026 16:51:44 -0800 Subject: build_bug.h: correct function parameters names in kernel-doc Use the correct function (or macro) names to avoid kernel-doc warnings: Warning: include/linux/build_bug.h:38 function parameter 'cond' not described in 'BUILD_BUG_ON_MSG' Warning: include/linux/build_bug.h:38 function parameter 'msg' not described in 'BUILD_BUG_ON_MSG' Warning: include/linux/build_bug.h:76 function parameter 'expr' not described in 'static_assert' Link: https://lkml.kernel.org/r/20260302005144.3467019-1-rdunlap@infradead.org Signed-off-by: Randy Dunlap Reviewed-by: SeongJae Park Signed-off-by: Andrew Morton --- include/linux/build_bug.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/include/linux/build_bug.h b/include/linux/build_bug.h index 2cfbb4c65c78..d3dc5dc5f916 100644 --- a/include/linux/build_bug.h +++ b/include/linux/build_bug.h @@ -32,7 +32,8 @@ /** * BUILD_BUG_ON_MSG - break compile if a condition is true & emit supplied * error message. - * @condition: the condition which the compiler should know is false. + * @cond: the condition which the compiler should know is false. + * @msg: build-time error message * * See BUILD_BUG_ON for description. */ @@ -60,6 +61,7 @@ /** * static_assert - check integer constant expression at build time + * @expr: expression to be checked * * static_assert() is a wrapper for the C11 _Static_assert, with a * little macro magic to make the message optional (defaulting to the -- cgit v1.2.3 From 28b225282d44e2ef40e7f46cfdbd5d1b20b8874f Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Mon, 9 Mar 2026 17:39:07 -0700 Subject: page_pool: store detach_time as ktime_t to avoid false-negatives While testing other changes in vng I noticed that nl_netdev.page_pool_check flakes. This never happens in real CI. Turns out vng may boot and get to that test in less than a second. page_pool_detached() records the detach time in seconds, so if vng is fast enough detach time is set to 0. Other code treats 0 as "not detached". detach_time is only used to report the state to the user, so it's not a huge deal in practice but let's fix it. Store the raw ktime_t (nanoseconds) instead. A nanosecond value of 0 is practically impossible. Acked-by: Jesper Dangaard Brouer Fixes: 69cb4952b6f6 ("net: page_pool: report when page pool was destroyed") Link: https://patch.msgid.link/20260310003907.3540019-1-kuba@kernel.org Signed-off-by: Jakub Kicinski --- include/net/page_pool/types.h | 2 +- net/core/page_pool_user.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/include/net/page_pool/types.h b/include/net/page_pool/types.h index 0d453484a585..cdd95477af7a 100644 --- a/include/net/page_pool/types.h +++ b/include/net/page_pool/types.h @@ -247,7 +247,7 @@ struct page_pool { /* User-facing fields, protected by page_pools_lock */ struct { struct hlist_node list; - u64 detach_time; + ktime_t detach_time; u32 id; } user; }; diff --git a/net/core/page_pool_user.c b/net/core/page_pool_user.c index c82a95beceff..ee5060d8eec0 100644 --- a/net/core/page_pool_user.c +++ b/net/core/page_pool_user.c @@ -245,7 +245,7 @@ page_pool_nl_fill(struct sk_buff *rsp, const struct page_pool *pool, goto err_cancel; if (pool->user.detach_time && nla_put_uint(rsp, NETDEV_A_PAGE_POOL_DETACH_TIME, - pool->user.detach_time)) + ktime_divns(pool->user.detach_time, NSEC_PER_SEC))) goto err_cancel; if (pool->mp_ops && pool->mp_ops->nl_fill(pool->mp_priv, rsp, NULL)) @@ -337,7 +337,7 @@ err_unlock: void page_pool_detached(struct page_pool *pool) { mutex_lock(&page_pools_lock); - pool->user.detach_time = ktime_get_boottime_seconds(); + pool->user.detach_time = ktime_get_boottime(); netdev_nl_page_pool_event(pool, NETDEV_CMD_PAGE_POOL_CHANGE_NTF); mutex_unlock(&page_pools_lock); } -- cgit v1.2.3 From b2e48c429ec54715d16fefa719dd2fbded2e65be Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 10 Mar 2026 21:28:53 +0100 Subject: sched/mmcid: Prevent CID stalls due to concurrent forks A newly forked task is accounted as MMCID user before the task is visible in the process' thread list and the global task list. This creates the following problem: CPU1 CPU2 fork() sched_mm_cid_fork(tnew1) tnew1->mm.mm_cid_users++; tnew1->mm_cid.cid = getcid() -> preemption fork() sched_mm_cid_fork(tnew2) tnew2->mm.mm_cid_users++; // Reaches the per CPU threshold mm_cid_fixup_tasks_to_cpus() for_each_other(current, p) .... As tnew1 is not visible yet, this fails to fix up the already allocated CID of tnew1. As a consequence a subsequent schedule in might fail to acquire a (transitional) CID and the machine stalls. Move the invocation of sched_mm_cid_fork() after the new task becomes visible in the thread and the task list to prevent this. This also makes it symmetrical vs. exit() where the task is removed as CID user before the task is removed from the thread and task lists. Fixes: fbd0e71dc370 ("sched/mmcid: Provide CID ownership mode fixup functions") Signed-off-by: Thomas Gleixner Signed-off-by: Peter Zijlstra (Intel) Tested-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260310202525.969061974@kernel.org --- include/linux/sched.h | 2 -- kernel/fork.c | 2 -- kernel/sched/core.c | 22 +++++++++++++++------- 3 files changed, 15 insertions(+), 11 deletions(-) (limited to 'include') diff --git a/include/linux/sched.h b/include/linux/sched.h index a7b4a980eb2f..5a5d3dbc9cdf 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -2354,7 +2354,6 @@ static __always_inline void alloc_tag_restore(struct alloc_tag *tag, struct allo #ifdef CONFIG_SCHED_MM_CID void sched_mm_cid_before_execve(struct task_struct *t); void sched_mm_cid_after_execve(struct task_struct *t); -void sched_mm_cid_fork(struct task_struct *t); void sched_mm_cid_exit(struct task_struct *t); static __always_inline int task_mm_cid(struct task_struct *t) { @@ -2363,7 +2362,6 @@ static __always_inline int task_mm_cid(struct task_struct *t) #else static inline void sched_mm_cid_before_execve(struct task_struct *t) { } static inline void sched_mm_cid_after_execve(struct task_struct *t) { } -static inline void sched_mm_cid_fork(struct task_struct *t) { } static inline void sched_mm_cid_exit(struct task_struct *t) { } static __always_inline int task_mm_cid(struct task_struct *t) { diff --git a/kernel/fork.c b/kernel/fork.c index 65113a304518..7febf4c2889e 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -1586,7 +1586,6 @@ static int copy_mm(u64 clone_flags, struct task_struct *tsk) tsk->mm = mm; tsk->active_mm = mm; - sched_mm_cid_fork(tsk); return 0; } @@ -2498,7 +2497,6 @@ bad_fork_cleanup_namespaces: exit_nsproxy_namespaces(p); bad_fork_cleanup_mm: if (p->mm) { - sched_mm_cid_exit(p); mm_clear_owner(p->mm, p); mmput(p->mm); } diff --git a/kernel/sched/core.c b/kernel/sched/core.c index b7f77c165a6e..d25427855b5d 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -4729,8 +4729,11 @@ void sched_cancel_fork(struct task_struct *p) scx_cancel_fork(p); } +static void sched_mm_cid_fork(struct task_struct *t); + void sched_post_fork(struct task_struct *p) { + sched_mm_cid_fork(p); uclamp_post_fork(p); scx_post_fork(p); } @@ -10646,12 +10649,13 @@ static void mm_cid_do_fixup_tasks_to_cpus(struct mm_struct *mm) * possible switch back to per task mode happens either in the * deferred handler function or in the next fork()/exit(). * - * The caller has already transferred. The newly incoming task is - * already accounted for, but not yet visible. + * The caller has already transferred so remove it from the users + * count. The incoming task is already visible and has mm_cid.active, + * but has task::mm_cid::cid == UNSET. Still it needs to be accounted + * for. Concurrent fork()s might add more threads, but all of them have + * task::mm_cid::active = 0, so they don't affect the accounting here. */ - users = mm->mm_cid.users - 2; - if (!users) - return; + users = mm->mm_cid.users - 1; guard(rcu)(); for_other_threads(current, t) { @@ -10688,12 +10692,15 @@ static bool sched_mm_cid_add_user(struct task_struct *t, struct mm_struct *mm) return mm_update_max_cids(mm); } -void sched_mm_cid_fork(struct task_struct *t) +static void sched_mm_cid_fork(struct task_struct *t) { struct mm_struct *mm = t->mm; bool percpu; - WARN_ON_ONCE(!mm || t->mm_cid.cid != MM_CID_UNSET); + if (!mm) + return; + + WARN_ON_ONCE(t->mm_cid.cid != MM_CID_UNSET); guard(mutex)(&mm->mm_cid.mutex); scoped_guard(raw_spinlock_irq, &mm->mm_cid.lock) { @@ -10885,6 +10892,7 @@ void mm_init_cid(struct mm_struct *mm, struct task_struct *p) } #else /* CONFIG_SCHED_MM_CID */ static inline void mm_update_cpus_allowed(struct mm_struct *mm, const struct cpumask *affmsk) { } +static inline void sched_mm_cid_fork(struct task_struct *t) { } #endif /* !CONFIG_SCHED_MM_CID */ static DEFINE_PER_CPU(struct sched_change_ctx, sched_change_ctx); -- cgit v1.2.3 From 192d852129b1b7c4f0ddbab95d0de1efd5ee1405 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 10 Mar 2026 21:29:09 +0100 Subject: sched/mmcid: Avoid full tasklist walks Chasing vfork()'ed tasks on a CID ownership mode switch requires a full task list walk, which is obviously expensive on large systems. Avoid that by keeping a list of tasks using a mm MMCID entity in mm::mm_cid and walk this list instead. This removes the proven to be flaky counting logic and avoids a full task list walk in the case of vfork()'ed tasks. Fixes: fbd0e71dc370 ("sched/mmcid: Provide CID ownership mode fixup functions") Signed-off-by: Thomas Gleixner Signed-off-by: Peter Zijlstra (Intel) Tested-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260310202526.183824481@kernel.org --- include/linux/rseq_types.h | 6 +++++- kernel/fork.c | 1 + kernel/sched/core.c | 54 +++++++++++----------------------------------- 3 files changed, 18 insertions(+), 43 deletions(-) (limited to 'include') diff --git a/include/linux/rseq_types.h b/include/linux/rseq_types.h index da5fa6f40294..0b42045988db 100644 --- a/include/linux/rseq_types.h +++ b/include/linux/rseq_types.h @@ -133,10 +133,12 @@ struct rseq_data { }; * @active: MM CID is active for the task * @cid: The CID associated to the task either permanently or * borrowed from the CPU + * @node: Queued in the per MM MMCID list */ struct sched_mm_cid { unsigned int active; unsigned int cid; + struct hlist_node node; }; /** @@ -157,6 +159,7 @@ struct mm_cid_pcpu { * @work: Regular work to handle the affinity mode change case * @lock: Spinlock to protect against affinity setting which can't take @mutex * @mutex: Mutex to serialize forks and exits related to this mm + * @user_list: List of the MM CID users of a MM * @nr_cpus_allowed: The number of CPUs in the per MM allowed CPUs map. The map * is growth only. * @users: The number of tasks sharing this MM. Separate from mm::mm_users @@ -177,13 +180,14 @@ struct mm_mm_cid { raw_spinlock_t lock; struct mutex mutex; + struct hlist_head user_list; /* Low frequency modified */ unsigned int nr_cpus_allowed; unsigned int users; unsigned int pcpu_thrs; unsigned int update_deferred; -}____cacheline_aligned_in_smp; +} ____cacheline_aligned; #else /* CONFIG_SCHED_MM_CID */ struct mm_mm_cid { }; struct sched_mm_cid { }; diff --git a/kernel/fork.c b/kernel/fork.c index 7febf4c2889e..bc2bf58b93b6 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -1000,6 +1000,7 @@ static struct task_struct *dup_task_struct(struct task_struct *orig, int node) #ifdef CONFIG_SCHED_MM_CID tsk->mm_cid.cid = MM_CID_UNSET; tsk->mm_cid.active = 0; + INIT_HLIST_NODE(&tsk->mm_cid.node); #endif return tsk; diff --git a/kernel/sched/core.c b/kernel/sched/core.c index f56156f91d08..496dff740dca 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -10620,13 +10620,10 @@ static inline void mm_cid_transit_to_cpu(struct task_struct *t, struct mm_cid_pc } } -static bool mm_cid_fixup_task_to_cpu(struct task_struct *t, struct mm_struct *mm) +static void mm_cid_fixup_task_to_cpu(struct task_struct *t, struct mm_struct *mm) { /* Remote access to mm::mm_cid::pcpu requires rq_lock */ guard(task_rq_lock)(t); - /* If the task is not active it is not in the users count */ - if (!t->mm_cid.active) - return false; if (cid_on_task(t->mm_cid.cid)) { /* If running on the CPU, put the CID in transit mode, otherwise drop it */ if (task_rq(t)->curr == t) @@ -10634,51 +10631,21 @@ static bool mm_cid_fixup_task_to_cpu(struct task_struct *t, struct mm_struct *mm else mm_unset_cid_on_task(t); } - return true; } -static void mm_cid_do_fixup_tasks_to_cpus(struct mm_struct *mm) +static void mm_cid_fixup_tasks_to_cpus(void) { - struct task_struct *p, *t; - unsigned int users; - - /* - * This can obviously race with a concurrent affinity change, which - * increases the number of allowed CPUs for this mm, but that does - * not affect the mode and only changes the CID constraints. A - * possible switch back to per task mode happens either in the - * deferred handler function or in the next fork()/exit(). - * - * The caller has already transferred so remove it from the users - * count. The incoming task is already visible and has mm_cid.active, - * but has task::mm_cid::cid == UNSET. Still it needs to be accounted - * for. Concurrent fork()s might add more threads, but all of them have - * task::mm_cid::active = 0, so they don't affect the accounting here. - */ - users = mm->mm_cid.users - 1; - - guard(rcu)(); - for_other_threads(current, t) { - if (mm_cid_fixup_task_to_cpu(t, mm)) - users--; - } + struct mm_struct *mm = current->mm; + struct task_struct *t; - if (!users) - return; + lockdep_assert_held(&mm->mm_cid.mutex); - /* Happens only for VM_CLONE processes. */ - for_each_process_thread(p, t) { - if (t == current || t->mm != mm) - continue; - mm_cid_fixup_task_to_cpu(t, mm); + hlist_for_each_entry(t, &mm->mm_cid.user_list, mm_cid.node) { + /* Current has already transferred before invoking the fixup. */ + if (t != current) + mm_cid_fixup_task_to_cpu(t, mm); } -} - -static void mm_cid_fixup_tasks_to_cpus(void) -{ - struct mm_struct *mm = current->mm; - mm_cid_do_fixup_tasks_to_cpus(mm); mm_cid_complete_transit(mm, MM_CID_ONCPU); } @@ -10687,6 +10654,7 @@ static bool sched_mm_cid_add_user(struct task_struct *t, struct mm_struct *mm) lockdep_assert_held(&mm->mm_cid.lock); t->mm_cid.active = 1; + hlist_add_head(&t->mm_cid.node, &mm->mm_cid.user_list); mm->mm_cid.users++; return mm_update_max_cids(mm); } @@ -10744,6 +10712,7 @@ static bool sched_mm_cid_remove_user(struct task_struct *t) /* Clear the transition bit */ t->mm_cid.cid = cid_from_transit_cid(t->mm_cid.cid); mm_unset_cid_on_task(t); + hlist_del_init(&t->mm_cid.node); t->mm->mm_cid.users--; return mm_update_max_cids(t->mm); } @@ -10886,6 +10855,7 @@ void mm_init_cid(struct mm_struct *mm, struct task_struct *p) mutex_init(&mm->mm_cid.mutex); mm->mm_cid.irq_work = IRQ_WORK_INIT_HARD(mm_cid_irq_work); INIT_WORK(&mm->mm_cid.work, mm_cid_work_fn); + INIT_HLIST_HEAD(&mm->mm_cid.user_list); cpumask_copy(mm_cpus_allowed(mm), &p->cpus_mask); bitmap_zero(mm_cidmask(mm), num_possible_cpus()); } -- cgit v1.2.3 From 227312b4a65c373d5d8b4683b7fc36203fedc516 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 28 Feb 2026 15:52:58 +0100 Subject: HID: input: Add HID_BATTERY_QUIRK_DYNAMIC for Elan touchscreens Elan touchscreens have a HID-battery device for the stylus which is always there even if there is no stylus. This is causing upower to report an empty battery for the stylus and some desktop-environments will show a notification about this, which is quite annoying. Because of this the HID-battery is being ignored on all Elan I2c and USB touchscreens, but this causes there to be no battery reporting for the stylus at all. This adds a new HID_BATTERY_QUIRK_DYNAMIC and uses these for the Elan touchscreens. This new quirks causes the present value of the battery to start at 0, which will make userspace ignore it and only sets present to 1 after receiving a battery input report which only happens when the stylus gets in range. Reported-by: ggrundik@gmail.com Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221118 Signed-off-by: Hans de Goede Reviewed-by: Sebastian Reichel Signed-off-by: Jiri Kosina --- drivers/hid/hid-input.c | 14 +++++++++++--- include/linux/hid.h | 1 + 2 files changed, 12 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 67ca1e88ce13..8fc20df99b97 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -354,6 +354,7 @@ static enum power_supply_property hidinput_battery_props[] = { #define HID_BATTERY_QUIRK_FEATURE (1 << 1) /* ask for feature report */ #define HID_BATTERY_QUIRK_IGNORE (1 << 2) /* completely ignore the battery */ #define HID_BATTERY_QUIRK_AVOID_QUERY (1 << 3) /* do not query the battery */ +#define HID_BATTERY_QUIRK_DYNAMIC (1 << 4) /* report present only after life signs */ static const struct hid_device_id hid_battery_quirks[] = { { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, @@ -398,8 +399,8 @@ static const struct hid_device_id hid_battery_quirks[] = { * Elan HID touchscreens seem to all report a non present battery, * set HID_BATTERY_QUIRK_IGNORE for all Elan I2C and USB HID devices. */ - { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, HID_ANY_ID), HID_BATTERY_QUIRK_IGNORE }, - { HID_USB_DEVICE(USB_VENDOR_ID_ELAN, HID_ANY_ID), HID_BATTERY_QUIRK_IGNORE }, + { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, HID_ANY_ID), HID_BATTERY_QUIRK_DYNAMIC }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELAN, HID_ANY_ID), HID_BATTERY_QUIRK_DYNAMIC }, {} }; @@ -456,11 +457,14 @@ static int hidinput_get_battery_property(struct power_supply *psy, int ret = 0; switch (prop) { - case POWER_SUPPLY_PROP_PRESENT: case POWER_SUPPLY_PROP_ONLINE: val->intval = 1; break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = dev->battery_present; + break; + case POWER_SUPPLY_PROP_CAPACITY: if (dev->battery_status != HID_BATTERY_REPORTED && !dev->battery_avoid_query) { @@ -573,6 +577,8 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type, if (quirks & HID_BATTERY_QUIRK_AVOID_QUERY) dev->battery_avoid_query = true; + dev->battery_present = (quirks & HID_BATTERY_QUIRK_DYNAMIC) ? false : true; + dev->battery = power_supply_register(&dev->dev, psy_desc, &psy_cfg); if (IS_ERR(dev->battery)) { error = PTR_ERR(dev->battery); @@ -628,6 +634,7 @@ static void hidinput_update_battery(struct hid_device *dev, unsigned int usage, return; if (hidinput_update_battery_charge_status(dev, usage, value)) { + dev->battery_present = true; power_supply_changed(dev->battery); return; } @@ -643,6 +650,7 @@ static void hidinput_update_battery(struct hid_device *dev, unsigned int usage, if (dev->battery_status != HID_BATTERY_REPORTED || capacity != dev->battery_capacity || ktime_after(ktime_get_coarse(), dev->battery_ratelimit_time)) { + dev->battery_present = true; dev->battery_capacity = capacity; dev->battery_status = HID_BATTERY_REPORTED; dev->battery_ratelimit_time = diff --git a/include/linux/hid.h b/include/linux/hid.h index 2990b9f94cb5..31324609af4d 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -682,6 +682,7 @@ struct hid_device { __s32 battery_charge_status; enum hid_battery_status battery_status; bool battery_avoid_query; + bool battery_present; ktime_t battery_ratelimit_time; #endif -- cgit v1.2.3 From 416909962e7cdf29fd01ac523c953f37708df93d Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Tue, 17 Feb 2026 22:07:47 -0500 Subject: USB: usbcore: Introduce usb_bulk_msg_killable() The synchronous message API in usbcore (usb_control_msg(), usb_bulk_msg(), and so on) uses uninterruptible waits. However, drivers may call these routines in the context of a user thread, which means it ought to be possible to at least kill them. For this reason, introduce a new usb_bulk_msg_killable() function which behaves the same as usb_bulk_msg() except for using wait_for_completion_killable_timeout() instead of wait_for_completion_timeout(). The same can be done later for usb_control_msg() later on, if it turns out to be needed. Signed-off-by: Alan Stern Suggested-by: Oliver Neukum Link: https://lore.kernel.org/linux-usb/3acfe838-6334-4f6d-be7c-4bb01704b33d@rowland.harvard.edu/ Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") CC: stable@vger.kernel.org Link: https://patch.msgid.link/248628b4-cc83-4e81-a620-3ce4e0376d41@rowland.harvard.edu Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/message.c | 79 ++++++++++++++++++++++++++++++++++++++++------ include/linux/usb.h | 5 +-- 2 files changed, 72 insertions(+), 12 deletions(-) (limited to 'include') diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index ea970ddf8879..d97ec7e8c280 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -42,16 +42,17 @@ static void usb_api_blocking_completion(struct urb *urb) /* - * Starts urb and waits for completion or timeout. Note that this call - * is NOT interruptible. Many device driver i/o requests should be - * interruptible and therefore these drivers should implement their - * own interruptible routines. + * Starts urb and waits for completion or timeout. + * Whether or not the wait is killable depends on the flag passed in. + * For example, compare usb_bulk_msg() and usb_bulk_msg_killable(). */ -static int usb_start_wait_urb(struct urb *urb, int timeout, int *actual_length) +static int usb_start_wait_urb(struct urb *urb, int timeout, int *actual_length, + bool killable) { struct api_context ctx; unsigned long expire; int retval; + long rc; init_completion(&ctx.done); urb->context = &ctx; @@ -61,12 +62,21 @@ static int usb_start_wait_urb(struct urb *urb, int timeout, int *actual_length) goto out; expire = timeout ? msecs_to_jiffies(timeout) : MAX_SCHEDULE_TIMEOUT; - if (!wait_for_completion_timeout(&ctx.done, expire)) { + if (killable) + rc = wait_for_completion_killable_timeout(&ctx.done, expire); + else + rc = wait_for_completion_timeout(&ctx.done, expire); + if (rc <= 0) { usb_kill_urb(urb); - retval = (ctx.status == -ENOENT ? -ETIMEDOUT : ctx.status); + if (ctx.status != -ENOENT) + retval = ctx.status; + else if (rc == 0) + retval = -ETIMEDOUT; + else + retval = rc; dev_dbg(&urb->dev->dev, - "%s timed out on ep%d%s len=%u/%u\n", + "%s timed out or killed on ep%d%s len=%u/%u\n", current->comm, usb_endpoint_num(&urb->ep->desc), usb_urb_dir_in(urb) ? "in" : "out", @@ -100,7 +110,7 @@ static int usb_internal_control_msg(struct usb_device *usb_dev, usb_fill_control_urb(urb, usb_dev, pipe, (unsigned char *)cmd, data, len, usb_api_blocking_completion, NULL); - retv = usb_start_wait_urb(urb, timeout, &length); + retv = usb_start_wait_urb(urb, timeout, &length, false); if (retv < 0) return retv; else @@ -385,10 +395,59 @@ int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe, usb_fill_bulk_urb(urb, usb_dev, pipe, data, len, usb_api_blocking_completion, NULL); - return usb_start_wait_urb(urb, timeout, actual_length); + return usb_start_wait_urb(urb, timeout, actual_length, false); } EXPORT_SYMBOL_GPL(usb_bulk_msg); +/** + * usb_bulk_msg_killable - Builds a bulk urb, sends it off and waits for completion in a killable state + * @usb_dev: pointer to the usb device to send the message to + * @pipe: endpoint "pipe" to send the message to + * @data: pointer to the data to send + * @len: length in bytes of the data to send + * @actual_length: pointer to a location to put the actual length transferred + * in bytes + * @timeout: time in msecs to wait for the message to complete before + * timing out (if 0 the wait is forever) + * + * Context: task context, might sleep. + * + * This function is just like usb_blk_msg() except that it waits in a + * killable state. + * + * Return: + * If successful, 0. Otherwise a negative error number. The number of actual + * bytes transferred will be stored in the @actual_length parameter. + * + */ +int usb_bulk_msg_killable(struct usb_device *usb_dev, unsigned int pipe, + void *data, int len, int *actual_length, int timeout) +{ + struct urb *urb; + struct usb_host_endpoint *ep; + + ep = usb_pipe_endpoint(usb_dev, pipe); + if (!ep || len < 0) + return -EINVAL; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + return -ENOMEM; + + if ((ep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == + USB_ENDPOINT_XFER_INT) { + pipe = (pipe & ~(3 << 30)) | (PIPE_INTERRUPT << 30); + usb_fill_int_urb(urb, usb_dev, pipe, data, len, + usb_api_blocking_completion, NULL, + ep->desc.bInterval); + } else + usb_fill_bulk_urb(urb, usb_dev, pipe, data, len, + usb_api_blocking_completion, NULL); + + return usb_start_wait_urb(urb, timeout, actual_length, true); +} +EXPORT_SYMBOL_GPL(usb_bulk_msg_killable); + /*-------------------------------------------------------------------*/ static void sg_clean(struct usb_sg_request *io) diff --git a/include/linux/usb.h b/include/linux/usb.h index fbfcc70b07fb..57ceeb02a7cb 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -1868,8 +1868,9 @@ extern int usb_control_msg(struct usb_device *dev, unsigned int pipe, extern int usb_interrupt_msg(struct usb_device *usb_dev, unsigned int pipe, void *data, int len, int *actual_length, int timeout); extern int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe, - void *data, int len, int *actual_length, - int timeout); + void *data, int len, int *actual_length, int timeout); +extern int usb_bulk_msg_killable(struct usb_device *usb_dev, unsigned int pipe, + void *data, int len, int *actual_length, int timeout); /* wrappers around usb_control_msg() for the most common standard requests */ int usb_control_msg_send(struct usb_device *dev, __u8 endpoint, __u8 request, -- cgit v1.2.3 From 1015c27a5e1a63efae2b18a9901494474b4d1dc3 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Tue, 17 Feb 2026 22:10:32 -0500 Subject: USB: core: Limit the length of unkillable synchronous timeouts The usb_control_msg(), usb_bulk_msg(), and usb_interrupt_msg() APIs in usbcore allow unlimited timeout durations. And since they use uninterruptible waits, this leaves open the possibility of hanging a task for an indefinitely long time, with no way to kill it short of unplugging the target device. To prevent this sort of problem, enforce a maximum limit on the length of these unkillable timeouts. The limit chosen here, somewhat arbitrarily, is 60 seconds. On many systems (although not all) this is short enough to avoid triggering the kernel's hung-task detector. In addition, clear up the ambiguity of negative timeout values by treating them the same as 0, i.e., using the maximum allowed timeout. Signed-off-by: Alan Stern Link: https://lore.kernel.org/linux-usb/3acfe838-6334-4f6d-be7c-4bb01704b33d@rowland.harvard.edu/ Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") CC: stable@vger.kernel.org Link: https://patch.msgid.link/15fc9773-a007-47b0-a703-df89a8cf83dd@rowland.harvard.edu Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/message.c | 27 +++++++++++++-------------- include/linux/usb.h | 3 +++ 2 files changed, 16 insertions(+), 14 deletions(-) (limited to 'include') diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index d97ec7e8c280..2ab120ce2fa8 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -45,6 +45,8 @@ static void usb_api_blocking_completion(struct urb *urb) * Starts urb and waits for completion or timeout. * Whether or not the wait is killable depends on the flag passed in. * For example, compare usb_bulk_msg() and usb_bulk_msg_killable(). + * + * For non-killable waits, we enforce a maximum limit on the timeout value. */ static int usb_start_wait_urb(struct urb *urb, int timeout, int *actual_length, bool killable) @@ -61,7 +63,9 @@ static int usb_start_wait_urb(struct urb *urb, int timeout, int *actual_length, if (unlikely(retval)) goto out; - expire = timeout ? msecs_to_jiffies(timeout) : MAX_SCHEDULE_TIMEOUT; + if (!killable && (timeout <= 0 || timeout > USB_MAX_SYNCHRONOUS_TIMEOUT)) + timeout = USB_MAX_SYNCHRONOUS_TIMEOUT; + expire = (timeout > 0) ? msecs_to_jiffies(timeout) : MAX_SCHEDULE_TIMEOUT; if (killable) rc = wait_for_completion_killable_timeout(&ctx.done, expire); else @@ -127,8 +131,7 @@ static int usb_internal_control_msg(struct usb_device *usb_dev, * @index: USB message index value * @data: pointer to the data to send * @size: length in bytes of the data to send - * @timeout: time in msecs to wait for the message to complete before timing - * out (if 0 the wait is forever) + * @timeout: time in msecs to wait for the message to complete before timing out * * Context: task context, might sleep. * @@ -183,8 +186,7 @@ EXPORT_SYMBOL_GPL(usb_control_msg); * @index: USB message index value * @driver_data: pointer to the data to send * @size: length in bytes of the data to send - * @timeout: time in msecs to wait for the message to complete before timing - * out (if 0 the wait is forever) + * @timeout: time in msecs to wait for the message to complete before timing out * @memflags: the flags for memory allocation for buffers * * Context: !in_interrupt () @@ -242,8 +244,7 @@ EXPORT_SYMBOL_GPL(usb_control_msg_send); * @index: USB message index value * @driver_data: pointer to the data to be filled in by the message * @size: length in bytes of the data to be received - * @timeout: time in msecs to wait for the message to complete before timing - * out (if 0 the wait is forever) + * @timeout: time in msecs to wait for the message to complete before timing out * @memflags: the flags for memory allocation for buffers * * Context: !in_interrupt () @@ -314,8 +315,7 @@ EXPORT_SYMBOL_GPL(usb_control_msg_recv); * @len: length in bytes of the data to send * @actual_length: pointer to a location to put the actual length transferred * in bytes - * @timeout: time in msecs to wait for the message to complete before - * timing out (if 0 the wait is forever) + * @timeout: time in msecs to wait for the message to complete before timing out * * Context: task context, might sleep. * @@ -347,8 +347,7 @@ EXPORT_SYMBOL_GPL(usb_interrupt_msg); * @len: length in bytes of the data to send * @actual_length: pointer to a location to put the actual length transferred * in bytes - * @timeout: time in msecs to wait for the message to complete before - * timing out (if 0 the wait is forever) + * @timeout: time in msecs to wait for the message to complete before timing out * * Context: task context, might sleep. * @@ -408,12 +407,12 @@ EXPORT_SYMBOL_GPL(usb_bulk_msg); * @actual_length: pointer to a location to put the actual length transferred * in bytes * @timeout: time in msecs to wait for the message to complete before - * timing out (if 0 the wait is forever) + * timing out (if <= 0, the wait is as long as possible) * * Context: task context, might sleep. * - * This function is just like usb_blk_msg() except that it waits in a - * killable state. + * This function is just like usb_blk_msg(), except that it waits in a + * killable state and there is no limit on the timeout length. * * Return: * If successful, 0. Otherwise a negative error number. The number of actual diff --git a/include/linux/usb.h b/include/linux/usb.h index 57ceeb02a7cb..04277af4bb9d 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -1862,6 +1862,9 @@ void usb_free_noncoherent(struct usb_device *dev, size_t size, * SYNCHRONOUS CALL SUPPORT * *-------------------------------------------------------------------*/ +/* Maximum value allowed for timeout in synchronous routines below */ +#define USB_MAX_SYNCHRONOUS_TIMEOUT 60000 /* ms */ + extern int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request, __u8 requesttype, __u16 value, __u16 index, void *data, __u16 size, int timeout); -- cgit v1.2.3 From 9f6a983cfa22ac662c86e60816d3a357d4b551e9 Mon Sep 17 00:00:00 2001 From: Jie Deng Date: Fri, 27 Feb 2026 16:49:31 +0800 Subject: usb: core: new quirk to handle devices with zero configurations Some USB devices incorrectly report bNumConfigurations as 0 in their device descriptor, which causes the USB core to reject them during enumeration. logs: usb 1-2: device descriptor read/64, error -71 usb 1-2: no configurations usb 1-2: can't read configurations, error -22 However, these devices actually work correctly when treated as having a single configuration. Add a new quirk USB_QUIRK_FORCE_ONE_CONFIG to handle such devices. When this quirk is set, assume the device has 1 configuration instead of failing with -EINVAL. This quirk is applied to the device with VID:PID 5131:2007 which exhibits this behavior. Signed-off-by: Jie Deng Link: https://patch.msgid.link/20260227084931.1527461-1-dengjie03@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- Documentation/admin-guide/kernel-parameters.txt | 3 +++ drivers/usb/core/config.c | 6 +++++- drivers/usb/core/quirks.c | 5 +++++ include/linux/usb/quirks.h | 3 +++ 4 files changed, 16 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index cb850e5290c2..7d907efe9f49 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -8183,6 +8183,9 @@ Kernel parameters p = USB_QUIRK_SHORT_SET_ADDRESS_REQ_TIMEOUT (Reduce timeout of the SET_ADDRESS request from 5000 ms to 500 ms); + q = USB_QUIRK_FORCE_ONE_CONFIG (Device + claims zero configurations, + forcing to 1); Example: quirks=0781:5580:bk,0a5c:5834:gij usbhid.mousepoll= diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c index 1cd5fa61dc76..6a1fd967e0a6 100644 --- a/drivers/usb/core/config.c +++ b/drivers/usb/core/config.c @@ -927,7 +927,11 @@ int usb_get_configuration(struct usb_device *dev) dev->descriptor.bNumConfigurations = ncfg = USB_MAXCONFIG; } - if (ncfg < 1) { + if (ncfg < 1 && dev->quirks & USB_QUIRK_FORCE_ONE_CONFIG) { + dev_info(ddev, "Device claims zero configurations, forcing to 1\n"); + dev->descriptor.bNumConfigurations = 1; + ncfg = 1; + } else if (ncfg < 1) { dev_err(ddev, "no configurations\n"); return -EINVAL; } diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c index e347236d83e8..7bd408db05f4 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c @@ -140,6 +140,8 @@ static int quirks_param_set(const char *value, const struct kernel_param *kp) case 'p': flags |= USB_QUIRK_SHORT_SET_ADDRESS_REQ_TIMEOUT; break; + case 'q': + flags |= USB_QUIRK_FORCE_ONE_CONFIG; /* Ignore unrecognized flag characters */ } } @@ -589,6 +591,9 @@ static const struct usb_device_id usb_quirk_list[] = { /* VCOM device */ { USB_DEVICE(0x4296, 0x7570), .driver_info = USB_QUIRK_CONFIG_INTF_STRINGS }, + /* Noji-MCS SmartCard Reader */ + { USB_DEVICE(0x5131, 0x2007), .driver_info = USB_QUIRK_FORCE_ONE_CONFIG }, + /* INTEL VALUE SSD */ { USB_DEVICE(0x8086, 0xf1a5), .driver_info = USB_QUIRK_RESET_RESUME }, diff --git a/include/linux/usb/quirks.h b/include/linux/usb/quirks.h index 2f7bd2fdc616..b3cc7beab4a3 100644 --- a/include/linux/usb/quirks.h +++ b/include/linux/usb/quirks.h @@ -78,4 +78,7 @@ /* skip BOS descriptor request */ #define USB_QUIRK_NO_BOS BIT(17) +/* Device claims zero configurations, forcing to 1 */ +#define USB_QUIRK_FORCE_ONE_CONFIG BIT(18) + #endif /* __LINUX_USB_QUIRKS_H */ -- cgit v1.2.3 From edd20cb693d9cb5e3d6fcecd858093dab4e2b0aa Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Wed, 11 Mar 2026 16:51:00 +0000 Subject: Revert "mshv: expose the scrub partition hypercall" This reverts commit 36d6cbb62133fc6eea28f380409e0fb190f3dfbe. Calling this as a passthrough hypercall leaves the VM in an inconsistent state. Revert before it is released. Signed-off-by: Wei Liu --- drivers/hv/mshv_root_main.c | 1 - include/hyperv/hvgdk_mini.h | 1 - 2 files changed, 2 deletions(-) (limited to 'include') diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c index 54c3e44d24ee..9d1b881764ed 100644 --- a/drivers/hv/mshv_root_main.c +++ b/drivers/hv/mshv_root_main.c @@ -120,7 +120,6 @@ static u16 mshv_passthru_hvcalls[] = { HVCALL_SET_VP_REGISTERS, HVCALL_TRANSLATE_VIRTUAL_ADDRESS, HVCALL_CLEAR_VIRTUAL_INTERRUPT, - HVCALL_SCRUB_PARTITION, HVCALL_REGISTER_INTERCEPT_RESULT, HVCALL_ASSERT_VIRTUAL_INTERRUPT, HVCALL_GET_GPA_PAGES_ACCESS_STATES, diff --git a/include/hyperv/hvgdk_mini.h b/include/hyperv/hvgdk_mini.h index 8bb3dd71c5b4..1823a290a7b7 100644 --- a/include/hyperv/hvgdk_mini.h +++ b/include/hyperv/hvgdk_mini.h @@ -477,7 +477,6 @@ union hv_vp_assist_msr_contents { /* HV_REGISTER_VP_ASSIST_PAGE */ #define HVCALL_NOTIFY_PARTITION_EVENT 0x0087 #define HVCALL_ENTER_SLEEP_STATE 0x0084 #define HVCALL_NOTIFY_PORT_RING_EMPTY 0x008b -#define HVCALL_SCRUB_PARTITION 0x008d #define HVCALL_REGISTER_INTERCEPT_RESULT 0x0091 #define HVCALL_ASSERT_VIRTUAL_INTERRUPT 0x0094 #define HVCALL_CREATE_PORT 0x0095 -- cgit v1.2.3 From 96189080265e6bb5dde3a4afbaf947af493e3f82 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Mon, 9 Mar 2026 14:21:37 -0600 Subject: io_uring: ensure ctx->rings is stable for task work flags manipulation If DEFER_TASKRUN | SETUP_TASKRUN is used and task work is added while the ring is being resized, it's possible for the OR'ing of IORING_SQ_TASKRUN to happen in the small window of swapping into the new rings and the old rings being freed. Prevent this by adding a 2nd ->rings pointer, ->rings_rcu, which is protected by RCU. The task work flags manipulation is inside RCU already, and if the resize ring freeing is done post an RCU synchronize, then there's no need to add locking to the fast path of task work additions. Note: this is only done for DEFER_TASKRUN, as that's the only setup mode that supports ring resizing. If this ever changes, then they too need to use the io_ctx_mark_taskrun() helper. Link: https://lore.kernel.org/io-uring/20260309062759.482210-1-naup96721@gmail.com/ Cc: stable@vger.kernel.org Fixes: 79cfe9e59c2a ("io_uring/register: add IORING_REGISTER_RESIZE_RINGS") Reported-by: Hao-Yu Yang Suggested-by: Pavel Begunkov Signed-off-by: Jens Axboe --- include/linux/io_uring_types.h | 1 + io_uring/io_uring.c | 2 ++ io_uring/register.c | 11 +++++++++++ io_uring/tw.c | 22 ++++++++++++++++++++-- 4 files changed, 34 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/linux/io_uring_types.h b/include/linux/io_uring_types.h index 3e4a82a6f817..dd1420bfcb73 100644 --- a/include/linux/io_uring_types.h +++ b/include/linux/io_uring_types.h @@ -388,6 +388,7 @@ struct io_ring_ctx { * regularly bounce b/w CPUs. */ struct { + struct io_rings __rcu *rings_rcu; struct llist_head work_llist; struct llist_head retry_llist; unsigned long check_cq; diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c index ccab8562d273..20fdc442e014 100644 --- a/io_uring/io_uring.c +++ b/io_uring/io_uring.c @@ -2066,6 +2066,7 @@ static void io_rings_free(struct io_ring_ctx *ctx) io_free_region(ctx->user, &ctx->sq_region); io_free_region(ctx->user, &ctx->ring_region); ctx->rings = NULL; + RCU_INIT_POINTER(ctx->rings_rcu, NULL); ctx->sq_sqes = NULL; } @@ -2703,6 +2704,7 @@ static __cold int io_allocate_scq_urings(struct io_ring_ctx *ctx, if (ret) return ret; ctx->rings = rings = io_region_get_ptr(&ctx->ring_region); + rcu_assign_pointer(ctx->rings_rcu, rings); if (!(ctx->flags & IORING_SETUP_NO_SQARRAY)) ctx->sq_array = (u32 *)((char *)rings + rl->sq_array_offset); diff --git a/io_uring/register.c b/io_uring/register.c index a839b22fd392..5f3eb018fb32 100644 --- a/io_uring/register.c +++ b/io_uring/register.c @@ -633,7 +633,15 @@ overflow: ctx->sq_entries = p->sq_entries; ctx->cq_entries = p->cq_entries; + /* + * Just mark any flag we may have missed and that the application + * should act on unconditionally. Worst case it'll be an extra + * syscall. + */ + atomic_or(IORING_SQ_TASKRUN | IORING_SQ_NEED_WAKEUP, &n.rings->sq_flags); ctx->rings = n.rings; + rcu_assign_pointer(ctx->rings_rcu, n.rings); + ctx->sq_sqes = n.sq_sqes; swap_old(ctx, o, n, ring_region); swap_old(ctx, o, n, sq_region); @@ -642,6 +650,9 @@ overflow: out: spin_unlock(&ctx->completion_lock); mutex_unlock(&ctx->mmap_lock); + /* Wait for concurrent io_ctx_mark_taskrun() */ + if (to_free == &o) + synchronize_rcu_expedited(); io_register_free_rings(ctx, to_free); if (ctx->sq_data) diff --git a/io_uring/tw.c b/io_uring/tw.c index 1ee2b8ab07c8..2f2b4ac4b126 100644 --- a/io_uring/tw.c +++ b/io_uring/tw.c @@ -152,6 +152,21 @@ void tctx_task_work(struct callback_head *cb) WARN_ON_ONCE(ret); } +/* + * Sets IORING_SQ_TASKRUN in the sq_flags shared with userspace, using the + * RCU protected rings pointer to be safe against concurrent ring resizing. + */ +static void io_ctx_mark_taskrun(struct io_ring_ctx *ctx) +{ + lockdep_assert_in_rcu_read_lock(); + + if (ctx->flags & IORING_SETUP_TASKRUN_FLAG) { + struct io_rings *rings = rcu_dereference(ctx->rings_rcu); + + atomic_or(IORING_SQ_TASKRUN, &rings->sq_flags); + } +} + void io_req_local_work_add(struct io_kiocb *req, unsigned flags) { struct io_ring_ctx *ctx = req->ctx; @@ -206,8 +221,7 @@ void io_req_local_work_add(struct io_kiocb *req, unsigned flags) */ if (!head) { - if (ctx->flags & IORING_SETUP_TASKRUN_FLAG) - atomic_or(IORING_SQ_TASKRUN, &ctx->rings->sq_flags); + io_ctx_mark_taskrun(ctx); if (ctx->has_evfd) io_eventfd_signal(ctx, false); } @@ -231,6 +245,10 @@ void io_req_normal_work_add(struct io_kiocb *req) if (!llist_add(&req->io_task_work.node, &tctx->task_list)) return; + /* + * Doesn't need to use ->rings_rcu, as resizing isn't supported for + * !DEFER_TASKRUN. + */ if (ctx->flags & IORING_SETUP_TASKRUN_FLAG) atomic_or(IORING_SQ_TASKRUN, &ctx->rings->sq_flags); -- cgit v1.2.3 From 94a4b1f959989de9c54d43c3a102fb1ee92e1414 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Sat, 7 Mar 2026 17:50:53 -0300 Subject: ipv6: move the disable_ipv6_mod knob to core code From: Jakub Kicinski Make sure disable_ipv6_mod itself is not part of the IPv6 module, in case core code wants to refer to it. We will remove support for IPv6=m soon, this change helps make fixes we commit before that less messy. Link: https://patch.msgid.link/20260307-net-nd_tbl_fixes-v4-1-e2677e85628c@suse.com Signed-off-by: Jakub Kicinski --- include/linux/ipv6.h | 7 ++++++- net/ipv4/af_inet.c | 6 ++++++ net/ipv6/af_inet6.c | 8 -------- 3 files changed, 12 insertions(+), 9 deletions(-) (limited to 'include') diff --git a/include/linux/ipv6.h b/include/linux/ipv6.h index 443053a76dcf..a7421382a916 100644 --- a/include/linux/ipv6.h +++ b/include/linux/ipv6.h @@ -333,7 +333,12 @@ struct tcp6_timewait_sock { }; #if IS_ENABLED(CONFIG_IPV6) -bool ipv6_mod_enabled(void); +extern int disable_ipv6_mod; + +static inline bool ipv6_mod_enabled(void) +{ + return disable_ipv6_mod == 0; +} static inline struct ipv6_pinfo *inet6_sk(const struct sock *__sk) { diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c index 8036e76aa1e4..c7731e300a44 100644 --- a/net/ipv4/af_inet.c +++ b/net/ipv4/af_inet.c @@ -124,6 +124,12 @@ #include +/* Keep the definition of IPv6 disable here for now, to avoid annoying linker + * issues in case IPv6=m + */ +int disable_ipv6_mod; +EXPORT_SYMBOL(disable_ipv6_mod); + /* The inetsw table contains everything that inet_create needs to * build a new socket. */ diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c index 23cc9b4cb2f1..4cbd45b68088 100644 --- a/net/ipv6/af_inet6.c +++ b/net/ipv6/af_inet6.c @@ -86,8 +86,6 @@ struct ipv6_params ipv6_defaults = { .autoconf = 1, }; -static int disable_ipv6_mod; - module_param_named(disable, disable_ipv6_mod, int, 0444); MODULE_PARM_DESC(disable, "Disable IPv6 module such that it is non-functional"); @@ -97,12 +95,6 @@ MODULE_PARM_DESC(disable_ipv6, "Disable IPv6 on all interfaces"); module_param_named(autoconf, ipv6_defaults.autoconf, int, 0444); MODULE_PARM_DESC(autoconf, "Enable IPv6 address autoconfiguration on all interfaces"); -bool ipv6_mod_enabled(void) -{ - return disable_ipv6_mod == 0; -} -EXPORT_SYMBOL_GPL(ipv6_mod_enabled); - static struct ipv6_pinfo *inet6_sk_generic(struct sock *sk) { const int offset = sk->sk_prot->ipv6_pinfo_offset; -- cgit v1.2.3 From d87f8bc47fbf012a7f115e311d0603d97e47c34c Mon Sep 17 00:00:00 2001 From: Sabrina Dubroca Date: Mon, 9 Mar 2026 11:32:43 +0100 Subject: xfrm: avoid RCU warnings around the per-netns netlink socket net->xfrm.nlsk is used in 2 types of contexts: - fully under RCU, with rcu_read_lock + rcu_dereference and a NULL check - in the netlink handlers, with requests coming from a userspace socket In the 2nd case, net->xfrm.nlsk is guaranteed to stay non-NULL and the object is alive, since we can't enter the netns destruction path while the user socket holds a reference on the netns. After adding the __rcu annotation to netns_xfrm.nlsk (which silences sparse warnings in the RCU users and __net_init code), we need to tell sparse that the 2nd case is safe. Add a helper for that. Signed-off-by: Sabrina Dubroca Reviewed-by: Simon Horman Signed-off-by: Steffen Klassert --- include/net/netns/xfrm.h | 2 +- net/xfrm/xfrm_user.c | 25 +++++++++++++++++-------- 2 files changed, 18 insertions(+), 9 deletions(-) (limited to 'include') diff --git a/include/net/netns/xfrm.h b/include/net/netns/xfrm.h index 23dd647fe024..b73983a17e08 100644 --- a/include/net/netns/xfrm.h +++ b/include/net/netns/xfrm.h @@ -59,7 +59,7 @@ struct netns_xfrm { struct list_head inexact_bins; - struct sock *nlsk; + struct sock __rcu *nlsk; struct sock *nlsk_stash; u32 sysctl_aevent_etime; diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c index 4dd8341225bc..1656b487f833 100644 --- a/net/xfrm/xfrm_user.c +++ b/net/xfrm/xfrm_user.c @@ -35,6 +35,15 @@ #endif #include +static struct sock *xfrm_net_nlsk(const struct net *net, const struct sk_buff *skb) +{ + /* get the source of this request, see netlink_unicast_kernel */ + const struct sock *sk = NETLINK_CB(skb).sk; + + /* sk is refcounted, the netns stays alive and nlsk with it */ + return rcu_dereference_protected(net->xfrm.nlsk, sk->sk_net_refcnt); +} + static int verify_one_alg(struct nlattr **attrs, enum xfrm_attr_type_t type, struct netlink_ext_ack *extack) { @@ -1727,7 +1736,7 @@ static int xfrm_get_spdinfo(struct sk_buff *skb, struct nlmsghdr *nlh, err = build_spdinfo(r_skb, net, sportid, seq, *flags); BUG_ON(err < 0); - return nlmsg_unicast(net->xfrm.nlsk, r_skb, sportid); + return nlmsg_unicast(xfrm_net_nlsk(net, skb), r_skb, sportid); } static inline unsigned int xfrm_sadinfo_msgsize(void) @@ -1787,7 +1796,7 @@ static int xfrm_get_sadinfo(struct sk_buff *skb, struct nlmsghdr *nlh, err = build_sadinfo(r_skb, net, sportid, seq, *flags); BUG_ON(err < 0); - return nlmsg_unicast(net->xfrm.nlsk, r_skb, sportid); + return nlmsg_unicast(xfrm_net_nlsk(net, skb), r_skb, sportid); } static int xfrm_get_sa(struct sk_buff *skb, struct nlmsghdr *nlh, @@ -1807,7 +1816,7 @@ static int xfrm_get_sa(struct sk_buff *skb, struct nlmsghdr *nlh, if (IS_ERR(resp_skb)) { err = PTR_ERR(resp_skb); } else { - err = nlmsg_unicast(net->xfrm.nlsk, resp_skb, NETLINK_CB(skb).portid); + err = nlmsg_unicast(xfrm_net_nlsk(net, skb), resp_skb, NETLINK_CB(skb).portid); } xfrm_state_put(x); out_noput: @@ -1898,7 +1907,7 @@ static int xfrm_alloc_userspi(struct sk_buff *skb, struct nlmsghdr *nlh, } } - err = nlmsg_unicast(net->xfrm.nlsk, resp_skb, NETLINK_CB(skb).portid); + err = nlmsg_unicast(xfrm_net_nlsk(net, skb), resp_skb, NETLINK_CB(skb).portid); out: xfrm_state_put(x); @@ -2543,7 +2552,7 @@ static int xfrm_get_default(struct sk_buff *skb, struct nlmsghdr *nlh, r_up->out = net->xfrm.policy_default[XFRM_POLICY_OUT]; nlmsg_end(r_skb, r_nlh); - return nlmsg_unicast(net->xfrm.nlsk, r_skb, portid); + return nlmsg_unicast(xfrm_net_nlsk(net, skb), r_skb, portid); } static int xfrm_get_policy(struct sk_buff *skb, struct nlmsghdr *nlh, @@ -2609,7 +2618,7 @@ static int xfrm_get_policy(struct sk_buff *skb, struct nlmsghdr *nlh, if (IS_ERR(resp_skb)) { err = PTR_ERR(resp_skb); } else { - err = nlmsg_unicast(net->xfrm.nlsk, resp_skb, + err = nlmsg_unicast(xfrm_net_nlsk(net, skb), resp_skb, NETLINK_CB(skb).portid); } } else { @@ -2782,7 +2791,7 @@ static int xfrm_get_ae(struct sk_buff *skb, struct nlmsghdr *nlh, err = build_aevent(r_skb, x, &c); BUG_ON(err < 0); - err = nlmsg_unicast(net->xfrm.nlsk, r_skb, NETLINK_CB(skb).portid); + err = nlmsg_unicast(xfrm_net_nlsk(net, skb), r_skb, NETLINK_CB(skb).portid); spin_unlock_bh(&x->lock); xfrm_state_put(x); return err; @@ -3486,7 +3495,7 @@ static int xfrm_user_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, goto err; } - err = netlink_dump_start(net->xfrm.nlsk, skb, nlh, &c); + err = netlink_dump_start(xfrm_net_nlsk(net, skb), skb, nlh, &c); goto err; } -- cgit v1.2.3 From 14de1552a4e3fece78bb20314887e70888c9d448 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Wed, 11 Mar 2026 16:14:55 -0700 Subject: include/linux/local_lock_internal.h: Make this header file again compatible with sparse There are two versions of the __this_cpu_local_lock() definitions in include/linux/local_lock_internal.h: one version that relies on the Clang overloading functionality and another version that does not. Select the latter version when using sparse. This patch fixes the following errors reported by sparse: include/linux/local_lock_internal.h:331:40: sparse: sparse: multiple definitions for function '__this_cpu_local_lock' include/linux/local_lock_internal.h:325:37: sparse: the previous one is here Closes: https://lore.kernel.org/oe-kbuild-all/202603062334.wgI5htP0-lkp@intel.com/ Fixes: d3febf16dee2 ("locking/local_lock: Support Clang's context analysis") Reported-by: kernel test robot Signed-off-by: Bart Van Assche Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Marco Elver Link: https://patch.msgid.link/20260311231455.1961413-1-bvanassche@acm.org --- include/linux/local_lock_internal.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include') diff --git a/include/linux/local_lock_internal.h b/include/linux/local_lock_internal.h index eff711bf973f..234be7f12c15 100644 --- a/include/linux/local_lock_internal.h +++ b/include/linux/local_lock_internal.h @@ -315,7 +315,7 @@ do { \ #endif /* CONFIG_PREEMPT_RT */ -#if defined(WARN_CONTEXT_ANALYSIS) +#if defined(WARN_CONTEXT_ANALYSIS) && !defined(__CHECKER__) /* * Because the compiler only knows about the base per-CPU variable, use this * helper function to make the compiler think we lock/unlock the @base variable, -- cgit v1.2.3 From 8324a54f604da18f21070702a8ad82ab2062787b Mon Sep 17 00:00:00 2001 From: Ilpo Järvinen Date: Tue, 3 Feb 2026 19:10:45 +0200 Subject: serial: 8250: Add serial8250_handle_irq_locked() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 8250_port exports serial8250_handle_irq() to HW specific 8250 drivers. It takes port's lock within but a HW specific 8250 driver may want to take port's lock itself, do something, and then call the generic handler in 8250_port but to do that, the caller has to release port's lock for no good reason. Introduce serial8250_handle_irq_locked() which a HW specific driver can call while already holding port's lock. As this is new export, put it straight into a namespace (where all 8250 exports should eventually be moved). Tested-by: Bandal, Shankar Tested-by: Murthy, Shanth Cc: stable Reviewed-by: Andy Shevchenko Signed-off-by: Ilpo Järvinen Link: https://patch.msgid.link/20260203171049.4353-4-ilpo.jarvinen@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_port.c | 24 ++++++++++++++++-------- include/linux/serial_8250.h | 1 + 2 files changed, 17 insertions(+), 8 deletions(-) (limited to 'include') diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c index 20cf123a0540..14d6aca44551 100644 --- a/drivers/tty/serial/8250/8250_port.c +++ b/drivers/tty/serial/8250/8250_port.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -1782,20 +1783,16 @@ static bool handle_rx_dma(struct uart_8250_port *up, unsigned int iir) } /* - * This handles the interrupt from one port. + * Context: port's lock must be held by the caller. */ -int serial8250_handle_irq(struct uart_port *port, unsigned int iir) +void serial8250_handle_irq_locked(struct uart_port *port, unsigned int iir) { struct uart_8250_port *up = up_to_u8250p(port); struct tty_port *tport = &port->state->port; bool skip_rx = false; - unsigned long flags; u16 status; - if (iir & UART_IIR_NO_INT) - return 0; - - uart_port_lock_irqsave(port, &flags); + lockdep_assert_held_once(&port->lock); status = serial_lsr_in(up); @@ -1828,8 +1825,19 @@ int serial8250_handle_irq(struct uart_port *port, unsigned int iir) else if (!up->dma->tx_running) __stop_tx(up); } +} +EXPORT_SYMBOL_NS_GPL(serial8250_handle_irq_locked, "SERIAL_8250"); - uart_unlock_and_check_sysrq_irqrestore(port, flags); +/* + * This handles the interrupt from one port. + */ +int serial8250_handle_irq(struct uart_port *port, unsigned int iir) +{ + if (iir & UART_IIR_NO_INT) + return 0; + + guard(uart_port_lock_irqsave)(port); + serial8250_handle_irq_locked(port, iir); return 1; } diff --git a/include/linux/serial_8250.h b/include/linux/serial_8250.h index 01efdce0fda0..a95b2d143d24 100644 --- a/include/linux/serial_8250.h +++ b/include/linux/serial_8250.h @@ -195,6 +195,7 @@ void serial8250_do_set_mctrl(struct uart_port *port, unsigned int mctrl); void serial8250_do_set_divisor(struct uart_port *port, unsigned int baud, unsigned int quot); int fsl8250_handle_irq(struct uart_port *port); +void serial8250_handle_irq_locked(struct uart_port *port, unsigned int iir); int serial8250_handle_irq(struct uart_port *port, unsigned int iir); u16 serial8250_rx_chars(struct uart_8250_port *up, u16 lsr); void serial8250_read_char(struct uart_8250_port *up, u16 lsr); -- cgit v1.2.3 From c38b8f5f791ecce13ab77e2257f8fd2444ba80f6 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 12 Mar 2026 04:39:08 +0000 Subject: net: prevent NULL deref in ip[6]tunnel_xmit() Blamed commit missed that both functions can be called with dev == NULL. Also add unlikely() hints for these conditions that only fuzzers can hit. Fixes: 6f1a9140ecda ("net: add xmit recursion limit to tunnel xmit functions") Signed-off-by: Eric Dumazet CC: Weiming Shi Link: https://patch.msgid.link/20260312043908.2790803-1-edumazet@google.com Signed-off-by: Paolo Abeni --- include/net/ip6_tunnel.h | 10 ++++++---- net/ipv4/ip_tunnel_core.c | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) (limited to 'include') diff --git a/include/net/ip6_tunnel.h b/include/net/ip6_tunnel.h index 1253cbb4b0a4..359b595f1df9 100644 --- a/include/net/ip6_tunnel.h +++ b/include/net/ip6_tunnel.h @@ -156,10 +156,12 @@ static inline void ip6tunnel_xmit(struct sock *sk, struct sk_buff *skb, { int pkt_len, err; - if (dev_recursion_level() > IP_TUNNEL_RECURSION_LIMIT) { - net_crit_ratelimited("Dead loop on virtual device %s, fix it urgently!\n", - dev->name); - DEV_STATS_INC(dev, tx_errors); + if (unlikely(dev_recursion_level() > IP_TUNNEL_RECURSION_LIMIT)) { + if (dev) { + net_crit_ratelimited("Dead loop on virtual device %s, fix it urgently!\n", + dev->name); + DEV_STATS_INC(dev, tx_errors); + } kfree_skb(skb); return; } diff --git a/net/ipv4/ip_tunnel_core.c b/net/ipv4/ip_tunnel_core.c index b1b6bf949f65..5683c328990f 100644 --- a/net/ipv4/ip_tunnel_core.c +++ b/net/ipv4/ip_tunnel_core.c @@ -58,10 +58,12 @@ void iptunnel_xmit(struct sock *sk, struct rtable *rt, struct sk_buff *skb, struct iphdr *iph; int err; - if (dev_recursion_level() > IP_TUNNEL_RECURSION_LIMIT) { - net_crit_ratelimited("Dead loop on virtual device %s, fix it urgently!\n", - dev->name); - DEV_STATS_INC(dev, tx_errors); + if (unlikely(dev_recursion_level() > IP_TUNNEL_RECURSION_LIMIT)) { + if (dev) { + net_crit_ratelimited("Dead loop on virtual device %s, fix it urgently!\n", + dev->name); + DEV_STATS_INC(dev, tx_errors); + } ip_rt_put(rt); kfree_skb(skb); return; -- cgit v1.2.3 From 8431c602f551549f082bbfa67f3003f2d8e3e132 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 11 Mar 2026 12:31:10 +0000 Subject: ip_tunnel: adapt iptunnel_xmit_stats() to NETDEV_PCPU_STAT_DSTATS Blamed commits forgot that vxlan/geneve use udp_tunnel[6]_xmit_skb() which call iptunnel_xmit_stats(). iptunnel_xmit_stats() was assuming tunnels were only using NETDEV_PCPU_STAT_TSTATS. @syncp offset in pcpu_sw_netstats and pcpu_dstats is different. 32bit kernels would either have corruptions or freezes if the syncp sequence was overwritten. This patch also moves pcpu_stat_type closer to dev->{t,d}stats to avoid a potential cache line miss since iptunnel_xmit_stats() needs to read it. Fixes: 6fa6de302246 ("geneve: Handle stats using NETDEV_PCPU_STAT_DSTATS.") Fixes: be226352e8dc ("vxlan: Handle stats using NETDEV_PCPU_STAT_DSTATS.") Signed-off-by: Eric Dumazet Reviewed-by: Guillaume Nault Link: https://patch.msgid.link/20260311123110.1471930-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- include/linux/netdevice.h | 3 +-- include/net/ip_tunnels.h | 30 +++++++++++++++++++++++------- 2 files changed, 24 insertions(+), 9 deletions(-) (limited to 'include') diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index ae269a2e7f4d..d7aac6f185bc 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -2155,6 +2155,7 @@ struct net_device { unsigned long state; unsigned int flags; unsigned short hard_header_len; + enum netdev_stat_type pcpu_stat_type:8; netdev_features_t features; struct inet6_dev __rcu *ip6_ptr; __cacheline_group_end(net_device_read_txrx); @@ -2404,8 +2405,6 @@ struct net_device { void *ml_priv; enum netdev_ml_priv_type ml_priv_type; - enum netdev_stat_type pcpu_stat_type:8; - #if IS_ENABLED(CONFIG_GARP) struct garp_port __rcu *garp_port; #endif diff --git a/include/net/ip_tunnels.h b/include/net/ip_tunnels.h index 80662f812080..1f577a4f8ce9 100644 --- a/include/net/ip_tunnels.h +++ b/include/net/ip_tunnels.h @@ -665,13 +665,29 @@ static inline int iptunnel_pull_offloads(struct sk_buff *skb) static inline void iptunnel_xmit_stats(struct net_device *dev, int pkt_len) { if (pkt_len > 0) { - struct pcpu_sw_netstats *tstats = get_cpu_ptr(dev->tstats); - - u64_stats_update_begin(&tstats->syncp); - u64_stats_add(&tstats->tx_bytes, pkt_len); - u64_stats_inc(&tstats->tx_packets); - u64_stats_update_end(&tstats->syncp); - put_cpu_ptr(tstats); + if (dev->pcpu_stat_type == NETDEV_PCPU_STAT_DSTATS) { + struct pcpu_dstats *dstats = get_cpu_ptr(dev->dstats); + + u64_stats_update_begin(&dstats->syncp); + u64_stats_add(&dstats->tx_bytes, pkt_len); + u64_stats_inc(&dstats->tx_packets); + u64_stats_update_end(&dstats->syncp); + put_cpu_ptr(dstats); + return; + } + if (dev->pcpu_stat_type == NETDEV_PCPU_STAT_TSTATS) { + struct pcpu_sw_netstats *tstats = get_cpu_ptr(dev->tstats); + + u64_stats_update_begin(&tstats->syncp); + u64_stats_add(&tstats->tx_bytes, pkt_len); + u64_stats_inc(&tstats->tx_packets); + u64_stats_update_end(&tstats->syncp); + put_cpu_ptr(tstats); + return; + } + pr_err_once("iptunnel_xmit_stats pcpu_stat_type=%d\n", + dev->pcpu_stat_type); + WARN_ON_ONCE(1); return; } -- cgit v1.2.3 From 5eb608319bb56464674a71b4a66ea65c6c435d64 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Tue, 27 Jan 2026 17:56:01 -0500 Subject: vt: save/restore unicode screen buffer for alternate screen The alternate screen support added by commit 23743ba64709 ("vt: add support for smput/rmput escape codes") only saves and restores the regular screen buffer (vc_origin), but completely ignores the corresponding unicode screen buffer (vc_uni_lines) creating a messed-up display. Add vc_saved_uni_lines to save the unicode screen buffer when entering the alternate screen, and restore it when leaving. Also ensure proper cleanup in reset_terminal() and vc_deallocate(). Fixes: 23743ba64709 ("vt: add support for smput/rmput escape codes") Cc: stable Signed-off-by: Nicolas Pitre Link: https://patch.msgid.link/5o2p6qp3-91pq-0p17-or02-1oors4417ns7@onlyvoer.pbz Signed-off-by: Greg Kroah-Hartman --- drivers/tty/vt/vt.c | 8 ++++++++ include/linux/console_struct.h | 1 + 2 files changed, 9 insertions(+) (limited to 'include') diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c index c1f152d8b03b..e2df99e3d458 100644 --- a/drivers/tty/vt/vt.c +++ b/drivers/tty/vt/vt.c @@ -1339,6 +1339,8 @@ struct vc_data *vc_deallocate(unsigned int currcons) kfree(vc->vc_saved_screen); vc->vc_saved_screen = NULL; } + vc_uniscr_free(vc->vc_saved_uni_lines); + vc->vc_saved_uni_lines = NULL; } return vc; } @@ -1884,6 +1886,8 @@ static void enter_alt_screen(struct vc_data *vc) vc->vc_saved_screen = kmemdup((u16 *)vc->vc_origin, size, GFP_KERNEL); if (vc->vc_saved_screen == NULL) return; + vc->vc_saved_uni_lines = vc->vc_uni_lines; + vc->vc_uni_lines = NULL; vc->vc_saved_rows = vc->vc_rows; vc->vc_saved_cols = vc->vc_cols; save_cur(vc); @@ -1905,6 +1909,8 @@ static void leave_alt_screen(struct vc_data *vc) dest = ((u16 *)vc->vc_origin) + r * vc->vc_cols; memcpy(dest, src, 2 * cols); } + vc_uniscr_set(vc, vc->vc_saved_uni_lines); + vc->vc_saved_uni_lines = NULL; restore_cur(vc); /* Update the entire screen */ if (con_should_update(vc)) @@ -2227,6 +2233,8 @@ static void reset_terminal(struct vc_data *vc, int do_clear) if (vc->vc_saved_screen != NULL) { kfree(vc->vc_saved_screen); vc->vc_saved_screen = NULL; + vc_uniscr_free(vc->vc_saved_uni_lines); + vc->vc_saved_uni_lines = NULL; vc->vc_saved_rows = 0; vc->vc_saved_cols = 0; } diff --git a/include/linux/console_struct.h b/include/linux/console_struct.h index 13b35637bd5a..d5ca855116df 100644 --- a/include/linux/console_struct.h +++ b/include/linux/console_struct.h @@ -160,6 +160,7 @@ struct vc_data { struct uni_pagedict **uni_pagedict_loc; /* [!] Location of uni_pagedict variable for this console */ u32 **vc_uni_lines; /* unicode screen content */ u16 *vc_saved_screen; + u32 **vc_saved_uni_lines; unsigned int vc_saved_cols; unsigned int vc_saved_rows; /* additional information is in vt_kern.h */ -- cgit v1.2.3 From 598adea720b97572c7028635cb1c59b3684e128c Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Wed, 11 Mar 2026 16:24:02 +0100 Subject: netfilter: revert nft_set_rbtree: validate open interval overlap This reverts commit 648946966a08 ("netfilter: nft_set_rbtree: validate open interval overlap"). There have been reports of nft failing to laod valid rulesets after this patch was merged into -stable. I can reproduce several such problem with recent nft versions, including nft 1.1.6 which is widely shipped by distributions. We currently have little choice here. This commit can be resurrected at some point once the nftables fix that triggers the false overlap positive has appeared in common distros (see e83e32c8d1cd ("mnl: restore create element command with large batches" in nftables.git). Fixes: 648946966a08 ("netfilter: nft_set_rbtree: validate open interval overlap") Acked-by: Pablo Neira Ayuso Signed-off-by: Florian Westphal --- include/net/netfilter/nf_tables.h | 4 --- net/netfilter/nf_tables_api.c | 21 +++--------- net/netfilter/nft_set_rbtree.c | 71 ++++++--------------------------------- 3 files changed, 14 insertions(+), 82 deletions(-) (limited to 'include') diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h index e2d2bfc1f989..6299af4ef423 100644 --- a/include/net/netfilter/nf_tables.h +++ b/include/net/netfilter/nf_tables.h @@ -277,8 +277,6 @@ struct nft_userdata { unsigned char data[]; }; -#define NFT_SET_ELEM_INTERNAL_LAST 0x1 - /* placeholder structure for opaque set element backend representation. */ struct nft_elem_priv { }; @@ -288,7 +286,6 @@ struct nft_elem_priv { }; * @key: element key * @key_end: closing element key * @data: element data - * @flags: flags * @priv: element private data and extensions */ struct nft_set_elem { @@ -304,7 +301,6 @@ struct nft_set_elem { u32 buf[NFT_DATA_VALUE_MAXLEN / sizeof(u32)]; struct nft_data val; } data; - u32 flags; struct nft_elem_priv *priv; }; diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index dacec5f8a11c..4ccdd33cf133 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -7156,8 +7156,7 @@ static u32 nft_set_maxsize(const struct nft_set *set) } static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, - const struct nlattr *attr, u32 nlmsg_flags, - bool last) + const struct nlattr *attr, u32 nlmsg_flags) { struct nft_expr *expr_array[NFT_SET_EXPR_MAX] = {}; struct nlattr *nla[NFTA_SET_ELEM_MAX + 1]; @@ -7444,11 +7443,6 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, if (flags) *nft_set_ext_flags(ext) = flags; - if (last) - elem.flags = NFT_SET_ELEM_INTERNAL_LAST; - else - elem.flags = 0; - if (obj) *nft_set_ext_obj(ext) = obj; @@ -7613,8 +7607,7 @@ static int nf_tables_newsetelem(struct sk_buff *skb, nft_ctx_init(&ctx, net, skb, info->nlh, family, table, NULL, nla); nla_for_each_nested(attr, nla[NFTA_SET_ELEM_LIST_ELEMENTS], rem) { - err = nft_add_set_elem(&ctx, set, attr, info->nlh->nlmsg_flags, - nla_is_last(attr, rem)); + err = nft_add_set_elem(&ctx, set, attr, info->nlh->nlmsg_flags); if (err < 0) { NL_SET_BAD_ATTR(extack, attr); return err; @@ -7738,7 +7731,7 @@ static void nft_trans_elems_destroy_abort(const struct nft_ctx *ctx, } static int nft_del_setelem(struct nft_ctx *ctx, struct nft_set *set, - const struct nlattr *attr, bool last) + const struct nlattr *attr) { struct nlattr *nla[NFTA_SET_ELEM_MAX + 1]; struct nft_set_ext_tmpl tmpl; @@ -7806,11 +7799,6 @@ static int nft_del_setelem(struct nft_ctx *ctx, struct nft_set *set, if (flags) *nft_set_ext_flags(ext) = flags; - if (last) - elem.flags = NFT_SET_ELEM_INTERNAL_LAST; - else - elem.flags = 0; - trans = nft_trans_elem_alloc(ctx, NFT_MSG_DELSETELEM, set); if (trans == NULL) goto fail_trans; @@ -7961,8 +7949,7 @@ static int nf_tables_delsetelem(struct sk_buff *skb, return nft_set_flush(&ctx, set, genmask); nla_for_each_nested(attr, nla[NFTA_SET_ELEM_LIST_ELEMENTS], rem) { - err = nft_del_setelem(&ctx, set, attr, - nla_is_last(attr, rem)); + err = nft_del_setelem(&ctx, set, attr); if (err == -ENOENT && NFNL_MSG_TYPE(info->nlh->nlmsg_type) == NFT_MSG_DESTROYSETELEM) continue; diff --git a/net/netfilter/nft_set_rbtree.c b/net/netfilter/nft_set_rbtree.c index ee3d4f5b9ff7..fe8bd497d74a 100644 --- a/net/netfilter/nft_set_rbtree.c +++ b/net/netfilter/nft_set_rbtree.c @@ -304,19 +304,10 @@ static void nft_rbtree_set_start_cookie(struct nft_rbtree *priv, priv->start_rbe_cookie = (unsigned long)rbe; } -static void nft_rbtree_set_start_cookie_open(struct nft_rbtree *priv, - const struct nft_rbtree_elem *rbe, - unsigned long open_interval) -{ - priv->start_rbe_cookie = (unsigned long)rbe | open_interval; -} - -#define NFT_RBTREE_OPEN_INTERVAL 1UL - static bool nft_rbtree_cmp_start_cookie(struct nft_rbtree *priv, const struct nft_rbtree_elem *rbe) { - return (priv->start_rbe_cookie & ~NFT_RBTREE_OPEN_INTERVAL) == (unsigned long)rbe; + return priv->start_rbe_cookie == (unsigned long)rbe; } static bool nft_rbtree_insert_same_interval(const struct net *net, @@ -346,14 +337,13 @@ static bool nft_rbtree_insert_same_interval(const struct net *net, static int __nft_rbtree_insert(const struct net *net, const struct nft_set *set, struct nft_rbtree_elem *new, - struct nft_elem_priv **elem_priv, u64 tstamp, bool last) + struct nft_elem_priv **elem_priv, u64 tstamp) { struct nft_rbtree_elem *rbe, *rbe_le = NULL, *rbe_ge = NULL, *rbe_prev; struct rb_node *node, *next, *parent, **p, *first = NULL; struct nft_rbtree *priv = nft_set_priv(set); u8 cur_genmask = nft_genmask_cur(net); u8 genmask = nft_genmask_next(net); - unsigned long open_interval = 0; int d; /* Descend the tree to search for an existing element greater than the @@ -459,18 +449,10 @@ static int __nft_rbtree_insert(const struct net *net, const struct nft_set *set, } } - if (nft_rbtree_interval_null(set, new)) { + if (nft_rbtree_interval_null(set, new)) + priv->start_rbe_cookie = 0; + else if (nft_rbtree_interval_start(new) && priv->start_rbe_cookie) priv->start_rbe_cookie = 0; - } else if (nft_rbtree_interval_start(new) && priv->start_rbe_cookie) { - if (nft_set_is_anonymous(set)) { - priv->start_rbe_cookie = 0; - } else if (priv->start_rbe_cookie & NFT_RBTREE_OPEN_INTERVAL) { - /* Previous element is an open interval that partially - * overlaps with an existing non-open interval. - */ - return -ENOTEMPTY; - } - } /* - new start element matching existing start element: full overlap * reported as -EEXIST, cleared by caller if NLM_F_EXCL is not given. @@ -478,27 +460,7 @@ static int __nft_rbtree_insert(const struct net *net, const struct nft_set *set, if (rbe_ge && !nft_rbtree_cmp(set, new, rbe_ge) && nft_rbtree_interval_start(rbe_ge) == nft_rbtree_interval_start(new)) { *elem_priv = &rbe_ge->priv; - - /* - Corner case: new start element of open interval (which - * comes as last element in the batch) overlaps the start of - * an existing interval with an end element: partial overlap. - */ - node = rb_first(&priv->root); - rbe = __nft_rbtree_next_active(node, genmask); - if (rbe && nft_rbtree_interval_end(rbe)) { - rbe = nft_rbtree_next_active(rbe, genmask); - if (rbe && - nft_rbtree_interval_start(rbe) && - !nft_rbtree_cmp(set, new, rbe)) { - if (last) - return -ENOTEMPTY; - - /* Maybe open interval? */ - open_interval = NFT_RBTREE_OPEN_INTERVAL; - } - } - nft_rbtree_set_start_cookie_open(priv, rbe_ge, open_interval); - + nft_rbtree_set_start_cookie(priv, rbe_ge); return -EEXIST; } @@ -553,12 +515,6 @@ static int __nft_rbtree_insert(const struct net *net, const struct nft_set *set, nft_rbtree_interval_end(rbe_ge) && nft_rbtree_interval_end(new)) return -ENOTEMPTY; - /* - start element overlaps an open interval but end element is new: - * partial overlap, reported as -ENOEMPTY. - */ - if (!rbe_ge && priv->start_rbe_cookie && nft_rbtree_interval_end(new)) - return -ENOTEMPTY; - /* Accepted element: pick insertion point depending on key value */ parent = NULL; p = &priv->root.rb_node; @@ -668,7 +624,6 @@ static int nft_rbtree_insert(const struct net *net, const struct nft_set *set, struct nft_elem_priv **elem_priv) { struct nft_rbtree_elem *rbe = nft_elem_priv_cast(elem->priv); - bool last = !!(elem->flags & NFT_SET_ELEM_INTERNAL_LAST); struct nft_rbtree *priv = nft_set_priv(set); u64 tstamp = nft_net_tstamp(net); int err; @@ -685,12 +640,8 @@ static int nft_rbtree_insert(const struct net *net, const struct nft_set *set, cond_resched(); write_lock_bh(&priv->lock); - err = __nft_rbtree_insert(net, set, rbe, elem_priv, tstamp, last); + err = __nft_rbtree_insert(net, set, rbe, elem_priv, tstamp); write_unlock_bh(&priv->lock); - - if (nft_rbtree_interval_end(rbe)) - priv->start_rbe_cookie = 0; - } while (err == -EAGAIN); return err; @@ -778,7 +729,6 @@ nft_rbtree_deactivate(const struct net *net, const struct nft_set *set, const struct nft_set_elem *elem) { struct nft_rbtree_elem *rbe, *this = nft_elem_priv_cast(elem->priv); - bool last = !!(elem->flags & NFT_SET_ELEM_INTERNAL_LAST); struct nft_rbtree *priv = nft_set_priv(set); const struct rb_node *parent = priv->root.rb_node; u8 genmask = nft_genmask_next(net); @@ -819,10 +769,9 @@ nft_rbtree_deactivate(const struct net *net, const struct nft_set *set, continue; } - if (nft_rbtree_interval_start(rbe)) { - if (!last) - nft_rbtree_set_start_cookie(priv, rbe); - } else if (!nft_rbtree_deactivate_same_interval(net, priv, rbe)) + if (nft_rbtree_interval_start(rbe)) + nft_rbtree_set_start_cookie(priv, rbe); + else if (!nft_rbtree_deactivate_same_interval(net, priv, rbe)) return NULL; nft_rbtree_flush(net, set, &rbe->priv); -- cgit v1.2.3 From 0548a13b5a145b16e4da0628b5936baf35f51b43 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Thu, 12 Mar 2026 12:38:59 +0100 Subject: nf_tables: nft_dynset: fix possible stateful expression memleak in error path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If cloning the second stateful expression in the element via GFP_ATOMIC fails, then the first stateful expression remains in place without being released.   unreferenced object (percpu) 0x607b97e9cab8 (size 16):     comm "softirq", pid 0, jiffies 4294931867     hex dump (first 16 bytes on cpu 3):       00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     backtrace (crc 0):       pcpu_alloc_noprof+0x453/0xd80       nft_counter_clone+0x9c/0x190 [nf_tables]       nft_expr_clone+0x8f/0x1b0 [nf_tables]       nft_dynset_new+0x2cb/0x5f0 [nf_tables]       nft_rhash_update+0x236/0x11c0 [nf_tables]       nft_dynset_eval+0x11f/0x670 [nf_tables]       nft_do_chain+0x253/0x1700 [nf_tables]       nft_do_chain_ipv4+0x18d/0x270 [nf_tables]       nf_hook_slow+0xaa/0x1e0       ip_local_deliver+0x209/0x330 Fixes: 563125a73ac3 ("netfilter: nftables: generalize set extension to support for several expressions") Reported-by: Gurpreet Shergill Signed-off-by: Pablo Neira Ayuso Signed-off-by: Florian Westphal --- include/net/netfilter/nf_tables.h | 2 ++ net/netfilter/nf_tables_api.c | 4 ++-- net/netfilter/nft_dynset.c | 10 +++++++++- 3 files changed, 13 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h index 6299af4ef423..ec8a8ec9c0aa 100644 --- a/include/net/netfilter/nf_tables.h +++ b/include/net/netfilter/nf_tables.h @@ -874,6 +874,8 @@ struct nft_elem_priv *nft_set_elem_init(const struct nft_set *set, u64 timeout, u64 expiration, gfp_t gfp); int nft_set_elem_expr_clone(const struct nft_ctx *ctx, struct nft_set *set, struct nft_expr *expr_array[]); +void nft_set_elem_expr_destroy(const struct nft_ctx *ctx, + struct nft_set_elem_expr *elem_expr); void nft_set_elem_destroy(const struct nft_set *set, const struct nft_elem_priv *elem_priv, bool destroy_expr); diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index 4ccdd33cf133..9b1c8d0a35fb 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -6744,8 +6744,8 @@ static void __nft_set_elem_expr_destroy(const struct nft_ctx *ctx, } } -static void nft_set_elem_expr_destroy(const struct nft_ctx *ctx, - struct nft_set_elem_expr *elem_expr) +void nft_set_elem_expr_destroy(const struct nft_ctx *ctx, + struct nft_set_elem_expr *elem_expr) { struct nft_expr *expr; u32 size; diff --git a/net/netfilter/nft_dynset.c b/net/netfilter/nft_dynset.c index 7807d8129664..9123277be03c 100644 --- a/net/netfilter/nft_dynset.c +++ b/net/netfilter/nft_dynset.c @@ -30,18 +30,26 @@ static int nft_dynset_expr_setup(const struct nft_dynset *priv, const struct nft_set_ext *ext) { struct nft_set_elem_expr *elem_expr = nft_set_ext_expr(ext); + struct nft_ctx ctx = { + .net = read_pnet(&priv->set->net), + .family = priv->set->table->family, + }; struct nft_expr *expr; int i; for (i = 0; i < priv->num_exprs; i++) { expr = nft_setelem_expr_at(elem_expr, elem_expr->size); if (nft_expr_clone(expr, priv->expr_array[i], GFP_ATOMIC) < 0) - return -1; + goto err_out; elem_expr->size += priv->expr_array[i]->ops->size; } return 0; +err_out: + nft_set_elem_expr_destroy(&ctx, elem_expr); + + return -1; } struct nft_elem_priv *nft_dynset_new(struct nft_set *set, -- cgit v1.2.3 From b7405dcf7385445e10821777143f18c3ce20fa04 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Sun, 15 Mar 2026 10:41:52 +0000 Subject: bonding: prevent potential infinite loop in bond_header_parse() bond_header_parse() can loop if a stack of two bonding devices is setup, because skb->dev always points to the hierarchy top. Add new "const struct net_device *dev" parameter to (struct header_ops)->parse() method to make sure the recursion is bounded, and that the final leaf parse method is called. Fixes: 950803f72547 ("bonding: fix type confusion in bond_setup_by_slave()") Signed-off-by: Eric Dumazet Reviewed-by: Jiayuan Chen Tested-by: Jiayuan Chen Cc: Jay Vosburgh Cc: Andrew Lunn Link: https://patch.msgid.link/20260315104152.1436867-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- drivers/firewire/net.c | 5 +++-- drivers/net/bonding/bond_main.c | 8 +++++--- include/linux/etherdevice.h | 3 ++- include/linux/if_ether.h | 3 ++- include/linux/netdevice.h | 6 ++++-- net/ethernet/eth.c | 9 +++------ net/ipv4/ip_gre.c | 3 ++- net/mac802154/iface.c | 4 +++- net/phonet/af_phonet.c | 5 ++++- 9 files changed, 28 insertions(+), 18 deletions(-) (limited to 'include') diff --git a/drivers/firewire/net.c b/drivers/firewire/net.c index f1a2bee39bf1..82b3b6d9ed2d 100644 --- a/drivers/firewire/net.c +++ b/drivers/firewire/net.c @@ -257,9 +257,10 @@ static void fwnet_header_cache_update(struct hh_cache *hh, memcpy((u8 *)hh->hh_data + HH_DATA_OFF(FWNET_HLEN), haddr, net->addr_len); } -static int fwnet_header_parse(const struct sk_buff *skb, unsigned char *haddr) +static int fwnet_header_parse(const struct sk_buff *skb, const struct net_device *dev, + unsigned char *haddr) { - memcpy(haddr, skb->dev->dev_addr, FWNET_ALEN); + memcpy(haddr, dev->dev_addr, FWNET_ALEN); return FWNET_ALEN; } diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c index 707419270ebf..33f414d03ab9 100644 --- a/drivers/net/bonding/bond_main.c +++ b/drivers/net/bonding/bond_main.c @@ -1530,9 +1530,11 @@ static int bond_header_create(struct sk_buff *skb, struct net_device *bond_dev, return ret; } -static int bond_header_parse(const struct sk_buff *skb, unsigned char *haddr) +static int bond_header_parse(const struct sk_buff *skb, + const struct net_device *dev, + unsigned char *haddr) { - struct bonding *bond = netdev_priv(skb->dev); + struct bonding *bond = netdev_priv(dev); const struct header_ops *slave_ops; struct slave *slave; int ret = 0; @@ -1542,7 +1544,7 @@ static int bond_header_parse(const struct sk_buff *skb, unsigned char *haddr) if (slave) { slave_ops = READ_ONCE(slave->dev->header_ops); if (slave_ops && slave_ops->parse) - ret = slave_ops->parse(skb, haddr); + ret = slave_ops->parse(skb, slave->dev, haddr); } rcu_read_unlock(); return ret; diff --git a/include/linux/etherdevice.h b/include/linux/etherdevice.h index 9a1eacf35d37..df8f88f63a70 100644 --- a/include/linux/etherdevice.h +++ b/include/linux/etherdevice.h @@ -42,7 +42,8 @@ extern const struct header_ops eth_header_ops; int eth_header(struct sk_buff *skb, struct net_device *dev, unsigned short type, const void *daddr, const void *saddr, unsigned len); -int eth_header_parse(const struct sk_buff *skb, unsigned char *haddr); +int eth_header_parse(const struct sk_buff *skb, const struct net_device *dev, + unsigned char *haddr); int eth_header_cache(const struct neighbour *neigh, struct hh_cache *hh, __be16 type); void eth_header_cache_update(struct hh_cache *hh, const struct net_device *dev, diff --git a/include/linux/if_ether.h b/include/linux/if_ether.h index 61b7335aa037..ca9afa824aa4 100644 --- a/include/linux/if_ether.h +++ b/include/linux/if_ether.h @@ -40,7 +40,8 @@ static inline struct ethhdr *inner_eth_hdr(const struct sk_buff *skb) return (struct ethhdr *)skb_inner_mac_header(skb); } -int eth_header_parse(const struct sk_buff *skb, unsigned char *haddr); +int eth_header_parse(const struct sk_buff *skb, const struct net_device *dev, + unsigned char *haddr); extern ssize_t sysfs_format_mac(char *buf, const unsigned char *addr, int len); diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index d7aac6f185bc..7ca01eb3f7d2 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -311,7 +311,9 @@ struct header_ops { int (*create) (struct sk_buff *skb, struct net_device *dev, unsigned short type, const void *daddr, const void *saddr, unsigned int len); - int (*parse)(const struct sk_buff *skb, unsigned char *haddr); + int (*parse)(const struct sk_buff *skb, + const struct net_device *dev, + unsigned char *haddr); int (*cache)(const struct neighbour *neigh, struct hh_cache *hh, __be16 type); void (*cache_update)(struct hh_cache *hh, const struct net_device *dev, @@ -3445,7 +3447,7 @@ static inline int dev_parse_header(const struct sk_buff *skb, if (!dev->header_ops || !dev->header_ops->parse) return 0; - return dev->header_ops->parse(skb, haddr); + return dev->header_ops->parse(skb, dev, haddr); } static inline __be16 dev_parse_header_protocol(const struct sk_buff *skb) diff --git a/net/ethernet/eth.c b/net/ethernet/eth.c index 13a63b48b7ee..d9faadbe9b6c 100644 --- a/net/ethernet/eth.c +++ b/net/ethernet/eth.c @@ -193,14 +193,11 @@ __be16 eth_type_trans(struct sk_buff *skb, struct net_device *dev) } EXPORT_SYMBOL(eth_type_trans); -/** - * eth_header_parse - extract hardware address from packet - * @skb: packet to extract header from - * @haddr: destination buffer - */ -int eth_header_parse(const struct sk_buff *skb, unsigned char *haddr) +int eth_header_parse(const struct sk_buff *skb, const struct net_device *dev, + unsigned char *haddr) { const struct ethhdr *eth = eth_hdr(skb); + memcpy(haddr, eth->h_source, ETH_ALEN); return ETH_ALEN; } diff --git a/net/ipv4/ip_gre.c b/net/ipv4/ip_gre.c index e13244729ad8..35f0baa99d40 100644 --- a/net/ipv4/ip_gre.c +++ b/net/ipv4/ip_gre.c @@ -919,7 +919,8 @@ static int ipgre_header(struct sk_buff *skb, struct net_device *dev, return -(t->hlen + sizeof(*iph)); } -static int ipgre_header_parse(const struct sk_buff *skb, unsigned char *haddr) +static int ipgre_header_parse(const struct sk_buff *skb, const struct net_device *dev, + unsigned char *haddr) { const struct iphdr *iph = (const struct iphdr *) skb_mac_header(skb); memcpy(haddr, &iph->saddr, 4); diff --git a/net/mac802154/iface.c b/net/mac802154/iface.c index 9e4631fade90..000be60d9580 100644 --- a/net/mac802154/iface.c +++ b/net/mac802154/iface.c @@ -469,7 +469,9 @@ static int mac802154_header_create(struct sk_buff *skb, } static int -mac802154_header_parse(const struct sk_buff *skb, unsigned char *haddr) +mac802154_header_parse(const struct sk_buff *skb, + const struct net_device *dev, + unsigned char *haddr) { struct ieee802154_hdr hdr; diff --git a/net/phonet/af_phonet.c b/net/phonet/af_phonet.c index 238a9638d2b0..d89225d6bfd3 100644 --- a/net/phonet/af_phonet.c +++ b/net/phonet/af_phonet.c @@ -129,9 +129,12 @@ static int pn_header_create(struct sk_buff *skb, struct net_device *dev, return 1; } -static int pn_header_parse(const struct sk_buff *skb, unsigned char *haddr) +static int pn_header_parse(const struct sk_buff *skb, + const struct net_device *dev, + unsigned char *haddr) { const u8 *media = skb_mac_header(skb); + *haddr = *media; return 1; } -- cgit v1.2.3 From 66360460cab63c248ca5b1070a01c0c29133b960 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Sun, 15 Mar 2026 11:54:22 -0400 Subject: net/sched: teql: Fix double-free in teql_master_xmit Whenever a TEQL devices has a lockless Qdisc as root, qdisc_reset should be called using the seq_lock to avoid racing with the datapath. Failure to do so may cause crashes like the following: [ 238.028993][ T318] BUG: KASAN: double-free in skb_release_data (net/core/skbuff.c:1139) [ 238.029328][ T318] Free of addr ffff88810c67ec00 by task poc_teql_uaf_ke/318 [ 238.029749][ T318] [ 238.029900][ T318] CPU: 3 UID: 0 PID: 318 Comm: poc_teql_ke Not tainted 7.0.0-rc3-00149-ge5b31d988a41 #704 PREEMPT(full) [ 238.029906][ T318] Hardware name: Bochs Bochs, BIOS Bochs 01/01/2011 [ 238.029910][ T318] Call Trace: [ 238.029913][ T318] [ 238.029916][ T318] dump_stack_lvl (lib/dump_stack.c:122) [ 238.029928][ T318] print_report (mm/kasan/report.c:379 mm/kasan/report.c:482) [ 238.029940][ T318] ? skb_release_data (net/core/skbuff.c:1139) [ 238.029944][ T318] ? srso_alias_return_thunk (arch/x86/lib/retpoline.S:221) ... [ 238.029957][ T318] ? skb_release_data (net/core/skbuff.c:1139) [ 238.029969][ T318] kasan_report_invalid_free (mm/kasan/report.c:221 mm/kasan/report.c:563) [ 238.029979][ T318] ? skb_release_data (net/core/skbuff.c:1139) [ 238.029989][ T318] check_slab_allocation (mm/kasan/common.c:231) [ 238.029995][ T318] kmem_cache_free (mm/slub.c:2637 (discriminator 1) mm/slub.c:6168 (discriminator 1) mm/slub.c:6298 (discriminator 1)) [ 238.030004][ T318] skb_release_data (net/core/skbuff.c:1139) ... [ 238.030025][ T318] sk_skb_reason_drop (net/core/skbuff.c:1256) [ 238.030032][ T318] pfifo_fast_reset (./include/linux/ptr_ring.h:171 ./include/linux/ptr_ring.h:309 ./include/linux/skb_array.h:98 net/sched/sch_generic.c:827) [ 238.030039][ T318] ? srso_alias_return_thunk (arch/x86/lib/retpoline.S:221) ... [ 238.030054][ T318] qdisc_reset (net/sched/sch_generic.c:1034) [ 238.030062][ T318] teql_destroy (./include/linux/spinlock.h:395 net/sched/sch_teql.c:157) [ 238.030071][ T318] __qdisc_destroy (./include/net/pkt_sched.h:328 net/sched/sch_generic.c:1077) [ 238.030077][ T318] qdisc_graft (net/sched/sch_api.c:1062 net/sched/sch_api.c:1053 net/sched/sch_api.c:1159) [ 238.030089][ T318] ? __pfx_qdisc_graft (net/sched/sch_api.c:1091) [ 238.030095][ T318] ? srso_alias_return_thunk (arch/x86/lib/retpoline.S:221) [ 238.030102][ T318] ? srso_alias_return_thunk (arch/x86/lib/retpoline.S:221) [ 238.030106][ T318] ? srso_alias_return_thunk (arch/x86/lib/retpoline.S:221) [ 238.030114][ T318] tc_get_qdisc (net/sched/sch_api.c:1529 net/sched/sch_api.c:1556) ... [ 238.072958][ T318] Allocated by task 303 on cpu 5 at 238.026275s: [ 238.073392][ T318] kasan_save_stack (mm/kasan/common.c:58) [ 238.073884][ T318] kasan_save_track (mm/kasan/common.c:64 (discriminator 5) mm/kasan/common.c:79 (discriminator 5)) [ 238.074230][ T318] __kasan_slab_alloc (mm/kasan/common.c:369) [ 238.074578][ T318] kmem_cache_alloc_node_noprof (./include/linux/kasan.h:253 mm/slub.c:4542 mm/slub.c:4869 mm/slub.c:4921) [ 238.076091][ T318] kmalloc_reserve (net/core/skbuff.c:616 (discriminator 107)) [ 238.076450][ T318] __alloc_skb (net/core/skbuff.c:713) [ 238.076834][ T318] alloc_skb_with_frags (./include/linux/skbuff.h:1383 net/core/skbuff.c:6763) [ 238.077178][ T318] sock_alloc_send_pskb (net/core/sock.c:2997) [ 238.077520][ T318] packet_sendmsg (net/packet/af_packet.c:2926 net/packet/af_packet.c:3019 net/packet/af_packet.c:3108) [ 238.081469][ T318] [ 238.081870][ T318] Freed by task 299 on cpu 1 at 238.028496s: [ 238.082761][ T318] kasan_save_stack (mm/kasan/common.c:58) [ 238.083481][ T318] kasan_save_track (mm/kasan/common.c:64 (discriminator 5) mm/kasan/common.c:79 (discriminator 5)) [ 238.085348][ T318] kasan_save_free_info (mm/kasan/generic.c:587 (discriminator 1)) [ 238.085900][ T318] __kasan_slab_free (mm/kasan/common.c:287) [ 238.086439][ T318] kmem_cache_free (mm/slub.c:6168 (discriminator 3) mm/slub.c:6298 (discriminator 3)) [ 238.087007][ T318] skb_release_data (net/core/skbuff.c:1139) [ 238.087491][ T318] consume_skb (net/core/skbuff.c:1451) [ 238.087757][ T318] teql_master_xmit (net/sched/sch_teql.c:358) [ 238.088116][ T318] dev_hard_start_xmit (./include/linux/netdevice.h:5324 ./include/linux/netdevice.h:5333 net/core/dev.c:3871 net/core/dev.c:3887) [ 238.088468][ T318] sch_direct_xmit (net/sched/sch_generic.c:347) [ 238.088820][ T318] __qdisc_run (net/sched/sch_generic.c:420 (discriminator 1)) [ 238.089166][ T318] __dev_queue_xmit (./include/net/sch_generic.h:229 ./include/net/pkt_sched.h:121 ./include/net/pkt_sched.h:117 net/core/dev.c:4196 net/core/dev.c:4802) Workflow to reproduce: 1. Initialize a TEQL topology (dummy0 and ifb0 as slaves, teql0 up). 2. Start multiple sender workers continuously transmitting packets through teql0 to drive teql_master_xmit(). 3. In parallel, repeatedly delete and re-add the root qdisc on dummy0 and ifb0 via RTNETLINK, forcing frequent teardown and reset activity (teql_destroy() / qdisc_reset()). 4. After running both workloads concurrently for several iterations, KASAN reports slab-use-after-free or double-free in the skb free path. Fix this by moving dev_reset_queue to sch_generic.h and calling it, instead of qdisc_reset, in teql_destroy since it handles both the lock and lockless cases correctly for root qdiscs. Fixes: 96009c7d500e ("sched: replace __QDISC_STATE_RUNNING bit with a spin lock") Reported-by: Xianrui Dong Tested-by: Xianrui Dong Co-developed-by: Victor Nogueira Signed-off-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/20260315155422.147256-1-jhs@mojatatu.com Signed-off-by: Jakub Kicinski --- include/net/sch_generic.h | 28 ++++++++++++++++++++++++++++ net/sched/sch_generic.c | 27 --------------------------- net/sched/sch_teql.c | 7 ++----- 3 files changed, 30 insertions(+), 32 deletions(-) (limited to 'include') diff --git a/include/net/sch_generic.h b/include/net/sch_generic.h index d5d55cb21686..cafb266a0b80 100644 --- a/include/net/sch_generic.h +++ b/include/net/sch_generic.h @@ -716,6 +716,34 @@ void qdisc_destroy(struct Qdisc *qdisc); void qdisc_put(struct Qdisc *qdisc); void qdisc_put_unlocked(struct Qdisc *qdisc); void qdisc_tree_reduce_backlog(struct Qdisc *qdisc, int n, int len); + +static inline void dev_reset_queue(struct net_device *dev, + struct netdev_queue *dev_queue, + void *_unused) +{ + struct Qdisc *qdisc; + bool nolock; + + qdisc = rtnl_dereference(dev_queue->qdisc_sleeping); + if (!qdisc) + return; + + nolock = qdisc->flags & TCQ_F_NOLOCK; + + if (nolock) + spin_lock_bh(&qdisc->seqlock); + spin_lock_bh(qdisc_lock(qdisc)); + + qdisc_reset(qdisc); + + spin_unlock_bh(qdisc_lock(qdisc)); + if (nolock) { + clear_bit(__QDISC_STATE_MISSED, &qdisc->state); + clear_bit(__QDISC_STATE_DRAINING, &qdisc->state); + spin_unlock_bh(&qdisc->seqlock); + } +} + #ifdef CONFIG_NET_SCHED int qdisc_offload_dump_helper(struct Qdisc *q, enum tc_setup_type type, void *type_data); diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c index 98ffe64de51f..9e726c3bd86b 100644 --- a/net/sched/sch_generic.c +++ b/net/sched/sch_generic.c @@ -1288,33 +1288,6 @@ static void dev_deactivate_queue(struct net_device *dev, } } -static void dev_reset_queue(struct net_device *dev, - struct netdev_queue *dev_queue, - void *_unused) -{ - struct Qdisc *qdisc; - bool nolock; - - qdisc = rtnl_dereference(dev_queue->qdisc_sleeping); - if (!qdisc) - return; - - nolock = qdisc->flags & TCQ_F_NOLOCK; - - if (nolock) - spin_lock_bh(&qdisc->seqlock); - spin_lock_bh(qdisc_lock(qdisc)); - - qdisc_reset(qdisc); - - spin_unlock_bh(qdisc_lock(qdisc)); - if (nolock) { - clear_bit(__QDISC_STATE_MISSED, &qdisc->state); - clear_bit(__QDISC_STATE_DRAINING, &qdisc->state); - spin_unlock_bh(&qdisc->seqlock); - } -} - static bool some_qdisc_is_busy(struct net_device *dev) { unsigned int i; diff --git a/net/sched/sch_teql.c b/net/sched/sch_teql.c index 783300d8b019..ec4039a201a2 100644 --- a/net/sched/sch_teql.c +++ b/net/sched/sch_teql.c @@ -146,15 +146,12 @@ teql_destroy(struct Qdisc *sch) master->slaves = NEXT_SLAVE(q); if (q == master->slaves) { struct netdev_queue *txq; - spinlock_t *root_lock; txq = netdev_get_tx_queue(master->dev, 0); master->slaves = NULL; - root_lock = qdisc_root_sleeping_lock(rtnl_dereference(txq->qdisc)); - spin_lock_bh(root_lock); - qdisc_reset(rtnl_dereference(txq->qdisc)); - spin_unlock_bh(root_lock); + dev_reset_queue(master->dev, + txq, NULL); } } skb_queue_purge(&dat->q); -- cgit v1.2.3 From a0671125d4f55e1e98d9bde8a0b671941987e208 Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Fri, 13 Mar 2026 07:55:31 +0100 Subject: clsact: Fix use-after-free in init/destroy rollback asymmetry Fix a use-after-free in the clsact qdisc upon init/destroy rollback asymmetry. The latter is achieved by first fully initializing a clsact instance, and then in a second step having a replacement failure for the new clsact qdisc instance. clsact_init() initializes ingress first and then takes care of the egress part. This can fail midway, for example, via tcf_block_get_ext(). Upon failure, the kernel will trigger the clsact_destroy() callback. Commit 1cb6f0bae504 ("bpf: Fix too early release of tcx_entry") details the way how the transition is happening. If tcf_block_get_ext on the q->ingress_block ends up failing, we took the tcx_miniq_inc reference count on the ingress side, but not yet on the egress side. clsact_destroy() tests whether the {ingress,egress}_entry was non-NULL. However, even in midway failure on the replacement, both are in fact non-NULL with a valid egress_entry from the previous clsact instance. What we really need to test for is whether the qdisc instance-specific ingress or egress side previously got initialized. This adds a small helper for checking the miniq initialization called mini_qdisc_pair_inited, and utilizes that upon clsact_destroy() in order to fix the use-after-free scenario. Convert the ingress_destroy() side as well so both are consistent to each other. Fixes: 1cb6f0bae504 ("bpf: Fix too early release of tcx_entry") Reported-by: Keenan Dong Signed-off-by: Daniel Borkmann Cc: Martin KaFai Lau Acked-by: Martin KaFai Lau Link: https://patch.msgid.link/20260313065531.98639-1-daniel@iogearbox.net Signed-off-by: Paolo Abeni --- include/net/sch_generic.h | 5 +++++ net/sched/sch_ingress.c | 14 ++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) (limited to 'include') diff --git a/include/net/sch_generic.h b/include/net/sch_generic.h index cafb266a0b80..c3d657359a3d 100644 --- a/include/net/sch_generic.h +++ b/include/net/sch_generic.h @@ -1457,6 +1457,11 @@ void mini_qdisc_pair_init(struct mini_Qdisc_pair *miniqp, struct Qdisc *qdisc, void mini_qdisc_pair_block_init(struct mini_Qdisc_pair *miniqp, struct tcf_block *block); +static inline bool mini_qdisc_pair_inited(struct mini_Qdisc_pair *miniqp) +{ + return !!miniqp->p_miniq; +} + void mq_change_real_num_tx(struct Qdisc *sch, unsigned int new_real_tx); int sch_frag_xmit_hook(struct sk_buff *skb, int (*xmit)(struct sk_buff *skb)); diff --git a/net/sched/sch_ingress.c b/net/sched/sch_ingress.c index cc6051d4f2ef..c3e18bae8fbf 100644 --- a/net/sched/sch_ingress.c +++ b/net/sched/sch_ingress.c @@ -113,14 +113,15 @@ static void ingress_destroy(struct Qdisc *sch) { struct ingress_sched_data *q = qdisc_priv(sch); struct net_device *dev = qdisc_dev(sch); - struct bpf_mprog_entry *entry = rtnl_dereference(dev->tcx_ingress); + struct bpf_mprog_entry *entry; if (sch->parent != TC_H_INGRESS) return; tcf_block_put_ext(q->block, sch, &q->block_info); - if (entry) { + if (mini_qdisc_pair_inited(&q->miniqp)) { + entry = rtnl_dereference(dev->tcx_ingress); tcx_miniq_dec(entry); if (!tcx_entry_is_active(entry)) { tcx_entry_update(dev, NULL, true); @@ -290,10 +291,9 @@ static int clsact_init(struct Qdisc *sch, struct nlattr *opt, static void clsact_destroy(struct Qdisc *sch) { + struct bpf_mprog_entry *ingress_entry, *egress_entry; struct clsact_sched_data *q = qdisc_priv(sch); struct net_device *dev = qdisc_dev(sch); - struct bpf_mprog_entry *ingress_entry = rtnl_dereference(dev->tcx_ingress); - struct bpf_mprog_entry *egress_entry = rtnl_dereference(dev->tcx_egress); if (sch->parent != TC_H_CLSACT) return; @@ -301,7 +301,8 @@ static void clsact_destroy(struct Qdisc *sch) tcf_block_put_ext(q->ingress_block, sch, &q->ingress_block_info); tcf_block_put_ext(q->egress_block, sch, &q->egress_block_info); - if (ingress_entry) { + if (mini_qdisc_pair_inited(&q->miniqp_ingress)) { + ingress_entry = rtnl_dereference(dev->tcx_ingress); tcx_miniq_dec(ingress_entry); if (!tcx_entry_is_active(ingress_entry)) { tcx_entry_update(dev, NULL, true); @@ -309,7 +310,8 @@ static void clsact_destroy(struct Qdisc *sch) } } - if (egress_entry) { + if (mini_qdisc_pair_inited(&q->miniqp_egress)) { + egress_entry = rtnl_dereference(dev->tcx_egress); tcx_miniq_dec(egress_entry); if (!tcx_entry_is_active(egress_entry)) { tcx_entry_update(dev, NULL, false); -- cgit v1.2.3 From 45c6a2dc7ec8339052666b06065c521a10cc29bb Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sun, 1 Mar 2026 16:52:14 -0800 Subject: iommu/io-pgtable: fix all kernel-doc warnings in io-pgtable.h Avoid kernel-doc warnings in io-pgtable.h: - use the correct struct member names or kernel-doc format - add a missing struct member description - add a missing function return comment section Warning: include/linux/io-pgtable.h:187 struct member 'coherent_walk' not described in 'io_pgtable_cfg' Warning: include/linux/io-pgtable.h:187 struct member 'arm_lpae_s1_cfg' not described in 'io_pgtable_cfg' Warning: include/linux/io-pgtable.h:187 struct member 'arm_lpae_s2_cfg' not described in 'io_pgtable_cfg' Warning: include/linux/io-pgtable.h:187 struct member 'arm_v7s_cfg' not described in 'io_pgtable_cfg' Warning: include/linux/io-pgtable.h:187 struct member 'arm_mali_lpae_cfg' not described in 'io_pgtable_cfg' Warning: include/linux/io-pgtable.h:187 struct member 'apple_dart_cfg' not described in 'io_pgtable_cfg' Warning: include/linux/io-pgtable.h:187 struct member 'amd' not described in 'io_pgtable_cfg' Warning: include/linux/io-pgtable.h:223 struct member 'read_and_clear_dirty' not described in 'io_pgtable_ops' Warning: include/linux/io-pgtable.h:237 No description found for return value of 'alloc_io_pgtable_ops' Signed-off-by: Randy Dunlap Reviewed-by: Jason Gunthorpe Signed-off-by: Joerg Roedel --- include/linux/io-pgtable.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/linux/io-pgtable.h b/include/linux/io-pgtable.h index 7a1516011ccf..e19872e37e06 100644 --- a/include/linux/io-pgtable.h +++ b/include/linux/io-pgtable.h @@ -53,7 +53,7 @@ struct iommu_flush_ops { * tables. * @ias: Input address (iova) size, in bits. * @oas: Output address (paddr) size, in bits. - * @coherent_walk A flag to indicate whether or not page table walks made + * @coherent_walk: A flag to indicate whether or not page table walks made * by the IOMMU are coherent with the CPU caches. * @tlb: TLB management callbacks for this set of tables. * @iommu_dev: The device representing the DMA configuration for the @@ -136,6 +136,7 @@ struct io_pgtable_cfg { void (*free)(void *cookie, void *pages, size_t size); /* Low-level data specific to the table format */ + /* private: */ union { struct { u64 ttbr; @@ -203,6 +204,9 @@ struct arm_lpae_io_pgtable_walk_data { * @unmap_pages: Unmap a range of virtually contiguous pages of the same size. * @iova_to_phys: Translate iova to physical address. * @pgtable_walk: (optional) Perform a page table walk for a given iova. + * @read_and_clear_dirty: Record dirty info per IOVA. If an IOVA is dirty, + * clear its dirty state from the PTE unless the + * IOMMU_DIRTY_NO_CLEAR flag is passed in. * * These functions map directly onto the iommu_ops member functions with * the same names. @@ -231,7 +235,9 @@ struct io_pgtable_ops { * the configuration actually provided by the allocator (e.g. the * pgsize_bitmap may be restricted). * @cookie: An opaque token provided by the IOMMU driver and passed back to - * the callback routines in cfg->tlb. + * the callback routines. + * + * Returns: Pointer to the &struct io_pgtable_ops for this set of page tables. */ struct io_pgtable_ops *alloc_io_pgtable_ops(enum io_pgtable_fmt fmt, struct io_pgtable_cfg *cfg, -- cgit v1.2.3 From 3ccc8a922906703cd0efdf1bdd6186f18f7e23ec Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Mon, 16 Mar 2026 14:15:02 +0200 Subject: drm/intel: add shared step.h and switch i915 to use it As the first step towards using shared definitions for step name enumerations, add shared include/drm/intel/step.h and switch i915 to use it. Reviewed-by: Luca Coelho Link: https://patch.msgid.link/e76412a316ddff44dc46633d80e9caa5df54ed6b.1773663208.git.jani.nikula@intel.com Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/intel_step.h | 57 ++--------------------------------- include/drm/intel/step.h | 62 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 55 deletions(-) create mode 100644 include/drm/intel/step.h (limited to 'include') diff --git a/drivers/gpu/drm/i915/intel_step.h b/drivers/gpu/drm/i915/intel_step.h index 22f1d6905160..2ca36eae4b5a 100644 --- a/drivers/gpu/drm/i915/intel_step.h +++ b/drivers/gpu/drm/i915/intel_step.h @@ -8,6 +8,8 @@ #include +#include + struct drm_i915_private; struct intel_step_info { @@ -19,61 +21,6 @@ struct intel_step_info { u8 media_step; }; -#define STEP_ENUM_VAL(name) STEP_##name, - -#define STEP_NAME_LIST(func) \ - func(A0) \ - func(A1) \ - func(A2) \ - func(A3) \ - func(B0) \ - func(B1) \ - func(B2) \ - func(B3) \ - func(C0) \ - func(C1) \ - func(C2) \ - func(C3) \ - func(D0) \ - func(D1) \ - func(D2) \ - func(D3) \ - func(E0) \ - func(E1) \ - func(E2) \ - func(E3) \ - func(F0) \ - func(F1) \ - func(F2) \ - func(F3) \ - func(G0) \ - func(G1) \ - func(G2) \ - func(G3) \ - func(H0) \ - func(H1) \ - func(H2) \ - func(H3) \ - func(I0) \ - func(I1) \ - func(I2) \ - func(I3) \ - func(J0) \ - func(J1) \ - func(J2) \ - func(J3) - -/* - * Symbolic steppings that do not match the hardware. These are valid both as gt - * and display steppings as symbolic names. - */ -enum intel_step { - STEP_NONE = 0, - STEP_NAME_LIST(STEP_ENUM_VAL) - STEP_FUTURE, - STEP_FOREVER, -}; - void intel_step_init(struct drm_i915_private *i915); const char *intel_step_name(enum intel_step step); diff --git a/include/drm/intel/step.h b/include/drm/intel/step.h new file mode 100644 index 000000000000..4de7520109bc --- /dev/null +++ b/include/drm/intel/step.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2026 Intel Corporation */ + +#ifndef __STEP_H__ +#define __STEP_H__ + +#define STEP_ENUM_VAL(name) STEP_##name, + +#define STEP_NAME_LIST(func) \ + func(A0) \ + func(A1) \ + func(A2) \ + func(A3) \ + func(B0) \ + func(B1) \ + func(B2) \ + func(B3) \ + func(C0) \ + func(C1) \ + func(C2) \ + func(C3) \ + func(D0) \ + func(D1) \ + func(D2) \ + func(D3) \ + func(E0) \ + func(E1) \ + func(E2) \ + func(E3) \ + func(F0) \ + func(F1) \ + func(F2) \ + func(F3) \ + func(G0) \ + func(G1) \ + func(G2) \ + func(G3) \ + func(H0) \ + func(H1) \ + func(H2) \ + func(H3) \ + func(I0) \ + func(I1) \ + func(I2) \ + func(I3) \ + func(J0) \ + func(J1) \ + func(J2) \ + func(J3) + +/* + * Symbolic steppings that do not match the hardware. These are valid both as gt + * and display steppings as symbolic names. + */ +enum intel_step { + STEP_NONE = 0, + STEP_NAME_LIST(STEP_ENUM_VAL) + STEP_FUTURE, + STEP_FOREVER, +}; + +#endif /* __STEP_H__ */ -- cgit v1.2.3 From cb3d1049f4ea77d5ad93f17d8ac1f2ed4da70501 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Tue, 3 Mar 2026 12:53:18 +0100 Subject: driver core: generalize driver_override in struct device Currently, there are 12 busses (including platform and PCI) that duplicate the driver_override logic for their individual devices. All of them seem to be prone to the bug described in [1]. While this could be solved for every bus individually using a separate lock, solving this in the driver-core generically results in less (and cleaner) changes overall. Thus, move driver_override to struct device, provide corresponding accessors for busses and handle locking with a separate lock internally. In particular, add device_set_driver_override(), device_has_driver_override(), device_match_driver_override() and generalize the sysfs store() and show() callbacks via a driver_override feature flag in struct bus_type. Until all busses have migrated, keep driver_set_override() in place. Note that we can't use the device lock for the reasons described in [2]. Link: https://bugzilla.kernel.org/show_bug.cgi?id=220789 [1] Link: https://lore.kernel.org/driver-core/DGRGTIRHA62X.3RY09D9SOK77P@kernel.org/ [2] Tested-by: Gui-Dong Han Co-developed-by: Gui-Dong Han Signed-off-by: Gui-Dong Han Reviewed-by: Greg Kroah-Hartman Link: https://patch.msgid.link/20260303115720.48783-2-dakr@kernel.org [ Use dev->bus instead of sp->bus for consistency; fix commit message to refer to the struct bus_type's driver_override feature flag. - Danilo ] Signed-off-by: Danilo Krummrich --- drivers/base/bus.c | 43 ++++++++++++++++++++++++++++++++- drivers/base/core.c | 2 ++ drivers/base/dd.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++ include/linux/device.h | 54 +++++++++++++++++++++++++++++++++++++++++ include/linux/device/bus.h | 4 ++++ 5 files changed, 162 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/drivers/base/bus.c b/drivers/base/bus.c index bb61d8adbab1..8b6722ff8590 100644 --- a/drivers/base/bus.c +++ b/drivers/base/bus.c @@ -504,6 +504,36 @@ int bus_for_each_drv(const struct bus_type *bus, struct device_driver *start, } EXPORT_SYMBOL_GPL(bus_for_each_drv); +static ssize_t driver_override_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + + ret = __device_set_driver_override(dev, buf, count); + if (ret) + return ret; + + return count; +} + +static ssize_t driver_override_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + guard(spinlock)(&dev->driver_override.lock); + return sysfs_emit(buf, "%s\n", dev->driver_override.name); +} +static DEVICE_ATTR_RW(driver_override); + +static struct attribute *driver_override_dev_attrs[] = { + &dev_attr_driver_override.attr, + NULL, +}; + +static const struct attribute_group driver_override_dev_group = { + .attrs = driver_override_dev_attrs, +}; + /** * bus_add_device - add device to bus * @dev: device being added @@ -537,9 +567,15 @@ int bus_add_device(struct device *dev) if (error) goto out_put; + if (dev->bus->driver_override) { + error = device_add_group(dev, &driver_override_dev_group); + if (error) + goto out_groups; + } + error = sysfs_create_link(&sp->devices_kset->kobj, &dev->kobj, dev_name(dev)); if (error) - goto out_groups; + goto out_override; error = sysfs_create_link(&dev->kobj, &sp->subsys.kobj, "subsystem"); if (error) @@ -550,6 +586,9 @@ int bus_add_device(struct device *dev) out_subsys: sysfs_remove_link(&sp->devices_kset->kobj, dev_name(dev)); +out_override: + if (dev->bus->driver_override) + device_remove_group(dev, &driver_override_dev_group); out_groups: device_remove_groups(dev, sp->bus->dev_groups); out_put: @@ -607,6 +646,8 @@ void bus_remove_device(struct device *dev) sysfs_remove_link(&dev->kobj, "subsystem"); sysfs_remove_link(&sp->devices_kset->kobj, dev_name(dev)); + if (dev->bus->driver_override) + device_remove_group(dev, &driver_override_dev_group); device_remove_groups(dev, dev->bus->dev_groups); if (klist_node_attached(&dev->p->knode_bus)) klist_del(&dev->p->knode_bus); diff --git a/drivers/base/core.c b/drivers/base/core.c index 791f9e444df8..09b98f02f559 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -2556,6 +2556,7 @@ static void device_release(struct kobject *kobj) devres_release_all(dev); kfree(dev->dma_range_map); + kfree(dev->driver_override.name); if (dev->release) dev->release(dev); @@ -3159,6 +3160,7 @@ void device_initialize(struct device *dev) kobject_init(&dev->kobj, &device_ktype); INIT_LIST_HEAD(&dev->dma_pools); mutex_init(&dev->mutex); + spin_lock_init(&dev->driver_override.lock); lockdep_set_novalidate_class(&dev->mutex); spin_lock_init(&dev->devres_lock); INIT_LIST_HEAD(&dev->devres_head); diff --git a/drivers/base/dd.c b/drivers/base/dd.c index bea8da5f8a3a..37c7e54e0e4c 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -381,6 +381,66 @@ static void __exit deferred_probe_exit(void) } __exitcall(deferred_probe_exit); +int __device_set_driver_override(struct device *dev, const char *s, size_t len) +{ + const char *new, *old; + char *cp; + + if (!s) + return -EINVAL; + + /* + * The stored value will be used in sysfs show callback (sysfs_emit()), + * which has a length limit of PAGE_SIZE and adds a trailing newline. + * Thus we can store one character less to avoid truncation during sysfs + * show. + */ + if (len >= (PAGE_SIZE - 1)) + return -EINVAL; + + /* + * Compute the real length of the string in case userspace sends us a + * bunch of \0 characters like python likes to do. + */ + len = strlen(s); + + if (!len) { + /* Empty string passed - clear override */ + spin_lock(&dev->driver_override.lock); + old = dev->driver_override.name; + dev->driver_override.name = NULL; + spin_unlock(&dev->driver_override.lock); + kfree(old); + + return 0; + } + + cp = strnchr(s, len, '\n'); + if (cp) + len = cp - s; + + new = kstrndup(s, len, GFP_KERNEL); + if (!new) + return -ENOMEM; + + spin_lock(&dev->driver_override.lock); + old = dev->driver_override.name; + if (cp != s) { + dev->driver_override.name = new; + spin_unlock(&dev->driver_override.lock); + } else { + /* "\n" passed - clear override */ + dev->driver_override.name = NULL; + spin_unlock(&dev->driver_override.lock); + + kfree(new); + } + kfree(old); + + return 0; +} +EXPORT_SYMBOL_GPL(__device_set_driver_override); + /** * device_is_bound() - Check if device is bound to a driver * @dev: device to check diff --git a/include/linux/device.h b/include/linux/device.h index 0be95294b6e6..e65d564f01cd 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -483,6 +483,8 @@ struct device_physical_location { * on. This shrinks the "Board Support Packages" (BSPs) and * minimizes board-specific #ifdefs in drivers. * @driver_data: Private pointer for driver specific info. + * @driver_override: Driver name to force a match. Do not touch directly; use + * device_set_driver_override() instead. * @links: Links to suppliers and consumers of this device. * @power: For device power management. * See Documentation/driver-api/pm/devices.rst for details. @@ -576,6 +578,10 @@ struct device { core doesn't touch it */ void *driver_data; /* Driver data, set and get with dev_set_drvdata/dev_get_drvdata */ + struct { + const char *name; + spinlock_t lock; + } driver_override; struct mutex mutex; /* mutex to synchronize calls to * its driver. */ @@ -701,6 +707,54 @@ struct device_link { #define kobj_to_dev(__kobj) container_of_const(__kobj, struct device, kobj) +int __device_set_driver_override(struct device *dev, const char *s, size_t len); + +/** + * device_set_driver_override() - Helper to set or clear driver override. + * @dev: Device to change + * @s: NUL-terminated string, new driver name to force a match, pass empty + * string to clear it ("" or "\n", where the latter is only for sysfs + * interface). + * + * Helper to set or clear driver override of a device. + * + * Returns: 0 on success or a negative error code on failure. + */ +static inline int device_set_driver_override(struct device *dev, const char *s) +{ + return __device_set_driver_override(dev, s, s ? strlen(s) : 0); +} + +/** + * device_has_driver_override() - Check if a driver override has been set. + * @dev: device to check + * + * Returns true if a driver override has been set for this device. + */ +static inline bool device_has_driver_override(struct device *dev) +{ + guard(spinlock)(&dev->driver_override.lock); + return !!dev->driver_override.name; +} + +/** + * device_match_driver_override() - Match a driver against the device's driver_override. + * @dev: device to check + * @drv: driver to match against + * + * Returns > 0 if a driver override is set and matches the given driver, 0 if a + * driver override is set but does not match, or < 0 if a driver override is not + * set at all. + */ +static inline int device_match_driver_override(struct device *dev, + const struct device_driver *drv) +{ + guard(spinlock)(&dev->driver_override.lock); + if (dev->driver_override.name) + return !strcmp(dev->driver_override.name, drv->name); + return -1; +} + /** * device_iommu_mapped - Returns true when the device DMA is translated * by an IOMMU diff --git a/include/linux/device/bus.h b/include/linux/device/bus.h index 63de5f053c33..c1b463cd6464 100644 --- a/include/linux/device/bus.h +++ b/include/linux/device/bus.h @@ -65,6 +65,9 @@ struct fwnode_handle; * this bus. * @pm: Power management operations of this bus, callback the specific * device driver's pm-ops. + * @driver_override: Set to true if this bus supports the driver_override + * mechanism, which allows userspace to force a specific + * driver to bind to a device via a sysfs attribute. * @need_parent_lock: When probing or removing a device on this bus, the * device core should lock the device's parent. * @@ -106,6 +109,7 @@ struct bus_type { const struct dev_pm_ops *pm; + bool driver_override; bool need_parent_lock; }; -- cgit v1.2.3 From 2b38efc05bf7a8568ec74bfffea0f5cfa62bc01d Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Tue, 3 Mar 2026 12:53:21 +0100 Subject: driver core: platform: use generic driver_override infrastructure When a driver is probed through __driver_attach(), the bus' match() callback is called without the device lock held, thus accessing the driver_override field without a lock, which can cause a UAF. Fix this by using the driver-core driver_override infrastructure taking care of proper locking internally. Note that calling match() from __driver_attach() without the device lock held is intentional. [1] Link: https://lore.kernel.org/driver-core/DGRGTIRHA62X.3RY09D9SOK77P@kernel.org/ [1] Reported-by: Gui-Dong Han Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220789 Fixes: 3d713e0e382e ("driver core: platform: add device binding path 'driver_override'") Reviewed-by: Greg Kroah-Hartman Link: https://patch.msgid.link/20260303115720.48783-5-dakr@kernel.org Signed-off-by: Danilo Krummrich --- drivers/base/platform.c | 37 +++++-------------------------------- drivers/bus/simple-pm-bus.c | 4 ++-- drivers/clk/imx/clk-scu.c | 3 +-- drivers/slimbus/qcom-ngd-ctrl.c | 6 ++---- include/linux/platform_device.h | 5 ----- sound/soc/samsung/i2s.c | 6 +++--- 6 files changed, 13 insertions(+), 48 deletions(-) (limited to 'include') diff --git a/drivers/base/platform.c b/drivers/base/platform.c index b45d41b018ca..d44591d52e36 100644 --- a/drivers/base/platform.c +++ b/drivers/base/platform.c @@ -603,7 +603,6 @@ static void platform_device_release(struct device *dev) kfree(pa->pdev.dev.platform_data); kfree(pa->pdev.mfd_cell); kfree(pa->pdev.resource); - kfree(pa->pdev.driver_override); kfree(pa); } @@ -1306,38 +1305,9 @@ static ssize_t numa_node_show(struct device *dev, } static DEVICE_ATTR_RO(numa_node); -static ssize_t driver_override_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct platform_device *pdev = to_platform_device(dev); - ssize_t len; - - device_lock(dev); - len = sysfs_emit(buf, "%s\n", pdev->driver_override); - device_unlock(dev); - - return len; -} - -static ssize_t driver_override_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct platform_device *pdev = to_platform_device(dev); - int ret; - - ret = driver_set_override(dev, &pdev->driver_override, buf, count); - if (ret) - return ret; - - return count; -} -static DEVICE_ATTR_RW(driver_override); - static struct attribute *platform_dev_attrs[] = { &dev_attr_modalias.attr, &dev_attr_numa_node.attr, - &dev_attr_driver_override.attr, NULL, }; @@ -1377,10 +1347,12 @@ static int platform_match(struct device *dev, const struct device_driver *drv) { struct platform_device *pdev = to_platform_device(dev); struct platform_driver *pdrv = to_platform_driver(drv); + int ret; /* When driver_override is set, only bind to the matching driver */ - if (pdev->driver_override) - return !strcmp(pdev->driver_override, drv->name); + ret = device_match_driver_override(dev, drv); + if (ret >= 0) + return ret; /* Attempt an OF style match first */ if (of_driver_match_device(dev, drv)) @@ -1516,6 +1488,7 @@ static const struct dev_pm_ops platform_dev_pm_ops = { const struct bus_type platform_bus_type = { .name = "platform", .dev_groups = platform_dev_groups, + .driver_override = true, .match = platform_match, .uevent = platform_uevent, .probe = platform_probe, diff --git a/drivers/bus/simple-pm-bus.c b/drivers/bus/simple-pm-bus.c index 3f00d953fb9a..c920bd6fbaaf 100644 --- a/drivers/bus/simple-pm-bus.c +++ b/drivers/bus/simple-pm-bus.c @@ -36,7 +36,7 @@ static int simple_pm_bus_probe(struct platform_device *pdev) * that's not listed in simple_pm_bus_of_match. We don't want to do any * of the simple-pm-bus tasks for these devices, so return early. */ - if (pdev->driver_override) + if (device_has_driver_override(&pdev->dev)) return 0; match = of_match_device(dev->driver->of_match_table, dev); @@ -78,7 +78,7 @@ static void simple_pm_bus_remove(struct platform_device *pdev) { const void *data = of_device_get_match_data(&pdev->dev); - if (pdev->driver_override || data) + if (device_has_driver_override(&pdev->dev) || data) return; dev_dbg(&pdev->dev, "%s\n", __func__); diff --git a/drivers/clk/imx/clk-scu.c b/drivers/clk/imx/clk-scu.c index a85ec48a798b..9b33df9967ec 100644 --- a/drivers/clk/imx/clk-scu.c +++ b/drivers/clk/imx/clk-scu.c @@ -706,8 +706,7 @@ struct clk_hw *imx_clk_scu_alloc_dev(const char *name, if (ret) goto put_device; - ret = driver_set_override(&pdev->dev, &pdev->driver_override, - "imx-scu-clk", strlen("imx-scu-clk")); + ret = device_set_driver_override(&pdev->dev, "imx-scu-clk"); if (ret) goto put_device; diff --git a/drivers/slimbus/qcom-ngd-ctrl.c b/drivers/slimbus/qcom-ngd-ctrl.c index 9aa7218b4e8d..1ed6be6e85d2 100644 --- a/drivers/slimbus/qcom-ngd-ctrl.c +++ b/drivers/slimbus/qcom-ngd-ctrl.c @@ -1535,10 +1535,8 @@ static int of_qcom_slim_ngd_register(struct device *parent, ngd->id = id; ngd->pdev->dev.parent = parent; - ret = driver_set_override(&ngd->pdev->dev, - &ngd->pdev->driver_override, - QCOM_SLIM_NGD_DRV_NAME, - strlen(QCOM_SLIM_NGD_DRV_NAME)); + ret = device_set_driver_override(&ngd->pdev->dev, + QCOM_SLIM_NGD_DRV_NAME); if (ret) { platform_device_put(ngd->pdev); kfree(ngd); diff --git a/include/linux/platform_device.h b/include/linux/platform_device.h index 813da101b5bf..ed1d50d1c3c1 100644 --- a/include/linux/platform_device.h +++ b/include/linux/platform_device.h @@ -31,11 +31,6 @@ struct platform_device { struct resource *resource; const struct platform_device_id *id_entry; - /* - * Driver name to force a match. Do not set directly, because core - * frees it. Use driver_set_override() to set or clear it. - */ - const char *driver_override; /* MFD cell pointer */ struct mfd_cell *mfd_cell; diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c index e9964f0e010a..140907a41a70 100644 --- a/sound/soc/samsung/i2s.c +++ b/sound/soc/samsung/i2s.c @@ -1360,10 +1360,10 @@ static int i2s_create_secondary_device(struct samsung_i2s_priv *priv) if (!pdev_sec) return -ENOMEM; - pdev_sec->driver_override = kstrdup("samsung-i2s", GFP_KERNEL); - if (!pdev_sec->driver_override) { + ret = device_set_driver_override(&pdev_sec->dev, "samsung-i2s"); + if (ret) { platform_device_put(pdev_sec); - return -ENOMEM; + return ret; } ret = platform_device_add(pdev_sec); -- cgit v1.2.3 From d5ad6ab61cbd89afdb60881f6274f74328af3ee9 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Sat, 14 Mar 2026 06:54:55 +0000 Subject: wifi: mac80211: always free skb on ieee80211_tx_prepare_skb() failure ieee80211_tx_prepare_skb() has three error paths, but only two of them free the skb. The first error path (ieee80211_tx_prepare() returning TX_DROP) does not free it, while invoke_tx_handlers() failure and the fragmentation check both do. Add kfree_skb() to the first error path so all three are consistent, and remove the now-redundant frees in callers (ath9k, mt76, mac80211_hwsim) to avoid double-free. Document the skb ownership guarantee in the function's kdoc. Signed-off-by: Felix Fietkau Link: https://patch.msgid.link/20260314065455.2462900-1-nbd@nbd.name Fixes: 06be6b149f7e ("mac80211: add ieee80211_tx_prepare_skb() helper function") Signed-off-by: Johannes Berg --- drivers/net/wireless/ath/ath9k/channel.c | 6 ++---- drivers/net/wireless/mediatek/mt76/scan.c | 4 +--- drivers/net/wireless/virtual/mac80211_hwsim.c | 1 - include/net/mac80211.h | 4 +++- net/mac80211/tx.c | 4 +++- 5 files changed, 9 insertions(+), 10 deletions(-) (limited to 'include') diff --git a/drivers/net/wireless/ath/ath9k/channel.c b/drivers/net/wireless/ath/ath9k/channel.c index 121e51ce1bc0..8b27d8cc086a 100644 --- a/drivers/net/wireless/ath/ath9k/channel.c +++ b/drivers/net/wireless/ath/ath9k/channel.c @@ -1006,7 +1006,7 @@ static void ath_scan_send_probe(struct ath_softc *sc, skb_set_queue_mapping(skb, IEEE80211_AC_VO); if (!ieee80211_tx_prepare_skb(sc->hw, vif, skb, band, NULL)) - goto error; + return; txctl.txq = sc->tx.txq_map[IEEE80211_AC_VO]; if (ath_tx_start(sc->hw, skb, &txctl)) @@ -1119,10 +1119,8 @@ ath_chanctx_send_vif_ps_frame(struct ath_softc *sc, struct ath_vif *avp, skb->priority = 7; skb_set_queue_mapping(skb, IEEE80211_AC_VO); - if (!ieee80211_tx_prepare_skb(sc->hw, vif, skb, band, &sta)) { - dev_kfree_skb_any(skb); + if (!ieee80211_tx_prepare_skb(sc->hw, vif, skb, band, &sta)) return false; - } break; default: return false; diff --git a/drivers/net/wireless/mediatek/mt76/scan.c b/drivers/net/wireless/mediatek/mt76/scan.c index ff9176cdee3d..63b0447e55c1 100644 --- a/drivers/net/wireless/mediatek/mt76/scan.c +++ b/drivers/net/wireless/mediatek/mt76/scan.c @@ -63,10 +63,8 @@ mt76_scan_send_probe(struct mt76_dev *dev, struct cfg80211_ssid *ssid) rcu_read_lock(); - if (!ieee80211_tx_prepare_skb(phy->hw, vif, skb, band, NULL)) { - ieee80211_free_txskb(phy->hw, skb); + if (!ieee80211_tx_prepare_skb(phy->hw, vif, skb, band, NULL)) goto out; - } info = IEEE80211_SKB_CB(skb); if (req->no_cck) diff --git a/drivers/net/wireless/virtual/mac80211_hwsim.c b/drivers/net/wireless/virtual/mac80211_hwsim.c index f6b890dea7e0..1b6e55eb81a2 100644 --- a/drivers/net/wireless/virtual/mac80211_hwsim.c +++ b/drivers/net/wireless/virtual/mac80211_hwsim.c @@ -3021,7 +3021,6 @@ static void hw_scan_work(struct work_struct *work) hwsim->tmp_chan->band, NULL)) { rcu_read_unlock(); - kfree_skb(probe); continue; } diff --git a/include/net/mac80211.h b/include/net/mac80211.h index 7f9d96939a4e..adce2144a678 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -7407,7 +7407,9 @@ void ieee80211_report_wowlan_wakeup(struct ieee80211_vif *vif, * @band: the band to transmit on * @sta: optional pointer to get the station to send the frame to * - * Return: %true if the skb was prepared, %false otherwise + * Return: %true if the skb was prepared, %false otherwise. + * On failure, the skb is freed by this function; callers must not + * free it again. * * Note: must be called under RCU lock */ diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index 8cdbd417d7be..b7aedaab8483 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -1899,8 +1899,10 @@ bool ieee80211_tx_prepare_skb(struct ieee80211_hw *hw, struct ieee80211_tx_data tx; struct sk_buff *skb2; - if (ieee80211_tx_prepare(sdata, &tx, NULL, skb) == TX_DROP) + if (ieee80211_tx_prepare(sdata, &tx, NULL, skb) == TX_DROP) { + kfree_skb(skb); return false; + } info->band = band; info->control.vif = vif; -- cgit v1.2.3 From de9e2b3d88af36411301c049a1b049f3e4fe0757 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 3 Mar 2026 21:24:17 +0200 Subject: uapi: Provide DIV_ROUND_CLOSEST() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently DIV_ROUND_CLOSEST() is only available for the kernel via include/linux/math.h. Expose it to userland as well by adding __KERNEL_DIV_ROUND_CLOSEST() as a common definition in uapi. Additionally, ensure it allows building ISO C applications by switching from the 'typeof' GNU extension to the ISO-friendly __typeof__. Reviewed-by: Nícolas F. R. A. Prado Tested-by: Diederik de Haas Acked-by: Andy Shevchenko Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Cristian Ciocaltea Link: https://patch.msgid.link/20260303-rk3588-bgcolor-v8-1-fee377037ad1@collabora.com Signed-off-by: Daniel Stone --- include/linux/math.h | 18 +----------------- include/uapi/linux/const.h | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 17 deletions(-) (limited to 'include') diff --git a/include/linux/math.h b/include/linux/math.h index 6dc1d1d32fbc..1e8fb3efbc8c 100644 --- a/include/linux/math.h +++ b/include/linux/math.h @@ -89,23 +89,7 @@ } \ ) -/* - * Divide positive or negative dividend by positive or negative divisor - * and round to closest integer. Result is undefined for negative - * divisors if the dividend variable type is unsigned and for negative - * dividends if the divisor variable type is unsigned. - */ -#define DIV_ROUND_CLOSEST(x, divisor)( \ -{ \ - typeof(x) __x = x; \ - typeof(divisor) __d = divisor; \ - (((typeof(x))-1) > 0 || \ - ((typeof(divisor))-1) > 0 || \ - (((__x) > 0) == ((__d) > 0))) ? \ - (((__x) + ((__d) / 2)) / (__d)) : \ - (((__x) - ((__d) / 2)) / (__d)); \ -} \ -) +#define DIV_ROUND_CLOSEST __KERNEL_DIV_ROUND_CLOSEST /* * Same as above but for u64 dividends. divisor must be a 32-bit * number. diff --git a/include/uapi/linux/const.h b/include/uapi/linux/const.h index b8f629ef135f..565f309b9df8 100644 --- a/include/uapi/linux/const.h +++ b/include/uapi/linux/const.h @@ -50,4 +50,22 @@ #define __KERNEL_DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) +/* + * Divide positive or negative dividend by positive or negative divisor + * and round to closest integer. Result is undefined for negative + * divisors if the dividend variable type is unsigned and for negative + * dividends if the divisor variable type is unsigned. + */ +#define __KERNEL_DIV_ROUND_CLOSEST(x, divisor) \ +({ \ + __typeof__(x) __x = x; \ + __typeof__(divisor) __d = divisor; \ + \ + (((__typeof__(x))-1) > 0 || \ + ((__typeof__(divisor))-1) > 0 || \ + (((__x) > 0) == ((__d) > 0))) ? \ + (((__x) + ((__d) / 2)) / (__d)) : \ + (((__x) - ((__d) / 2)) / (__d)); \ +}) + #endif /* _UAPI_LINUX_CONST_H */ -- cgit v1.2.3 From 4c684596cde44d03dfd9624c86e1de4db0dcf121 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 3 Mar 2026 21:24:18 +0200 Subject: drm: Add CRTC background color property MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some display controllers can be hardware programmed to show non-black colors for pixels that are either not covered by any plane or are exposed through transparent regions of higher planes. This feature can help reduce memory bandwidth usage, e.g. in compositors managing a UI with a solid background color while using smaller planes to render the remaining content. To support this capability, introduce the BACKGROUND_COLOR standard DRM mode property, which can be attached to a CRTC through the drm_crtc_attach_background_color_property() helper function. Additionally, define a 64-bit ARGB format value to be built with the help of a couple of dedicated DRM_ARGB64_PREP*() helpers. Individual color components can be extracted with desired precision using the corresponding DRM_ARGB64_GET*() macros. Co-developed-by: Matt Roper Signed-off-by: Matt Roper Reviewed-by: Nícolas F. R. A. Prado Tested-by: Diederik de Haas Signed-off-by: Cristian Ciocaltea Link: https://patch.msgid.link/20260303-rk3588-bgcolor-v8-2-fee377037ad1@collabora.com Signed-off-by: Daniel Stone --- drivers/gpu/drm/drm_atomic.c | 1 + drivers/gpu/drm/drm_atomic_state_helper.c | 1 + drivers/gpu/drm/drm_atomic_uapi.c | 4 ++ drivers/gpu/drm/drm_blend.c | 39 +++++++++++++-- drivers/gpu/drm/drm_mode_config.c | 6 +++ include/drm/drm_blend.h | 4 +- include/drm/drm_crtc.h | 12 +++++ include/drm/drm_mode_config.h | 5 ++ include/uapi/drm/drm_mode.h | 80 +++++++++++++++++++++++++++++++ 9 files changed, 147 insertions(+), 5 deletions(-) (limited to 'include') diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c index dd9f27cfe991..6a395e5e3885 100644 --- a/drivers/gpu/drm/drm_atomic.c +++ b/drivers/gpu/drm/drm_atomic.c @@ -475,6 +475,7 @@ static void drm_atomic_crtc_print_state(struct drm_printer *p, drm_printf(p, "\tconnector_mask=%x\n", state->connector_mask); drm_printf(p, "\tencoder_mask=%x\n", state->encoder_mask); drm_printf(p, "\tmode: " DRM_MODE_FMT "\n", DRM_MODE_ARG(&state->mode)); + drm_printf(p, "\tbackground_color=%llx\n", state->background_color); if (crtc->funcs->atomic_print_state) crtc->funcs->atomic_print_state(p, state); diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c index bd6faa09f83b..76746ad4a1bb 100644 --- a/drivers/gpu/drm/drm_atomic_state_helper.c +++ b/drivers/gpu/drm/drm_atomic_state_helper.c @@ -75,6 +75,7 @@ __drm_atomic_helper_crtc_state_reset(struct drm_crtc_state *crtc_state, struct drm_crtc *crtc) { crtc_state->crtc = crtc; + crtc_state->background_color = DRM_ARGB64_PREP(0xffff, 0, 0, 0); } EXPORT_SYMBOL(__drm_atomic_helper_crtc_state_reset); diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c index 87de41fb4459..5bd5bf6661df 100644 --- a/drivers/gpu/drm/drm_atomic_uapi.c +++ b/drivers/gpu/drm/drm_atomic_uapi.c @@ -454,6 +454,8 @@ static int drm_atomic_crtc_set_property(struct drm_crtc *crtc, &replaced); state->color_mgmt_changed |= replaced; return ret; + } else if (property == config->background_color_property) { + state->background_color = val; } else if (property == config->prop_out_fence_ptr) { s32 __user *fence_ptr = u64_to_user_ptr(val); @@ -501,6 +503,8 @@ drm_atomic_crtc_get_property(struct drm_crtc *crtc, *val = (state->ctm) ? state->ctm->base.id : 0; else if (property == config->gamma_lut_property) *val = (state->gamma_lut) ? state->gamma_lut->base.id : 0; + else if (property == config->background_color_property) + *val = state->background_color; else if (property == config->prop_out_fence_ptr) *val = 0; else if (property == crtc->scaling_filter_property) diff --git a/drivers/gpu/drm/drm_blend.c b/drivers/gpu/drm/drm_blend.c index 3b1f5f72885e..1f3af27d2418 100644 --- a/drivers/gpu/drm/drm_blend.c +++ b/drivers/gpu/drm/drm_blend.c @@ -191,10 +191,6 @@ * plane does not expose the "alpha" property, then this is * assumed to be 1.0 * - * Note that all the property extensions described here apply either to the - * plane or the CRTC (e.g. for the background color, which currently is not - * exposed and assumed to be black). - * * SCALING_FILTER: * Indicates scaling filter to be used for plane scaler * @@ -207,6 +203,25 @@ * * Drivers can set up this property for a plane by calling * drm_plane_create_scaling_filter_property + * + * The property extensions described above all apply to the plane. Drivers + * may also expose the following crtc property extension: + * + * BACKGROUND_COLOR: + * Background color is set up with drm_crtc_attach_background_color_property(), + * and expects a 64-bit ARGB value following DRM_FORMAT_ARGB16161616, as + * generated by the DRM_ARGB64_PREP*() helpers. It controls the color of a + * full-screen layer that exists below all planes. This color will be used + * for pixels not covered by any plane and may also be blended with plane + * contents as allowed by a plane's alpha values. + * The background color defaults to black, and is assumed to be black for + * drivers that do not expose this property. Although background color + * isn't a plane, it is assumed that the color provided here undergoes the + * CRTC degamma/CSC/gamma transformations applied after the planes blending. + * Note that the color value includes an alpha channel, hence non-opaque + * background color values are allowed, but since physically transparent + * monitors do not (yet) exists, the final alpha value may not reach the + * video sink or it may simply ignore it. */ /** @@ -621,3 +636,19 @@ int drm_plane_create_blend_mode_property(struct drm_plane *plane, return 0; } EXPORT_SYMBOL(drm_plane_create_blend_mode_property); + +/** + * drm_crtc_attach_background_color_property - attach background color property + * @crtc: drm crtc + * + * Attaches the background color property to @crtc. The property defaults to + * solid black and will accept 64-bit ARGB values in the format generated by + * DRM_ARGB64_PREP*() helpers. + */ +void drm_crtc_attach_background_color_property(struct drm_crtc *crtc) +{ + drm_object_attach_property(&crtc->base, + crtc->dev->mode_config.background_color_property, + DRM_ARGB64_PREP(0xffff, 0, 0, 0)); +} +EXPORT_SYMBOL(drm_crtc_attach_background_color_property); diff --git a/drivers/gpu/drm/drm_mode_config.c b/drivers/gpu/drm/drm_mode_config.c index 84ae8a23a367..66f7dc37b597 100644 --- a/drivers/gpu/drm/drm_mode_config.c +++ b/drivers/gpu/drm/drm_mode_config.c @@ -380,6 +380,12 @@ static int drm_mode_create_standard_properties(struct drm_device *dev) return -ENOMEM; dev->mode_config.gamma_lut_size_property = prop; + prop = drm_property_create_range(dev, 0, + "BACKGROUND_COLOR", 0, U64_MAX); + if (!prop) + return -ENOMEM; + dev->mode_config.background_color_property = prop; + prop = drm_property_create(dev, DRM_MODE_PROP_IMMUTABLE | DRM_MODE_PROP_BLOB, "IN_FORMATS", 0); diff --git a/include/drm/drm_blend.h b/include/drm/drm_blend.h index 88bdfec3bd88..c7e888767c81 100644 --- a/include/drm/drm_blend.h +++ b/include/drm/drm_blend.h @@ -31,8 +31,9 @@ #define DRM_MODE_BLEND_COVERAGE 1 #define DRM_MODE_BLEND_PIXEL_NONE 2 -struct drm_device; struct drm_atomic_state; +struct drm_crtc; +struct drm_device; struct drm_plane; static inline bool drm_rotation_90_or_270(unsigned int rotation) @@ -58,4 +59,5 @@ int drm_atomic_normalize_zpos(struct drm_device *dev, struct drm_atomic_state *state); int drm_plane_create_blend_mode_property(struct drm_plane *plane, unsigned int supported_modes); +void drm_crtc_attach_background_color_property(struct drm_crtc *crtc); #endif diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index 66278ffeebd6..312fc1e745d2 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -274,6 +274,18 @@ struct drm_crtc_state { */ struct drm_property_blob *gamma_lut; + /** + * @background_color: + * + * RGB value representing the CRTC's background color. The background + * color (aka "canvas color") of a CRTC is the color that will be used + * for pixels not covered by a plane, or covered by transparent pixels + * of a plane. The value here should be built using DRM_ARGB64_PREP*() + * helpers, while the individual color components can be extracted with + * desired precision via the DRM_ARGB64_GET*() macros. + */ + u64 background_color; + /** * @target_vblank: * diff --git a/include/drm/drm_mode_config.h b/include/drm/drm_mode_config.h index 5e1dd0cfccde..687c0ee163d2 100644 --- a/include/drm/drm_mode_config.h +++ b/include/drm/drm_mode_config.h @@ -836,6 +836,11 @@ struct drm_mode_config { * gamma LUT as supported by the driver (read-only). */ struct drm_property *gamma_lut_size_property; + /** + * @background_color_property: Optional CRTC property to set the + * background color. + */ + struct drm_property *background_color_property; /** * @suggested_x_property: Optional connector property with a hint for diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h index 3693d82b5279..a4bdc4bd11bc 100644 --- a/include/uapi/drm/drm_mode.h +++ b/include/uapi/drm/drm_mode.h @@ -27,6 +27,9 @@ #ifndef _DRM_MODE_H #define _DRM_MODE_H +#include +#include + #include "drm.h" #if defined(__cplusplus) @@ -1549,6 +1552,83 @@ struct drm_mode_closefb { __u32 pad; }; +/* + * Put 16-bit ARGB values into a standard 64-bit representation that can be + * used for ioctl parameters, inter-driver communication, etc. + * + * If the component values being provided contain less than 16 bits of + * precision, use a conversion ratio to get a better color approximation. + * The ratio is computed as (2^16 - 1) / (2^bpc - 1), where bpc and 16 are + * the input and output precision, respectively. + * Also note bpc must be greater than 0. + */ +#define __DRM_ARGB64_PREP(c, shift) \ + (((__u64)(c) & __GENMASK(15, 0)) << (shift)) + +#define __DRM_ARGB64_PREP_BPC(c, shift, bpc) \ +({ \ + __u16 mask = __GENMASK((bpc) - 1, 0); \ + __u16 conv = __KERNEL_DIV_ROUND_CLOSEST((mask & (c)) * \ + __GENMASK(15, 0), mask);\ + __DRM_ARGB64_PREP(conv, shift); \ +}) + +#define DRM_ARGB64_PREP(alpha, red, green, blue) \ +( \ + __DRM_ARGB64_PREP(alpha, 48) | \ + __DRM_ARGB64_PREP(red, 32) | \ + __DRM_ARGB64_PREP(green, 16) | \ + __DRM_ARGB64_PREP(blue, 0) \ +) + +#define DRM_ARGB64_PREP_BPC(alpha, red, green, blue, bpc) \ +({ \ + __typeof__(bpc) __bpc = bpc; \ + __DRM_ARGB64_PREP_BPC(alpha, 48, __bpc) | \ + __DRM_ARGB64_PREP_BPC(red, 32, __bpc) | \ + __DRM_ARGB64_PREP_BPC(green, 16, __bpc) | \ + __DRM_ARGB64_PREP_BPC(blue, 0, __bpc); \ +}) + +/* + * Extract the specified color component from a standard 64-bit ARGB value. + * + * If the requested precision is less than 16 bits, make use of a conversion + * ratio calculated as (2^bpc - 1) / (2^16 - 1), where bpc and 16 are the + * output and input precision, respectively. + * + * If speed is more important than accuracy, use DRM_ARGB64_GET*_BPCS() + * instead of DRM_ARGB64_GET*_BPC() in order to replace the expensive + * division with a simple bit right-shift operation. + */ +#define __DRM_ARGB64_GET(c, shift) \ + ((__u16)(((__u64)(c) >> (shift)) & __GENMASK(15, 0))) + +#define __DRM_ARGB64_GET_BPC(c, shift, bpc) \ +({ \ + __u16 comp = __DRM_ARGB64_GET(c, shift); \ + __KERNEL_DIV_ROUND_CLOSEST(comp * __GENMASK((bpc) - 1, 0), \ + __GENMASK(15, 0)); \ +}) + +#define __DRM_ARGB64_GET_BPCS(c, shift, bpc) \ + (__DRM_ARGB64_GET(c, shift) >> (16 - (bpc))) + +#define DRM_ARGB64_GETA(c) __DRM_ARGB64_GET(c, 48) +#define DRM_ARGB64_GETR(c) __DRM_ARGB64_GET(c, 32) +#define DRM_ARGB64_GETG(c) __DRM_ARGB64_GET(c, 16) +#define DRM_ARGB64_GETB(c) __DRM_ARGB64_GET(c, 0) + +#define DRM_ARGB64_GETA_BPC(c, bpc) __DRM_ARGB64_GET_BPC(c, 48, bpc) +#define DRM_ARGB64_GETR_BPC(c, bpc) __DRM_ARGB64_GET_BPC(c, 32, bpc) +#define DRM_ARGB64_GETG_BPC(c, bpc) __DRM_ARGB64_GET_BPC(c, 16, bpc) +#define DRM_ARGB64_GETB_BPC(c, bpc) __DRM_ARGB64_GET_BPC(c, 0, bpc) + +#define DRM_ARGB64_GETA_BPCS(c, bpc) __DRM_ARGB64_GET_BPCS(c, 48, bpc) +#define DRM_ARGB64_GETR_BPCS(c, bpc) __DRM_ARGB64_GET_BPCS(c, 32, bpc) +#define DRM_ARGB64_GETG_BPCS(c, bpc) __DRM_ARGB64_GET_BPCS(c, 16, bpc) +#define DRM_ARGB64_GETB_BPCS(c, bpc) __DRM_ARGB64_GET_BPCS(c, 0, bpc) + #if defined(__cplusplus) } #endif -- cgit v1.2.3 From b3a6df291fecf5f8a308953b65ca72b7fc9e015d Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Mon, 16 Mar 2026 18:02:41 -0700 Subject: udp_tunnel: fix NULL deref caused by udp_sock_create6 when CONFIG_IPV6=n When CONFIG_IPV6 is disabled, the udp_sock_create6() function returns 0 (success) without actually creating a socket. Callers such as fou_create() then proceed to dereference the uninitialized socket pointer, resulting in a NULL pointer dereference. The captured NULL deref crash: BUG: kernel NULL pointer dereference, address: 0000000000000018 RIP: 0010:fou_nl_add_doit (net/ipv4/fou_core.c:590 net/ipv4/fou_core.c:764) [...] Call Trace: genl_family_rcv_msg_doit.constprop.0 (net/netlink/genetlink.c:1114) genl_rcv_msg (net/netlink/genetlink.c:1194 net/netlink/genetlink.c:1209) [...] netlink_rcv_skb (net/netlink/af_netlink.c:2550) genl_rcv (net/netlink/genetlink.c:1219) netlink_unicast (net/netlink/af_netlink.c:1319 net/netlink/af_netlink.c:1344) netlink_sendmsg (net/netlink/af_netlink.c:1894) __sock_sendmsg (net/socket.c:727 (discriminator 1) net/socket.c:742 (discriminator 1)) __sys_sendto (./include/linux/file.h:62 (discriminator 1) ./include/linux/file.h:83 (discriminator 1) net/socket.c:2183 (discriminator 1)) __x64_sys_sendto (net/socket.c:2213 (discriminator 1) net/socket.c:2209 (discriminator 1) net/socket.c:2209 (discriminator 1)) do_syscall_64 (arch/x86/entry/syscall_64.c:63 (discriminator 1) arch/x86/entry/syscall_64.c:94 (discriminator 1)) entry_SYSCALL_64_after_hwframe (net/arch/x86/entry/entry_64.S:130) This patch makes udp_sock_create6 return -EPFNOSUPPORT instead, so callers correctly take their error paths. There is only one caller of the vulnerable function and only privileged users can trigger it. Fixes: fd384412e199b ("udp_tunnel: Seperate ipv6 functions into its own file.") Reported-by: Weiming Shi Signed-off-by: Xiang Mei Link: https://patch.msgid.link/20260317010241.1893893-1-xmei5@asu.edu Signed-off-by: Jakub Kicinski --- include/net/udp_tunnel.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include') diff --git a/include/net/udp_tunnel.h b/include/net/udp_tunnel.h index d9c6d04bb3b5..fc1fc43345b5 100644 --- a/include/net/udp_tunnel.h +++ b/include/net/udp_tunnel.h @@ -52,7 +52,7 @@ int udp_sock_create6(struct net *net, struct udp_port_cfg *cfg, static inline int udp_sock_create6(struct net *net, struct udp_port_cfg *cfg, struct socket **sockp) { - return 0; + return -EPFNOSUPPORT; } #endif -- cgit v1.2.3 From 761fb8ec8778f0caf2bba5a41e3cff1ea86974f3 Mon Sep 17 00:00:00 2001 From: Luiz Augusto von Dentz Date: Tue, 17 Mar 2026 11:54:01 -0400 Subject: Bluetooth: L2CAP: Fix regressions caused by reusing ident This attempt to fix regressions caused by reusing ident which apparently is not handled well on certain stacks causing the stack to not respond to requests, so instead of simple returning the first unallocated id this stores the last used tx_ident and then attempt to use the next until all available ids are exausted and then cycle starting over to 1. Link: https://bugzilla.kernel.org/show_bug.cgi?id=221120 Link: https://bugzilla.kernel.org/show_bug.cgi?id=221177 Fixes: 6c3ea155e5ee ("Bluetooth: L2CAP: Fix not tracking outstanding TX ident") Signed-off-by: Luiz Augusto von Dentz Tested-by: Christian Eggers --- include/net/bluetooth/l2cap.h | 1 + net/bluetooth/l2cap_core.c | 29 ++++++++++++++++++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h index 010f1a8fd15f..5172afee5494 100644 --- a/include/net/bluetooth/l2cap.h +++ b/include/net/bluetooth/l2cap.h @@ -658,6 +658,7 @@ struct l2cap_conn { struct sk_buff *rx_skb; __u32 rx_len; struct ida tx_ida; + __u8 tx_ident; struct sk_buff_head pending_rx; struct work_struct pending_rx_work; diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 30fd6848938e..3de3e3c8e966 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -926,16 +926,39 @@ int l2cap_chan_check_security(struct l2cap_chan *chan, bool initiator) static int l2cap_get_ident(struct l2cap_conn *conn) { + u8 max; + int ident; + /* LE link does not support tools like l2ping so use the full range */ if (conn->hcon->type == LE_LINK) - return ida_alloc_range(&conn->tx_ida, 1, 255, GFP_ATOMIC); - + max = 255; /* Get next available identificator. * 1 - 128 are used by kernel. * 129 - 199 are reserved. * 200 - 254 are used by utilities like l2ping, etc. */ - return ida_alloc_range(&conn->tx_ida, 1, 128, GFP_ATOMIC); + else + max = 128; + + /* Allocate ident using min as last used + 1 (cyclic) */ + ident = ida_alloc_range(&conn->tx_ida, READ_ONCE(conn->tx_ident) + 1, + max, GFP_ATOMIC); + /* Force min 1 to start over */ + if (ident <= 0) { + ident = ida_alloc_range(&conn->tx_ida, 1, max, GFP_ATOMIC); + if (ident <= 0) { + /* If all idents are in use, log an error, this is + * extremely unlikely to happen and would indicate a bug + * in the code that idents are not being freed properly. + */ + BT_ERR("Unable to allocate ident: %d", ident); + return 0; + } + } + + WRITE_ONCE(conn->tx_ident, ident); + + return ident; } static void l2cap_send_acl(struct l2cap_conn *conn, struct sk_buff *skb, -- cgit v1.2.3 From 418eab7a6f3c002d8e64d6e95ec27118017019af Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Thu, 19 Mar 2026 14:29:20 -0600 Subject: io_uring/kbuf: propagate BUF_MORE through early buffer commit path When io_should_commit() returns true (eg for non-pollable files), buffer commit happens at buffer selection time and sel->buf_list is set to NULL. When __io_put_kbufs() generates CQE flags at completion time, it calls __io_put_kbuf_ring() which finds a NULL buffer_list and hence cannot determine whether the buffer was consumed or not. This means that IORING_CQE_F_BUF_MORE is never set for non-pollable input with incrementally consumed buffers. Likewise for io_buffers_select(), which always commits upfront and discards the return value of io_kbuf_commit(). Add REQ_F_BUF_MORE to store the result of io_kbuf_commit() during early commit. Then __io_put_kbuf_ring() can check this flag and set IORING_F_BUF_MORE accordingy. Reported-by: Martin Michaelis Cc: stable@vger.kernel.org Fixes: ae98dbf43d75 ("io_uring/kbuf: add support for incremental buffer consumption") Link: https://github.com/axboe/liburing/issues/1553 Signed-off-by: Jens Axboe --- include/linux/io_uring_types.h | 3 +++ io_uring/kbuf.c | 10 +++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/include/linux/io_uring_types.h b/include/linux/io_uring_types.h index dd1420bfcb73..214fdbd49052 100644 --- a/include/linux/io_uring_types.h +++ b/include/linux/io_uring_types.h @@ -541,6 +541,7 @@ enum { REQ_F_BL_NO_RECYCLE_BIT, REQ_F_BUFFERS_COMMIT_BIT, REQ_F_BUF_NODE_BIT, + REQ_F_BUF_MORE_BIT, REQ_F_HAS_METADATA_BIT, REQ_F_IMPORT_BUFFER_BIT, REQ_F_SQE_COPIED_BIT, @@ -626,6 +627,8 @@ enum { REQ_F_BUFFERS_COMMIT = IO_REQ_FLAG(REQ_F_BUFFERS_COMMIT_BIT), /* buf node is valid */ REQ_F_BUF_NODE = IO_REQ_FLAG(REQ_F_BUF_NODE_BIT), + /* incremental buffer consumption, more space available */ + REQ_F_BUF_MORE = IO_REQ_FLAG(REQ_F_BUF_MORE_BIT), /* request has read/write metadata assigned */ REQ_F_HAS_METADATA = IO_REQ_FLAG(REQ_F_HAS_METADATA_BIT), /* diff --git a/io_uring/kbuf.c b/io_uring/kbuf.c index a4cb6752b7aa..f72f38d22d2b 100644 --- a/io_uring/kbuf.c +++ b/io_uring/kbuf.c @@ -216,7 +216,8 @@ static struct io_br_sel io_ring_buffer_select(struct io_kiocb *req, size_t *len, sel.addr = u64_to_user_ptr(READ_ONCE(buf->addr)); if (io_should_commit(req, issue_flags)) { - io_kbuf_commit(req, sel.buf_list, *len, 1); + if (!io_kbuf_commit(req, sel.buf_list, *len, 1)) + req->flags |= REQ_F_BUF_MORE; sel.buf_list = NULL; } return sel; @@ -349,7 +350,8 @@ int io_buffers_select(struct io_kiocb *req, struct buf_sel_arg *arg, */ if (ret > 0) { req->flags |= REQ_F_BUFFERS_COMMIT | REQ_F_BL_NO_RECYCLE; - io_kbuf_commit(req, sel->buf_list, arg->out_len, ret); + if (!io_kbuf_commit(req, sel->buf_list, arg->out_len, ret)) + req->flags |= REQ_F_BUF_MORE; } } else { ret = io_provided_buffers_select(req, &arg->out_len, sel->buf_list, arg->iovs); @@ -395,8 +397,10 @@ static inline bool __io_put_kbuf_ring(struct io_kiocb *req, if (bl) ret = io_kbuf_commit(req, bl, len, nr); + if (ret && (req->flags & REQ_F_BUF_MORE)) + ret = false; - req->flags &= ~REQ_F_BUFFER_RING; + req->flags &= ~(REQ_F_BUFFER_RING | REQ_F_BUF_MORE); return ret; } -- cgit v1.2.3 From 3590a52f0d0903e600dd01e2cf30820c404beca4 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 24 Feb 2026 17:10:29 +0100 Subject: drm/atomic: Remove state argument to drm_atomic_private_obj_init MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now that all drm_private_objs users have been converted to use atomic_create_state instead of the old ad-hoc initialization, we can remove the state parameter from drm_private_obj_init and the fallback code. Reviewed-by: Dmitry Baryshkov Reviewed-by: Tomi Valkeinen Reviewed-by: Liviu Dudau Reviewed-by: Maíra Canal Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/20260224-drm-private-obj-reset-v5-4-5a72f8ec9934@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 1 - .../drm/arm/display/komeda/komeda_private_obj.c | 16 ++++++++-------- drivers/gpu/drm/display/drm_dp_mst_topology.c | 1 - drivers/gpu/drm/display/drm_dp_tunnel.c | 2 +- drivers/gpu/drm/drm_atomic.c | 22 +++++----------------- drivers/gpu/drm/drm_bridge.c | 1 - drivers/gpu/drm/ingenic/ingenic-drm-drv.c | 2 +- drivers/gpu/drm/ingenic/ingenic-ipu.c | 2 +- drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c | 1 - drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c | 1 - drivers/gpu/drm/omapdrm/omap_drv.c | 2 +- drivers/gpu/drm/tegra/hub.c | 2 +- drivers/gpu/drm/vc4/vc4_kms.c | 4 +--- include/drm/drm_atomic.h | 1 - 14 files changed, 19 insertions(+), 39 deletions(-) (limited to 'include') 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 d44701cbde6d..fc5e0bf121d2 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -4896,7 +4896,6 @@ static int amdgpu_dm_mode_config_init(struct amdgpu_device *adev) drm_atomic_private_obj_init(adev_to_drm(adev), &adev->dm.atomic_obj, - NULL, &dm_atomic_state_funcs); r = amdgpu_display_modeset_create_props(adev); diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_private_obj.c b/drivers/gpu/drm/arm/display/komeda/komeda_private_obj.c index 49b934c6dbdf..77b3f361091f 100644 --- a/drivers/gpu/drm/arm/display/komeda/komeda_private_obj.c +++ b/drivers/gpu/drm/arm/display/komeda/komeda_private_obj.c @@ -65,7 +65,7 @@ static const struct drm_private_state_funcs komeda_layer_obj_funcs = { static int komeda_layer_obj_add(struct komeda_kms_dev *kms, struct komeda_layer *layer) { - drm_atomic_private_obj_init(&kms->base, &layer->base.obj, NULL, + drm_atomic_private_obj_init(&kms->base, &layer->base.obj, &komeda_layer_obj_funcs); return 0; } @@ -118,7 +118,7 @@ static int komeda_scaler_obj_add(struct komeda_kms_dev *kms, struct komeda_scaler *scaler) { drm_atomic_private_obj_init(&kms->base, - &scaler->base.obj, NULL, + &scaler->base.obj, &komeda_scaler_obj_funcs); return 0; } @@ -170,7 +170,7 @@ static const struct drm_private_state_funcs komeda_compiz_obj_funcs = { static int komeda_compiz_obj_add(struct komeda_kms_dev *kms, struct komeda_compiz *compiz) { - drm_atomic_private_obj_init(&kms->base, &compiz->base.obj, NULL, + drm_atomic_private_obj_init(&kms->base, &compiz->base.obj, &komeda_compiz_obj_funcs); return 0; @@ -224,7 +224,7 @@ static int komeda_splitter_obj_add(struct komeda_kms_dev *kms, struct komeda_splitter *splitter) { drm_atomic_private_obj_init(&kms->base, - &splitter->base.obj, NULL, + &splitter->base.obj, &komeda_splitter_obj_funcs); return 0; @@ -277,7 +277,7 @@ static int komeda_merger_obj_add(struct komeda_kms_dev *kms, struct komeda_merger *merger) { drm_atomic_private_obj_init(&kms->base, - &merger->base.obj, NULL, + &merger->base.obj, &komeda_merger_obj_funcs); return 0; @@ -330,7 +330,7 @@ static const struct drm_private_state_funcs komeda_improc_obj_funcs = { static int komeda_improc_obj_add(struct komeda_kms_dev *kms, struct komeda_improc *improc) { - drm_atomic_private_obj_init(&kms->base, &improc->base.obj, NULL, + drm_atomic_private_obj_init(&kms->base, &improc->base.obj, &komeda_improc_obj_funcs); return 0; @@ -383,7 +383,7 @@ static const struct drm_private_state_funcs komeda_timing_ctrlr_obj_funcs = { static int komeda_timing_ctrlr_obj_add(struct komeda_kms_dev *kms, struct komeda_timing_ctrlr *ctrlr) { - drm_atomic_private_obj_init(&kms->base, &ctrlr->base.obj, NULL, + drm_atomic_private_obj_init(&kms->base, &ctrlr->base.obj, &komeda_timing_ctrlr_obj_funcs); return 0; @@ -437,7 +437,7 @@ static const struct drm_private_state_funcs komeda_pipeline_obj_funcs = { static int komeda_pipeline_obj_add(struct komeda_kms_dev *kms, struct komeda_pipeline *pipe) { - drm_atomic_private_obj_init(&kms->base, &pipe->obj, NULL, + drm_atomic_private_obj_init(&kms->base, &pipe->obj, &komeda_pipeline_obj_funcs); return 0; diff --git a/drivers/gpu/drm/display/drm_dp_mst_topology.c b/drivers/gpu/drm/display/drm_dp_mst_topology.c index d8a732f21d3c..8757972e8e24 100644 --- a/drivers/gpu/drm/display/drm_dp_mst_topology.c +++ b/drivers/gpu/drm/display/drm_dp_mst_topology.c @@ -5765,7 +5765,6 @@ int drm_dp_mst_topology_mgr_init(struct drm_dp_mst_topology_mgr *mgr, mgr->conn_base_id = conn_base_id; drm_atomic_private_obj_init(dev, &mgr->base, - NULL, &drm_dp_mst_topology_state_funcs); return 0; diff --git a/drivers/gpu/drm/display/drm_dp_tunnel.c b/drivers/gpu/drm/display/drm_dp_tunnel.c index f442430d8de7..6519b4244728 100644 --- a/drivers/gpu/drm/display/drm_dp_tunnel.c +++ b/drivers/gpu/drm/display/drm_dp_tunnel.c @@ -1600,7 +1600,7 @@ static bool init_group(struct drm_dp_tunnel_mgr *mgr, struct drm_dp_tunnel_group group->available_bw = -1; INIT_LIST_HEAD(&group->tunnels); - drm_atomic_private_obj_init(mgr->dev, &group->base, NULL, + drm_atomic_private_obj_init(mgr->dev, &group->base, &tunnel_group_funcs); return true; diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c index 6a395e5e3885..41c57063f3b4 100644 --- a/drivers/gpu/drm/drm_atomic.c +++ b/drivers/gpu/drm/drm_atomic.c @@ -921,7 +921,6 @@ static void drm_atomic_plane_print_state(struct drm_printer *p, * drm_atomic_private_obj_init - initialize private object * @dev: DRM device this object will be attached to * @obj: private object - * @state: initial private object state * @funcs: pointer to the struct of function pointers that identify the object * type * @@ -933,9 +932,9 @@ static void drm_atomic_plane_print_state(struct drm_printer *p, */ int drm_atomic_private_obj_init(struct drm_device *dev, struct drm_private_obj *obj, - struct drm_private_state *state, const struct drm_private_state_funcs *funcs) { + struct drm_private_state *state; memset(obj, 0, sizeof(*obj)); drm_modeset_lock_init(&obj->lock); @@ -944,22 +943,11 @@ int drm_atomic_private_obj_init(struct drm_device *dev, obj->funcs = funcs; list_add_tail(&obj->head, &dev->mode_config.privobj_list); - /* - * Not all users of drm_atomic_private_obj_init have been - * converted to using &drm_private_obj_funcs.atomic_create_state yet. - * For the time being, let's only call reset if the passed state is - * NULL. Otherwise, we will fallback to the previous behaviour. - */ - if (!state) { - state = obj->funcs->atomic_create_state(obj); - if (IS_ERR(state)) - return PTR_ERR(state); + state = obj->funcs->atomic_create_state(obj); + if (IS_ERR(state)) + return PTR_ERR(state); - obj->state = state; - } else { - obj->state = state; - state->obj = obj; - } + obj->state = state; return 0; } diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index f8b0333a0a3b..1868d512ffa1 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -553,7 +553,6 @@ int drm_bridge_attach(struct drm_encoder *encoder, struct drm_bridge *bridge, if (drm_bridge_is_atomic(bridge)) drm_atomic_private_obj_init(bridge->dev, &bridge->base, - NULL, &drm_bridge_priv_state_funcs); return 0; diff --git a/drivers/gpu/drm/ingenic/ingenic-drm-drv.c b/drivers/gpu/drm/ingenic/ingenic-drm-drv.c index 9522a2e6ecd4..4068114adf8c 100644 --- a/drivers/gpu/drm/ingenic/ingenic-drm-drv.c +++ b/drivers/gpu/drm/ingenic/ingenic-drm-drv.c @@ -1401,7 +1401,7 @@ static int ingenic_drm_bind(struct device *dev, bool has_components) goto err_devclk_disable; } - drm_atomic_private_obj_init(drm, &priv->private_obj, NULL, + drm_atomic_private_obj_init(drm, &priv->private_obj, &ingenic_drm_private_state_funcs); ret = drmm_add_action_or_reset(drm, ingenic_drm_atomic_private_obj_fini, diff --git a/drivers/gpu/drm/ingenic/ingenic-ipu.c b/drivers/gpu/drm/ingenic/ingenic-ipu.c index 4fec37c63e7c..34545b9c8c33 100644 --- a/drivers/gpu/drm/ingenic/ingenic-ipu.c +++ b/drivers/gpu/drm/ingenic/ingenic-ipu.c @@ -901,7 +901,7 @@ static int ingenic_ipu_bind(struct device *dev, struct device *master, void *d) return err; } - drm_atomic_private_obj_init(drm, &ipu->private_obj, NULL, + drm_atomic_private_obj_init(drm, &ipu->private_obj, &ingenic_ipu_private_state_funcs); return 0; diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c index 449552513997..31743699d05f 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c @@ -1161,7 +1161,6 @@ static int dpu_kms_hw_init(struct msm_kms *kms) dev->mode_config.cursor_height = 512; drm_atomic_private_obj_init(dpu_kms->dev, &dpu_kms->global_state, - NULL, &dpu_kms_global_state_funcs); atomic_set(&dpu_kms->bandwidth_ref, 0); diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c index 1e3dc9bf9494..2d26b07b06f5 100644 --- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c @@ -717,7 +717,6 @@ static int mdp5_init(struct platform_device *pdev, struct drm_device *dev) mdp5_kms->dev = dev; drm_atomic_private_obj_init(mdp5_kms->dev, &mdp5_kms->glob_state, - NULL, &mdp5_global_state_funcs); /* we need to set a default rate before enabling. Set a safe diff --git a/drivers/gpu/drm/omapdrm/omap_drv.c b/drivers/gpu/drm/omapdrm/omap_drv.c index 90832b4b8c9d..ae678696fbac 100644 --- a/drivers/gpu/drm/omapdrm/omap_drv.c +++ b/drivers/gpu/drm/omapdrm/omap_drv.c @@ -299,7 +299,7 @@ static int omap_global_obj_init(struct drm_device *dev) { struct omap_drm_private *priv = dev->dev_private; - drm_atomic_private_obj_init(dev, &priv->glob_obj, NULL, + drm_atomic_private_obj_init(dev, &priv->glob_obj, &omap_global_state_funcs); return 0; } diff --git a/drivers/gpu/drm/tegra/hub.c b/drivers/gpu/drm/tegra/hub.c index 73190a4b4d05..10d993b8d043 100644 --- a/drivers/gpu/drm/tegra/hub.c +++ b/drivers/gpu/drm/tegra/hub.c @@ -957,7 +957,7 @@ static int tegra_display_hub_init(struct host1x_client *client) struct drm_device *drm = dev_get_drvdata(client->host); struct tegra_drm *tegra = drm->dev_private; - drm_atomic_private_obj_init(drm, &hub->base, NULL, + drm_atomic_private_obj_init(drm, &hub->base, &tegra_display_hub_state_funcs); tegra->hub = hub; diff --git a/drivers/gpu/drm/vc4/vc4_kms.c b/drivers/gpu/drm/vc4/vc4_kms.c index 0507f24adcdd..264b5e80c24d 100644 --- a/drivers/gpu/drm/vc4/vc4_kms.c +++ b/drivers/gpu/drm/vc4/vc4_kms.c @@ -116,7 +116,7 @@ static int vc4_ctm_obj_init(struct vc4_dev *vc4) { drm_modeset_lock_init(&vc4->ctm_state_lock); - drm_atomic_private_obj_init(&vc4->base, &vc4->ctm_manager, NULL, + drm_atomic_private_obj_init(&vc4->base, &vc4->ctm_manager, &vc4_ctm_state_funcs); return drmm_add_action_or_reset(&vc4->base, vc4_ctm_obj_fini, NULL); @@ -757,7 +757,6 @@ static void vc4_load_tracker_obj_fini(struct drm_device *dev, void *unused) static int vc4_load_tracker_obj_init(struct vc4_dev *vc4) { drm_atomic_private_obj_init(&vc4->base, &vc4->load_tracker, - NULL, &vc4_load_tracker_state_funcs); return drmm_add_action_or_reset(&vc4->base, vc4_load_tracker_obj_fini, NULL); @@ -849,7 +848,6 @@ static void vc4_hvs_channels_obj_fini(struct drm_device *dev, void *unused) static int vc4_hvs_channels_obj_init(struct vc4_dev *vc4) { drm_atomic_private_obj_init(&vc4->base, &vc4->hvs_channels, - NULL, &vc4_hvs_state_funcs); return drmm_add_action_or_reset(&vc4->base, vc4_hvs_channels_obj_fini, NULL); diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h index 0b1b32bcd2bd..f03cd199aee7 100644 --- a/include/drm/drm_atomic.h +++ b/include/drm/drm_atomic.h @@ -738,7 +738,6 @@ drm_atomic_get_connector_state(struct drm_atomic_state *state, int drm_atomic_private_obj_init(struct drm_device *dev, struct drm_private_obj *obj, - struct drm_private_state *state, const struct drm_private_state_funcs *funcs); void drm_atomic_private_obj_fini(struct drm_private_obj *obj); -- cgit v1.2.3 From c6135f67aa37a4a744869f726d706bda091e6dfa Mon Sep 17 00:00:00 2001 From: Thomas Hellström Date: Tue, 17 Mar 2026 15:18:55 +0100 Subject: drm/ttm: Avoid invoking the OOM killer when reading back swapped content MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In situations where the system is very short on RAM, the shmem readback from swap-space may invoke the OOM killer. However, since this might be a recoverable situation where the caller is indicating this by setting struct ttm_operation_ctx::gfp_retry_mayfail to true, adjust the gfp value used by the allocation accordingly. Signed-off-by: Thomas Hellström Reviewed-by: Maarten Lankhorst Acked-by: Christian König Link: https://patch.msgid.link/20260317141856.237876-3-thomas.hellstrom@linux.intel.com --- drivers/gpu/drm/ttm/ttm_backup.c | 6 ++++-- drivers/gpu/drm/ttm/ttm_pool.c | 5 ++++- include/drm/ttm/ttm_backup.h | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/drivers/gpu/drm/ttm/ttm_backup.c b/drivers/gpu/drm/ttm/ttm_backup.c index 6bd4c123d94c..81df4cb5606b 100644 --- a/drivers/gpu/drm/ttm/ttm_backup.c +++ b/drivers/gpu/drm/ttm/ttm_backup.c @@ -44,18 +44,20 @@ void ttm_backup_drop(struct file *backup, pgoff_t handle) * @dst: The struct page to copy into. * @handle: The handle returned when the page was backed up. * @intr: Try to perform waits interruptible or at least killable. + * @additional_gfp: GFP mask to add to the default GFP mask if any. * * Return: 0 on success, Negative error code on failure, notably * -EINTR if @intr was set to true and a signal is pending. */ int ttm_backup_copy_page(struct file *backup, struct page *dst, - pgoff_t handle, bool intr) + pgoff_t handle, bool intr, gfp_t additional_gfp) { struct address_space *mapping = backup->f_mapping; struct folio *from_folio; pgoff_t idx = ttm_backup_handle_to_shmem_idx(handle); - from_folio = shmem_read_folio(mapping, idx); + from_folio = shmem_read_folio_gfp(mapping, idx, mapping_gfp_mask(mapping) + | additional_gfp); if (IS_ERR(from_folio)) return PTR_ERR(from_folio); diff --git a/drivers/gpu/drm/ttm/ttm_pool.c b/drivers/gpu/drm/ttm/ttm_pool.c index 8fa9e09f6ee5..aa41099c5ecf 100644 --- a/drivers/gpu/drm/ttm/ttm_pool.c +++ b/drivers/gpu/drm/ttm/ttm_pool.c @@ -530,6 +530,8 @@ static int ttm_pool_restore_commit(struct ttm_pool_tt_restore *restore, p = first_page[i]; if (ttm_backup_page_ptr_is_handle(p)) { unsigned long handle = ttm_backup_page_ptr_to_handle(p); + gfp_t additional_gfp = ctx->gfp_retry_mayfail ? + __GFP_RETRY_MAYFAIL | __GFP_NOWARN : 0; if (IS_ENABLED(CONFIG_FAULT_INJECTION) && ctx->interruptible && should_fail(&backup_fault_inject, 1)) { @@ -543,7 +545,8 @@ static int ttm_pool_restore_commit(struct ttm_pool_tt_restore *restore, } ret = ttm_backup_copy_page(backup, restore->alloced_page + i, - handle, ctx->interruptible); + handle, ctx->interruptible, + additional_gfp); if (ret) break; diff --git a/include/drm/ttm/ttm_backup.h b/include/drm/ttm/ttm_backup.h index c33cba111171..29b9c855af77 100644 --- a/include/drm/ttm/ttm_backup.h +++ b/include/drm/ttm/ttm_backup.h @@ -56,7 +56,7 @@ ttm_backup_page_ptr_to_handle(const struct page *page) void ttm_backup_drop(struct file *backup, pgoff_t handle); int ttm_backup_copy_page(struct file *backup, struct page *dst, - pgoff_t handle, bool intr); + pgoff_t handle, bool intr, gfp_t additional_gfp); s64 ttm_backup_backup_page(struct file *backup, struct page *page, -- cgit v1.2.3 From 5f1d250a8fc4a4975b692e30229ad93a20c3778a Mon Sep 17 00:00:00 2001 From: Thomas Hellström Date: Tue, 17 Mar 2026 15:18:56 +0100 Subject: drm/ttm: Update the struct ttm_operation_ctx kerneldoc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update the kerneldoc with a more elaborate description of some members, including the gfp_retry_mayfail member. Use inline kerneldoc. Suggested-by: Simona Vetter Signed-off-by: Thomas Hellström Reviewed-by: Maarten Lankhorst Acked-by: Christian König Link: https://patch.msgid.link/20260317141856.237876-4-thomas.hellstrom@linux.intel.com --- include/drm/ttm/ttm_bo.h | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) (limited to 'include') diff --git a/include/drm/ttm/ttm_bo.h b/include/drm/ttm/ttm_bo.h index bca3a8849d47..8310bc3d55f9 100644 --- a/include/drm/ttm/ttm_bo.h +++ b/include/drm/ttm/ttm_bo.h @@ -167,24 +167,34 @@ struct ttm_bo_kmap_obj { /** * struct ttm_operation_ctx * - * @interruptible: Sleep interruptible if sleeping. - * @no_wait_gpu: Return immediately if the GPU is busy. - * @gfp_retry_mayfail: Set the __GFP_RETRY_MAYFAIL when allocation pages. - * @allow_res_evict: Allow eviction of reserved BOs. Can be used when multiple - * BOs share the same reservation object. - * faults. Should only be used by TTM internally. - * @resv: Reservation object to allow reserved evictions with. - * @bytes_moved: Statistics on how many bytes have been moved. - * * Context for TTM operations like changing buffer placement or general memory * allocation. */ struct ttm_operation_ctx { + /** @interruptible: Sleep interruptible if sleeping. */ bool interruptible; + /** @no_wait_gpu: Return immediately if the GPU is busy. */ bool no_wait_gpu; + /** + * @gfp_retry_mayfail: Use __GFP_RETRY_MAYFAIL | __GFP_NOWARN + * when allocation pages. This is to avoid invoking the OOM + * killer when populating a buffer object, in order to + * forward the error for it to be dealt with. + */ bool gfp_retry_mayfail; + /** + * @allow_res_evict: Allow eviction of reserved BOs. Can be used + * when multiple BOs share the same reservation object @resv. + */ bool allow_res_evict; + /** + * @resv: Reservation object to be used together with + * @allow_res_evict. + */ struct dma_resv *resv; + /** + * @bytes_moved: Statistics on how many bytes have been moved. + */ uint64_t bytes_moved; }; -- cgit v1.2.3 From 8f3c83720555ffa96799896f2a0bb985a03a89f4 Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Tue, 10 Mar 2026 13:13:23 +0100 Subject: drm/bridge: add drm_bridge_clear_and_put() Drivers having a struct drm_bridge pointer pointing to a bridge in many cases hold that reference until the owning device is removed. In those cases the reference to the bridge can be put in the .remove callback (possibly using devm actions) or in the .destroy func (possibly with the help of struct drm_bridge::next_bridge). At those moments the driver should not be operating anymore and won't dereference the bridge pointer after it is put. However there are cases when drivers need to stop holding a reference to a bridge even when their device is not being removed. This is the case for bridge hot-unplug, when a bridge is removed but the previous entity (bridge or encoder) is staying. In such case the "previous entity" needs to put it but cannot do it via devm or .destroy, because it is not being removed. The easy way to dispose of such pointer is: drm_bridge_put(my_priv->some_bridge); my_priv->some_bridge = NULL; However this is risky because there is a time window between the two lines where the reference is put, and thus the bridge could be deallocated, but the pointer is still assigned. If other functions of the same driver were invoked concurrently they might dereference my_priv->some_bridge during that window, resulting in use-after-free. A correct solution is to clear the pointer before putting the reference, but that needs a temporary variable: struct drm_bridge *temp = my_priv->some_bridge; my_priv->some_bridge = NULL; drm_bridge_put(temp); This solution is however annoying to write, so the incorrect version might still sneak in. Add a simple, easy to use function to put a bridge after setting its pointer to NULL in the correct way. Acked-by: Maxime Ripard Link: https://patch.msgid.link/20260310-drm-bridge-atomic-vs-remove-clear_and_put-v2-1-51fe222f3cf0@bootlin.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/drm_bridge.c | 34 ++++++++++++++++++++++++++++++++++ include/drm/drm_bridge.h | 1 + 2 files changed, 35 insertions(+) (limited to 'include') diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index 1868d512ffa1..30d957675d87 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -304,6 +304,9 @@ EXPORT_SYMBOL(drm_bridge_get); * * This function decrements the bridge's reference count and frees the * object if the reference count drops to zero. + * + * See also drm_bridge_clear_and_put() if you also need to set the pointer + * to NULL */ void drm_bridge_put(struct drm_bridge *bridge) { @@ -312,6 +315,37 @@ void drm_bridge_put(struct drm_bridge *bridge) } EXPORT_SYMBOL(drm_bridge_put); +/** + * drm_bridge_clear_and_put - Given a bridge pointer, clear the pointer + * then put the bridge + * @bridge_pp: pointer to pointer to a struct drm_bridge; ``bridge_pp`` + * must be non-NULL; if ``*bridge_pp`` is NULL this function + * does nothing + * + * Helper to put a DRM bridge, but only after setting its pointer to + * NULL. Useful when a struct drm_bridge reference must be dropped without + * leaving a use-after-free window where the pointed bridge might have been + * freed while still holding a pointer to it. + * + * For struct ``drm_bridge *some_bridge``, this code:: + * + * drm_bridge_clear_and_put(&some_bridge); + * + * is equivalent to the more complex:: + * + * struct drm_bridge *temp = some_bridge; + * some_bridge = NULL; + * drm_bridge_put(temp); + */ +void drm_bridge_clear_and_put(struct drm_bridge **bridge_pp) +{ + struct drm_bridge *bridge = *bridge_pp; + + *bridge_pp = NULL; + drm_bridge_put(bridge); +} +EXPORT_SYMBOL(drm_bridge_clear_and_put); + /** * drm_bridge_put_void - wrapper to drm_bridge_put() taking a void pointer * diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h index 4f19f7064ee3..66ab89cf48aa 100644 --- a/include/drm/drm_bridge.h +++ b/include/drm/drm_bridge.h @@ -1290,6 +1290,7 @@ void drm_bridge_unplug(struct drm_bridge *bridge); struct drm_bridge *drm_bridge_get(struct drm_bridge *bridge); void drm_bridge_put(struct drm_bridge *bridge); +void drm_bridge_clear_and_put(struct drm_bridge **bridge_pp); /* Cleanup action for use with __free() */ DEFINE_FREE(drm_bridge_put, struct drm_bridge *, if (_T) drm_bridge_put(_T)) -- cgit v1.2.3 From 6f45b1604cf43945ef472ae4ef30354025307c19 Mon Sep 17 00:00:00 2001 From: Leon Romanovsky Date: Mon, 16 Mar 2026 21:06:46 +0200 Subject: dma-mapping: handle DMA_ATTR_CPU_CACHE_CLEAN in trace output Tracing prints decoded DMA attribute flags, but it does not yet include the recently added DMA_ATTR_CPU_CACHE_CLEAN. Add support for decoding and displaying this attribute in the trace output. Fixes: 61868dc55a11 ("dma-mapping: add DMA_ATTR_CPU_CACHE_CLEAN") Signed-off-by: Leon Romanovsky Signed-off-by: Marek Szyprowski Link: https://lore.kernel.org/r/20260316-dma-debug-overlap-v3-2-1dde90a7f08b@nvidia.com --- include/trace/events/dma.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/include/trace/events/dma.h b/include/trace/events/dma.h index 33e99e792f1a..69cb3805ee81 100644 --- a/include/trace/events/dma.h +++ b/include/trace/events/dma.h @@ -32,7 +32,8 @@ TRACE_DEFINE_ENUM(DMA_NONE); { DMA_ATTR_ALLOC_SINGLE_PAGES, "ALLOC_SINGLE_PAGES" }, \ { DMA_ATTR_NO_WARN, "NO_WARN" }, \ { DMA_ATTR_PRIVILEGED, "PRIVILEGED" }, \ - { DMA_ATTR_MMIO, "MMIO" }) + { DMA_ATTR_MMIO, "MMIO" }, \ + { DMA_ATTR_CPU_CACHE_CLEAN, "CACHE_CLEAN" }) DECLARE_EVENT_CLASS(dma_map, TP_PROTO(struct device *dev, phys_addr_t phys_addr, dma_addr_t dma_addr, -- cgit v1.2.3 From 9bb0a4d6a4433b75274204b083dac8e515d2007d Mon Sep 17 00:00:00 2001 From: Leon Romanovsky Date: Mon, 16 Mar 2026 21:06:47 +0200 Subject: dma-mapping: Clarify valid conditions for CPU cache line overlap Rename the DMA_ATTR_CPU_CACHE_CLEAN attribute to better reflect that it is debugging aid to inform DMA core code that CPU cache line overlaps are allowed, and refine the documentation describing its use. Signed-off-by: Leon Romanovsky Signed-off-by: Marek Szyprowski Link: https://lore.kernel.org/r/20260316-dma-debug-overlap-v3-3-1dde90a7f08b@nvidia.com --- Documentation/core-api/dma-attributes.rst | 22 ++++++++++++++-------- drivers/virtio/virtio_ring.c | 10 +++++----- include/linux/dma-mapping.h | 8 ++++---- include/trace/events/dma.h | 2 +- kernel/dma/debug.c | 2 +- 5 files changed, 25 insertions(+), 19 deletions(-) (limited to 'include') diff --git a/Documentation/core-api/dma-attributes.rst b/Documentation/core-api/dma-attributes.rst index 1d7bfad73b1c..48cfe86cc06d 100644 --- a/Documentation/core-api/dma-attributes.rst +++ b/Documentation/core-api/dma-attributes.rst @@ -149,11 +149,17 @@ For architectures that require cache flushing for DMA coherence DMA_ATTR_MMIO will not perform any cache flushing. The address provided must never be mapped cacheable into the CPU. -DMA_ATTR_CPU_CACHE_CLEAN ------------------------- - -This attribute indicates the CPU will not dirty any cacheline overlapping this -DMA_FROM_DEVICE/DMA_BIDIRECTIONAL buffer while it is mapped. This allows -multiple small buffers to safely share a cacheline without risk of data -corruption, suppressing DMA debug warnings about overlapping mappings. -All mappings sharing a cacheline should have this attribute. +DMA_ATTR_DEBUGGING_IGNORE_CACHELINES +------------------------------------ + +This attribute indicates that CPU cache lines may overlap for buffers mapped +with DMA_FROM_DEVICE or DMA_BIDIRECTIONAL. + +Such overlap may occur when callers map multiple small buffers that reside +within the same cache line. In this case, callers must guarantee that the CPU +will not dirty these cache lines after the mappings are established. When this +condition is met, multiple buffers can safely share a cache line without risking +data corruption. + +All mappings that share a cache line must set this attribute to suppress DMA +debug warnings about overlapping mappings. diff --git a/drivers/virtio/virtio_ring.c b/drivers/virtio/virtio_ring.c index 335692d41617..fbca7ce1c6bf 100644 --- a/drivers/virtio/virtio_ring.c +++ b/drivers/virtio/virtio_ring.c @@ -2912,10 +2912,10 @@ EXPORT_SYMBOL_GPL(virtqueue_add_inbuf); * @data: the token identifying the buffer. * @gfp: how to do memory allocations (if necessary). * - * Same as virtqueue_add_inbuf but passes DMA_ATTR_CPU_CACHE_CLEAN to indicate - * that the CPU will not dirty any cacheline overlapping this buffer while it - * is available, and to suppress overlapping cacheline warnings in DMA debug - * builds. + * Same as virtqueue_add_inbuf but passes DMA_ATTR_DEBUGGING_IGNORE_CACHELINES + * to indicate that the CPU will not dirty any cacheline overlapping this buffer + * while it is available, and to suppress overlapping cacheline warnings in DMA + * debug builds. * * Caller must ensure we don't call this with other virtqueue operations * at the same time (except where noted). @@ -2928,7 +2928,7 @@ int virtqueue_add_inbuf_cache_clean(struct virtqueue *vq, gfp_t gfp) { return virtqueue_add(vq, &sg, num, 0, 1, data, NULL, false, gfp, - DMA_ATTR_CPU_CACHE_CLEAN); + DMA_ATTR_DEBUGGING_IGNORE_CACHELINES); } EXPORT_SYMBOL_GPL(virtqueue_add_inbuf_cache_clean); diff --git a/include/linux/dma-mapping.h b/include/linux/dma-mapping.h index 29973baa0581..da44394b3a1a 100644 --- a/include/linux/dma-mapping.h +++ b/include/linux/dma-mapping.h @@ -80,11 +80,11 @@ #define DMA_ATTR_MMIO (1UL << 10) /* - * DMA_ATTR_CPU_CACHE_CLEAN: Indicates the CPU will not dirty any cacheline - * overlapping this buffer while it is mapped for DMA. All mappings sharing - * a cacheline must have this attribute for this to be considered safe. + * DMA_ATTR_DEBUGGING_IGNORE_CACHELINES: Indicates the CPU cache line can be + * overlapped. All mappings sharing a cacheline must have this attribute for + * this to be considered safe. */ -#define DMA_ATTR_CPU_CACHE_CLEAN (1UL << 11) +#define DMA_ATTR_DEBUGGING_IGNORE_CACHELINES (1UL << 11) /* * A dma_addr_t can hold any valid DMA or bus address for the platform. It can diff --git a/include/trace/events/dma.h b/include/trace/events/dma.h index 69cb3805ee81..8c64bc0721fe 100644 --- a/include/trace/events/dma.h +++ b/include/trace/events/dma.h @@ -33,7 +33,7 @@ TRACE_DEFINE_ENUM(DMA_NONE); { DMA_ATTR_NO_WARN, "NO_WARN" }, \ { DMA_ATTR_PRIVILEGED, "PRIVILEGED" }, \ { DMA_ATTR_MMIO, "MMIO" }, \ - { DMA_ATTR_CPU_CACHE_CLEAN, "CACHE_CLEAN" }) + { DMA_ATTR_DEBUGGING_IGNORE_CACHELINES, "CACHELINES_OVERLAP" }) DECLARE_EVENT_CLASS(dma_map, TP_PROTO(struct device *dev, phys_addr_t phys_addr, dma_addr_t dma_addr, diff --git a/kernel/dma/debug.c b/kernel/dma/debug.c index be207be74996..83e1cfe05f08 100644 --- a/kernel/dma/debug.c +++ b/kernel/dma/debug.c @@ -601,7 +601,7 @@ static void add_dma_entry(struct dma_debug_entry *entry, unsigned long attrs) unsigned long flags; int rc; - entry->is_cache_clean = !!(attrs & DMA_ATTR_CPU_CACHE_CLEAN); + entry->is_cache_clean = attrs & DMA_ATTR_DEBUGGING_IGNORE_CACHELINES; bucket = get_hash_bucket(entry, &flags); hash_bucket_add(bucket, entry); -- cgit v1.2.3 From e6a58fa2556203a7f6731b4071705dc81cca5ca5 Mon Sep 17 00:00:00 2001 From: Leon Romanovsky Date: Mon, 16 Mar 2026 21:06:48 +0200 Subject: dma-mapping: Introduce DMA require coherency attribute The mapping buffers which carry this attribute require DMA coherent system. This means that they can't take SWIOTLB path, can perform CPU cache overlap and doesn't perform cache flushing. Signed-off-by: Leon Romanovsky Signed-off-by: Marek Szyprowski Link: https://lore.kernel.org/r/20260316-dma-debug-overlap-v3-4-1dde90a7f08b@nvidia.com --- Documentation/core-api/dma-attributes.rst | 16 ++++++++++++++++ include/linux/dma-mapping.h | 7 +++++++ include/trace/events/dma.h | 3 ++- kernel/dma/debug.c | 3 ++- kernel/dma/mapping.c | 6 ++++++ 5 files changed, 33 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/Documentation/core-api/dma-attributes.rst b/Documentation/core-api/dma-attributes.rst index 48cfe86cc06d..123c8468d58f 100644 --- a/Documentation/core-api/dma-attributes.rst +++ b/Documentation/core-api/dma-attributes.rst @@ -163,3 +163,19 @@ data corruption. All mappings that share a cache line must set this attribute to suppress DMA debug warnings about overlapping mappings. + +DMA_ATTR_REQUIRE_COHERENT +------------------------- + +DMA mapping requests with the DMA_ATTR_REQUIRE_COHERENT fail on any +system where SWIOTLB or cache management is required. This should only +be used to support uAPI designs that require continuous HW DMA +coherence with userspace processes, for example RDMA and DRM. At a +minimum the memory being mapped must be userspace memory from +pin_user_pages() or similar. + +Drivers should consider using dma_mmap_pages() instead of this +interface when building their uAPIs, when possible. + +It must never be used in an in-kernel driver that only works with +kernel memory. diff --git a/include/linux/dma-mapping.h b/include/linux/dma-mapping.h index da44394b3a1a..482b919f040f 100644 --- a/include/linux/dma-mapping.h +++ b/include/linux/dma-mapping.h @@ -86,6 +86,13 @@ */ #define DMA_ATTR_DEBUGGING_IGNORE_CACHELINES (1UL << 11) +/* + * DMA_ATTR_REQUIRE_COHERENT: Indicates that DMA coherency is required. + * All mappings that carry this attribute can't work with SWIOTLB and cache + * flushing. + */ +#define DMA_ATTR_REQUIRE_COHERENT (1UL << 12) + /* * A dma_addr_t can hold any valid DMA or bus address for the platform. It can * be given to a device to use as a DMA source or target. It is specific to a diff --git a/include/trace/events/dma.h b/include/trace/events/dma.h index 8c64bc0721fe..63597b004424 100644 --- a/include/trace/events/dma.h +++ b/include/trace/events/dma.h @@ -33,7 +33,8 @@ TRACE_DEFINE_ENUM(DMA_NONE); { DMA_ATTR_NO_WARN, "NO_WARN" }, \ { DMA_ATTR_PRIVILEGED, "PRIVILEGED" }, \ { DMA_ATTR_MMIO, "MMIO" }, \ - { DMA_ATTR_DEBUGGING_IGNORE_CACHELINES, "CACHELINES_OVERLAP" }) + { DMA_ATTR_DEBUGGING_IGNORE_CACHELINES, "CACHELINES_OVERLAP" }, \ + { DMA_ATTR_REQUIRE_COHERENT, "REQUIRE_COHERENT" }) DECLARE_EVENT_CLASS(dma_map, TP_PROTO(struct device *dev, phys_addr_t phys_addr, dma_addr_t dma_addr, diff --git a/kernel/dma/debug.c b/kernel/dma/debug.c index 83e1cfe05f08..0677918f06a8 100644 --- a/kernel/dma/debug.c +++ b/kernel/dma/debug.c @@ -601,7 +601,8 @@ static void add_dma_entry(struct dma_debug_entry *entry, unsigned long attrs) unsigned long flags; int rc; - entry->is_cache_clean = attrs & DMA_ATTR_DEBUGGING_IGNORE_CACHELINES; + entry->is_cache_clean = attrs & (DMA_ATTR_DEBUGGING_IGNORE_CACHELINES | + DMA_ATTR_REQUIRE_COHERENT); bucket = get_hash_bucket(entry, &flags); hash_bucket_add(bucket, entry); diff --git a/kernel/dma/mapping.c b/kernel/dma/mapping.c index 3928a509c44c..6d3dd0bd3a88 100644 --- a/kernel/dma/mapping.c +++ b/kernel/dma/mapping.c @@ -164,6 +164,9 @@ dma_addr_t dma_map_phys(struct device *dev, phys_addr_t phys, size_t size, if (WARN_ON_ONCE(!dev->dma_mask)) return DMA_MAPPING_ERROR; + if (!dev_is_dma_coherent(dev) && (attrs & DMA_ATTR_REQUIRE_COHERENT)) + return DMA_MAPPING_ERROR; + if (dma_map_direct(dev, ops) || (!is_mmio && arch_dma_map_phys_direct(dev, phys + size))) addr = dma_direct_map_phys(dev, phys, size, dir, attrs); @@ -235,6 +238,9 @@ static int __dma_map_sg_attrs(struct device *dev, struct scatterlist *sg, BUG_ON(!valid_dma_direction(dir)); + if (!dev_is_dma_coherent(dev) && (attrs & DMA_ATTR_REQUIRE_COHERENT)) + return -EOPNOTSUPP; + if (WARN_ON_ONCE(!dev->dma_mask)) return 0; -- cgit v1.2.3 From 1613462be621ad5103ec338a7b0ca0746ec4e5f1 Mon Sep 17 00:00:00 2001 From: Juergen Gross Date: Tue, 14 Oct 2025 13:28:15 +0200 Subject: xen/privcmd: add boot control for restricted usage in domU When running in an unprivileged domU under Xen, the privcmd driver is restricted to allow only hypercalls against a target domain, for which the current domU is acting as a device model. Add a boot parameter "unrestricted" to allow all hypercalls (the hypervisor will still refuse destructive hypercalls affecting other guests). Make this new parameter effective only in case the domU wasn't started using secure boot, as otherwise hypercalls targeting the domU itself might result in violating the secure boot functionality. This is achieved by adding another lockdown reason, which can be tested to not being set when applying the "unrestricted" option. This is part of XSA-482 Signed-off-by: Juergen Gross --- V2: - new patch --- drivers/xen/privcmd.c | 13 +++++++++++++ include/linux/security.h | 1 + security/security.c | 1 + 3 files changed, 15 insertions(+) (limited to 'include') diff --git a/drivers/xen/privcmd.c b/drivers/xen/privcmd.c index a83bad69f4f2..bbf9ee21306c 100644 --- a/drivers/xen/privcmd.c +++ b/drivers/xen/privcmd.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -72,6 +73,11 @@ module_param_named(dm_op_buf_max_size, privcmd_dm_op_buf_max_size, uint, MODULE_PARM_DESC(dm_op_buf_max_size, "Maximum size of a dm_op hypercall buffer"); +static bool unrestricted; +module_param(unrestricted, bool, 0); +MODULE_PARM_DESC(unrestricted, + "Don't restrict hypercalls to target domain if running in a domU"); + struct privcmd_data { domid_t domid; }; @@ -1708,6 +1714,13 @@ static struct notifier_block xenstore_notifier = { static void __init restrict_driver(void) { + if (unrestricted) { + if (security_locked_down(LOCKDOWN_XEN_USER_ACTIONS)) + pr_warn("Kernel is locked down, parameter \"unrestricted\" ignored\n"); + else + return; + } + restrict_wait = true; register_xenstore_notifier(&xenstore_notifier); diff --git a/include/linux/security.h b/include/linux/security.h index 83a646d72f6f..ee88dd2d2d1f 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -145,6 +145,7 @@ enum lockdown_reason { LOCKDOWN_BPF_WRITE_USER, LOCKDOWN_DBG_WRITE_KERNEL, LOCKDOWN_RTAS_ERROR_INJECTION, + LOCKDOWN_XEN_USER_ACTIONS, LOCKDOWN_INTEGRITY_MAX, LOCKDOWN_KCORE, LOCKDOWN_KPROBES, diff --git a/security/security.c b/security/security.c index 67af9228c4e9..a26c1474e2e4 100644 --- a/security/security.c +++ b/security/security.c @@ -61,6 +61,7 @@ const char *const lockdown_reasons[LOCKDOWN_CONFIDENTIALITY_MAX + 1] = { [LOCKDOWN_BPF_WRITE_USER] = "use of bpf to write user RAM", [LOCKDOWN_DBG_WRITE_KERNEL] = "use of kgdb/kdb to write kernel RAM", [LOCKDOWN_RTAS_ERROR_INJECTION] = "RTAS error injection", + [LOCKDOWN_XEN_USER_ACTIONS] = "Xen guest user action", [LOCKDOWN_INTEGRITY_MAX] = "integrity", [LOCKDOWN_KCORE] = "/proc/kcore access", [LOCKDOWN_KPROBES] = "use of kprobes", -- cgit v1.2.3 From 76f9377cd2ab7a9220c25d33940d9ca20d368172 Mon Sep 17 00:00:00 2001 From: Joanne Koong Date: Thu, 19 Mar 2026 17:51:45 -0700 Subject: writeback: don't block sync for filesystems with no data integrity guarantees Add a SB_I_NO_DATA_INTEGRITY superblock flag for filesystems that cannot guarantee data persistence on sync (eg fuse). For superblocks with this flag set, sync kicks off writeback of dirty inodes but does not wait for the flusher threads to complete the writeback. This replaces the per-inode AS_NO_DATA_INTEGRITY mapping flag added in commit f9a49aa302a0 ("fs/writeback: skip AS_NO_DATA_INTEGRITY mappings in wait_sb_inodes()"). The flag belongs at the superblock level because data integrity is a filesystem-wide property, not a per-inode one. Having this flag at the superblock level also allows us to skip having to iterate every dirty inode in wait_sb_inodes() only to skip each inode individually. Prior to this commit, mappings with no data integrity guarantees skipped waiting on writeback completion but still waited on the flusher threads to finish initiating the writeback. Waiting on the flusher threads is unnecessary. This commit kicks off writeback but does not wait on the flusher threads. This change properly addresses a recent report [1] for a suspend-to-RAM hang seen on fuse-overlayfs that was caused by waiting on the flusher threads to finish: Workqueue: pm_fs_sync pm_fs_sync_work_fn Call Trace: __schedule+0x457/0x1720 schedule+0x27/0xd0 wb_wait_for_completion+0x97/0xe0 sync_inodes_sb+0xf8/0x2e0 __iterate_supers+0xdc/0x160 ksys_sync+0x43/0xb0 pm_fs_sync_work_fn+0x17/0xa0 process_one_work+0x193/0x350 worker_thread+0x1a1/0x310 kthread+0xfc/0x240 ret_from_fork+0x243/0x280 ret_from_fork_asm+0x1a/0x30 On fuse this is problematic because there are paths that may cause the flusher thread to block (eg if systemd freezes the user session cgroups first, which freezes the fuse daemon, before invoking the kernel suspend. The kernel suspend triggers ->write_node() which on fuse issues a synchronous setattr request, which cannot be processed since the daemon is frozen. Or if the daemon is buggy and cannot properly complete writeback, initiating writeback on a dirty folio already under writeback leads to writeback_get_folio() -> folio_prepare_writeback() -> unconditional wait on writeback to finish, which will cause a hang). This commit restores fuse to its prior behavior before tmp folios were removed, where sync was essentially a no-op. [1] https://lore.kernel.org/linux-fsdevel/CAJnrk1a-asuvfrbKXbEwwDSctvemF+6zfhdnuzO65Pt8HsFSRw@mail.gmail.com/T/#m632c4648e9cafc4239299887109ebd880ac6c5c1 Fixes: 0c58a97f919c ("fuse: remove tmp folio for writebacks and internal rb tree") Reported-by: John Cc: stable@vger.kernel.org Signed-off-by: Joanne Koong Link: https://patch.msgid.link/20260320005145.2483161-2-joannelkoong@gmail.com Reviewed-by: Jan Kara Reviewed-by: David Hildenbrand (Arm) Signed-off-by: Christian Brauner --- fs/fs-writeback.c | 18 ++++++++++++------ fs/fuse/file.c | 4 +--- fs/fuse/inode.c | 1 + include/linux/fs/super_types.h | 1 + include/linux/pagemap.h | 11 ----------- 5 files changed, 15 insertions(+), 20 deletions(-) (limited to 'include') diff --git a/fs/fs-writeback.c b/fs/fs-writeback.c index d8dac1931595..3c75ee025bda 100644 --- a/fs/fs-writeback.c +++ b/fs/fs-writeback.c @@ -2787,13 +2787,8 @@ static void wait_sb_inodes(struct super_block *sb) * The mapping can appear untagged while still on-list since we * do not have the mapping lock. Skip it here, wb completion * will remove it. - * - * If the mapping does not have data integrity semantics, - * there's no need to wait for the writeout to complete, as the - * mapping cannot guarantee that data is persistently stored. */ - if (!mapping_tagged(mapping, PAGECACHE_TAG_WRITEBACK) || - mapping_no_data_integrity(mapping)) + if (!mapping_tagged(mapping, PAGECACHE_TAG_WRITEBACK)) continue; spin_unlock_irq(&sb->s_inode_wblist_lock); @@ -2928,6 +2923,17 @@ void sync_inodes_sb(struct super_block *sb) */ if (bdi == &noop_backing_dev_info) return; + + /* + * If the superblock has SB_I_NO_DATA_INTEGRITY set, there's no need to + * wait for the writeout to complete, as the filesystem cannot guarantee + * data persistence on sync. Just kick off writeback and return. + */ + if (sb->s_iflags & SB_I_NO_DATA_INTEGRITY) { + wakeup_flusher_threads_bdi(bdi, WB_REASON_SYNC); + return; + } + WARN_ON(!rwsem_is_locked(&sb->s_umount)); /* protect against inode wb switch, see inode_switch_wbs_work_fn() */ diff --git a/fs/fuse/file.c b/fs/fuse/file.c index b1bb7153cb78..676fd9856bfb 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -3201,10 +3201,8 @@ void fuse_init_file_inode(struct inode *inode, unsigned int flags) inode->i_fop = &fuse_file_operations; inode->i_data.a_ops = &fuse_file_aops; - if (fc->writeback_cache) { + if (fc->writeback_cache) mapping_set_writeback_may_deadlock_on_reclaim(&inode->i_data); - mapping_set_no_data_integrity(&inode->i_data); - } INIT_LIST_HEAD(&fi->write_files); INIT_LIST_HEAD(&fi->queued_writes); diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index e57b8af06be9..c795abe47a4f 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -1709,6 +1709,7 @@ static void fuse_sb_defaults(struct super_block *sb) sb->s_export_op = &fuse_export_operations; sb->s_iflags |= SB_I_IMA_UNVERIFIABLE_SIGNATURE; sb->s_iflags |= SB_I_NOIDMAP; + sb->s_iflags |= SB_I_NO_DATA_INTEGRITY; if (sb->s_user_ns != &init_user_ns) sb->s_iflags |= SB_I_UNTRUSTED_MOUNTER; sb->s_flags &= ~(SB_NOSEC | SB_I_VERSION); diff --git a/include/linux/fs/super_types.h b/include/linux/fs/super_types.h index fa7638b81246..383050e7fdf5 100644 --- a/include/linux/fs/super_types.h +++ b/include/linux/fs/super_types.h @@ -338,5 +338,6 @@ struct super_block { #define SB_I_NOUMASK 0x00001000 /* VFS does not apply umask */ #define SB_I_NOIDMAP 0x00002000 /* No idmapped mounts on this superblock */ #define SB_I_ALLOW_HSM 0x00004000 /* Allow HSM events on this superblock */ +#define SB_I_NO_DATA_INTEGRITY 0x00008000 /* fs cannot guarantee data persistence on sync */ #endif /* _LINUX_FS_SUPER_TYPES_H */ diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h index ec442af3f886..31a848485ad9 100644 --- a/include/linux/pagemap.h +++ b/include/linux/pagemap.h @@ -210,7 +210,6 @@ enum mapping_flags { AS_WRITEBACK_MAY_DEADLOCK_ON_RECLAIM = 9, AS_KERNEL_FILE = 10, /* mapping for a fake kernel file that shouldn't account usage to user cgroups */ - AS_NO_DATA_INTEGRITY = 11, /* no data integrity guarantees */ /* Bits 16-25 are used for FOLIO_ORDER */ AS_FOLIO_ORDER_BITS = 5, AS_FOLIO_ORDER_MIN = 16, @@ -346,16 +345,6 @@ static inline bool mapping_writeback_may_deadlock_on_reclaim(const struct addres return test_bit(AS_WRITEBACK_MAY_DEADLOCK_ON_RECLAIM, &mapping->flags); } -static inline void mapping_set_no_data_integrity(struct address_space *mapping) -{ - set_bit(AS_NO_DATA_INTEGRITY, &mapping->flags); -} - -static inline bool mapping_no_data_integrity(const struct address_space *mapping) -{ - return test_bit(AS_NO_DATA_INTEGRITY, &mapping->flags); -} - static inline gfp_t mapping_gfp_mask(const struct address_space *mapping) { return mapping->gfp_mask; -- cgit v1.2.3 From d76856beb4a4a6c42244054cd780c00f2d33de4e Mon Sep 17 00:00:00 2001 From: Max Zhen Date: Fri, 20 Mar 2026 14:06:14 -0700 Subject: accel/amdxdna: Refactor GEM BO handling and add helper APIs for address retrieval Refactor amdxdna GEM buffer object (BO) handling to simplify address management and unify BO type semantics. Introduce helper APIs to retrieve commonly used BO addresses: - User virtual address (UVA) - Kernel virtual address (KVA) - Device address (IOVA/PA) These helpers centralize address lookup logic and avoid duplicating BO-specific handling across submission and execution paths. This also improves readability and reduces the risk of inconsistent address handling in future changes. As part of the refactor: - Rename SHMEM BO type to SHARE to better reflect its usage. - Merge CMD BO handling into SHARE, removing special-case logic for command buffers. - Consolidate BO type handling paths to reduce code duplication and simplify maintenance. No functional change is intended. The refactor prepares the driver for future enhancements by providing a cleaner abstraction for BO address management. Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Max Zhen Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20260320210615.1973016-1-lizhi.hou@amd.com --- drivers/accel/amdxdna/aie2_ctx.c | 8 +- drivers/accel/amdxdna/aie2_message.c | 33 +-- drivers/accel/amdxdna/amdxdna_ctx.c | 23 +- drivers/accel/amdxdna/amdxdna_ctx.h | 15 +- drivers/accel/amdxdna/amdxdna_gem.c | 400 +++++++++++++++----------------- drivers/accel/amdxdna/amdxdna_gem.h | 32 +-- drivers/accel/amdxdna/amdxdna_pci_drv.c | 2 +- drivers/accel/amdxdna/amdxdna_ubuf.c | 17 +- drivers/accel/amdxdna/amdxdna_ubuf.h | 5 - include/uapi/drm/amdxdna_accel.h | 9 +- 10 files changed, 266 insertions(+), 278 deletions(-) (limited to 'include') diff --git a/drivers/accel/amdxdna/aie2_ctx.c b/drivers/accel/amdxdna/aie2_ctx.c index 6292349868c5..66dbbfd322a2 100644 --- a/drivers/accel/amdxdna/aie2_ctx.c +++ b/drivers/accel/amdxdna/aie2_ctx.c @@ -79,7 +79,7 @@ static int aie2_hwctx_restart(struct amdxdna_dev *xdna, struct amdxdna_hwctx *hw } ret = aie2_map_host_buf(xdna->dev_handle, hwctx->fw_ctx_id, - amdxdna_obj_dma_addr(hwctx->client, heap), + amdxdna_obj_dma_addr(heap), heap->mem.size); if (ret) { XDNA_ERR(xdna, "Map host buf failed, ret %d", ret); @@ -659,14 +659,14 @@ int aie2_hwctx_init(struct amdxdna_hwctx *hwctx) .size = MAX_CHAIN_CMDBUF_SIZE, }; - abo = amdxdna_drm_alloc_dev_bo(&xdna->ddev, &args, client->filp); + abo = amdxdna_drm_create_dev_bo(&xdna->ddev, &args, client->filp); if (IS_ERR(abo)) { ret = PTR_ERR(abo); goto free_cmd_bufs; } XDNA_DBG(xdna, "Command buf %d addr 0x%llx size 0x%lx", - i, abo->mem.dev_addr, abo->mem.size); + i, amdxdna_gem_dev_addr(abo), abo->mem.size); priv->cmd_buf[i] = abo; } @@ -707,7 +707,7 @@ int aie2_hwctx_init(struct amdxdna_hwctx *hwctx) } ret = aie2_map_host_buf(xdna->dev_handle, hwctx->fw_ctx_id, - amdxdna_obj_dma_addr(hwctx->client, heap), + amdxdna_obj_dma_addr(heap), heap->mem.size); if (ret) { XDNA_ERR(xdna, "Map host buffer failed, ret %d", ret); diff --git a/drivers/accel/amdxdna/aie2_message.c b/drivers/accel/amdxdna/aie2_message.c index 4ec591306854..7e219a5eda56 100644 --- a/drivers/accel/amdxdna/aie2_message.c +++ b/drivers/accel/amdxdna/aie2_message.c @@ -548,10 +548,10 @@ int aie2_config_cu(struct amdxdna_hwctx *hwctx, } req.cfgs[i] = FIELD_PREP(AIE2_MSG_CFG_CU_PDI_ADDR, - abo->mem.dev_addr >> shift); + amdxdna_gem_dev_addr(abo) >> shift); req.cfgs[i] |= FIELD_PREP(AIE2_MSG_CFG_CU_FUNC, cu->cu_func); XDNA_DBG(xdna, "CU %d full addr 0x%llx, cfg 0x%x", i, - abo->mem.dev_addr, req.cfgs[i]); + amdxdna_gem_dev_addr(abo), req.cfgs[i]); drm_gem_object_put(gobj); } req.num_cus = hwctx->cus->num_cus; @@ -998,6 +998,7 @@ int aie2_cmdlist_multi_execbuf(struct amdxdna_hwctx *hwctx, struct mailbox_channel *chann = hwctx->priv->mbox_chann; struct amdxdna_client *client = hwctx->client; struct amdxdna_gem_obj *cmd_abo = job->cmd_bo; + void *cmd_buf = amdxdna_gem_vmap(cmdbuf_abo); struct amdxdna_dev *xdna = client->xdna; struct amdxdna_cmd_chain *payload; struct xdna_mailbox_msg msg; @@ -1009,6 +1010,9 @@ int aie2_cmdlist_multi_execbuf(struct amdxdna_hwctx *hwctx, u32 op; u32 i; + if (!cmd_buf) + return -ENOMEM; + op = amdxdna_cmd_get_op(cmd_abo); payload = amdxdna_cmd_get_payload(cmd_abo, &payload_len); if (op != ERT_CMD_CHAIN) { @@ -1032,15 +1036,14 @@ int aie2_cmdlist_multi_execbuf(struct amdxdna_hwctx *hwctx, u32 boh = (u32)(payload->data[i]); struct amdxdna_gem_obj *abo; - abo = amdxdna_gem_get_obj(client, boh, AMDXDNA_BO_CMD); + abo = amdxdna_gem_get_obj(client, boh, AMDXDNA_BO_SHARE); if (!abo) { XDNA_ERR(xdna, "Failed to find cmd BO %d", boh); return -ENOENT; } size = cmdbuf_abo->mem.size - offset; - ret = aie2_cmdlist_fill_slot(cmdbuf_abo->mem.kva + offset, - abo, &size, &op); + ret = aie2_cmdlist_fill_slot(cmd_buf + offset, abo, &size, &op); amdxdna_gem_put_obj(abo); if (ret) return ret; @@ -1050,16 +1053,16 @@ int aie2_cmdlist_multi_execbuf(struct amdxdna_hwctx *hwctx, XDNA_DBG(xdna, "Total %d commands:", ccnt); print_hex_dump_debug("cmdbufs: ", DUMP_PREFIX_OFFSET, 16, 4, - cmdbuf_abo->mem.kva, offset, false); + cmd_buf, offset, false); msg.opcode = EXEC_MSG_OPS(xdna)->get_chain_msg_op(op); if (msg.opcode == MSG_OP_MAX_OPCODE) return -EOPNOTSUPP; /* The offset is the accumulated total size of the cmd buffer */ - EXEC_MSG_OPS(xdna)->init_chain_req(&req, cmdbuf_abo->mem.dev_addr, + EXEC_MSG_OPS(xdna)->init_chain_req(&req, amdxdna_gem_dev_addr(cmdbuf_abo), offset, ccnt); - drm_clflush_virt_range(cmdbuf_abo->mem.kva, offset); + drm_clflush_virt_range(cmd_buf, offset); msg.handle = job; msg.notify_cb = notify_cb; @@ -1084,27 +1087,29 @@ int aie2_cmdlist_single_execbuf(struct amdxdna_hwctx *hwctx, struct mailbox_channel *chann = hwctx->priv->mbox_chann; struct amdxdna_dev *xdna = hwctx->client->xdna; struct amdxdna_gem_obj *cmd_abo = job->cmd_bo; + void *cmd_buf = amdxdna_gem_vmap(cmdbuf_abo); struct xdna_mailbox_msg msg; union exec_chain_req req; u32 op = ERT_INVALID_CMD; size_t size; int ret; + if (!cmd_buf) + return -ENOMEM; + size = cmdbuf_abo->mem.size; - ret = aie2_cmdlist_fill_slot(cmdbuf_abo->mem.kva, cmd_abo, &size, &op); + ret = aie2_cmdlist_fill_slot(cmd_buf, cmd_abo, &size, &op); if (ret) return ret; - print_hex_dump_debug("cmdbuf: ", DUMP_PREFIX_OFFSET, 16, 4, - cmdbuf_abo->mem.kva, size, false); + print_hex_dump_debug("cmdbuf: ", DUMP_PREFIX_OFFSET, 16, 4, cmd_buf, size, false); msg.opcode = EXEC_MSG_OPS(xdna)->get_chain_msg_op(op); if (msg.opcode == MSG_OP_MAX_OPCODE) return -EOPNOTSUPP; - EXEC_MSG_OPS(xdna)->init_chain_req(&req, cmdbuf_abo->mem.dev_addr, - size, 1); - drm_clflush_virt_range(cmdbuf_abo->mem.kva, size); + EXEC_MSG_OPS(xdna)->init_chain_req(&req, amdxdna_gem_dev_addr(cmdbuf_abo), size, 1); + drm_clflush_virt_range(cmd_buf, size); msg.handle = job; msg.notify_cb = notify_cb; diff --git a/drivers/accel/amdxdna/amdxdna_ctx.c b/drivers/accel/amdxdna/amdxdna_ctx.c index 4b921715176d..55598343b422 100644 --- a/drivers/accel/amdxdna/amdxdna_ctx.c +++ b/drivers/accel/amdxdna/amdxdna_ctx.c @@ -94,9 +94,12 @@ int amdxdna_hwctx_walk(struct amdxdna_client *client, void *arg, void *amdxdna_cmd_get_payload(struct amdxdna_gem_obj *abo, u32 *size) { - struct amdxdna_cmd *cmd = abo->mem.kva; + struct amdxdna_cmd *cmd = amdxdna_gem_vmap(abo); u32 num_masks, count; + if (!cmd) + return NULL; + if (amdxdna_cmd_get_op(abo) == ERT_CMD_CHAIN) num_masks = 0; else @@ -118,10 +121,13 @@ void *amdxdna_cmd_get_payload(struct amdxdna_gem_obj *abo, u32 *size) u32 amdxdna_cmd_get_cu_idx(struct amdxdna_gem_obj *abo) { - struct amdxdna_cmd *cmd = abo->mem.kva; + struct amdxdna_cmd *cmd = amdxdna_gem_vmap(abo); u32 num_masks, i; u32 *cu_mask; + if (!cmd) + return INVALID_CU_IDX; + if (amdxdna_cmd_get_op(abo) == ERT_CMD_CHAIN) return INVALID_CU_IDX; @@ -141,19 +147,24 @@ int amdxdna_cmd_set_error(struct amdxdna_gem_obj *abo, void *err_data, size_t size) { struct amdxdna_client *client = job->hwctx->client; - struct amdxdna_cmd *cmd = abo->mem.kva; + struct amdxdna_cmd *cmd = amdxdna_gem_vmap(abo); struct amdxdna_cmd_chain *cc = NULL; + if (!cmd) + return -ENOMEM; + cmd->header &= ~AMDXDNA_CMD_STATE; cmd->header |= FIELD_PREP(AMDXDNA_CMD_STATE, error_state); if (amdxdna_cmd_get_op(abo) == ERT_CMD_CHAIN) { cc = amdxdna_cmd_get_payload(abo, NULL); cc->error_index = (cmd_idx < cc->command_count) ? cmd_idx : 0; - abo = amdxdna_gem_get_obj(client, cc->data[0], AMDXDNA_BO_CMD); + abo = amdxdna_gem_get_obj(client, cc->data[0], AMDXDNA_BO_SHARE); if (!abo) return -EINVAL; - cmd = abo->mem.kva; + cmd = amdxdna_gem_vmap(abo); + if (!cmd) + return -ENOMEM; } memset(cmd->data, 0xff, abo->mem.size - sizeof(*cmd)); @@ -472,7 +483,7 @@ int amdxdna_cmd_submit(struct amdxdna_client *client, job->drv_cmd = drv_cmd; if (cmd_bo_hdl != AMDXDNA_INVALID_BO_HANDLE) { - job->cmd_bo = amdxdna_gem_get_obj(client, cmd_bo_hdl, AMDXDNA_BO_CMD); + job->cmd_bo = amdxdna_gem_get_obj(client, cmd_bo_hdl, AMDXDNA_BO_SHARE); if (!job->cmd_bo) { XDNA_ERR(xdna, "Failed to get cmd bo from %d", cmd_bo_hdl); ret = -EINVAL; diff --git a/drivers/accel/amdxdna/amdxdna_ctx.h b/drivers/accel/amdxdna/amdxdna_ctx.h index 57db1527a93b..a8557d7e8923 100644 --- a/drivers/accel/amdxdna/amdxdna_ctx.h +++ b/drivers/accel/amdxdna/amdxdna_ctx.h @@ -158,7 +158,10 @@ struct amdxdna_sched_job { static inline u32 amdxdna_cmd_get_op(struct amdxdna_gem_obj *abo) { - struct amdxdna_cmd *cmd = abo->mem.kva; + struct amdxdna_cmd *cmd = amdxdna_gem_vmap(abo); + + if (!cmd) + return ERT_INVALID_CMD; return FIELD_GET(AMDXDNA_CMD_OPCODE, cmd->header); } @@ -166,7 +169,10 @@ amdxdna_cmd_get_op(struct amdxdna_gem_obj *abo) static inline void amdxdna_cmd_set_state(struct amdxdna_gem_obj *abo, enum ert_cmd_state s) { - struct amdxdna_cmd *cmd = abo->mem.kva; + struct amdxdna_cmd *cmd = amdxdna_gem_vmap(abo); + + if (!cmd) + return; cmd->header &= ~AMDXDNA_CMD_STATE; cmd->header |= FIELD_PREP(AMDXDNA_CMD_STATE, s); @@ -175,7 +181,10 @@ amdxdna_cmd_set_state(struct amdxdna_gem_obj *abo, enum ert_cmd_state s) static inline enum ert_cmd_state amdxdna_cmd_get_state(struct amdxdna_gem_obj *abo) { - struct amdxdna_cmd *cmd = abo->mem.kva; + struct amdxdna_cmd *cmd = amdxdna_gem_vmap(abo); + + if (!cmd) + return ERT_CMD_STATE_INVALID; return FIELD_GET(AMDXDNA_CMD_STATE, cmd->header); } diff --git a/drivers/accel/amdxdna/amdxdna_gem.c b/drivers/accel/amdxdna/amdxdna_gem.c index d80cf164740c..27712704e42d 100644 --- a/drivers/accel/amdxdna/amdxdna_gem.c +++ b/drivers/accel/amdxdna/amdxdna_gem.c @@ -30,7 +30,6 @@ amdxdna_gem_heap_alloc(struct amdxdna_gem_obj *abo) struct amdxdna_dev *xdna = client->xdna; struct amdxdna_mem *mem = &abo->mem; struct amdxdna_gem_obj *heap; - u64 offset; u32 align; int ret; @@ -42,7 +41,7 @@ amdxdna_gem_heap_alloc(struct amdxdna_gem_obj *abo) goto unlock_out; } - if (heap->mem.userptr == AMDXDNA_INVALID_ADDR) { + if (amdxdna_gem_uva(heap) == AMDXDNA_INVALID_ADDR) { XDNA_ERR(xdna, "Invalid dev heap userptr"); ret = -EINVAL; goto unlock_out; @@ -64,11 +63,6 @@ amdxdna_gem_heap_alloc(struct amdxdna_gem_obj *abo) goto unlock_out; } - mem->dev_addr = abo->mm_node.start; - offset = mem->dev_addr - heap->mem.dev_addr; - mem->userptr = heap->mem.userptr + offset; - mem->kva = heap->mem.kva + offset; - drm_gem_object_get(to_gobj(heap)); unlock_out: @@ -77,13 +71,6 @@ unlock_out: return ret; } -static void -amdxdna_gem_destroy_obj(struct amdxdna_gem_obj *abo) -{ - mutex_destroy(&abo->lock); - kfree(abo); -} - static void amdxdna_gem_heap_free(struct amdxdna_gem_obj *abo) { @@ -99,6 +86,105 @@ amdxdna_gem_heap_free(struct amdxdna_gem_obj *abo) mutex_unlock(&abo->client->mm_lock); } +static struct amdxdna_gem_obj * +amdxdna_gem_create_obj(struct drm_device *dev, size_t size) +{ + struct amdxdna_gem_obj *abo; + + abo = kzalloc_obj(*abo); + if (!abo) + return ERR_PTR(-ENOMEM); + + abo->pinned = false; + abo->assigned_hwctx = AMDXDNA_INVALID_CTX_HANDLE; + mutex_init(&abo->lock); + + abo->mem.dma_addr = AMDXDNA_INVALID_ADDR; + abo->mem.uva = AMDXDNA_INVALID_ADDR; + abo->mem.size = size; + INIT_LIST_HEAD(&abo->mem.umap_list); + + return abo; +} + +static void +amdxdna_gem_destroy_obj(struct amdxdna_gem_obj *abo) +{ + mutex_destroy(&abo->lock); + kfree(abo); +} + +/* + * Obtains a kernel virtual address on the BO (usually of small size). + * The mapping is established on the first call and stays valid until + * amdxdna_gem_vunmap() is called. + */ +void *amdxdna_gem_vmap(struct amdxdna_gem_obj *abo) +{ + struct iosys_map map = IOSYS_MAP_INIT_VADDR(NULL); + int ret; + + if (abo->mem.kva) + return abo->mem.kva; + + /* The first call to get the kva, taking slow path. */ + guard(mutex)(&abo->lock); + + if (!abo->mem.kva) { + ret = drm_gem_vmap(to_gobj(abo), &map); + if (ret) + XDNA_ERR(abo->client->xdna, "Vmap bo failed, ret %d", ret); + else + abo->mem.kva = map.vaddr; + } + return abo->mem.kva; +} + +/* + * Free mapping established through amdxdna_gem_vmap() + */ +static void amdxdna_gem_vunmap(struct amdxdna_gem_obj *abo) +{ + guard(mutex)(&abo->lock); + + if (abo->mem.kva) { + struct iosys_map map = IOSYS_MAP_INIT_VADDR(abo->mem.kva); + + drm_gem_vunmap(to_gobj(abo), &map); + abo->mem.kva = NULL; + } +} + +/* + * Obtain the user virtual address for accessing the BO. + * It can be used for device to access the BO when PASID is enabled. + */ +u64 amdxdna_gem_uva(struct amdxdna_gem_obj *abo) +{ + if (abo->type == AMDXDNA_BO_DEV) { + struct amdxdna_gem_obj *heap = abo->client->dev_heap; + u64 off = amdxdna_dev_bo_offset(abo); + + if (amdxdna_gem_uva(heap) != AMDXDNA_INVALID_ADDR) + return amdxdna_gem_uva(heap) + off; + return AMDXDNA_INVALID_ADDR; + } + + return abo->mem.uva; +} + +/* + * Obtain the address for device to access the BO. + */ +u64 amdxdna_gem_dev_addr(struct amdxdna_gem_obj *abo) +{ + if (abo->type == AMDXDNA_BO_DEV_HEAP) + return abo->client->xdna->dev_info->dev_mem_base; + if (abo->type == AMDXDNA_BO_DEV) + return abo->mm_node.start; + return amdxdna_obj_dma_addr(abo); +} + static bool amdxdna_hmm_invalidate(struct mmu_interval_notifier *mni, const struct mmu_notifier_range *range, unsigned long cur_seq) @@ -161,16 +247,19 @@ static void amdxdna_hmm_unregister(struct amdxdna_gem_obj *abo, static void amdxdna_umap_release(struct kref *ref) { struct amdxdna_umap *mapp = container_of(ref, struct amdxdna_umap, refcnt); + struct amdxdna_gem_obj *abo = mapp->abo; struct vm_area_struct *vma = mapp->vma; struct amdxdna_dev *xdna; mmu_interval_notifier_remove(&mapp->notifier); - if (is_import_bo(mapp->abo) && vma->vm_file && vma->vm_file->f_mapping) + if (is_import_bo(abo) && vma->vm_file && vma->vm_file->f_mapping) mapping_clear_unevictable(vma->vm_file->f_mapping); xdna = to_xdna_dev(to_gobj(mapp->abo)->dev); down_write(&xdna->notifier_lock); list_del(&mapp->node); + if (list_empty(&abo->mem.umap_list)) + abo->mem.uva = AMDXDNA_INVALID_ADDR; up_write(&xdna->notifier_lock); kvfree(mapp->range.hmm_pfns); @@ -232,13 +321,13 @@ static int amdxdna_hmm_register(struct amdxdna_gem_obj *abo, mapp->abo = abo; kref_init(&mapp->refcnt); - if (abo->mem.userptr == AMDXDNA_INVALID_ADDR) - abo->mem.userptr = addr; INIT_WORK(&mapp->hmm_unreg_work, amdxdna_hmm_unreg_work); if (is_import_bo(abo) && vma->vm_file && vma->vm_file->f_mapping) mapping_set_unevictable(vma->vm_file->f_mapping); down_write(&xdna->notifier_lock); + if (list_empty(&abo->mem.umap_list)) + abo->mem.uva = addr; list_add_tail(&mapp->node, &abo->mem.umap_list); up_write(&xdna->notifier_lock); @@ -256,10 +345,11 @@ static void amdxdna_gem_dev_obj_free(struct drm_gem_object *gobj) struct amdxdna_dev *xdna = to_xdna_dev(gobj->dev); struct amdxdna_gem_obj *abo = to_xdna_obj(gobj); - XDNA_DBG(xdna, "BO type %d xdna_addr 0x%llx", abo->type, abo->mem.dev_addr); + XDNA_DBG(xdna, "BO type %d xdna_addr 0x%llx", abo->type, amdxdna_gem_dev_addr(abo)); if (abo->pinned) amdxdna_gem_unpin(abo); + amdxdna_gem_vunmap(abo); amdxdna_gem_heap_free(abo); drm_gem_object_release(gobj); amdxdna_gem_destroy_obj(abo); @@ -390,35 +480,6 @@ static const struct dma_buf_ops amdxdna_dmabuf_ops = { .vunmap = drm_gem_dmabuf_vunmap, }; -static int amdxdna_gem_obj_vmap(struct amdxdna_gem_obj *abo, void **vaddr) -{ - struct iosys_map map = IOSYS_MAP_INIT_VADDR(NULL); - int ret; - - if (is_import_bo(abo)) - ret = dma_buf_vmap_unlocked(abo->dma_buf, &map); - else - ret = drm_gem_vmap(to_gobj(abo), &map); - - *vaddr = map.vaddr; - return ret; -} - -static void amdxdna_gem_obj_vunmap(struct amdxdna_gem_obj *abo) -{ - struct iosys_map map; - - if (!abo->mem.kva) - return; - - iosys_map_set_vaddr(&map, abo->mem.kva); - - if (is_import_bo(abo)) - dma_buf_vunmap_unlocked(abo->dma_buf, &map); - else - drm_gem_vunmap(to_gobj(abo), &map); -} - static struct dma_buf *amdxdna_gem_prime_export(struct drm_gem_object *gobj, int flags) { struct amdxdna_gem_obj *abo = to_xdna_obj(gobj); @@ -452,7 +513,7 @@ static void amdxdna_gem_obj_free(struct drm_gem_object *gobj) struct amdxdna_dev *xdna = to_xdna_dev(gobj->dev); struct amdxdna_gem_obj *abo = to_xdna_obj(gobj); - XDNA_DBG(xdna, "BO type %d xdna_addr 0x%llx", abo->type, abo->mem.dev_addr); + XDNA_DBG(xdna, "BO type %d xdna_addr 0x%llx", abo->type, amdxdna_gem_dev_addr(abo)); amdxdna_hmm_unregister(abo, NULL); flush_workqueue(xdna->notifier_wq); @@ -463,15 +524,16 @@ static void amdxdna_gem_obj_free(struct drm_gem_object *gobj) if (abo->type == AMDXDNA_BO_DEV_HEAP) drm_mm_takedown(&abo->mm); - amdxdna_gem_obj_vunmap(abo); + if (amdxdna_iova_on(xdna)) + amdxdna_iommu_unmap_bo(xdna, abo); + + amdxdna_gem_vunmap(abo); mutex_destroy(&abo->lock); - if (is_import_bo(abo)) { + if (is_import_bo(abo)) amdxdna_imported_obj_free(abo); - return; - } - - drm_gem_shmem_free(&abo->base); + else + drm_gem_shmem_free(&abo->base); } static int amdxdna_gem_obj_open(struct drm_gem_object *gobj, struct drm_file *filp) @@ -481,43 +543,38 @@ static int amdxdna_gem_obj_open(struct drm_gem_object *gobj, struct drm_file *fi int ret; guard(mutex)(&abo->lock); - if (abo->ref) { - abo->ref++; - return 0; - } + if (!abo->client) + abo->client = filp->driver_priv; if (amdxdna_iova_on(xdna)) { ret = amdxdna_iommu_map_bo(xdna, abo); if (ret) return ret; } - abo->ref++; return 0; } -static void amdxdna_gem_obj_close(struct drm_gem_object *gobj, struct drm_file *filp) +static int amdxdna_gem_dev_obj_vmap(struct drm_gem_object *obj, struct iosys_map *map) { - struct amdxdna_dev *xdna = to_xdna_dev(gobj->dev); - struct amdxdna_gem_obj *abo = to_xdna_obj(gobj); + struct amdxdna_gem_obj *abo = to_xdna_obj(obj); + void *base = amdxdna_gem_vmap(abo->client->dev_heap); + u64 offset = amdxdna_dev_bo_offset(abo); - guard(mutex)(&abo->lock); - abo->ref--; - if (abo->ref) - return; - - if (amdxdna_iova_on(xdna)) - amdxdna_iommu_unmap_bo(xdna, abo); + if (!base) + return -ENOMEM; + iosys_map_set_vaddr(map, base + offset); + return 0; } static const struct drm_gem_object_funcs amdxdna_gem_dev_obj_funcs = { .free = amdxdna_gem_dev_obj_free, + .vmap = amdxdna_gem_dev_obj_vmap, }; static const struct drm_gem_object_funcs amdxdna_gem_shmem_funcs = { .free = amdxdna_gem_obj_free, .open = amdxdna_gem_obj_open, - .close = amdxdna_gem_obj_close, .print_info = drm_gem_shmem_object_print_info, .pin = drm_gem_shmem_object_pin, .unpin = drm_gem_shmem_object_unpin, @@ -529,31 +586,9 @@ static const struct drm_gem_object_funcs amdxdna_gem_shmem_funcs = { .export = amdxdna_gem_prime_export, }; -static struct amdxdna_gem_obj * -amdxdna_gem_create_obj(struct drm_device *dev, size_t size) -{ - struct amdxdna_gem_obj *abo; - - abo = kzalloc_obj(*abo); - if (!abo) - return ERR_PTR(-ENOMEM); - - abo->pinned = false; - abo->assigned_hwctx = AMDXDNA_INVALID_CTX_HANDLE; - mutex_init(&abo->lock); - - abo->mem.userptr = AMDXDNA_INVALID_ADDR; - abo->mem.dev_addr = AMDXDNA_INVALID_ADDR; - abo->mem.dma_addr = AMDXDNA_INVALID_ADDR; - abo->mem.size = size; - INIT_LIST_HEAD(&abo->mem.umap_list); - - return abo; -} - /* For drm_driver->gem_create_object callback */ struct drm_gem_object * -amdxdna_gem_create_object_cb(struct drm_device *dev, size_t size) +amdxdna_gem_create_shmem_object_cb(struct drm_device *dev, size_t size) { struct amdxdna_gem_obj *abo; @@ -567,8 +602,9 @@ amdxdna_gem_create_object_cb(struct drm_device *dev, size_t size) } static struct amdxdna_gem_obj * -amdxdna_gem_create_shmem_object(struct drm_device *dev, size_t size) +amdxdna_gem_create_shmem_object(struct drm_device *dev, struct amdxdna_drm_create_bo *args) { + size_t size = args->size; struct drm_gem_shmem_object *shmem = drm_gem_shmem_create(dev, size); if (IS_ERR(shmem)) @@ -582,7 +618,6 @@ static struct amdxdna_gem_obj * amdxdna_gem_create_ubuf_object(struct drm_device *dev, struct amdxdna_drm_create_bo *args) { struct amdxdna_dev *xdna = to_xdna_dev(dev); - enum amdxdna_ubuf_flag flags = 0; struct amdxdna_drm_va_tbl va_tbl; struct drm_gem_object *gobj; struct dma_buf *dma_buf; @@ -593,10 +628,7 @@ amdxdna_gem_create_ubuf_object(struct drm_device *dev, struct amdxdna_drm_create } if (va_tbl.num_entries) { - if (args->type == AMDXDNA_BO_CMD) - flags |= AMDXDNA_UBUF_FLAG_MAP_DMA; - - dma_buf = amdxdna_get_ubuf(dev, flags, va_tbl.num_entries, + dma_buf = amdxdna_get_ubuf(dev, va_tbl.num_entries, u64_to_user_ptr(args->vaddr + sizeof(va_tbl))); } else { dma_buf = dma_buf_get(va_tbl.dmabuf_fd); @@ -616,18 +648,6 @@ amdxdna_gem_create_ubuf_object(struct drm_device *dev, struct amdxdna_drm_create return to_xdna_obj(gobj); } -static struct amdxdna_gem_obj * -amdxdna_gem_create_object(struct drm_device *dev, - struct amdxdna_drm_create_bo *args) -{ - size_t aligned_sz = PAGE_ALIGN(args->size); - - if (args->vaddr) - return amdxdna_gem_create_ubuf_object(dev, args); - - return amdxdna_gem_create_shmem_object(dev, aligned_sz); -} - struct drm_gem_object * amdxdna_gem_prime_import(struct drm_device *dev, struct dma_buf *dma_buf) { @@ -660,7 +680,8 @@ amdxdna_gem_prime_import(struct drm_device *dev, struct dma_buf *dma_buf) abo = to_xdna_obj(gobj); abo->attach = attach; abo->dma_buf = dma_buf; - abo->type = AMDXDNA_BO_SHMEM; + abo->type = AMDXDNA_BO_SHARE; + gobj->resv = dma_buf->resv; return gobj; @@ -675,92 +696,92 @@ put_buf: } static struct amdxdna_gem_obj * -amdxdna_drm_alloc_shmem(struct drm_device *dev, - struct amdxdna_drm_create_bo *args, - struct drm_file *filp) +amdxdna_drm_create_share_bo(struct drm_device *dev, + struct amdxdna_drm_create_bo *args, struct drm_file *filp) { - struct amdxdna_client *client = filp->driver_priv; struct amdxdna_gem_obj *abo; - abo = amdxdna_gem_create_object(dev, args); + if (args->vaddr) + abo = amdxdna_gem_create_ubuf_object(dev, args); + else + abo = amdxdna_gem_create_shmem_object(dev, args); if (IS_ERR(abo)) return ERR_CAST(abo); - abo->client = client; - abo->type = AMDXDNA_BO_SHMEM; + if (args->type == AMDXDNA_BO_DEV_HEAP) + abo->type = AMDXDNA_BO_DEV_HEAP; + else + abo->type = AMDXDNA_BO_SHARE; return abo; } static struct amdxdna_gem_obj * -amdxdna_drm_create_dev_heap(struct drm_device *dev, - struct amdxdna_drm_create_bo *args, - struct drm_file *filp) +amdxdna_drm_create_dev_heap_bo(struct drm_device *dev, + struct amdxdna_drm_create_bo *args, struct drm_file *filp) { struct amdxdna_client *client = filp->driver_priv; struct amdxdna_dev *xdna = to_xdna_dev(dev); struct amdxdna_gem_obj *abo; int ret; - if (args->size > xdna->dev_info->dev_mem_size) { - XDNA_DBG(xdna, "Invalid dev heap size 0x%llx, limit 0x%lx", + WARN_ON(!is_power_of_2(xdna->dev_info->dev_mem_size)); + XDNA_DBG(xdna, "Requested dev heap size 0x%llx", args->size); + if (!args->size || !IS_ALIGNED(args->size, xdna->dev_info->dev_mem_size)) { + XDNA_ERR(xdna, "The dev heap size 0x%llx is not multiple of 0x%lx", args->size, xdna->dev_info->dev_mem_size); return ERR_PTR(-EINVAL); } + /* HEAP BO is a special case of SHARE BO. */ + abo = amdxdna_drm_create_share_bo(dev, args, filp); + if (IS_ERR(abo)) + return ERR_CAST(abo); + + /* Set up heap for this client. */ mutex_lock(&client->mm_lock); + if (client->dev_heap) { XDNA_DBG(client->xdna, "dev heap is already created"); ret = -EBUSY; goto mm_unlock; } - - abo = amdxdna_gem_create_object(dev, args); - if (IS_ERR(abo)) { - ret = PTR_ERR(abo); - goto mm_unlock; - } - - abo->type = AMDXDNA_BO_DEV_HEAP; - abo->client = client; - abo->mem.dev_addr = client->xdna->dev_info->dev_mem_base; - drm_mm_init(&abo->mm, abo->mem.dev_addr, abo->mem.size); - - ret = amdxdna_gem_obj_vmap(abo, &abo->mem.kva); - if (ret) { - XDNA_ERR(xdna, "Vmap heap bo failed, ret %d", ret); - goto release_obj; - } - client->dev_heap = abo; drm_gem_object_get(to_gobj(abo)); + + drm_mm_init(&abo->mm, xdna->dev_info->dev_mem_base, abo->mem.size); + mutex_unlock(&client->mm_lock); return abo; -release_obj: - drm_gem_object_put(to_gobj(abo)); mm_unlock: mutex_unlock(&client->mm_lock); + drm_gem_object_put(to_gobj(abo)); return ERR_PTR(ret); } struct amdxdna_gem_obj * -amdxdna_drm_alloc_dev_bo(struct drm_device *dev, - struct amdxdna_drm_create_bo *args, - struct drm_file *filp) +amdxdna_drm_create_dev_bo(struct drm_device *dev, + struct amdxdna_drm_create_bo *args, struct drm_file *filp) { + size_t aligned_sz = PAGE_ALIGN(args->size); struct amdxdna_client *client = filp->driver_priv; struct amdxdna_dev *xdna = to_xdna_dev(dev); - size_t aligned_sz = PAGE_ALIGN(args->size); struct amdxdna_gem_obj *abo; + struct drm_gem_object *gobj; int ret; - abo = amdxdna_gem_create_obj(&xdna->ddev, aligned_sz); + if (!aligned_sz) { + XDNA_ERR(xdna, "Invalid BO size 0x%llx", args->size); + return ERR_PTR(-EINVAL); + } + + abo = amdxdna_gem_create_obj(dev, aligned_sz); if (IS_ERR(abo)) return abo; - - to_gobj(abo)->funcs = &amdxdna_gem_dev_obj_funcs; + gobj = to_gobj(abo); + gobj->funcs = &amdxdna_gem_dev_obj_funcs; abo->type = AMDXDNA_BO_DEV; abo->client = client; @@ -770,31 +791,7 @@ amdxdna_drm_alloc_dev_bo(struct drm_device *dev, amdxdna_gem_destroy_obj(abo); return ERR_PTR(ret); } - - drm_gem_private_object_init(&xdna->ddev, to_gobj(abo), aligned_sz); - - return abo; -} - -static struct amdxdna_gem_obj * -amdxdna_drm_create_cmd_bo(struct drm_device *dev, - struct amdxdna_drm_create_bo *args, - struct drm_file *filp) -{ - struct amdxdna_dev *xdna = to_xdna_dev(dev); - struct amdxdna_gem_obj *abo; - - if (args->size < sizeof(struct amdxdna_cmd)) { - XDNA_DBG(xdna, "Command BO size 0x%llx too small", args->size); - return ERR_PTR(-EINVAL); - } - - abo = amdxdna_gem_create_object(dev, args); - if (IS_ERR(abo)) - return ERR_CAST(abo); - - abo->type = AMDXDNA_BO_CMD; - abo->client = filp->driver_priv; + drm_gem_private_object_init(dev, gobj, aligned_sz); return abo; } @@ -812,17 +809,16 @@ int amdxdna_drm_create_bo_ioctl(struct drm_device *dev, void *data, struct drm_f XDNA_DBG(xdna, "BO arg type %d vaddr 0x%llx size 0x%llx flags 0x%llx", args->type, args->vaddr, args->size, args->flags); switch (args->type) { - case AMDXDNA_BO_SHMEM: - abo = amdxdna_drm_alloc_shmem(dev, args, filp); + case AMDXDNA_BO_CMD: + fallthrough; + case AMDXDNA_BO_SHARE: + abo = amdxdna_drm_create_share_bo(dev, args, filp); break; case AMDXDNA_BO_DEV_HEAP: - abo = amdxdna_drm_create_dev_heap(dev, args, filp); + abo = amdxdna_drm_create_dev_heap_bo(dev, args, filp); break; case AMDXDNA_BO_DEV: - abo = amdxdna_drm_alloc_dev_bo(dev, args, filp); - break; - case AMDXDNA_BO_CMD: - abo = amdxdna_drm_create_cmd_bo(dev, args, filp); + abo = amdxdna_drm_create_dev_bo(dev, args, filp); break; default: return -EINVAL; @@ -838,8 +834,8 @@ int amdxdna_drm_create_bo_ioctl(struct drm_device *dev, void *data, struct drm_f } XDNA_DBG(xdna, "BO hdl %d type %d userptr 0x%llx xdna_addr 0x%llx size 0x%lx", - args->handle, args->type, abo->mem.userptr, - abo->mem.dev_addr, abo->mem.size); + args->handle, args->type, amdxdna_gem_uva(abo), + amdxdna_gem_dev_addr(abo), abo->mem.size); put_obj: /* Dereference object reference. Handle holds it now. */ drm_gem_object_put(to_gobj(abo)); @@ -890,38 +886,19 @@ void amdxdna_gem_unpin(struct amdxdna_gem_obj *abo) struct amdxdna_gem_obj *amdxdna_gem_get_obj(struct amdxdna_client *client, u32 bo_hdl, u8 bo_type) { - struct amdxdna_dev *xdna = client->xdna; struct amdxdna_gem_obj *abo; struct drm_gem_object *gobj; - int ret; gobj = drm_gem_object_lookup(client->filp, bo_hdl); if (!gobj) { - XDNA_DBG(xdna, "Can not find bo %d", bo_hdl); + XDNA_DBG(client->xdna, "Can not find bo %d", bo_hdl); return NULL; } abo = to_xdna_obj(gobj); - if (bo_type != AMDXDNA_BO_INVALID && abo->type != bo_type) - goto put_obj; - - if (bo_type != AMDXDNA_BO_CMD || abo->mem.kva) + if (bo_type == AMDXDNA_BO_INVALID || abo->type == bo_type) return abo; - if (abo->mem.size > SZ_32K) { - XDNA_ERR(xdna, "Cmd bo is too big %ld", abo->mem.size); - goto put_obj; - } - - ret = amdxdna_gem_obj_vmap(abo, &abo->mem.kva); - if (ret) { - XDNA_ERR(xdna, "Vmap cmd bo failed, ret %d", ret); - goto put_obj; - } - - return abo; - -put_obj: drm_gem_object_put(gobj); return NULL; } @@ -944,11 +921,8 @@ int amdxdna_drm_get_bo_info_ioctl(struct drm_device *dev, void *data, struct drm } abo = to_xdna_obj(gobj); - args->vaddr = abo->mem.userptr; - if (abo->mem.dev_addr != AMDXDNA_INVALID_ADDR) - args->xdna_addr = abo->mem.dev_addr; - else - args->xdna_addr = abo->mem.dma_addr; + args->vaddr = amdxdna_gem_uva(abo); + args->xdna_addr = amdxdna_gem_dev_addr(abo); if (abo->type != AMDXDNA_BO_DEV) args->map_offset = drm_vma_node_offset_addr(&gobj->vma_node); @@ -993,8 +967,8 @@ int amdxdna_drm_sync_bo_ioctl(struct drm_device *dev, if (is_import_bo(abo)) drm_clflush_sg(abo->base.sgt); - else if (abo->mem.kva) - drm_clflush_virt_range(abo->mem.kva + args->offset, args->size); + else if (amdxdna_gem_vmap(abo)) + drm_clflush_virt_range(amdxdna_gem_vmap(abo) + args->offset, args->size); else if (abo->base.pages) drm_clflush_pages(abo->base.pages, gobj->size >> PAGE_SHIFT); else diff --git a/drivers/accel/amdxdna/amdxdna_gem.h b/drivers/accel/amdxdna/amdxdna_gem.h index fbeb622e7cf9..a77d9344f8a4 100644 --- a/drivers/accel/amdxdna/amdxdna_gem.h +++ b/drivers/accel/amdxdna/amdxdna_gem.h @@ -24,15 +24,16 @@ struct amdxdna_umap { }; struct amdxdna_mem { - u64 userptr; void *kva; - u64 dev_addr; u64 dma_addr; size_t size; - struct page **pages; - u32 nr_pages; struct list_head umap_list; bool map_invalid; + /* + * Cache the first mmap uva as PASID addr, which can be accessed by driver + * without taking notifier_lock. + */ + u64 uva; }; struct amdxdna_gem_obj { @@ -40,11 +41,10 @@ struct amdxdna_gem_obj { struct amdxdna_client *client; u8 type; bool pinned; - struct mutex lock; /* Protects: pinned */ + struct mutex lock; /* Protects: pinned, mem.kva */ struct amdxdna_mem mem; - u32 ref; - /* Below members is uninitialized when needed */ + /* Below members are initialized when needed */ struct drm_mm mm; /* For AMDXDNA_BO_DEV_HEAP */ struct drm_mm_node mm_node; /* For AMDXDNA_BO_DEV */ u32 assigned_hwctx; @@ -67,27 +67,29 @@ static inline void amdxdna_gem_put_obj(struct amdxdna_gem_obj *abo) drm_gem_object_put(to_gobj(abo)); } +void *amdxdna_gem_vmap(struct amdxdna_gem_obj *abo); +u64 amdxdna_gem_uva(struct amdxdna_gem_obj *abo); +u64 amdxdna_gem_dev_addr(struct amdxdna_gem_obj *abo); + static inline u64 amdxdna_dev_bo_offset(struct amdxdna_gem_obj *abo) { - return abo->mem.dev_addr - abo->client->dev_heap->mem.dev_addr; + return amdxdna_gem_dev_addr(abo) - amdxdna_gem_dev_addr(abo->client->dev_heap); } -static inline u64 amdxdna_obj_dma_addr(struct amdxdna_client *client, - struct amdxdna_gem_obj *abo) +static inline u64 amdxdna_obj_dma_addr(struct amdxdna_gem_obj *abo) { - return amdxdna_pasid_on(client) ? abo->mem.userptr : abo->mem.dma_addr; + return amdxdna_pasid_on(abo->client) ? amdxdna_gem_uva(abo) : abo->mem.dma_addr; } void amdxdna_umap_put(struct amdxdna_umap *mapp); struct drm_gem_object * -amdxdna_gem_create_object_cb(struct drm_device *dev, size_t size); +amdxdna_gem_create_shmem_object_cb(struct drm_device *dev, size_t size); struct drm_gem_object * amdxdna_gem_prime_import(struct drm_device *dev, struct dma_buf *dma_buf); struct amdxdna_gem_obj * -amdxdna_drm_alloc_dev_bo(struct drm_device *dev, - struct amdxdna_drm_create_bo *args, - struct drm_file *filp); +amdxdna_drm_create_dev_bo(struct drm_device *dev, + struct amdxdna_drm_create_bo *args, struct drm_file *filp); int amdxdna_gem_pin_nolock(struct amdxdna_gem_obj *abo); int amdxdna_gem_pin(struct amdxdna_gem_obj *abo); diff --git a/drivers/accel/amdxdna/amdxdna_pci_drv.c b/drivers/accel/amdxdna/amdxdna_pci_drv.c index 5143b8c9b92b..d83be00daf2b 100644 --- a/drivers/accel/amdxdna/amdxdna_pci_drv.c +++ b/drivers/accel/amdxdna/amdxdna_pci_drv.c @@ -245,7 +245,7 @@ const struct drm_driver amdxdna_drm_drv = { .ioctls = amdxdna_drm_ioctls, .num_ioctls = ARRAY_SIZE(amdxdna_drm_ioctls), - .gem_create_object = amdxdna_gem_create_object_cb, + .gem_create_object = amdxdna_gem_create_shmem_object_cb, .gem_prime_import = amdxdna_gem_prime_import, }; diff --git a/drivers/accel/amdxdna/amdxdna_ubuf.c b/drivers/accel/amdxdna/amdxdna_ubuf.c index fb71d6e3f44d..fb999aa25318 100644 --- a/drivers/accel/amdxdna/amdxdna_ubuf.c +++ b/drivers/accel/amdxdna/amdxdna_ubuf.c @@ -17,7 +17,6 @@ struct amdxdna_ubuf_priv { struct page **pages; u64 nr_pages; - enum amdxdna_ubuf_flag flags; struct mm_struct *mm; }; @@ -37,11 +36,9 @@ static struct sg_table *amdxdna_ubuf_map(struct dma_buf_attachment *attach, if (ret) goto err_free_sg; - if (ubuf->flags & AMDXDNA_UBUF_FLAG_MAP_DMA) { - ret = dma_map_sgtable(attach->dev, sg, direction, 0); - if (ret) - goto err_free_table; - } + ret = dma_map_sgtable(attach->dev, sg, direction, 0); + if (ret) + goto err_free_table; return sg; @@ -56,11 +53,7 @@ static void amdxdna_ubuf_unmap(struct dma_buf_attachment *attach, struct sg_table *sg, enum dma_data_direction direction) { - struct amdxdna_ubuf_priv *ubuf = attach->dmabuf->priv; - - if (ubuf->flags & AMDXDNA_UBUF_FLAG_MAP_DMA) - dma_unmap_sgtable(attach->dev, sg, direction, 0); - + dma_unmap_sgtable(attach->dev, sg, direction, 0); sg_free_table(sg); kfree(sg); } @@ -133,7 +126,6 @@ static const struct dma_buf_ops amdxdna_ubuf_dmabuf_ops = { }; struct dma_buf *amdxdna_get_ubuf(struct drm_device *dev, - enum amdxdna_ubuf_flag flags, u32 num_entries, void __user *va_entries) { struct amdxdna_dev *xdna = to_xdna_dev(dev); @@ -152,7 +144,6 @@ struct dma_buf *amdxdna_get_ubuf(struct drm_device *dev, if (!ubuf) return ERR_PTR(-ENOMEM); - ubuf->flags = flags; ubuf->mm = current->mm; mmgrab(ubuf->mm); diff --git a/drivers/accel/amdxdna/amdxdna_ubuf.h b/drivers/accel/amdxdna/amdxdna_ubuf.h index e5cb3bdb3ec9..8900a6dc4371 100644 --- a/drivers/accel/amdxdna/amdxdna_ubuf.h +++ b/drivers/accel/amdxdna/amdxdna_ubuf.h @@ -8,12 +8,7 @@ #include #include -enum amdxdna_ubuf_flag { - AMDXDNA_UBUF_FLAG_MAP_DMA = 1, -}; - struct dma_buf *amdxdna_get_ubuf(struct drm_device *dev, - enum amdxdna_ubuf_flag flags, u32 num_entries, void __user *va_entries); #endif /* _AMDXDNA_UBUF_H_ */ diff --git a/include/uapi/drm/amdxdna_accel.h b/include/uapi/drm/amdxdna_accel.h index 5bd13f4435f5..bddaaaf945cf 100644 --- a/include/uapi/drm/amdxdna_accel.h +++ b/include/uapi/drm/amdxdna_accel.h @@ -156,10 +156,11 @@ struct amdxdna_drm_config_hwctx { enum amdxdna_bo_type { AMDXDNA_BO_INVALID = 0, - AMDXDNA_BO_SHMEM, - AMDXDNA_BO_DEV_HEAP, - AMDXDNA_BO_DEV, - AMDXDNA_BO_CMD, + AMDXDNA_BO_SHMEM = 1, /* Be compatible with legacy application code. */ + AMDXDNA_BO_SHARE = 1, + AMDXDNA_BO_DEV_HEAP = 2, + AMDXDNA_BO_DEV = 3, + AMDXDNA_BO_CMD = 4, }; /** -- cgit v1.2.3 From a6f22e50c7d51aa225c392c62c33f0fae11f734d Mon Sep 17 00:00:00 2001 From: Xuewen Yan Date: Fri, 6 Mar 2026 15:59:54 +0800 Subject: tracing: Revert "tracing: Remove pid in task_rename tracing output" This reverts commit e3f6a42272e028c46695acc83fc7d7c42f2750ad. The commit says that the tracepoint only deals with the current task, however the following case is not current task: comm_write() { p = get_proc_task(inode); if (!p) return -ESRCH; if (same_thread_group(current, p)) set_task_comm(p, buffer); } where set_task_comm() calls __set_task_comm() which records the update of p and not current. So revert the patch to show pid. Cc: Cc: Cc: Cc: Link: https://patch.msgid.link/20260306075954.4533-1-xuewen.yan@unisoc.com Fixes: e3f6a42272e0 ("tracing: Remove pid in task_rename tracing output") Reported-by: Guohua Yan Signed-off-by: Xuewen Yan Reviewed-by: Steven Rostedt (Google) Signed-off-by: Steven Rostedt (Google) --- include/trace/events/task.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/trace/events/task.h b/include/trace/events/task.h index 4f0759634306..b9a129eb54d9 100644 --- a/include/trace/events/task.h +++ b/include/trace/events/task.h @@ -38,19 +38,22 @@ TRACE_EVENT(task_rename, TP_ARGS(task, comm), TP_STRUCT__entry( + __field( pid_t, pid) __array( char, oldcomm, TASK_COMM_LEN) __array( char, newcomm, TASK_COMM_LEN) __field( short, oom_score_adj) ), TP_fast_assign( + __entry->pid = task->pid; memcpy(entry->oldcomm, task->comm, TASK_COMM_LEN); strscpy(entry->newcomm, comm, TASK_COMM_LEN); __entry->oom_score_adj = task->signal->oom_score_adj; ), - TP_printk("oldcomm=%s newcomm=%s oom_score_adj=%hd", - __entry->oldcomm, __entry->newcomm, __entry->oom_score_adj) + TP_printk("pid=%d oldcomm=%s newcomm=%s oom_score_adj=%hd", + __entry->pid, __entry->oldcomm, + __entry->newcomm, __entry->oom_score_adj) ); /** -- cgit v1.2.3 From 26f775a054c3cda86ad465a64141894a90a9e145 Mon Sep 17 00:00:00 2001 From: SeongJae Park Date: Thu, 19 Mar 2026 07:52:17 -0700 Subject: mm/damon/core: avoid use of half-online-committed context One major usage of damon_call() is online DAMON parameters update. It is done by calling damon_commit_ctx() inside the damon_call() callback function. damon_commit_ctx() can fail for two reasons: 1) invalid parameters and 2) internal memory allocation failures. In case of failures, the damon_ctx that attempted to be updated (commit destination) can be partially updated (or, corrupted from a perspective), and therefore shouldn't be used anymore. The function only ensures the damon_ctx object can safely deallocated using damon_destroy_ctx(). The API callers are, however, calling damon_commit_ctx() only after asserting the parameters are valid, to avoid damon_commit_ctx() fails due to invalid input parameters. But it can still theoretically fail if the internal memory allocation fails. In the case, DAMON may run with the partially updated damon_ctx. This can result in unexpected behaviors including even NULL pointer dereference in case of damos_commit_dests() failure [1]. Such allocation failure is arguably too small to fail, so the real world impact would be rare. But, given the bad consequence, this needs to be fixed. Avoid such partially-committed (maybe-corrupted) damon_ctx use by saving the damon_commit_ctx() failure on the damon_ctx object. For this, introduce damon_ctx->maybe_corrupted field. damon_commit_ctx() sets it when it is failed. kdamond_call() checks if the field is set after each damon_call_control->fn() is executed. If it is set, ignore remaining callback requests and return. All kdamond_call() callers including kdamond_fn() also check the maybe_corrupted field right after kdamond_call() invocations. If the field is set, break the kdamond_fn() main loop so that DAMON sill doesn't use the context that might be corrupted. [sj@kernel.org: let kdamond_call() with cancel regardless of maybe_corrupted] Link: https://lkml.kernel.org/r/20260320031553.2479-1-sj@kernel.org Link: https://sashiko.dev/#/patchset/20260319145218.86197-1-sj%40kernel.org Link: https://lkml.kernel.org/r/20260319145218.86197-1-sj@kernel.org Link: https://lore.kernel.org/20260319043309.97966-1-sj@kernel.org [1] Fixes: 3301f1861d34 ("mm/damon/sysfs: handle commit command using damon_call()") Signed-off-by: SeongJae Park Cc: [6.15+] Signed-off-by: Andrew Morton --- include/linux/damon.h | 6 ++++++ mm/damon/core.c | 8 ++++++++ 2 files changed, 14 insertions(+) (limited to 'include') diff --git a/include/linux/damon.h b/include/linux/damon.h index a4fea23da857..be3d198043ff 100644 --- a/include/linux/damon.h +++ b/include/linux/damon.h @@ -810,6 +810,12 @@ struct damon_ctx { struct damos_walk_control *walk_control; struct mutex walk_control_lock; + /* + * indicate if this may be corrupted. Currentonly this is set only for + * damon_commit_ctx() failure. + */ + bool maybe_corrupted; + /* Working thread of the given DAMON context */ struct task_struct *kdamond; /* Protects @kdamond field access */ diff --git a/mm/damon/core.c b/mm/damon/core.c index c1d1091d307e..3e1890d64d06 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -1252,6 +1252,7 @@ int damon_commit_ctx(struct damon_ctx *dst, struct damon_ctx *src) { int err; + dst->maybe_corrupted = true; if (!is_power_of_2(src->min_region_sz)) return -EINVAL; @@ -1277,6 +1278,7 @@ int damon_commit_ctx(struct damon_ctx *dst, struct damon_ctx *src) dst->addr_unit = src->addr_unit; dst->min_region_sz = src->min_region_sz; + dst->maybe_corrupted = false; return 0; } @@ -2678,6 +2680,8 @@ static void kdamond_call(struct damon_ctx *ctx, bool cancel) complete(&control->completion); else if (control->canceled && control->dealloc_on_cancel) kfree(control); + if (!cancel && ctx->maybe_corrupted) + break; } mutex_lock(&ctx->call_controls_lock); @@ -2707,6 +2711,8 @@ static int kdamond_wait_activation(struct damon_ctx *ctx) kdamond_usleep(min_wait_time); kdamond_call(ctx, false); + if (ctx->maybe_corrupted) + return -EINVAL; damos_walk_cancel(ctx); } return -EBUSY; @@ -2790,6 +2796,8 @@ static int kdamond_fn(void *data) * kdamond_merge_regions() if possible, to reduce overhead */ kdamond_call(ctx, false); + if (ctx->maybe_corrupted) + break; if (!list_empty(&ctx->schemes)) kdamond_apply_schemes(ctx); else -- cgit v1.2.3 From a85b46db143fda5869e7d8df8f258ccef5fa1719 Mon Sep 17 00:00:00 2001 From: Goldwyn Rodrigues Date: Fri, 13 Mar 2026 14:11:39 -0400 Subject: btrfs: tracepoints: get correct superblock from dentry in event btrfs_sync_file() If overlay is used on top of btrfs, dentry->d_sb translates to overlay's super block and fsid assignment will lead to a crash. Use file_inode(file)->i_sb to always get btrfs_sb. Reviewed-by: Boris Burkov Signed-off-by: Goldwyn Rodrigues Signed-off-by: David Sterba --- include/trace/events/btrfs.h | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/include/trace/events/btrfs.h b/include/trace/events/btrfs.h index 125bdc166bfe..0864700f76e0 100644 --- a/include/trace/events/btrfs.h +++ b/include/trace/events/btrfs.h @@ -769,12 +769,15 @@ TRACE_EVENT(btrfs_sync_file, ), TP_fast_assign( - const struct dentry *dentry = file->f_path.dentry; - const struct inode *inode = d_inode(dentry); + struct dentry *dentry = file_dentry(file); + struct inode *inode = file_inode(file); + struct dentry *parent = dget_parent(dentry); + struct inode *parent_inode = d_inode(parent); - TP_fast_assign_fsid(btrfs_sb(file->f_path.dentry->d_sb)); + dput(parent); + TP_fast_assign_fsid(btrfs_sb(inode->i_sb)); __entry->ino = btrfs_ino(BTRFS_I(inode)); - __entry->parent = btrfs_ino(BTRFS_I(d_inode(dentry->d_parent))); + __entry->parent = btrfs_ino(BTRFS_I(parent_inode)); __entry->datasync = datasync; __entry->root_objectid = btrfs_root_id(BTRFS_I(inode)->root); ), -- cgit v1.2.3 From 4be7b99c253f0c85a255cc1db7127ba3232dfa30 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Fri, 20 Mar 2026 07:23:00 +0000 Subject: ipv6: Don't remove permanent routes with exceptions from tb6_gc_hlist. The cited commit mechanically put fib6_remove_gc_list() just after every fib6_clean_expires() call. When a temporary route is promoted to a permanent route, there may already be exception routes tied to it. If fib6_remove_gc_list() removes the route from tb6_gc_hlist, such exception routes will no longer be aged. Let's replace fib6_remove_gc_list() with a new helper fib6_may_remove_gc_list() and use fib6_age_exceptions() there. Note that net->ipv6 is only compiled when CONFIG_IPV6 is enabled, so fib6_{add,remove,may_remove}_gc_list() are guarded. Fixes: 5eb902b8e719 ("net/ipv6: Remove expired routes with a separated list of routes.") Signed-off-by: Kuniyuki Iwashima Reviewed-by: David Ahern Link: https://patch.msgid.link/20260320072317.2561779-3-kuniyu@google.com Signed-off-by: Jakub Kicinski --- include/net/ip6_fib.h | 21 ++++++++++++++++++++- net/ipv6/addrconf.c | 4 ++-- net/ipv6/ip6_fib.c | 6 +++--- net/ipv6/route.c | 2 +- 4 files changed, 26 insertions(+), 7 deletions(-) (limited to 'include') diff --git a/include/net/ip6_fib.h b/include/net/ip6_fib.h index 88b0dd4d8e09..9f8b6814a96a 100644 --- a/include/net/ip6_fib.h +++ b/include/net/ip6_fib.h @@ -507,12 +507,14 @@ void fib6_rt_update(struct net *net, struct fib6_info *rt, void inet6_rt_notify(int event, struct fib6_info *rt, struct nl_info *info, unsigned int flags); +void fib6_age_exceptions(struct fib6_info *rt, struct fib6_gc_args *gc_args, + unsigned long now); void fib6_run_gc(unsigned long expires, struct net *net, bool force); - void fib6_gc_cleanup(void); int fib6_init(void); +#if IS_ENABLED(CONFIG_IPV6) /* Add the route to the gc list if it is not already there * * The callers should hold f6i->fib6_table->tb6_lock. @@ -545,6 +547,23 @@ static inline void fib6_remove_gc_list(struct fib6_info *f6i) hlist_del_init(&f6i->gc_link); } +static inline void fib6_may_remove_gc_list(struct net *net, + struct fib6_info *f6i) +{ + struct fib6_gc_args gc_args; + + if (hlist_unhashed(&f6i->gc_link)) + return; + + gc_args.timeout = READ_ONCE(net->ipv6.sysctl.ip6_rt_gc_interval); + gc_args.more = 0; + + rcu_read_lock(); + fib6_age_exceptions(f6i, &gc_args, jiffies); + rcu_read_unlock(); +} +#endif + struct ipv6_route_iter { struct seq_net_private p; struct fib6_walker w; diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c index 0e55f139e05d..f4e23b543585 100644 --- a/net/ipv6/addrconf.c +++ b/net/ipv6/addrconf.c @@ -2862,7 +2862,7 @@ void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao) fib6_add_gc_list(rt); } else { fib6_clean_expires(rt); - fib6_remove_gc_list(rt); + fib6_may_remove_gc_list(net, rt); } spin_unlock_bh(&table->tb6_lock); @@ -4840,7 +4840,7 @@ static int modify_prefix_route(struct net *net, struct inet6_ifaddr *ifp, if (!(flags & RTF_EXPIRES)) { fib6_clean_expires(f6i); - fib6_remove_gc_list(f6i); + fib6_may_remove_gc_list(net, f6i); } else { fib6_set_expires(f6i, expires); fib6_add_gc_list(f6i); diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c index fadfca49d6b1..dd26657b6a4a 100644 --- a/net/ipv6/ip6_fib.c +++ b/net/ipv6/ip6_fib.c @@ -1133,7 +1133,7 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct fib6_info *rt, return -EEXIST; if (!(rt->fib6_flags & RTF_EXPIRES)) { fib6_clean_expires(iter); - fib6_remove_gc_list(iter); + fib6_may_remove_gc_list(info->nl_net, iter); } else { fib6_set_expires(iter, rt->expires); fib6_add_gc_list(iter); @@ -2348,8 +2348,8 @@ static void fib6_flush_trees(struct net *net) /* * Garbage collection */ -static void fib6_age_exceptions(struct fib6_info *rt, struct fib6_gc_args *gc_args, - unsigned long now) +void fib6_age_exceptions(struct fib6_info *rt, struct fib6_gc_args *gc_args, + unsigned long now) { bool may_expire = rt->fib6_flags & RTF_EXPIRES && rt->expires; int old_more = gc_args->more; diff --git a/net/ipv6/route.c b/net/ipv6/route.c index 08cd86f49bf9..cb521700cee7 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -1033,7 +1033,7 @@ int rt6_route_rcv(struct net_device *dev, u8 *opt, int len, if (!addrconf_finite_timeout(lifetime)) { fib6_clean_expires(rt); - fib6_remove_gc_list(rt); + fib6_may_remove_gc_list(net, rt); } else { fib6_set_expires(rt, jiffies + HZ * lifetime); fib6_add_gc_list(rt); -- cgit v1.2.3 From e537dd15d0d4ad989d56a1021290f0c674dd8b28 Mon Sep 17 00:00:00 2001 From: Martin KaFai Lau Date: Thu, 19 Mar 2026 11:18:17 -0700 Subject: udp: Fix wildcard bind conflict check when using hash2 When binding a udp_sock to a local address and port, UDP uses two hashes (udptable->hash and udptable->hash2) for collision detection. The current code switches to "hash2" when hslot->count > 10. "hash2" is keyed by local address and local port. "hash" is keyed by local port only. The issue can be shown in the following bind sequence (pseudo code): bind(fd1, "[fd00::1]:8888") bind(fd2, "[fd00::2]:8888") bind(fd3, "[fd00::3]:8888") bind(fd4, "[fd00::4]:8888") bind(fd5, "[fd00::5]:8888") bind(fd6, "[fd00::6]:8888") bind(fd7, "[fd00::7]:8888") bind(fd8, "[fd00::8]:8888") bind(fd9, "[fd00::9]:8888") bind(fd10, "[fd00::10]:8888") /* Correctly return -EADDRINUSE because "hash" is used * instead of "hash2". udp_lib_lport_inuse() detects the * conflict. */ bind(fail_fd, "[::]:8888") /* After one more socket is bound to "[fd00::11]:8888", * hslot->count exceeds 10 and "hash2" is used instead. */ bind(fd11, "[fd00::11]:8888") bind(fail_fd, "[::]:8888") /* succeeds unexpectedly */ The same issue applies to the IPv4 wildcard address "0.0.0.0" and the IPv4-mapped wildcard address "::ffff:0.0.0.0". For example, if there are existing sockets bound to "192.168.1.[1-11]:8888", then binding "0.0.0.0:8888" or "[::ffff:0.0.0.0]:8888" can also miss the conflict when hslot->count > 10. TCP inet_csk_get_port() already has the correct check in inet_use_bhash2_on_bind(). Rename it to inet_use_hash2_on_bind() and move it to inet_hashtables.h so udp.c can reuse it in this fix. Fixes: 30fff9231fad ("udp: bind() optimisation") Reported-by: Andrew Onyshchuk Signed-off-by: Martin KaFai Lau Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260319181817.1901357-1-martin.lau@linux.dev Signed-off-by: Jakub Kicinski --- include/net/inet_hashtables.h | 14 ++++++++++++++ net/ipv4/inet_connection_sock.c | 20 +++----------------- net/ipv4/udp.c | 2 +- 3 files changed, 18 insertions(+), 18 deletions(-) (limited to 'include') diff --git a/include/net/inet_hashtables.h b/include/net/inet_hashtables.h index 5a979dcab538..6d936e9f2fd3 100644 --- a/include/net/inet_hashtables.h +++ b/include/net/inet_hashtables.h @@ -264,6 +264,20 @@ inet_bhashfn_portaddr(const struct inet_hashinfo *hinfo, const struct sock *sk, return &hinfo->bhash2[hash & (hinfo->bhash_size - 1)]; } +static inline bool inet_use_hash2_on_bind(const struct sock *sk) +{ +#if IS_ENABLED(CONFIG_IPV6) + if (sk->sk_family == AF_INET6) { + if (ipv6_addr_any(&sk->sk_v6_rcv_saddr)) + return false; + + if (!ipv6_addr_v4mapped(&sk->sk_v6_rcv_saddr)) + return true; + } +#endif + return sk->sk_rcv_saddr != htonl(INADDR_ANY); +} + struct inet_bind_hashbucket * inet_bhash2_addr_any_hashbucket(const struct sock *sk, const struct net *net, int port); diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c index 5dfac6ce1110..e961936b6be7 100644 --- a/net/ipv4/inet_connection_sock.c +++ b/net/ipv4/inet_connection_sock.c @@ -154,20 +154,6 @@ bool inet_sk_get_local_port_range(const struct sock *sk, int *low, int *high) } EXPORT_SYMBOL(inet_sk_get_local_port_range); -static bool inet_use_bhash2_on_bind(const struct sock *sk) -{ -#if IS_ENABLED(CONFIG_IPV6) - if (sk->sk_family == AF_INET6) { - if (ipv6_addr_any(&sk->sk_v6_rcv_saddr)) - return false; - - if (!ipv6_addr_v4mapped(&sk->sk_v6_rcv_saddr)) - return true; - } -#endif - return sk->sk_rcv_saddr != htonl(INADDR_ANY); -} - static bool inet_bind_conflict(const struct sock *sk, struct sock *sk2, kuid_t uid, bool relax, bool reuseport_cb_ok, bool reuseport_ok) @@ -259,7 +245,7 @@ static int inet_csk_bind_conflict(const struct sock *sk, * checks separately because their spinlocks have to be acquired/released * independently of each other, to prevent possible deadlocks */ - if (inet_use_bhash2_on_bind(sk)) + if (inet_use_hash2_on_bind(sk)) return tb2 && inet_bhash2_conflict(sk, tb2, uid, relax, reuseport_cb_ok, reuseport_ok); @@ -376,7 +362,7 @@ other_parity_scan: head = &hinfo->bhash[inet_bhashfn(net, port, hinfo->bhash_size)]; spin_lock_bh(&head->lock); - if (inet_use_bhash2_on_bind(sk)) { + if (inet_use_hash2_on_bind(sk)) { if (inet_bhash2_addr_any_conflict(sk, port, l3mdev, relax, false)) goto next_port; } @@ -562,7 +548,7 @@ int inet_csk_get_port(struct sock *sk, unsigned short snum) check_bind_conflict = false; } - if (check_bind_conflict && inet_use_bhash2_on_bind(sk)) { + if (check_bind_conflict && inet_use_hash2_on_bind(sk)) { if (inet_bhash2_addr_any_conflict(sk, port, l3mdev, true, true)) goto fail_unlock; } diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c index b60fad393e18..cb99a3c27053 100644 --- a/net/ipv4/udp.c +++ b/net/ipv4/udp.c @@ -287,7 +287,7 @@ int udp_lib_get_port(struct sock *sk, unsigned short snum, } else { hslot = udp_hashslot(udptable, net, snum); spin_lock_bh(&hslot->lock); - if (hslot->count > 10) { + if (inet_use_hash2_on_bind(sk) && hslot->count > 10) { int exist; unsigned int slot2 = udp_sk(sk)->udp_portaddr_hash ^ snum; -- cgit v1.2.3 From 38ec410b99a5ee6566f75650ce3d4fd632940fd0 Mon Sep 17 00:00:00 2001 From: Xuan Zhuo Date: Fri, 20 Mar 2026 10:18:17 +0800 Subject: virtio-net: correct hdr_len handling for VIRTIO_NET_F_GUEST_HDRLEN The commit be50da3e9d4a ("net: virtio_net: implement exact header length guest feature") introduces support for the VIRTIO_NET_F_GUEST_HDRLEN feature in virtio-net. This feature requires virtio-net to set hdr_len to the actual header length of the packet when transmitting, the number of bytes from the start of the packet to the beginning of the transport-layer payload. However, in practice, hdr_len was being set using skb_headlen(skb), which is clearly incorrect. This commit fixes that issue. Fixes: be50da3e9d4a ("net: virtio_net: implement exact header length guest feature") Signed-off-by: Xuan Zhuo Link: https://patch.msgid.link/20260320021818.111741-2-xuanzhuo@linux.alibaba.com Acked-by: Michael S. Tsirkin Signed-off-by: Paolo Abeni --- drivers/net/tun_vnet.h | 2 +- drivers/net/virtio_net.c | 6 +++++- include/linux/virtio_net.h | 34 ++++++++++++++++++++++++++++++---- 3 files changed, 36 insertions(+), 6 deletions(-) (limited to 'include') diff --git a/drivers/net/tun_vnet.h b/drivers/net/tun_vnet.h index a5f93b6c4482..fa5cab9d3e55 100644 --- a/drivers/net/tun_vnet.h +++ b/drivers/net/tun_vnet.h @@ -244,7 +244,7 @@ tun_vnet_hdr_tnl_from_skb(unsigned int flags, if (virtio_net_hdr_tnl_from_skb(skb, tnl_hdr, has_tnl_offload, tun_vnet_is_little_endian(flags), - vlan_hlen, true)) { + vlan_hlen, true, false)) { struct virtio_net_hdr_v1 *hdr = &tnl_hdr->hash_hdr.hdr; struct skb_shared_info *sinfo = skb_shinfo(skb); diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c index 72d6a9c6a5a2..7106333ef904 100644 --- a/drivers/net/virtio_net.c +++ b/drivers/net/virtio_net.c @@ -3267,8 +3267,12 @@ static int xmit_skb(struct send_queue *sq, struct sk_buff *skb, bool orphan) struct virtio_net_hdr_v1_hash_tunnel *hdr; int num_sg; unsigned hdr_len = vi->hdr_len; + bool feature_hdrlen; bool can_push; + feature_hdrlen = virtio_has_feature(vi->vdev, + VIRTIO_NET_F_GUEST_HDRLEN); + pr_debug("%s: xmit %p %pM\n", vi->dev->name, skb, dest); /* Make sure it's safe to cast between formats */ @@ -3288,7 +3292,7 @@ static int xmit_skb(struct send_queue *sq, struct sk_buff *skb, bool orphan) if (virtio_net_hdr_tnl_from_skb(skb, hdr, vi->tx_tnl, virtio_is_little_endian(vi->vdev), 0, - false)) + false, feature_hdrlen)) return -EPROTO; if (vi->mergeable_rx_bufs) diff --git a/include/linux/virtio_net.h b/include/linux/virtio_net.h index 75dabb763c65..361b60c8be68 100644 --- a/include/linux/virtio_net.h +++ b/include/linux/virtio_net.h @@ -207,6 +207,23 @@ static inline int virtio_net_hdr_to_skb(struct sk_buff *skb, return __virtio_net_hdr_to_skb(skb, hdr, little_endian, hdr->gso_type); } +/* This function must be called after virtio_net_hdr_from_skb(). */ +static inline void __virtio_net_set_hdrlen(const struct sk_buff *skb, + struct virtio_net_hdr *hdr, + bool little_endian) +{ + u16 hdr_len; + + hdr_len = skb_transport_offset(skb); + + if (hdr->gso_type == VIRTIO_NET_HDR_GSO_UDP_L4) + hdr_len += sizeof(struct udphdr); + else + hdr_len += tcp_hdrlen(skb); + + hdr->hdr_len = __cpu_to_virtio16(little_endian, hdr_len); +} + static inline int virtio_net_hdr_from_skb(const struct sk_buff *skb, struct virtio_net_hdr *hdr, bool little_endian, @@ -385,7 +402,8 @@ virtio_net_hdr_tnl_from_skb(const struct sk_buff *skb, bool tnl_hdr_negotiated, bool little_endian, int vlan_hlen, - bool has_data_valid) + bool has_data_valid, + bool feature_hdrlen) { struct virtio_net_hdr *hdr = (struct virtio_net_hdr *)vhdr; unsigned int inner_nh, outer_th; @@ -394,9 +412,17 @@ virtio_net_hdr_tnl_from_skb(const struct sk_buff *skb, tnl_gso_type = skb_shinfo(skb)->gso_type & (SKB_GSO_UDP_TUNNEL | SKB_GSO_UDP_TUNNEL_CSUM); - if (!tnl_gso_type) - return virtio_net_hdr_from_skb(skb, hdr, little_endian, - has_data_valid, vlan_hlen); + if (!tnl_gso_type) { + ret = virtio_net_hdr_from_skb(skb, hdr, little_endian, + has_data_valid, vlan_hlen); + if (ret) + return ret; + + if (feature_hdrlen && hdr->hdr_len) + __virtio_net_set_hdrlen(skb, hdr, little_endian); + + return ret; + } /* Tunnel support not negotiated but skb ask for it. */ if (!tnl_hdr_negotiated) -- cgit v1.2.3 From 6c860dc02a8e60b438e26940227dfa641fcdb66a Mon Sep 17 00:00:00 2001 From: Xuan Zhuo Date: Fri, 20 Mar 2026 10:18:18 +0800 Subject: virtio-net: correct hdr_len handling for tunnel gso The commit a2fb4bc4e2a6a03 ("net: implement virtio helpers to handle UDP GSO tunneling.") introduces support for the UDP GSO tunnel feature in virtio-net. The virtio spec says: If the \field{gso_type} has the VIRTIO_NET_HDR_GSO_UDP_TUNNEL_IPV4 bit or VIRTIO_NET_HDR_GSO_UDP_TUNNEL_IPV6 bit set, \field{hdr_len} accounts for all the headers up to and including the inner transport. The commit did not update the hdr_len to include the inner transport. I observed that the "hdr_len" is 116 for this packet: 17:36:18.241105 52:55:00:d1:27:0a > 2e:2c:df:46:a9:e1, ethertype IPv4 (0x0800), length 2912: (tos 0x0, ttl 64, id 45197, offset 0, flags [none], proto UDP (17), length 2898) 192.168.122.100.50613 > 192.168.122.1.4789: [bad udp cksum 0x8106 -> 0x26a0!] VXLAN, flags [I] (0x08), vni 1 fa:c3:ba:82:05:ee > ce:85:0c:31:77:e5, ethertype IPv4 (0x0800), length 2862: (tos 0x0, ttl 64, id 14678, offset 0, flags [DF], proto TCP (6), length 2848) 192.168.3.1.49880 > 192.168.3.2.9898: Flags [P.], cksum 0x9266 (incorrect -> 0xaa20), seq 515667:518463, ack 1, win 64, options [nop,nop,TS val 2990048824 ecr 2798801412], length 2796 116 = 14(mac) + 20(ip) + 8(udp) + 8(vxlan) + 14(inner mac) + 20(inner ip) + 32(innner tcp) Fixes: a2fb4bc4e2a6a03 ("net: implement virtio helpers to handle UDP GSO tunneling.") Signed-off-by: Xuan Zhuo Link: https://patch.msgid.link/20260320021818.111741-3-xuanzhuo@linux.alibaba.com Acked-by: Michael S. Tsirkin Signed-off-by: Paolo Abeni --- include/linux/virtio_net.h | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'include') diff --git a/include/linux/virtio_net.h b/include/linux/virtio_net.h index 361b60c8be68..f36d21b5bc19 100644 --- a/include/linux/virtio_net.h +++ b/include/linux/virtio_net.h @@ -224,6 +224,22 @@ static inline void __virtio_net_set_hdrlen(const struct sk_buff *skb, hdr->hdr_len = __cpu_to_virtio16(little_endian, hdr_len); } +/* This function must be called after virtio_net_hdr_from_skb(). */ +static inline void __virtio_net_set_tnl_hdrlen(const struct sk_buff *skb, + struct virtio_net_hdr *hdr) +{ + u16 hdr_len; + + hdr_len = skb_inner_transport_offset(skb); + + if (hdr->gso_type == VIRTIO_NET_HDR_GSO_UDP_L4) + hdr_len += sizeof(struct udphdr); + else + hdr_len += inner_tcp_hdrlen(skb); + + hdr->hdr_len = __cpu_to_virtio16(true, hdr_len); +} + static inline int virtio_net_hdr_from_skb(const struct sk_buff *skb, struct virtio_net_hdr *hdr, bool little_endian, @@ -440,6 +456,9 @@ virtio_net_hdr_tnl_from_skb(const struct sk_buff *skb, if (ret) return ret; + if (feature_hdrlen && hdr->hdr_len) + __virtio_net_set_tnl_hdrlen(skb, hdr); + if (skb->protocol == htons(ETH_P_IPV6)) hdr->gso_type |= VIRTIO_NET_HDR_GSO_UDP_TUNNEL_IPV6; else -- cgit v1.2.3 From f3934e12f20c552f764e6488aaa1ab76cdc20343 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Thu, 5 Mar 2026 10:04:53 +0100 Subject: drm/connector: Introduce drm_output_color_format enum The EDID parsing code initially introduced the DRM_COLOR_FORMAT_* defines to represent the sink capabilities. Since a given sink could support multiple formats, it was first defined as a bitmask. However, the core and drivers have since leveraged those defines to represent both the supported formats but also the current format being used. Considering the latter case, the more natural, and consistent, thing to do would be to create an enum of all the possible formats, and then list the supported formats using a bitmask of the individual enum values. Let's create a new enum following that pattern, drm_output_color_format, while maintaining the DRM_COLOR_FORMAT_* compatibility to make the transition easier. Acked-by: Jani Nikula Reviewed-by: Liviu Dudau Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20260305-drm-rework-color-formats-v3-1-f3935f6db579@kernel.org Signed-off-by: Maxime Ripard --- include/drm/drm_connector.h | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) (limited to 'include') diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index c18be8c19de0..227f61904386 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -556,6 +556,31 @@ enum drm_colorspace { DRM_MODE_COLORIMETRY_COUNT }; +/** + * enum drm_output_color_format - Output Color Format + * + * This enum is a consolidated color format list supported by + * connectors. It's only ever really been used for HDMI and DP so far, + * so it's not exhaustive and can be extended to represent other formats + * in the future. + * + * + * @DRM_OUTPUT_COLOR_FORMAT_RGB444: + * RGB output format + * @DRM_OUTPUT_COLOR_FORMAT_YCBCR444: + * YCbCr 4:4:4 output format (ie. not subsampled) + * @DRM_OUTPUT_COLOR_FORMAT_YCBCR422: + * YCbCr 4:2:2 output format (ie. with horizontal subsampling) + * @DRM_OUTPUT_COLOR_FORMAT_YCBCR420: + * YCbCr 4:2:0 output format (ie. with horizontal and vertical subsampling) + */ +enum drm_output_color_format { + DRM_OUTPUT_COLOR_FORMAT_RGB444 = 0, + DRM_OUTPUT_COLOR_FORMAT_YCBCR444, + DRM_OUTPUT_COLOR_FORMAT_YCBCR422, + DRM_OUTPUT_COLOR_FORMAT_YCBCR420, +}; + /** * enum drm_bus_flags - bus_flags info for &drm_display_info * @@ -699,10 +724,10 @@ struct drm_display_info { */ enum subpixel_order subpixel_order; -#define DRM_COLOR_FORMAT_RGB444 (1<<0) -#define DRM_COLOR_FORMAT_YCBCR444 (1<<1) -#define DRM_COLOR_FORMAT_YCBCR422 (1<<2) -#define DRM_COLOR_FORMAT_YCBCR420 (1<<3) +#define DRM_COLOR_FORMAT_RGB444 (1 << DRM_OUTPUT_COLOR_FORMAT_RGB444) +#define DRM_COLOR_FORMAT_YCBCR444 (1 << DRM_OUTPUT_COLOR_FORMAT_YCBCR444) +#define DRM_COLOR_FORMAT_YCBCR422 (1 << DRM_OUTPUT_COLOR_FORMAT_YCBCR422) +#define DRM_COLOR_FORMAT_YCBCR420 (1 << DRM_OUTPUT_COLOR_FORMAT_YCBCR420) /** * @panel_orientation: Read only connector property for built-in panels, @@ -714,10 +739,11 @@ struct drm_display_info { int panel_orientation; /** - * @color_formats: HDMI Color formats, selects between RGB and YCrCb - * modes. Used DRM_COLOR_FORMAT\_ defines, which are _not_ the same ones - * as used to describe the pixel format in framebuffers, and also don't - * match the formats in @bus_formats which are shared with v4l. + * @color_formats: HDMI Color formats, selects between RGB and + * YCbCr modes. Uses a bitmask of DRM_OUTPUT_COLOR_FORMAT\_ + * defines, which are _not_ the same ones as used to describe + * the pixel format in framebuffers, and also don't match the + * formats in @bus_formats which are shared with v4l. */ u32 color_formats; -- cgit v1.2.3 From 720c618e383c90c79b4ff3f90e71c4aceb1568d3 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Thu, 5 Mar 2026 10:05:05 +0100 Subject: drm/connector: Remove DRM_COLOR_FORMAT defines Now that all users of DRM_COLOR_FORMAT_* defines have been converted to the new enum, we can get rid of those defines. Acked-by: Jani Nikula Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20260305-drm-rework-color-formats-v3-13-f3935f6db579@kernel.org Signed-off-by: Maxime Ripard --- include/drm/drm_connector.h | 5 ----- 1 file changed, 5 deletions(-) (limited to 'include') diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index 227f61904386..562f6da90fac 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -724,11 +724,6 @@ struct drm_display_info { */ enum subpixel_order subpixel_order; -#define DRM_COLOR_FORMAT_RGB444 (1 << DRM_OUTPUT_COLOR_FORMAT_RGB444) -#define DRM_COLOR_FORMAT_YCBCR444 (1 << DRM_OUTPUT_COLOR_FORMAT_YCBCR444) -#define DRM_COLOR_FORMAT_YCBCR422 (1 << DRM_OUTPUT_COLOR_FORMAT_YCBCR422) -#define DRM_COLOR_FORMAT_YCBCR420 (1 << DRM_OUTPUT_COLOR_FORMAT_YCBCR420) - /** * @panel_orientation: Read only connector property for built-in panels, * indicating the orientation of the panel vs the device's casing. -- cgit v1.2.3 From 00cf406a0abe7cdf61d899d218520f6bc0414979 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Thu, 5 Mar 2026 10:05:06 +0100 Subject: drm/display: hdmi: Use drm_output_color_format instead of hdmi_colorspace The hdmi_colorspace enum was defined to represent the colorspace value of the HDMI infoframes. It was later used by some HDMI drivers to express the output format they should be setting up. During the introduction of the HDMI helpers, it then was used to represent it in the drm_connector_hdmi_state structure. However, it's always been somewhat redundant with the DRM_COLOR_FORMAT_* defines, and now with the drm_output_color_format enum. Let's consolidate around drm_output_color_format in drm_connector_hdmi_state to facilitate the current effort to provide a global output format selection mechanism. Acked-by: Jani Nikula Reviewed-by: Nicolas Frattaroli Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20260305-drm-rework-color-formats-v3-14-f3935f6db579@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/bridge/inno-hdmi.c | 6 +- drivers/gpu/drm/bridge/ite-it6263.c | 2 +- drivers/gpu/drm/display/drm_bridge_connector.c | 4 +- drivers/gpu/drm/display/drm_hdmi_helper.c | 7 +- drivers/gpu/drm/display/drm_hdmi_state_helper.c | 52 ++++-- drivers/gpu/drm/drm_bridge.c | 2 +- drivers/gpu/drm/drm_connector.c | 16 +- drivers/gpu/drm/mediatek/mtk_hdmi_v2.c | 8 +- drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c | 2 +- drivers/gpu/drm/tests/drm_connector_test.c | 80 ++++----- drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c | 182 ++++++++++----------- drivers/gpu/drm/vc4/vc4_hdmi.c | 18 +- drivers/gpu/drm/vc4/vc4_hdmi.h | 2 +- include/drm/bridge/dw_hdmi_qp.h | 2 +- include/drm/display/drm_hdmi_helper.h | 3 +- include/drm/drm_bridge.h | 5 +- include/drm/drm_connector.h | 9 +- 17 files changed, 213 insertions(+), 187 deletions(-) (limited to 'include') diff --git a/drivers/gpu/drm/bridge/inno-hdmi.c b/drivers/gpu/drm/bridge/inno-hdmi.c index a26b99b101c4..87422d15d9a2 100644 --- a/drivers/gpu/drm/bridge/inno-hdmi.c +++ b/drivers/gpu/drm/bridge/inno-hdmi.c @@ -653,7 +653,7 @@ static int inno_hdmi_config_video_csc(struct inno_hdmi *hdmi, v_VIDEO_INPUT_CSP(0); hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL2, value); - if (conn_state->hdmi.output_format == HDMI_COLORSPACE_RGB) { + if (conn_state->hdmi.output_format == DRM_OUTPUT_COLOR_FORMAT_RGB444) { if (conn_state->hdmi.is_limited_range) { csc_mode = CSC_RGB_0_255_TO_RGB_16_235_8BIT; auto_csc = AUTO_CSC_DISABLE; @@ -672,14 +672,14 @@ static int inno_hdmi_config_video_csc(struct inno_hdmi *hdmi, } } else { if (colorimetry == HDMI_COLORIMETRY_ITU_601) { - if (conn_state->hdmi.output_format == HDMI_COLORSPACE_YUV444) { + if (conn_state->hdmi.output_format == DRM_OUTPUT_COLOR_FORMAT_YCBCR444) { csc_mode = CSC_RGB_0_255_TO_ITU601_16_235_8BIT; auto_csc = AUTO_CSC_DISABLE; c0_c2_change = C0_C2_CHANGE_DISABLE; csc_enable = v_CSC_ENABLE; } } else { - if (conn_state->hdmi.output_format == HDMI_COLORSPACE_YUV444) { + if (conn_state->hdmi.output_format == DRM_OUTPUT_COLOR_FORMAT_YCBCR444) { csc_mode = CSC_RGB_0_255_TO_ITU709_16_235_8BIT; auto_csc = AUTO_CSC_DISABLE; c0_c2_change = C0_C2_CHANGE_DISABLE; diff --git a/drivers/gpu/drm/bridge/ite-it6263.c b/drivers/gpu/drm/bridge/ite-it6263.c index e77681047bb2..4f3ebb7af4d4 100644 --- a/drivers/gpu/drm/bridge/ite-it6263.c +++ b/drivers/gpu/drm/bridge/ite-it6263.c @@ -666,7 +666,7 @@ it6263_bridge_mode_valid(struct drm_bridge *bridge, { unsigned long long rate; - rate = drm_hdmi_compute_mode_clock(mode, 8, HDMI_COLORSPACE_RGB); + rate = drm_hdmi_compute_mode_clock(mode, 8, DRM_OUTPUT_COLOR_FORMAT_RGB444); if (rate == 0) return MODE_NOCLOCK; diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c index f686aa5c0ed9..39cc18f78eda 100644 --- a/drivers/gpu/drm/display/drm_bridge_connector.c +++ b/drivers/gpu/drm/display/drm_bridge_connector.c @@ -789,7 +789,7 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm, struct drm_connector *connector; struct i2c_adapter *ddc = NULL; struct drm_bridge *panel_bridge __free(drm_bridge_put) = NULL; - unsigned int supported_formats = BIT(HDMI_COLORSPACE_RGB); + unsigned int supported_formats = BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444); unsigned int max_bpc = 8; bool support_hdcp = false; int connector_type; @@ -960,7 +960,7 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm, if (bridge_connector->bridge_hdmi) { if (!connector->ycbcr_420_allowed) - supported_formats &= ~BIT(HDMI_COLORSPACE_YUV420); + supported_formats &= ~BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420); bridge_connector->hdmi_funcs = drm_bridge_connector_hdmi_funcs; diff --git a/drivers/gpu/drm/display/drm_hdmi_helper.c b/drivers/gpu/drm/display/drm_hdmi_helper.c index a237dc55805d..5cb0b033b171 100644 --- a/drivers/gpu/drm/display/drm_hdmi_helper.c +++ b/drivers/gpu/drm/display/drm_hdmi_helper.c @@ -210,7 +210,8 @@ EXPORT_SYMBOL(drm_hdmi_avi_infoframe_content_type); */ unsigned long long drm_hdmi_compute_mode_clock(const struct drm_display_mode *mode, - unsigned int bpc, enum hdmi_colorspace fmt) + unsigned int bpc, + enum drm_output_color_format fmt) { unsigned long long clock = mode->clock * 1000ULL; unsigned int vic = drm_match_cea_mode(mode); @@ -222,7 +223,7 @@ drm_hdmi_compute_mode_clock(const struct drm_display_mode *mode, if (vic == 1 && bpc != 8) return 0; - if (fmt == HDMI_COLORSPACE_YUV422) { + if (fmt == DRM_OUTPUT_COLOR_FORMAT_YCBCR422) { /* * HDMI 1.0 Spec, section 6.5 - Pixel Encoding states that * YUV422 sends 24 bits over three channels, with Cb and Cr @@ -248,7 +249,7 @@ drm_hdmi_compute_mode_clock(const struct drm_display_mode *mode, * specifies that YUV420 encoding is carried at a TMDS Character Rate * equal to half the pixel clock rate. */ - if (fmt == HDMI_COLORSPACE_YUV420) + if (fmt == DRM_OUTPUT_COLOR_FORMAT_YCBCR420) clock = clock / 2; if (mode->flags & DRM_MODE_FLAG_DBLCLK) diff --git a/drivers/gpu/drm/display/drm_hdmi_state_helper.c b/drivers/gpu/drm/display/drm_hdmi_state_helper.c index f2aec6f65e7a..9f3b696aceeb 100644 --- a/drivers/gpu/drm/display/drm_hdmi_state_helper.c +++ b/drivers/gpu/drm/display/drm_hdmi_state_helper.c @@ -326,6 +326,25 @@ void __drm_atomic_helper_connector_hdmi_reset(struct drm_connector *connector, } EXPORT_SYMBOL(__drm_atomic_helper_connector_hdmi_reset); +static enum hdmi_colorspace +output_color_format_to_hdmi_colorspace(const struct drm_connector *connector, + enum drm_output_color_format fmt) +{ + switch (fmt) { + case DRM_OUTPUT_COLOR_FORMAT_YCBCR420: + return HDMI_COLORSPACE_YUV420; + case DRM_OUTPUT_COLOR_FORMAT_YCBCR422: + return HDMI_COLORSPACE_YUV422; + case DRM_OUTPUT_COLOR_FORMAT_YCBCR444: + return HDMI_COLORSPACE_YUV444; + default: + drm_warn(connector->dev, "Unsupported output color format. Defaulting to RGB."); + fallthrough; + case DRM_OUTPUT_COLOR_FORMAT_RGB444: + return HDMI_COLORSPACE_RGB; + } +} + static const struct drm_display_mode * connector_state_get_mode(const struct drm_connector_state *conn_state) { @@ -360,7 +379,7 @@ static bool hdmi_is_limited_range(const struct drm_connector *connector, * i915 just assumes limited range for YCbCr output, so let's * just do the same. */ - if (conn_state->hdmi.output_format != HDMI_COLORSPACE_RGB) + if (conn_state->hdmi.output_format != DRM_OUTPUT_COLOR_FORMAT_RGB444) return true; if (conn_state->hdmi.broadcast_rgb == DRM_HDMI_BROADCAST_RGB_FULL) @@ -379,7 +398,8 @@ static bool sink_supports_format_bpc(const struct drm_connector *connector, const struct drm_display_info *info, const struct drm_display_mode *mode, - unsigned int format, unsigned int bpc) + enum drm_output_color_format format, + unsigned int bpc) { struct drm_device *dev = connector->dev; u8 vic = drm_match_cea_mode(mode); @@ -400,7 +420,7 @@ sink_supports_format_bpc(const struct drm_connector *connector, } if (!info->is_hdmi && - (format != HDMI_COLORSPACE_RGB || bpc != 8)) { + (format != DRM_OUTPUT_COLOR_FORMAT_RGB444 || bpc != 8)) { drm_dbg_kms(dev, "DVI Monitors require an RGB output at 8 bpc\n"); return false; } @@ -411,13 +431,13 @@ sink_supports_format_bpc(const struct drm_connector *connector, return false; } - if (drm_mode_is_420_only(info, mode) && format != HDMI_COLORSPACE_YUV420) { + if (drm_mode_is_420_only(info, mode) && format != DRM_OUTPUT_COLOR_FORMAT_YCBCR420) { drm_dbg_kms(dev, "Mode can be only supported in YUV420 format.\n"); return false; } switch (format) { - case HDMI_COLORSPACE_RGB: + case DRM_OUTPUT_COLOR_FORMAT_RGB444: drm_dbg_kms(dev, "RGB Format, checking the constraints.\n"); /* @@ -445,7 +465,7 @@ sink_supports_format_bpc(const struct drm_connector *connector, return true; - case HDMI_COLORSPACE_YUV420: + case DRM_OUTPUT_COLOR_FORMAT_YCBCR420: drm_dbg_kms(dev, "YUV420 format, checking the constraints.\n"); if (!(info->color_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420))) { @@ -477,7 +497,7 @@ sink_supports_format_bpc(const struct drm_connector *connector, return true; - case HDMI_COLORSPACE_YUV422: + case DRM_OUTPUT_COLOR_FORMAT_YCBCR422: drm_dbg_kms(dev, "YUV422 format, checking the constraints.\n"); if (!(info->color_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422))) { @@ -500,7 +520,7 @@ sink_supports_format_bpc(const struct drm_connector *connector, return true; - case HDMI_COLORSPACE_YUV444: + case DRM_OUTPUT_COLOR_FORMAT_YCBCR444: drm_dbg_kms(dev, "YUV444 format, checking the constraints.\n"); if (!(info->color_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444))) { @@ -553,7 +573,7 @@ static int hdmi_compute_clock(const struct drm_connector *connector, struct drm_connector_state *conn_state, const struct drm_display_mode *mode, - unsigned int bpc, enum hdmi_colorspace fmt) + unsigned int bpc, enum drm_output_color_format fmt) { enum drm_mode_status status; unsigned long long clock; @@ -575,7 +595,7 @@ static bool hdmi_try_format_bpc(const struct drm_connector *connector, struct drm_connector_state *conn_state, const struct drm_display_mode *mode, - unsigned int bpc, enum hdmi_colorspace fmt) + unsigned int bpc, enum drm_output_color_format fmt) { const struct drm_display_info *info = &connector->display_info; struct drm_device *dev = connector->dev; @@ -611,7 +631,7 @@ static int hdmi_compute_format_bpc(const struct drm_connector *connector, struct drm_connector_state *conn_state, const struct drm_display_mode *mode, - unsigned int max_bpc, enum hdmi_colorspace fmt) + unsigned int max_bpc, enum drm_output_color_format fmt) { struct drm_device *dev = connector->dev; unsigned int bpc; @@ -652,12 +672,12 @@ hdmi_compute_config(const struct drm_connector *connector, int ret; ret = hdmi_compute_format_bpc(connector, conn_state, mode, max_bpc, - HDMI_COLORSPACE_RGB); + DRM_OUTPUT_COLOR_FORMAT_RGB444); if (ret) { if (connector->ycbcr_420_allowed) { ret = hdmi_compute_format_bpc(connector, conn_state, mode, max_bpc, - HDMI_COLORSPACE_YUV420); + DRM_OUTPUT_COLOR_FORMAT_YCBCR420); if (ret) drm_dbg_kms(connector->dev, "YUV420 output format doesn't work.\n"); @@ -691,7 +711,9 @@ static int hdmi_generate_avi_infoframe(const struct drm_connector *connector, if (ret) return ret; - frame->colorspace = conn_state->hdmi.output_format; + frame->colorspace = + output_color_format_to_hdmi_colorspace(connector, + conn_state->hdmi.output_format); /* * FIXME: drm_hdmi_avi_infoframe_quant_range() doesn't handle @@ -889,7 +911,7 @@ drm_hdmi_connector_mode_valid(struct drm_connector *connector, { unsigned long long clock; - clock = drm_hdmi_compute_mode_clock(mode, 8, HDMI_COLORSPACE_RGB); + clock = drm_hdmi_compute_mode_clock(mode, 8, DRM_OUTPUT_COLOR_FORMAT_RGB444); if (!clock) return MODE_ERROR; diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index 30d957675d87..1c2903c6e44b 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -421,7 +421,7 @@ void drm_bridge_add(struct drm_bridge *bridge) if (bridge->ops & DRM_BRIDGE_OP_HDMI) bridge->ycbcr_420_allowed = !!(bridge->supported_formats & - BIT(HDMI_COLORSPACE_YUV420)); + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420)); mutex_lock(&bridge_lock); list_add_tail(&bridge->list, &bridge_list); diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index e70699c59c43..47dc53c4a738 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -552,7 +552,7 @@ EXPORT_SYMBOL(drmm_connector_init); * @hdmi_funcs: HDMI-related callbacks for this connector * @connector_type: user visible type of the connector * @ddc: optional pointer to the associated ddc adapter - * @supported_formats: Bitmask of @hdmi_colorspace listing supported output formats + * @supported_formats: Bitmask of @drm_output_color_format listing supported output formats * @max_bpc: Maximum bits per char the HDMI connector supports * * Initialises a preallocated HDMI connector. Connectors can be @@ -591,10 +591,10 @@ int drmm_connector_hdmi_init(struct drm_device *dev, connector_type == DRM_MODE_CONNECTOR_HDMIB)) return -EINVAL; - if (!supported_formats || !(supported_formats & BIT(HDMI_COLORSPACE_RGB))) + if (!supported_formats || !(supported_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444))) return -EINVAL; - if (connector->ycbcr_420_allowed != !!(supported_formats & BIT(HDMI_COLORSPACE_YUV420))) + if (connector->ycbcr_420_allowed != !!(supported_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420))) return -EINVAL; if (!(max_bpc == 8 || max_bpc == 10 || max_bpc == 12)) @@ -1431,10 +1431,10 @@ drm_hdmi_connector_get_broadcast_rgb_name(enum drm_hdmi_broadcast_rgb broadcast_ EXPORT_SYMBOL(drm_hdmi_connector_get_broadcast_rgb_name); static const char * const output_format_str[] = { - [HDMI_COLORSPACE_RGB] = "RGB", - [HDMI_COLORSPACE_YUV420] = "YUV 4:2:0", - [HDMI_COLORSPACE_YUV422] = "YUV 4:2:2", - [HDMI_COLORSPACE_YUV444] = "YUV 4:4:4", + [DRM_OUTPUT_COLOR_FORMAT_RGB444] = "RGB", + [DRM_OUTPUT_COLOR_FORMAT_YCBCR420] = "YUV 4:2:0", + [DRM_OUTPUT_COLOR_FORMAT_YCBCR422] = "YUV 4:2:2", + [DRM_OUTPUT_COLOR_FORMAT_YCBCR444] = "YUV 4:4:4", }; /* @@ -1445,7 +1445,7 @@ static const char * const output_format_str[] = { * valid. */ const char * -drm_hdmi_connector_get_output_format_name(enum hdmi_colorspace fmt) +drm_hdmi_connector_get_output_format_name(enum drm_output_color_format fmt) { if (fmt >= ARRAY_SIZE(output_format_str)) return NULL; diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_v2.c b/drivers/gpu/drm/mediatek/mtk_hdmi_v2.c index 279ca896b0a2..b5c738380dc2 100644 --- a/drivers/gpu/drm/mediatek/mtk_hdmi_v2.c +++ b/drivers/gpu/drm/mediatek/mtk_hdmi_v2.c @@ -747,12 +747,12 @@ static void mtk_hdmi_v2_change_video_resolution(struct mtk_hdmi *hdmi, switch (conn_state->hdmi.output_format) { default: - case HDMI_COLORSPACE_RGB: - case HDMI_COLORSPACE_YUV444: + case DRM_OUTPUT_COLOR_FORMAT_RGB444: + case DRM_OUTPUT_COLOR_FORMAT_YCBCR444: /* Disable YUV420 downsampling for RGB and YUV444 */ mtk_hdmi_yuv420_downsampling(hdmi, false); break; - case HDMI_COLORSPACE_YUV422: + case DRM_OUTPUT_COLOR_FORMAT_YCBCR422: /* * YUV420 downsampling is special and needs a bit of setup * so we disable everything there before doing anything else. @@ -763,7 +763,7 @@ static void mtk_hdmi_v2_change_video_resolution(struct mtk_hdmi *hdmi, regmap_set_bits(hdmi->regs, VID_DOWNSAMPLE_CONFIG, C444_C422_CONFIG_ENABLE); break; - case HDMI_COLORSPACE_YUV420: + case DRM_OUTPUT_COLOR_FORMAT_YCBCR420: mtk_hdmi_yuv420_downsampling(hdmi, true); break; } diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c index a50f260c73e4..dd2a78defdb4 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c @@ -661,7 +661,7 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master, &sun4i_hdmi_hdmi_connector_funcs, DRM_MODE_CONNECTOR_HDMIA, hdmi->ddc_i2c, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8); if (ret) { dev_err(dev, diff --git a/drivers/gpu/drm/tests/drm_connector_test.c b/drivers/gpu/drm/tests/drm_connector_test.c index 86860ad0861c..beb1d50a6646 100644 --- a/drivers/gpu/drm/tests/drm_connector_test.c +++ b/drivers/gpu/drm/tests/drm_connector_test.c @@ -675,7 +675,7 @@ static void drm_test_connector_hdmi_init_valid(struct kunit *test) &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, &priv->ddc, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8); KUNIT_EXPECT_EQ(test, ret, 0); } @@ -695,7 +695,7 @@ static void drm_test_connector_hdmi_init_null_ddc(struct kunit *test) &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, NULL, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8); KUNIT_EXPECT_EQ(test, ret, 0); } @@ -715,7 +715,7 @@ static void drm_test_connector_hdmi_init_null_vendor(struct kunit *test) &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, &priv->ddc, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8); KUNIT_EXPECT_LT(test, ret, 0); } @@ -735,7 +735,7 @@ static void drm_test_connector_hdmi_init_null_product(struct kunit *test) &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, &priv->ddc, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8); KUNIT_EXPECT_LT(test, ret, 0); } @@ -761,7 +761,7 @@ static void drm_test_connector_hdmi_init_product_valid(struct kunit *test) &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, &priv->ddc, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8); KUNIT_EXPECT_EQ(test, ret, 0); KUNIT_EXPECT_MEMEQ(test, @@ -794,7 +794,7 @@ static void drm_test_connector_hdmi_init_product_length_exact(struct kunit *test &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, &priv->ddc, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8); KUNIT_EXPECT_EQ(test, ret, 0); KUNIT_EXPECT_MEMEQ(test, @@ -821,7 +821,7 @@ static void drm_test_connector_hdmi_init_product_length_too_long(struct kunit *t &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, &priv->ddc, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8); KUNIT_EXPECT_LT(test, ret, 0); } @@ -847,7 +847,7 @@ static void drm_test_connector_hdmi_init_vendor_valid(struct kunit *test) &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, &priv->ddc, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8); KUNIT_EXPECT_EQ(test, ret, 0); KUNIT_EXPECT_MEMEQ(test, @@ -879,7 +879,7 @@ static void drm_test_connector_hdmi_init_vendor_length_exact(struct kunit *test) &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, &priv->ddc, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8); KUNIT_EXPECT_EQ(test, ret, 0); KUNIT_EXPECT_MEMEQ(test, @@ -906,7 +906,7 @@ static void drm_test_connector_hdmi_init_vendor_length_too_long(struct kunit *te &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, &priv->ddc, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8); KUNIT_EXPECT_LT(test, ret, 0); } @@ -926,7 +926,7 @@ static void drm_test_connector_hdmi_init_bpc_invalid(struct kunit *test) &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, &priv->ddc, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 9); KUNIT_EXPECT_LT(test, ret, 0); } @@ -946,7 +946,7 @@ static void drm_test_connector_hdmi_init_bpc_null(struct kunit *test) &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, &priv->ddc, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 0); KUNIT_EXPECT_LT(test, ret, 0); } @@ -971,7 +971,7 @@ static void drm_test_connector_hdmi_init_bpc_8(struct kunit *test) &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, &priv->ddc, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8); KUNIT_EXPECT_EQ(test, ret, 0); @@ -1012,7 +1012,7 @@ static void drm_test_connector_hdmi_init_bpc_10(struct kunit *test) &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, &priv->ddc, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 10); KUNIT_EXPECT_EQ(test, ret, 0); @@ -1053,7 +1053,7 @@ static void drm_test_connector_hdmi_init_bpc_12(struct kunit *test) &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, &priv->ddc, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 12); KUNIT_EXPECT_EQ(test, ret, 0); @@ -1109,7 +1109,7 @@ static void drm_test_connector_hdmi_init_formats_no_rgb(struct kunit *test) &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, &priv->ddc, - BIT(HDMI_COLORSPACE_YUV422), + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422), 8); KUNIT_EXPECT_LT(test, ret, 0); } @@ -1122,17 +1122,17 @@ struct drm_connector_hdmi_init_formats_yuv420_allowed_test { #define YUV420_ALLOWED_TEST(_formats, _allowed, _result) \ { \ - .supported_formats = BIT(HDMI_COLORSPACE_RGB) | (_formats), \ + .supported_formats = BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) | (_formats), \ .yuv420_allowed = _allowed, \ .expected_result = _result, \ } static const struct drm_connector_hdmi_init_formats_yuv420_allowed_test drm_connector_hdmi_init_formats_yuv420_allowed_tests[] = { - YUV420_ALLOWED_TEST(BIT(HDMI_COLORSPACE_YUV420), true, 0), - YUV420_ALLOWED_TEST(BIT(HDMI_COLORSPACE_YUV420), false, -EINVAL), - YUV420_ALLOWED_TEST(BIT(HDMI_COLORSPACE_YUV422), true, -EINVAL), - YUV420_ALLOWED_TEST(BIT(HDMI_COLORSPACE_YUV422), false, 0), + YUV420_ALLOWED_TEST(BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420), true, 0), + YUV420_ALLOWED_TEST(BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420), false, -EINVAL), + YUV420_ALLOWED_TEST(BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422), true, -EINVAL), + YUV420_ALLOWED_TEST(BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422), false, 0), }; static void @@ -1188,7 +1188,7 @@ static void drm_test_connector_hdmi_init_type_valid(struct kunit *test) &dummy_hdmi_funcs, connector_type, &priv->ddc, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8); KUNIT_EXPECT_EQ(test, ret, 0); } @@ -1223,7 +1223,7 @@ static void drm_test_connector_hdmi_init_type_invalid(struct kunit *test) &dummy_hdmi_funcs, connector_type, &priv->ddc, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8); KUNIT_EXPECT_LT(test, ret, 0); } @@ -1432,10 +1432,10 @@ static void drm_test_drm_hdmi_connector_get_output_format_name(struct kunit *tes static const struct drm_hdmi_connector_get_output_format_name_test drm_hdmi_connector_get_output_format_name_valid_tests[] = { - OUTPUT_FORMAT_TEST(HDMI_COLORSPACE_RGB, "RGB"), - OUTPUT_FORMAT_TEST(HDMI_COLORSPACE_YUV420, "YUV 4:2:0"), - OUTPUT_FORMAT_TEST(HDMI_COLORSPACE_YUV422, "YUV 4:2:2"), - OUTPUT_FORMAT_TEST(HDMI_COLORSPACE_YUV444, "YUV 4:4:4"), + OUTPUT_FORMAT_TEST(DRM_OUTPUT_COLOR_FORMAT_RGB444, "RGB"), + OUTPUT_FORMAT_TEST(DRM_OUTPUT_COLOR_FORMAT_YCBCR420, "YUV 4:2:0"), + OUTPUT_FORMAT_TEST(DRM_OUTPUT_COLOR_FORMAT_YCBCR422, "YUV 4:2:2"), + OUTPUT_FORMAT_TEST(DRM_OUTPUT_COLOR_FORMAT_YCBCR444, "YUV 4:4:4"), }; static void @@ -1500,7 +1500,7 @@ static void drm_test_drm_connector_attach_broadcast_rgb_property_hdmi_connector( &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, &priv->ddc, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8); KUNIT_EXPECT_EQ(test, ret, 0); @@ -1540,7 +1540,7 @@ static void drm_test_drm_hdmi_compute_mode_clock_rgb(struct kunit *test) KUNIT_ASSERT_FALSE(test, mode->flags & DRM_MODE_FLAG_DBLCLK); - rate = drm_hdmi_compute_mode_clock(mode, 8, HDMI_COLORSPACE_RGB); + rate = drm_hdmi_compute_mode_clock(mode, 8, DRM_OUTPUT_COLOR_FORMAT_RGB444); KUNIT_ASSERT_GT(test, rate, 0); KUNIT_EXPECT_EQ(test, mode->clock * 1000ULL, rate); } @@ -1561,7 +1561,7 @@ static void drm_test_drm_hdmi_compute_mode_clock_rgb_10bpc(struct kunit *test) KUNIT_ASSERT_FALSE(test, mode->flags & DRM_MODE_FLAG_DBLCLK); - rate = drm_hdmi_compute_mode_clock(mode, 10, HDMI_COLORSPACE_RGB); + rate = drm_hdmi_compute_mode_clock(mode, 10, DRM_OUTPUT_COLOR_FORMAT_RGB444); KUNIT_ASSERT_GT(test, rate, 0); KUNIT_EXPECT_EQ(test, mode->clock * 1250, rate); } @@ -1580,7 +1580,7 @@ static void drm_test_drm_hdmi_compute_mode_clock_rgb_10bpc_vic_1(struct kunit *t mode = drm_kunit_display_mode_from_cea_vic(test, drm, 1); KUNIT_ASSERT_NOT_NULL(test, mode); - rate = drm_hdmi_compute_mode_clock(mode, 10, HDMI_COLORSPACE_RGB); + rate = drm_hdmi_compute_mode_clock(mode, 10, DRM_OUTPUT_COLOR_FORMAT_RGB444); KUNIT_EXPECT_EQ(test, rate, 0); } @@ -1600,7 +1600,7 @@ static void drm_test_drm_hdmi_compute_mode_clock_rgb_12bpc(struct kunit *test) KUNIT_ASSERT_FALSE(test, mode->flags & DRM_MODE_FLAG_DBLCLK); - rate = drm_hdmi_compute_mode_clock(mode, 12, HDMI_COLORSPACE_RGB); + rate = drm_hdmi_compute_mode_clock(mode, 12, DRM_OUTPUT_COLOR_FORMAT_RGB444); KUNIT_ASSERT_GT(test, rate, 0); KUNIT_EXPECT_EQ(test, mode->clock * 1500, rate); } @@ -1619,7 +1619,7 @@ static void drm_test_drm_hdmi_compute_mode_clock_rgb_12bpc_vic_1(struct kunit *t mode = drm_kunit_display_mode_from_cea_vic(test, drm, 1); KUNIT_ASSERT_NOT_NULL(test, mode); - rate = drm_hdmi_compute_mode_clock(mode, 12, HDMI_COLORSPACE_RGB); + rate = drm_hdmi_compute_mode_clock(mode, 12, DRM_OUTPUT_COLOR_FORMAT_RGB444); KUNIT_EXPECT_EQ(test, rate, 0); } @@ -1639,7 +1639,7 @@ static void drm_test_drm_hdmi_compute_mode_clock_rgb_double(struct kunit *test) KUNIT_ASSERT_TRUE(test, mode->flags & DRM_MODE_FLAG_DBLCLK); - rate = drm_hdmi_compute_mode_clock(mode, 8, HDMI_COLORSPACE_RGB); + rate = drm_hdmi_compute_mode_clock(mode, 8, DRM_OUTPUT_COLOR_FORMAT_RGB444); KUNIT_ASSERT_GT(test, rate, 0); KUNIT_EXPECT_EQ(test, (mode->clock * 1000ULL) * 2, rate); } @@ -1662,7 +1662,7 @@ static void drm_test_connector_hdmi_compute_mode_clock_yuv420_valid(struct kunit KUNIT_ASSERT_FALSE(test, mode->flags & DRM_MODE_FLAG_DBLCLK); - rate = drm_hdmi_compute_mode_clock(mode, 8, HDMI_COLORSPACE_YUV420); + rate = drm_hdmi_compute_mode_clock(mode, 8, DRM_OUTPUT_COLOR_FORMAT_YCBCR420); KUNIT_ASSERT_GT(test, rate, 0); KUNIT_EXPECT_EQ(test, (mode->clock * 1000ULL) / 2, rate); } @@ -1699,7 +1699,7 @@ static void drm_test_connector_hdmi_compute_mode_clock_yuv420_10_bpc(struct kuni KUNIT_ASSERT_FALSE(test, mode->flags & DRM_MODE_FLAG_DBLCLK); - rate = drm_hdmi_compute_mode_clock(mode, 10, HDMI_COLORSPACE_YUV420); + rate = drm_hdmi_compute_mode_clock(mode, 10, DRM_OUTPUT_COLOR_FORMAT_YCBCR420); KUNIT_ASSERT_GT(test, rate, 0); KUNIT_EXPECT_EQ(test, mode->clock * 625, rate); @@ -1724,7 +1724,7 @@ static void drm_test_connector_hdmi_compute_mode_clock_yuv420_12_bpc(struct kuni KUNIT_ASSERT_FALSE(test, mode->flags & DRM_MODE_FLAG_DBLCLK); - rate = drm_hdmi_compute_mode_clock(mode, 12, HDMI_COLORSPACE_YUV420); + rate = drm_hdmi_compute_mode_clock(mode, 12, DRM_OUTPUT_COLOR_FORMAT_YCBCR420); KUNIT_ASSERT_GT(test, rate, 0); KUNIT_EXPECT_EQ(test, mode->clock * 750, rate); @@ -1747,7 +1747,7 @@ static void drm_test_connector_hdmi_compute_mode_clock_yuv422_8_bpc(struct kunit KUNIT_ASSERT_FALSE(test, mode->flags & DRM_MODE_FLAG_DBLCLK); - rate = drm_hdmi_compute_mode_clock(mode, 8, HDMI_COLORSPACE_YUV422); + rate = drm_hdmi_compute_mode_clock(mode, 8, DRM_OUTPUT_COLOR_FORMAT_YCBCR422); KUNIT_ASSERT_GT(test, rate, 0); KUNIT_EXPECT_EQ(test, mode->clock * 1000, rate); } @@ -1769,7 +1769,7 @@ static void drm_test_connector_hdmi_compute_mode_clock_yuv422_10_bpc(struct kuni KUNIT_ASSERT_FALSE(test, mode->flags & DRM_MODE_FLAG_DBLCLK); - rate = drm_hdmi_compute_mode_clock(mode, 10, HDMI_COLORSPACE_YUV422); + rate = drm_hdmi_compute_mode_clock(mode, 10, DRM_OUTPUT_COLOR_FORMAT_YCBCR422); KUNIT_ASSERT_GT(test, rate, 0); KUNIT_EXPECT_EQ(test, mode->clock * 1000, rate); } @@ -1791,7 +1791,7 @@ static void drm_test_connector_hdmi_compute_mode_clock_yuv422_12_bpc(struct kuni KUNIT_ASSERT_FALSE(test, mode->flags & DRM_MODE_FLAG_DBLCLK); - rate = drm_hdmi_compute_mode_clock(mode, 12, HDMI_COLORSPACE_YUV422); + rate = drm_hdmi_compute_mode_clock(mode, 12, DRM_OUTPUT_COLOR_FORMAT_YCBCR422); KUNIT_ASSERT_GT(test, rate, 0); KUNIT_EXPECT_EQ(test, mode->clock * 1000, rate); } diff --git a/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c b/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c index 4bdcea3c7435..a4357efaa983 100644 --- a/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c +++ b/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c @@ -239,7 +239,7 @@ __connector_hdmi_init(struct kunit *test, enc->possible_crtcs = drm_crtc_mask(priv->crtc); conn = &priv->connector; - conn->ycbcr_420_allowed = !!(formats & BIT(HDMI_COLORSPACE_YUV420)); + conn->ycbcr_420_allowed = !!(formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420)); ret = drmm_connector_hdmi_init(drm, conn, "Vendor", "Product", @@ -300,7 +300,7 @@ static void drm_test_check_broadcast_rgb_crtc_mode_changed(struct kunit *test) int ret; priv = drm_kunit_helper_connector_hdmi_init(test, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8); KUNIT_ASSERT_NOT_NULL(test, priv); @@ -375,7 +375,7 @@ static void drm_test_check_broadcast_rgb_crtc_mode_not_changed(struct kunit *tes int ret; priv = drm_kunit_helper_connector_hdmi_init(test, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8); KUNIT_ASSERT_NOT_NULL(test, priv); @@ -450,7 +450,7 @@ static void drm_test_check_broadcast_rgb_auto_cea_mode(struct kunit *test) int ret; priv = drm_kunit_helper_connector_hdmi_init(test, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8); KUNIT_ASSERT_NOT_NULL(test, priv); @@ -517,7 +517,7 @@ static void drm_test_check_broadcast_rgb_auto_cea_mode_vic_1(struct kunit *test) int ret; priv = drm_kunit_helper_connector_hdmi_init(test, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8); KUNIT_ASSERT_NOT_NULL(test, priv); @@ -584,7 +584,7 @@ static void drm_test_check_broadcast_rgb_full_cea_mode(struct kunit *test) int ret; priv = drm_kunit_helper_connector_hdmi_init(test, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8); KUNIT_ASSERT_NOT_NULL(test, priv); @@ -653,7 +653,7 @@ static void drm_test_check_broadcast_rgb_full_cea_mode_vic_1(struct kunit *test) int ret; priv = drm_kunit_helper_connector_hdmi_init(test, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8); KUNIT_ASSERT_NOT_NULL(test, priv); @@ -722,7 +722,7 @@ static void drm_test_check_broadcast_rgb_limited_cea_mode(struct kunit *test) int ret; priv = drm_kunit_helper_connector_hdmi_init(test, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8); KUNIT_ASSERT_NOT_NULL(test, priv); @@ -791,7 +791,7 @@ static void drm_test_check_broadcast_rgb_limited_cea_mode_vic_1(struct kunit *te int ret; priv = drm_kunit_helper_connector_hdmi_init(test, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8); KUNIT_ASSERT_NOT_NULL(test, priv); @@ -863,8 +863,8 @@ static void drm_test_check_broadcast_rgb_cea_mode_yuv420(struct kunit *test) broadcast_rgb = *(enum drm_hdmi_broadcast_rgb *)test->param_value; priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, - BIT(HDMI_COLORSPACE_RGB) | - BIT(HDMI_COLORSPACE_YUV420), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420), 8, &dummy_connector_hdmi_funcs, test_edid_hdmi_1080p_rgb_yuv_4k_yuv420_dc_max_200mhz); @@ -918,7 +918,7 @@ retry_conn_state: KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state); KUNIT_ASSERT_EQ(test, conn_state->hdmi.broadcast_rgb, broadcast_rgb); - KUNIT_ASSERT_EQ(test, conn_state->hdmi.output_format, HDMI_COLORSPACE_YUV420); + KUNIT_ASSERT_EQ(test, conn_state->hdmi.output_format, DRM_OUTPUT_COLOR_FORMAT_YCBCR420); KUNIT_EXPECT_TRUE(test, conn_state->hdmi.is_limited_range); @@ -963,7 +963,7 @@ static void drm_test_check_output_bpc_crtc_mode_changed(struct kunit *test) int ret; priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 10, &dummy_connector_hdmi_funcs, test_edid_hdmi_1080p_rgb_yuv_dc_max_200mhz); @@ -1045,7 +1045,7 @@ static void drm_test_check_output_bpc_crtc_mode_not_changed(struct kunit *test) int ret; priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 10, &dummy_connector_hdmi_funcs, test_edid_hdmi_1080p_rgb_yuv_dc_max_200mhz); @@ -1122,9 +1122,9 @@ static void drm_test_check_output_bpc_dvi(struct kunit *test) int ret; priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, - BIT(HDMI_COLORSPACE_RGB) | - BIT(HDMI_COLORSPACE_YUV422) | - BIT(HDMI_COLORSPACE_YUV444), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444), 12, &dummy_connector_hdmi_funcs, test_edid_dvi_1080p); @@ -1157,7 +1157,7 @@ retry_conn_enable: KUNIT_ASSERT_NOT_NULL(test, conn_state); KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_bpc, 8); - KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, HDMI_COLORSPACE_RGB); + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, DRM_OUTPUT_COLOR_FORMAT_RGB444); drm_modeset_drop_locks(&ctx); drm_modeset_acquire_fini(&ctx); @@ -1179,7 +1179,7 @@ static void drm_test_check_tmds_char_rate_rgb_8bpc(struct kunit *test) int ret; priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8, &dummy_connector_hdmi_funcs, test_edid_hdmi_1080p_rgb_max_200mhz); @@ -1210,7 +1210,7 @@ retry_conn_enable: KUNIT_ASSERT_NOT_NULL(test, conn_state); KUNIT_ASSERT_EQ(test, conn_state->hdmi.output_bpc, 8); - KUNIT_ASSERT_EQ(test, conn_state->hdmi.output_format, HDMI_COLORSPACE_RGB); + KUNIT_ASSERT_EQ(test, conn_state->hdmi.output_format, DRM_OUTPUT_COLOR_FORMAT_RGB444); KUNIT_EXPECT_EQ(test, conn_state->hdmi.tmds_char_rate, preferred->clock * 1000); drm_modeset_drop_locks(&ctx); @@ -1234,7 +1234,7 @@ static void drm_test_check_tmds_char_rate_rgb_10bpc(struct kunit *test) int ret; priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 10, &dummy_connector_hdmi_funcs, test_edid_hdmi_1080p_rgb_yuv_dc_max_340mhz); @@ -1265,7 +1265,7 @@ retry_conn_enable: KUNIT_ASSERT_NOT_NULL(test, conn_state); KUNIT_ASSERT_EQ(test, conn_state->hdmi.output_bpc, 10); - KUNIT_ASSERT_EQ(test, conn_state->hdmi.output_format, HDMI_COLORSPACE_RGB); + KUNIT_ASSERT_EQ(test, conn_state->hdmi.output_format, DRM_OUTPUT_COLOR_FORMAT_RGB444); KUNIT_EXPECT_EQ(test, conn_state->hdmi.tmds_char_rate, preferred->clock * 1250); drm_modeset_drop_locks(&ctx); @@ -1289,7 +1289,7 @@ static void drm_test_check_tmds_char_rate_rgb_12bpc(struct kunit *test) int ret; priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 12, &dummy_connector_hdmi_funcs, test_edid_hdmi_1080p_rgb_yuv_dc_max_340mhz); @@ -1320,7 +1320,7 @@ retry_conn_enable: KUNIT_ASSERT_NOT_NULL(test, conn_state); KUNIT_ASSERT_EQ(test, conn_state->hdmi.output_bpc, 12); - KUNIT_ASSERT_EQ(test, conn_state->hdmi.output_format, HDMI_COLORSPACE_RGB); + KUNIT_ASSERT_EQ(test, conn_state->hdmi.output_format, DRM_OUTPUT_COLOR_FORMAT_RGB444); KUNIT_EXPECT_EQ(test, conn_state->hdmi.tmds_char_rate, preferred->clock * 1500); drm_modeset_drop_locks(&ctx); @@ -1348,7 +1348,7 @@ static void drm_test_check_hdmi_funcs_reject_rate(struct kunit *test) int ret; priv = drm_kunit_helper_connector_hdmi_init(test, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8); KUNIT_ASSERT_NOT_NULL(test, priv); @@ -1416,7 +1416,7 @@ static void drm_test_check_max_tmds_rate_bpc_fallback_rgb(struct kunit *test) int ret; priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 12, &dummy_connector_hdmi_funcs, test_edid_hdmi_1080p_rgb_yuv_dc_max_200mhz); @@ -1433,10 +1433,10 @@ static void drm_test_check_max_tmds_rate_bpc_fallback_rgb(struct kunit *test) KUNIT_ASSERT_NOT_NULL(test, preferred); KUNIT_ASSERT_FALSE(test, preferred->flags & DRM_MODE_FLAG_DBLCLK); - rate = drm_hdmi_compute_mode_clock(preferred, 12, HDMI_COLORSPACE_RGB); + rate = drm_hdmi_compute_mode_clock(preferred, 12, DRM_OUTPUT_COLOR_FORMAT_RGB444); KUNIT_ASSERT_GT(test, rate, info->max_tmds_clock * 1000); - rate = drm_hdmi_compute_mode_clock(preferred, 10, HDMI_COLORSPACE_RGB); + rate = drm_hdmi_compute_mode_clock(preferred, 10, DRM_OUTPUT_COLOR_FORMAT_RGB444); KUNIT_ASSERT_LT(test, rate, info->max_tmds_clock * 1000); drm_modeset_acquire_init(&ctx, 0); @@ -1457,7 +1457,7 @@ retry_conn_enable: KUNIT_ASSERT_NOT_NULL(test, conn_state); KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_bpc, 10); - KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, HDMI_COLORSPACE_RGB); + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, DRM_OUTPUT_COLOR_FORMAT_RGB444); KUNIT_EXPECT_EQ(test, conn_state->hdmi.tmds_char_rate, preferred->clock * 1250); drm_modeset_drop_locks(&ctx); @@ -1490,8 +1490,8 @@ static void drm_test_check_max_tmds_rate_bpc_fallback_yuv420(struct kunit *test) int ret; priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, - BIT(HDMI_COLORSPACE_RGB) | - BIT(HDMI_COLORSPACE_YUV420), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420), 12, &dummy_connector_hdmi_funcs, test_edid_hdmi_1080p_rgb_yuv_4k_yuv420_dc_max_200mhz); @@ -1509,10 +1509,10 @@ static void drm_test_check_max_tmds_rate_bpc_fallback_yuv420(struct kunit *test) KUNIT_ASSERT_NOT_NULL(test, yuv420_only_mode); KUNIT_ASSERT_TRUE(test, drm_mode_is_420_only(info, yuv420_only_mode)); - rate = drm_hdmi_compute_mode_clock(yuv420_only_mode, 12, HDMI_COLORSPACE_YUV420); + rate = drm_hdmi_compute_mode_clock(yuv420_only_mode, 12, DRM_OUTPUT_COLOR_FORMAT_YCBCR420); KUNIT_ASSERT_GT(test, rate, info->max_tmds_clock * 1000); - rate = drm_hdmi_compute_mode_clock(yuv420_only_mode, 10, HDMI_COLORSPACE_YUV420); + rate = drm_hdmi_compute_mode_clock(yuv420_only_mode, 10, DRM_OUTPUT_COLOR_FORMAT_YCBCR420); KUNIT_ASSERT_LT(test, rate, info->max_tmds_clock * 1000); drm_modeset_acquire_init(&ctx, 0); @@ -1531,7 +1531,7 @@ retry_conn_enable: KUNIT_ASSERT_NOT_NULL(test, conn_state); KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_bpc, 10); - KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, HDMI_COLORSPACE_YUV420); + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, DRM_OUTPUT_COLOR_FORMAT_YCBCR420); KUNIT_EXPECT_EQ(test, conn_state->hdmi.tmds_char_rate, yuv420_only_mode->clock * 625); drm_modeset_drop_locks(&ctx); @@ -1565,9 +1565,9 @@ static void drm_test_check_max_tmds_rate_bpc_fallback_ignore_yuv422(struct kunit int ret; priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, - BIT(HDMI_COLORSPACE_RGB) | - BIT(HDMI_COLORSPACE_YUV422) | - BIT(HDMI_COLORSPACE_YUV444), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444), 12, &dummy_connector_hdmi_funcs, test_edid_hdmi_1080p_rgb_yuv_dc_max_200mhz); @@ -1584,13 +1584,13 @@ static void drm_test_check_max_tmds_rate_bpc_fallback_ignore_yuv422(struct kunit KUNIT_ASSERT_NOT_NULL(test, preferred); KUNIT_ASSERT_FALSE(test, preferred->flags & DRM_MODE_FLAG_DBLCLK); - rate = drm_hdmi_compute_mode_clock(preferred, 10, HDMI_COLORSPACE_RGB); + rate = drm_hdmi_compute_mode_clock(preferred, 10, DRM_OUTPUT_COLOR_FORMAT_RGB444); KUNIT_ASSERT_LT(test, rate, info->max_tmds_clock * 1000); - rate = drm_hdmi_compute_mode_clock(preferred, 12, HDMI_COLORSPACE_RGB); + rate = drm_hdmi_compute_mode_clock(preferred, 12, DRM_OUTPUT_COLOR_FORMAT_RGB444); KUNIT_ASSERT_GT(test, rate, info->max_tmds_clock * 1000); - rate = drm_hdmi_compute_mode_clock(preferred, 12, HDMI_COLORSPACE_YUV422); + rate = drm_hdmi_compute_mode_clock(preferred, 12, DRM_OUTPUT_COLOR_FORMAT_YCBCR422); KUNIT_ASSERT_LT(test, rate, info->max_tmds_clock * 1000); drm_modeset_acquire_init(&ctx, 0); @@ -1611,7 +1611,7 @@ retry_conn_enable: KUNIT_ASSERT_NOT_NULL(test, conn_state); KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_bpc, 10); - KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, HDMI_COLORSPACE_RGB); + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, DRM_OUTPUT_COLOR_FORMAT_RGB444); drm_modeset_drop_locks(&ctx); drm_modeset_acquire_fini(&ctx); @@ -1644,8 +1644,8 @@ static void drm_test_check_max_tmds_rate_bpc_fallback_ignore_yuv420(struct kunit int ret; priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, - BIT(HDMI_COLORSPACE_RGB) | - BIT(HDMI_COLORSPACE_YUV420), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420), 12, &dummy_connector_hdmi_funcs, test_edid_hdmi_4k_rgb_yuv420_dc_max_340mhz); @@ -1664,13 +1664,13 @@ static void drm_test_check_max_tmds_rate_bpc_fallback_ignore_yuv420(struct kunit KUNIT_ASSERT_FALSE(test, preferred->flags & DRM_MODE_FLAG_DBLCLK); KUNIT_ASSERT_TRUE(test, drm_mode_is_420_also(info, preferred)); - rate = drm_hdmi_compute_mode_clock(preferred, 8, HDMI_COLORSPACE_RGB); + rate = drm_hdmi_compute_mode_clock(preferred, 8, DRM_OUTPUT_COLOR_FORMAT_RGB444); KUNIT_ASSERT_LT(test, rate, info->max_tmds_clock * 1000); - rate = drm_hdmi_compute_mode_clock(preferred, 10, HDMI_COLORSPACE_RGB); + rate = drm_hdmi_compute_mode_clock(preferred, 10, DRM_OUTPUT_COLOR_FORMAT_RGB444); KUNIT_ASSERT_GT(test, rate, info->max_tmds_clock * 1000); - rate = drm_hdmi_compute_mode_clock(preferred, 12, HDMI_COLORSPACE_YUV420); + rate = drm_hdmi_compute_mode_clock(preferred, 12, DRM_OUTPUT_COLOR_FORMAT_YCBCR420); KUNIT_ASSERT_LT(test, rate, info->max_tmds_clock * 1000); drm_modeset_acquire_init(&ctx, 0); @@ -1689,7 +1689,7 @@ retry_conn_enable: KUNIT_ASSERT_NOT_NULL(test, conn_state); KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_bpc, 8); - KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, HDMI_COLORSPACE_RGB); + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, DRM_OUTPUT_COLOR_FORMAT_RGB444); drm_modeset_drop_locks(&ctx); drm_modeset_acquire_fini(&ctx); @@ -1715,7 +1715,7 @@ static void drm_test_check_driver_unsupported_fallback_yuv420(struct kunit *test int ret; priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 12, &dummy_connector_hdmi_funcs, test_edid_hdmi_1080p_rgb_yuv_4k_yuv420_dc_max_200mhz); @@ -1750,7 +1750,7 @@ retry_conn_enable: conn_state = conn->state; KUNIT_ASSERT_NOT_NULL(test, conn_state); - KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, HDMI_COLORSPACE_RGB); + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, DRM_OUTPUT_COLOR_FORMAT_RGB444); state = drm_kunit_helper_atomic_state_alloc(test, drm, &ctx); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state); @@ -1800,9 +1800,9 @@ static void drm_test_check_output_bpc_format_vic_1(struct kunit *test) int ret; priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, - BIT(HDMI_COLORSPACE_RGB) | - BIT(HDMI_COLORSPACE_YUV422) | - BIT(HDMI_COLORSPACE_YUV444), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444), 12, &dummy_connector_hdmi_funcs, test_edid_hdmi_1080p_rgb_yuv_dc_max_200mhz); @@ -1847,7 +1847,7 @@ retry_conn_enable: KUNIT_ASSERT_NOT_NULL(test, conn_state); KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_bpc, 8); - KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, HDMI_COLORSPACE_RGB); + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, DRM_OUTPUT_COLOR_FORMAT_RGB444); drm_modeset_drop_locks(&ctx); drm_modeset_acquire_fini(&ctx); @@ -1871,7 +1871,7 @@ static void drm_test_check_output_bpc_format_driver_rgb_only(struct kunit *test) int ret; priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 12, &dummy_connector_hdmi_funcs, test_edid_hdmi_1080p_rgb_yuv_dc_max_200mhz); @@ -1896,10 +1896,10 @@ static void drm_test_check_output_bpc_format_driver_rgb_only(struct kunit *test) * But since the driver only supports RGB, we should fallback to * a lower bpc with RGB. */ - rate = drm_hdmi_compute_mode_clock(preferred, 12, HDMI_COLORSPACE_RGB); + rate = drm_hdmi_compute_mode_clock(preferred, 12, DRM_OUTPUT_COLOR_FORMAT_RGB444); KUNIT_ASSERT_GT(test, rate, info->max_tmds_clock * 1000); - rate = drm_hdmi_compute_mode_clock(preferred, 12, HDMI_COLORSPACE_YUV422); + rate = drm_hdmi_compute_mode_clock(preferred, 12, DRM_OUTPUT_COLOR_FORMAT_YCBCR422); KUNIT_ASSERT_LT(test, rate, info->max_tmds_clock * 1000); drm_modeset_acquire_init(&ctx, 0); @@ -1920,7 +1920,7 @@ retry_conn_enable: KUNIT_ASSERT_NOT_NULL(test, conn_state); KUNIT_EXPECT_LT(test, conn_state->hdmi.output_bpc, 12); - KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, HDMI_COLORSPACE_RGB); + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, DRM_OUTPUT_COLOR_FORMAT_RGB444); drm_modeset_drop_locks(&ctx); drm_modeset_acquire_fini(&ctx); @@ -1944,9 +1944,9 @@ static void drm_test_check_output_bpc_format_display_rgb_only(struct kunit *test int ret; priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, - BIT(HDMI_COLORSPACE_RGB) | - BIT(HDMI_COLORSPACE_YUV422) | - BIT(HDMI_COLORSPACE_YUV444), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444), 12, &dummy_connector_hdmi_funcs, test_edid_hdmi_1080p_rgb_max_200mhz); @@ -1971,10 +1971,10 @@ static void drm_test_check_output_bpc_format_display_rgb_only(struct kunit *test * But since the display only supports RGB, we should fallback to * a lower bpc with RGB. */ - rate = drm_hdmi_compute_mode_clock(preferred, 12, HDMI_COLORSPACE_RGB); + rate = drm_hdmi_compute_mode_clock(preferred, 12, DRM_OUTPUT_COLOR_FORMAT_RGB444); KUNIT_ASSERT_GT(test, rate, info->max_tmds_clock * 1000); - rate = drm_hdmi_compute_mode_clock(preferred, 12, HDMI_COLORSPACE_YUV422); + rate = drm_hdmi_compute_mode_clock(preferred, 12, DRM_OUTPUT_COLOR_FORMAT_YCBCR422); KUNIT_ASSERT_LT(test, rate, info->max_tmds_clock * 1000); drm_modeset_acquire_init(&ctx, 0); @@ -1995,7 +1995,7 @@ retry_conn_enable: KUNIT_ASSERT_NOT_NULL(test, conn_state); KUNIT_EXPECT_LT(test, conn_state->hdmi.output_bpc, 12); - KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, HDMI_COLORSPACE_RGB); + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, DRM_OUTPUT_COLOR_FORMAT_RGB444); drm_modeset_drop_locks(&ctx); drm_modeset_acquire_fini(&ctx); @@ -2020,7 +2020,7 @@ static void drm_test_check_output_bpc_format_driver_8bpc_only(struct kunit *test int ret; priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8, &dummy_connector_hdmi_funcs, test_edid_hdmi_1080p_rgb_yuv_dc_max_340mhz); @@ -2040,7 +2040,7 @@ static void drm_test_check_output_bpc_format_driver_8bpc_only(struct kunit *test * We're making sure that we have headroom on the TMDS character * clock to actually use 12bpc. */ - rate = drm_hdmi_compute_mode_clock(preferred, 12, HDMI_COLORSPACE_RGB); + rate = drm_hdmi_compute_mode_clock(preferred, 12, DRM_OUTPUT_COLOR_FORMAT_RGB444); KUNIT_ASSERT_LT(test, rate, info->max_tmds_clock * 1000); drm_modeset_acquire_init(&ctx, 0); @@ -2061,7 +2061,7 @@ retry_conn_enable: KUNIT_ASSERT_NOT_NULL(test, conn_state); KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_bpc, 8); - KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, HDMI_COLORSPACE_RGB); + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, DRM_OUTPUT_COLOR_FORMAT_RGB444); drm_modeset_drop_locks(&ctx); drm_modeset_acquire_fini(&ctx); @@ -2086,9 +2086,9 @@ static void drm_test_check_output_bpc_format_display_8bpc_only(struct kunit *tes int ret; priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, - BIT(HDMI_COLORSPACE_RGB) | - BIT(HDMI_COLORSPACE_YUV422) | - BIT(HDMI_COLORSPACE_YUV444), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444), 12, &dummy_connector_hdmi_funcs, test_edid_hdmi_1080p_rgb_max_340mhz); @@ -2108,7 +2108,7 @@ static void drm_test_check_output_bpc_format_display_8bpc_only(struct kunit *tes * We're making sure that we have headroom on the TMDS character * clock to actually use 12bpc. */ - rate = drm_hdmi_compute_mode_clock(preferred, 12, HDMI_COLORSPACE_RGB); + rate = drm_hdmi_compute_mode_clock(preferred, 12, DRM_OUTPUT_COLOR_FORMAT_RGB444); KUNIT_ASSERT_LT(test, rate, info->max_tmds_clock * 1000); drm_modeset_acquire_init(&ctx, 0); @@ -2129,7 +2129,7 @@ retry_conn_enable: KUNIT_ASSERT_NOT_NULL(test, conn_state); KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_bpc, 8); - KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, HDMI_COLORSPACE_RGB); + KUNIT_EXPECT_EQ(test, conn_state->hdmi.output_format, DRM_OUTPUT_COLOR_FORMAT_RGB444); drm_modeset_drop_locks(&ctx); drm_modeset_acquire_fini(&ctx); @@ -2150,7 +2150,7 @@ static void drm_test_check_disable_connector(struct kunit *test) int ret; priv = drm_kunit_helper_connector_hdmi_init(test, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8); KUNIT_ASSERT_NOT_NULL(test, priv); @@ -2255,7 +2255,7 @@ static void drm_test_check_broadcast_rgb_value(struct kunit *test) struct drm_connector *conn; priv = drm_kunit_helper_connector_hdmi_init(test, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8); KUNIT_ASSERT_NOT_NULL(test, priv); @@ -2277,7 +2277,7 @@ static void drm_test_check_bpc_8_value(struct kunit *test) struct drm_connector *conn; priv = drm_kunit_helper_connector_hdmi_init(test, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8); KUNIT_ASSERT_NOT_NULL(test, priv); @@ -2301,7 +2301,7 @@ static void drm_test_check_bpc_10_value(struct kunit *test) struct drm_connector *conn; priv = drm_kunit_helper_connector_hdmi_init(test, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 10); KUNIT_ASSERT_NOT_NULL(test, priv); @@ -2325,7 +2325,7 @@ static void drm_test_check_bpc_12_value(struct kunit *test) struct drm_connector *conn; priv = drm_kunit_helper_connector_hdmi_init(test, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 12); KUNIT_ASSERT_NOT_NULL(test, priv); @@ -2347,9 +2347,9 @@ static void drm_test_check_format_value(struct kunit *test) struct drm_connector *conn; priv = drm_kunit_helper_connector_hdmi_init(test, - BIT(HDMI_COLORSPACE_RGB) | - BIT(HDMI_COLORSPACE_YUV422) | - BIT(HDMI_COLORSPACE_YUV444), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444), 8); KUNIT_ASSERT_NOT_NULL(test, priv); @@ -2369,9 +2369,9 @@ static void drm_test_check_tmds_char_value(struct kunit *test) struct drm_connector *conn; priv = drm_kunit_helper_connector_hdmi_init(test, - BIT(HDMI_COLORSPACE_RGB) | - BIT(HDMI_COLORSPACE_YUV422) | - BIT(HDMI_COLORSPACE_YUV444), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444), 12); KUNIT_ASSERT_NOT_NULL(test, priv); @@ -2407,7 +2407,7 @@ static void drm_test_check_mode_valid(struct kunit *test) struct drm_display_mode *preferred; priv = drm_kunit_helper_connector_hdmi_init(test, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8); KUNIT_ASSERT_NOT_NULL(test, priv); @@ -2431,7 +2431,7 @@ static void drm_test_check_mode_valid_reject_rate(struct kunit *test) struct drm_display_mode *preferred; priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8, &reject_100mhz_connector_hdmi_funcs, test_edid_hdmi_1080p_rgb_max_200mhz); @@ -2463,7 +2463,7 @@ static void drm_test_check_mode_valid_reject(struct kunit *test) int ret; priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8, &reject_connector_hdmi_funcs, no_edid); @@ -2493,7 +2493,7 @@ static void drm_test_check_mode_valid_reject_max_clock(struct kunit *test) struct drm_display_mode *preferred; priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8, &dummy_connector_hdmi_funcs, test_edid_hdmi_1080p_rgb_max_100mhz); @@ -2540,7 +2540,7 @@ static void drm_test_check_infoframes(struct kunit *test) int ret; priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8, &dummy_connector_hdmi_funcs, test_edid_hdmi_1080p_rgb_max_200mhz); @@ -2643,7 +2643,7 @@ static void drm_test_check_reject_avi_infoframe(struct kunit *test) int ret; priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8, &reject_avi_infoframe_hdmi_funcs, test_edid_hdmi_1080p_rgb_max_200mhz); @@ -2747,7 +2747,7 @@ static void drm_test_check_reject_hdr_infoframe_bpc_8(struct kunit *test) int ret; priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8, &reject_hdr_infoframe_hdmi_funcs, test_edid_hdmi_1080p_rgb_max_200mhz_hdr); @@ -2861,7 +2861,7 @@ static void drm_test_check_reject_hdr_infoframe_bpc_10(struct kunit *test) int ret; priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 10, &reject_hdr_infoframe_hdmi_funcs, test_edid_hdmi_1080p_rgb_max_200mhz_hdr); @@ -2996,7 +2996,7 @@ static void drm_test_check_reject_audio_infoframe(struct kunit *test) int ret; priv = drm_kunit_helper_connector_hdmi_init_with_edid_funcs(test, - BIT(HDMI_COLORSPACE_RGB), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), 8, &reject_audio_infoframe_hdmi_funcs, test_edid_hdmi_1080p_rgb_max_200mhz); diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 9898e5451a07..a99f53dadb28 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -133,7 +133,7 @@ static bool vc4_hdmi_supports_scrambling(struct vc4_hdmi *vc4_hdmi) static bool vc4_hdmi_mode_needs_scrambling(const struct drm_display_mode *mode, unsigned int bpc, - enum hdmi_colorspace fmt) + enum drm_output_color_format fmt) { unsigned long long clock = drm_hdmi_compute_mode_clock(mode, bpc, fmt); @@ -444,7 +444,7 @@ static int vc4_hdmi_connector_get_modes(struct drm_connector *connector) const struct drm_display_mode *mode; list_for_each_entry(mode, &connector->probed_modes, head) { - if (vc4_hdmi_mode_needs_scrambling(mode, 8, HDMI_COLORSPACE_RGB)) { + if (vc4_hdmi_mode_needs_scrambling(mode, 8, DRM_OUTPUT_COLOR_FORMAT_RGB444)) { drm_warn_once(drm, "The core clock cannot reach frequencies high enough to support 4k @ 60Hz."); drm_warn_once(drm, "Please change your config.txt file to add hdmi_enable_4kp60."); } @@ -547,9 +547,9 @@ static int vc4_hdmi_connector_init(struct drm_device *dev, &vc4_hdmi_hdmi_connector_funcs, DRM_MODE_CONNECTOR_HDMIA, vc4_hdmi->ddc, - BIT(HDMI_COLORSPACE_RGB) | - BIT(HDMI_COLORSPACE_YUV422) | - BIT(HDMI_COLORSPACE_YUV444), + BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444), max_bpc); if (ret) return ret; @@ -1214,13 +1214,13 @@ static void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); switch (state->hdmi.output_format) { - case HDMI_COLORSPACE_YUV444: + case DRM_OUTPUT_COLOR_FORMAT_YCBCR444: csc = vc5_hdmi_find_yuv_csc_coeffs(vc4_hdmi, state->colorspace, !!lim_range); vc5_hdmi_set_csc_coeffs_swap(vc4_hdmi, csc); break; - case HDMI_COLORSPACE_YUV422: + case DRM_OUTPUT_COLOR_FORMAT_YCBCR422: csc = vc5_hdmi_find_yuv_csc_coeffs(vc4_hdmi, state->colorspace, !!lim_range); csc_ctl |= VC4_SET_FIELD(VC5_MT_CP_CSC_CTL_FILTER_MODE_444_TO_422_STANDARD, @@ -1237,7 +1237,7 @@ static void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, vc5_hdmi_set_csc_coeffs(vc4_hdmi, csc); break; - case HDMI_COLORSPACE_RGB: + case DRM_OUTPUT_COLOR_FORMAT_RGB444: if_xbar = 0x354021; vc5_hdmi_set_csc_coeffs(vc4_hdmi, vc5_hdmi_csc_full_rgb_to_rgb[lim_range]); @@ -1394,7 +1394,7 @@ static void vc5_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi, * YCC422 is always 36-bit and not considered deep colour so * doesn't signal in GCP. */ - if (state->hdmi.output_format == HDMI_COLORSPACE_YUV422) { + if (state->hdmi.output_format == DRM_OUTPUT_COLOR_FORMAT_YCBCR422) { gcp = 0; } diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.h b/drivers/gpu/drm/vc4/vc4_hdmi.h index 8d069718df00..29d461d4ee49 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.h +++ b/drivers/gpu/drm/vc4/vc4_hdmi.h @@ -210,7 +210,7 @@ struct vc4_hdmi { * @drm_connector_state.hdmi.output_format for use outside of * KMS hooks. Protected by @mutex. */ - enum hdmi_colorspace output_format; + enum drm_output_color_format output_format; /** * @hdmi_jack: Represents the connection state of the HDMI plug, for diff --git a/include/drm/bridge/dw_hdmi_qp.h b/include/drm/bridge/dw_hdmi_qp.h index 3af12f82da2c..6ea9c561cfef 100644 --- a/include/drm/bridge/dw_hdmi_qp.h +++ b/include/drm/bridge/dw_hdmi_qp.h @@ -25,7 +25,7 @@ struct dw_hdmi_qp_plat_data { int main_irq; int cec_irq; unsigned long ref_clk_rate; - /* Supported output formats: bitmask of @hdmi_colorspace */ + /* Supported output formats: bitmask of @drm_output_color_format */ unsigned int supported_formats; /* Maximum bits per color channel: 8, 10 or 12 */ unsigned int max_bpc; diff --git a/include/drm/display/drm_hdmi_helper.h b/include/drm/display/drm_hdmi_helper.h index 09145c9ee9fc..9c31ed90516b 100644 --- a/include/drm/display/drm_hdmi_helper.h +++ b/include/drm/display/drm_hdmi_helper.h @@ -8,6 +8,7 @@ struct drm_connector; struct drm_connector_state; struct drm_display_mode; +enum drm_output_color_format; void drm_hdmi_avi_infoframe_colorimetry(struct hdmi_avi_infoframe *frame, @@ -26,7 +27,7 @@ void drm_hdmi_avi_infoframe_content_type(struct hdmi_avi_infoframe *frame, unsigned long long drm_hdmi_compute_mode_clock(const struct drm_display_mode *mode, - unsigned int bpc, enum hdmi_colorspace fmt); + unsigned int bpc, enum drm_output_color_format fmt); void drm_hdmi_acr_get_n_cts(unsigned long long tmds_char_rate, diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h index 66ab89cf48aa..a8d67bd9ee50 100644 --- a/include/drm/drm_bridge.h +++ b/include/drm/drm_bridge.h @@ -1188,8 +1188,9 @@ struct drm_bridge { const char *product; /** - * @supported_formats: Bitmask of @hdmi_colorspace listing supported - * output formats. This is only relevant if @DRM_BRIDGE_OP_HDMI is set. + * @supported_formats: Bitmask of @drm_output_color_format listing + * supported output formats. This is only relevant if + * @DRM_BRIDGE_OP_HDMI is set. */ unsigned int supported_formats; diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index 562f6da90fac..af8b92d2d5b7 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -402,8 +402,6 @@ enum drm_hdmi_broadcast_rgb { const char * drm_hdmi_connector_get_broadcast_rgb_name(enum drm_hdmi_broadcast_rgb broadcast_rgb); -const char * -drm_hdmi_connector_get_output_format_name(enum hdmi_colorspace fmt); /** * struct drm_monitor_range_info - Panel's Monitor range in EDID for @@ -581,6 +579,9 @@ enum drm_output_color_format { DRM_OUTPUT_COLOR_FORMAT_YCBCR420, }; +const char * +drm_hdmi_connector_get_output_format_name(enum drm_output_color_format fmt); + /** * enum drm_bus_flags - bus_flags info for &drm_display_info * @@ -1012,7 +1013,7 @@ struct drm_connector_hdmi_state { /** * @output_format: Pixel format to output in. */ - enum hdmi_colorspace output_format; + enum drm_output_color_format output_format; /** * @tmds_char_rate: TMDS Character Rate, in Hz. @@ -1900,7 +1901,7 @@ struct drm_connector_hdmi { unsigned char product[DRM_CONNECTOR_HDMI_PRODUCT_LEN] __nonstring; /** - * @supported_formats: Bitmask of @hdmi_colorspace + * @supported_formats: Bitmask of @drm_output_color_format * supported by the controller. */ unsigned long supported_formats; -- cgit v1.2.3 From cc34d77dd48708d810c12bfd6f5bf03304f6c824 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Tue, 24 Mar 2026 01:59:15 +0100 Subject: spi: use generic driver_override infrastructure When a driver is probed through __driver_attach(), the bus' match() callback is called without the device lock held, thus accessing the driver_override field without a lock, which can cause a UAF. Fix this by using the driver-core driver_override infrastructure taking care of proper locking internally. Note that calling match() from __driver_attach() without the device lock held is intentional. [1] Also note that we do not enable the driver_override feature of struct bus_type, as SPI - in contrast to most other buses - passes "" to sysfs_emit() when the driver_override pointer is NULL. Thus, printing "\n" instead of "(null)\n". Link: https://lore.kernel.org/driver-core/DGRGTIRHA62X.3RY09D9SOK77P@kernel.org/ [1] Reported-by: Gui-Dong Han Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220789 Fixes: 5039563e7c25 ("spi: Add driver_override SPI device attribute") Signed-off-by: Danilo Krummrich Link: https://patch.msgid.link/20260324005919.2408620-12-dakr@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi.c | 19 +++++++------------ include/linux/spi/spi.h | 5 ----- 2 files changed, 7 insertions(+), 17 deletions(-) (limited to 'include') diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 53dee314d76a..4101c2803eb3 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -50,7 +50,6 @@ static void spidev_release(struct device *dev) struct spi_device *spi = to_spi_device(dev); spi_controller_put(spi->controller); - kfree(spi->driver_override); free_percpu(spi->pcpu_statistics); kfree(spi); } @@ -73,10 +72,9 @@ static ssize_t driver_override_store(struct device *dev, struct device_attribute *a, const char *buf, size_t count) { - struct spi_device *spi = to_spi_device(dev); int ret; - ret = driver_set_override(dev, &spi->driver_override, buf, count); + ret = __device_set_driver_override(dev, buf, count); if (ret) return ret; @@ -86,13 +84,8 @@ static ssize_t driver_override_store(struct device *dev, static ssize_t driver_override_show(struct device *dev, struct device_attribute *a, char *buf) { - const struct spi_device *spi = to_spi_device(dev); - ssize_t len; - - device_lock(dev); - len = sysfs_emit(buf, "%s\n", spi->driver_override ? : ""); - device_unlock(dev); - return len; + guard(spinlock)(&dev->driver_override.lock); + return sysfs_emit(buf, "%s\n", dev->driver_override.name ?: ""); } static DEVICE_ATTR_RW(driver_override); @@ -376,10 +369,12 @@ static int spi_match_device(struct device *dev, const struct device_driver *drv) { const struct spi_device *spi = to_spi_device(dev); const struct spi_driver *sdrv = to_spi_driver(drv); + int ret; /* Check override first, and if set, only use the named driver */ - if (spi->driver_override) - return strcmp(spi->driver_override, drv->name) == 0; + ret = device_match_driver_override(dev, drv); + if (ret >= 0) + return ret; /* Attempt an OF style match */ if (of_driver_match_device(dev, drv)) diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h index af7cfee7b8f6..0dc671c07d3a 100644 --- a/include/linux/spi/spi.h +++ b/include/linux/spi/spi.h @@ -159,10 +159,6 @@ extern void spi_transfer_cs_change_delay_exec(struct spi_message *msg, * @modalias: Name of the driver to use with this device, or an alias * for that name. This appears in the sysfs "modalias" attribute * for driver coldplugging, and in uevents used for hotplugging - * @driver_override: If the name of a driver is written to this attribute, then - * the device will bind to the named driver and only the named driver. - * Do not set directly, because core frees it; use driver_set_override() to - * set or clear it. * @pcpu_statistics: statistics for the spi_device * @word_delay: delay to be inserted between consecutive * words of a transfer @@ -224,7 +220,6 @@ struct spi_device { void *controller_state; void *controller_data; char modalias[SPI_NAME_SIZE]; - const char *driver_override; /* The statistics */ struct spi_statistics __percpu *pcpu_statistics; -- cgit v1.2.3 From 1dfa2fd98f17d639f47ceb1d3b07926131db5c16 Mon Sep 17 00:00:00 2001 From: Marcin Slusarz Date: Tue, 24 Mar 2026 14:25:57 +0100 Subject: drm/panthor: extend timestamp query with flags Flags now control which data user space wants to query, there is more information sources, and there's ability to query duration of multiple timestamp reads. New sources: - CPU's monotonic, - CPU's monotonic raw, - GPU's cycle count These changes should make the implementation of VK_KHR_calibrated_timestamps more accurate and much simpler. Signed-off-by: Marcin Slusarz Reviewed-by: Boris Brezillon Reviewed-by: Liviu Dudau Signed-off-by: Liviu Dudau Link: https://patch.msgid.link/20260324132557.1707286-1-marcin.slusarz@arm.com --- drivers/gpu/drm/panthor/panthor_drv.c | 134 ++++++++++++++++++++++++++++++++-- include/uapi/drm/panthor_drm.h | 63 +++++++++++++++- 2 files changed, 189 insertions(+), 8 deletions(-) (limited to 'include') diff --git a/drivers/gpu/drm/panthor/panthor_drv.c b/drivers/gpu/drm/panthor/panthor_drv.c index 1bcec6a2e3e0..87d27c3c1456 100644 --- a/drivers/gpu/drm/panthor/panthor_drv.c +++ b/drivers/gpu/drm/panthor/panthor_drv.c @@ -13,7 +13,9 @@ #include #include #include +#include #include +#include #include #include @@ -761,22 +763,135 @@ static void panthor_submit_ctx_cleanup(struct panthor_submit_ctx *ctx, kvfree(ctx->jobs); } +#define VALID_TIMESTAMP_QUERY_FLAGS \ + (DRM_PANTHOR_TIMESTAMP_GPU | \ + DRM_PANTHOR_TIMESTAMP_CPU_TYPE_MASK | \ + DRM_PANTHOR_TIMESTAMP_GPU_OFFSET | \ + DRM_PANTHOR_TIMESTAMP_GPU_CYCLE_COUNT | \ + DRM_PANTHOR_TIMESTAMP_FREQ | \ + DRM_PANTHOR_TIMESTAMP_DURATION) + static int panthor_query_timestamp_info(struct panthor_device *ptdev, struct drm_panthor_timestamp_info *arg) { int ret; + u32 flags; + unsigned long irq_flags; + struct timespec64 cpu_ts; + u64 query_start_time; + bool minimize_interruption; + u32 timestamp_types = 0; + + if (arg->flags != 0) { + flags = arg->flags; + } else { + /* + * If flags are 0, then ask for the same things that we asked + * for before flags were added. + */ + flags = DRM_PANTHOR_TIMESTAMP_GPU | + DRM_PANTHOR_TIMESTAMP_GPU_OFFSET | + DRM_PANTHOR_TIMESTAMP_FREQ; + } + + switch (flags & DRM_PANTHOR_TIMESTAMP_CPU_TYPE_MASK) { + case DRM_PANTHOR_TIMESTAMP_CPU_NONE: + break; + case DRM_PANTHOR_TIMESTAMP_CPU_MONOTONIC: + case DRM_PANTHOR_TIMESTAMP_CPU_MONOTONIC_RAW: + timestamp_types++; + break; + default: + return -EINVAL; + } + + if (flags & ~VALID_TIMESTAMP_QUERY_FLAGS) + return -EINVAL; + + if (flags & DRM_PANTHOR_TIMESTAMP_GPU) + timestamp_types++; + if (flags & DRM_PANTHOR_TIMESTAMP_GPU_CYCLE_COUNT) + timestamp_types++; + + /* If user asked to obtain timestamps from more than one source, + * then it very likely means they want them to be as close as possible. + * If they asked for duration, then that likely means that they + * want to know how long obtaining timestamp takes, without random + * events, like process scheduling or interrupts. + */ + minimize_interruption = + (flags & DRM_PANTHOR_TIMESTAMP_DURATION) || + (timestamp_types >= 2); ret = panthor_device_resume_and_get(ptdev); if (ret) return ret; + if (flags & DRM_PANTHOR_TIMESTAMP_FREQ) { #ifdef CONFIG_ARM_ARCH_TIMER - arg->timestamp_frequency = arch_timer_get_cntfrq(); + arg->timestamp_frequency = arch_timer_get_cntfrq(); #else - arg->timestamp_frequency = 0; + arg->timestamp_frequency = 0; #endif - arg->current_timestamp = gpu_read64_counter(ptdev, GPU_TIMESTAMP); - arg->timestamp_offset = gpu_read64(ptdev, GPU_TIMESTAMP_OFFSET); + } else { + arg->timestamp_frequency = 0; + } + + if (flags & DRM_PANTHOR_TIMESTAMP_GPU_OFFSET) + arg->timestamp_offset = gpu_read64(ptdev, GPU_TIMESTAMP_OFFSET); + else + arg->timestamp_offset = 0; + + if (minimize_interruption) { + preempt_disable(); + local_irq_save(irq_flags); + } + + if (flags & DRM_PANTHOR_TIMESTAMP_DURATION) + query_start_time = local_clock(); + else + query_start_time = 0; + + if (flags & DRM_PANTHOR_TIMESTAMP_GPU) + arg->current_timestamp = gpu_read64_counter(ptdev, GPU_TIMESTAMP); + else + arg->current_timestamp = 0; + + switch (flags & DRM_PANTHOR_TIMESTAMP_CPU_TYPE_MASK) { + case DRM_PANTHOR_TIMESTAMP_CPU_MONOTONIC: + ktime_get_ts64(&cpu_ts); + break; + case DRM_PANTHOR_TIMESTAMP_CPU_MONOTONIC_RAW: + ktime_get_raw_ts64(&cpu_ts); + break; + default: + break; + } + + if (flags & DRM_PANTHOR_TIMESTAMP_GPU_CYCLE_COUNT) + arg->cycle_count = gpu_read64_counter(ptdev, GPU_CYCLE_COUNT); + else + arg->cycle_count = 0; + + if (flags & DRM_PANTHOR_TIMESTAMP_DURATION) + arg->duration_nsec = local_clock() - query_start_time; + else + arg->duration_nsec = 0; + + if (minimize_interruption) { + local_irq_restore(irq_flags); + preempt_enable(); + } + + if (flags & DRM_PANTHOR_TIMESTAMP_CPU_TYPE_MASK) { + timens_add_monotonic(&cpu_ts); + + arg->cpu_timestamp_sec = cpu_ts.tv_sec; + arg->cpu_timestamp_nsec = cpu_ts.tv_nsec; + } else { + arg->cpu_timestamp_sec = 0; + arg->cpu_timestamp_nsec = 0; + } pm_runtime_put(ptdev->base.dev); return 0; @@ -851,8 +966,14 @@ static int panthor_ioctl_dev_query(struct drm_device *ddev, void *data, struct d return PANTHOR_UOBJ_SET(args->pointer, args->size, ptdev->csif_info); case DRM_PANTHOR_DEV_QUERY_TIMESTAMP_INFO: - ret = panthor_query_timestamp_info(ptdev, ×tamp_info); + ret = copy_struct_from_user(×tamp_info, + sizeof(timestamp_info), + u64_to_user_ptr(args->pointer), + args->size); + if (ret) + return ret; + ret = panthor_query_timestamp_info(ptdev, ×tamp_info); if (ret) return ret; @@ -1680,6 +1801,7 @@ static void panthor_debugfs_init(struct drm_minor *minor) * - adds DRM_IOCTL_PANTHOR_BO_SYNC ioctl * - adds DRM_IOCTL_PANTHOR_BO_QUERY_INFO ioctl * - adds drm_panthor_gpu_info::selected_coherency + * - 1.8 - extends DEV_QUERY_TIMESTAMP_INFO with flags */ static const struct drm_driver panthor_drm_driver = { .driver_features = DRIVER_RENDER | DRIVER_GEM | DRIVER_SYNCOBJ | @@ -1693,7 +1815,7 @@ static const struct drm_driver panthor_drm_driver = { .name = "panthor", .desc = "Panthor DRM driver", .major = 1, - .minor = 7, + .minor = 8, .gem_create_object = panthor_gem_create_object, .gem_prime_import_sg_table = drm_gem_shmem_prime_import_sg_table, diff --git a/include/uapi/drm/panthor_drm.h b/include/uapi/drm/panthor_drm.h index b401ac585d6a..0e455d91e77d 100644 --- a/include/uapi/drm/panthor_drm.h +++ b/include/uapi/drm/panthor_drm.h @@ -409,6 +409,38 @@ struct drm_panthor_csif_info { __u32 pad; }; +/** + * enum drm_panthor_timestamp_info_flags - drm_panthor_timestamp_info.flags + */ +enum drm_panthor_timestamp_info_flags { + /** @DRM_PANTHOR_TIMESTAMP_GPU: Query GPU time. */ + DRM_PANTHOR_TIMESTAMP_GPU = 1 << 0, + + /** @DRM_PANTHOR_TIMESTAMP_CPU_NONE: Don't query CPU time. */ + DRM_PANTHOR_TIMESTAMP_CPU_NONE = 0 << 1, + + /** @DRM_PANTHOR_TIMESTAMP_CPU_MONOTONIC: Query CPU time using CLOCK_MONOTONIC. */ + DRM_PANTHOR_TIMESTAMP_CPU_MONOTONIC = 1 << 1, + + /** @DRM_PANTHOR_TIMESTAMP_CPU_MONOTONIC_RAW: Query CPU time using CLOCK_MONOTONIC_RAW. */ + DRM_PANTHOR_TIMESTAMP_CPU_MONOTONIC_RAW = 2 << 1, + + /** @DRM_PANTHOR_TIMESTAMP_CPU_TYPE_MASK: Space reserved for CPU clock type. */ + DRM_PANTHOR_TIMESTAMP_CPU_TYPE_MASK = 7 << 1, + + /** @DRM_PANTHOR_TIMESTAMP_GPU_OFFSET: Query GPU offset. */ + DRM_PANTHOR_TIMESTAMP_GPU_OFFSET = 1 << 4, + + /** @DRM_PANTHOR_TIMESTAMP_GPU_CYCLE_COUNT: Query GPU cycle count. */ + DRM_PANTHOR_TIMESTAMP_GPU_CYCLE_COUNT = 1 << 5, + + /** @DRM_PANTHOR_TIMESTAMP_FREQ: Query timestamp frequency. */ + DRM_PANTHOR_TIMESTAMP_FREQ = 1 << 6, + + /** @DRM_PANTHOR_TIMESTAMP_DURATION: Return duration of time query. */ + DRM_PANTHOR_TIMESTAMP_DURATION = 1 << 7, +}; + /** * struct drm_panthor_timestamp_info - Timestamp information * @@ -421,11 +453,38 @@ struct drm_panthor_timestamp_info { */ __u64 timestamp_frequency; - /** @current_timestamp: The current timestamp. */ + /** @current_timestamp: The current GPU timestamp. */ __u64 current_timestamp; - /** @timestamp_offset: The offset of the timestamp timer. */ + /** @timestamp_offset: The offset of the GPU timestamp timer. */ __u64 timestamp_offset; + + /** + * @flags: Bitmask of drm_panthor_timestamp_info_flags. + * + * If set to 0, then it is interpreted as: + * DRM_PANTHOR_TIMESTAMP_GPU | + * DRM_PANTHOR_TIMESTAMP_GPU_OFFSET | + * DRM_PANTHOR_TIMESTAMP_FREQ + * + * Note: these flags are exclusive to each other (only one can be used): + * - DRM_PANTHOR_TIMESTAMP_CPU_NONE + * - DRM_PANTHOR_TIMESTAMP_CPU_MONOTONIC + * - DRM_PANTHOR_TIMESTAMP_CPU_MONOTONIC_RAW + */ + __u32 flags; + + /** @duration_nsec: Duration of time query. */ + __u32 duration_nsec; + + /** @cycle_count: Value of GPU_CYCLE_COUNT. */ + __u64 cycle_count; + + /** @cpu_timestamp_sec: Seconds part of CPU timestamp. */ + __u64 cpu_timestamp_sec; + + /** @cpu_timestamp_nsec: Nanseconds part of CPU timestamp. */ + __u64 cpu_timestamp_nsec; }; /** -- cgit v1.2.3 From 1b164b876c36c3eb5561dd9b37702b04401b0166 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Tue, 24 Mar 2026 10:21:25 -1000 Subject: cgroup: Wait for dying tasks to leave on rmdir a72f73c4dd9b ("cgroup: Don't expose dead tasks in cgroup") hid PF_EXITING tasks from cgroup.procs so that systemd doesn't see tasks that have already been reaped via waitpid(). However, the populated counter (nr_populated_csets) is only decremented when the task later passes through cgroup_task_dead() in finish_task_switch(). This means cgroup.procs can appear empty while the cgroup is still populated, causing rmdir to fail with -EBUSY. Fix this by making cgroup_rmdir() wait for dying tasks to fully leave. If the cgroup is populated but all remaining tasks have PF_EXITING set (the task iterator returns none due to the existing filter), wait for a kick from cgroup_task_dead() and retry. The wait is brief as tasks are removed from the cgroup's css_set between PF_EXITING assertion in do_exit() and cgroup_task_dead() in finish_task_switch(). v2: cgroup_is_populated() true to false transition happens under css_set_lock not cgroup_mutex, so retest under css_set_lock before sleeping to avoid missed wakeups (Sebastian). Fixes: a72f73c4dd9b ("cgroup: Don't expose dead tasks in cgroup") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-lkp/202603222104.2c81684e-lkp@intel.com Reported-by: Sebastian Andrzej Siewior Signed-off-by: Tejun Heo Reviewed-by: Sebastian Andrzej Siewior Cc: Bert Karwatzki Cc: Michal Koutny Cc: cgroups@vger.kernel.org --- include/linux/cgroup-defs.h | 3 ++ kernel/cgroup/cgroup.c | 86 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 86 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/include/linux/cgroup-defs.h b/include/linux/cgroup-defs.h index bb92f5c169ca..7f87399938fa 100644 --- a/include/linux/cgroup-defs.h +++ b/include/linux/cgroup-defs.h @@ -609,6 +609,9 @@ struct cgroup { /* used to wait for offlining of csses */ wait_queue_head_t offline_waitq; + /* used by cgroup_rmdir() to wait for dying tasks to leave */ + wait_queue_head_t dying_populated_waitq; + /* used to schedule release agent */ struct work_struct release_agent_work; diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c index 01fc2a93f3ef..2163054e1aa6 100644 --- a/kernel/cgroup/cgroup.c +++ b/kernel/cgroup/cgroup.c @@ -2126,6 +2126,7 @@ static void init_cgroup_housekeeping(struct cgroup *cgrp) #endif init_waitqueue_head(&cgrp->offline_waitq); + init_waitqueue_head(&cgrp->dying_populated_waitq); INIT_WORK(&cgrp->release_agent_work, cgroup1_release_agent); } @@ -6224,6 +6225,76 @@ static int cgroup_destroy_locked(struct cgroup *cgrp) return 0; }; +/** + * cgroup_drain_dying - wait for dying tasks to leave before rmdir + * @cgrp: the cgroup being removed + * + * The PF_EXITING filter in css_task_iter_advance() hides exiting tasks from + * cgroup.procs so that userspace (e.g. systemd) doesn't see tasks that have + * already been reaped via waitpid(). However, the populated counter + * (nr_populated_csets) is only decremented when the task later passes through + * cgroup_task_dead() in finish_task_switch(). This creates a window where + * cgroup.procs appears empty but cgroup_is_populated() is still true, causing + * rmdir to fail with -EBUSY. + * + * This function bridges that gap. If the cgroup is populated but all remaining + * tasks have PF_EXITING set, we wait for cgroup_task_dead() to process them. + * Tasks are removed from the cgroup's css_set in cgroup_task_dead() called from + * finish_task_switch(). As the window between PF_EXITING and cgroup_task_dead() + * is short, the number of PF_EXITING tasks on the list is small and the wait + * is brief. + * + * Each cgroup_task_dead() kicks the waitqueue via cset->cgrp_links, and we + * retry the full check from scratch. + * + * Must be called with cgroup_mutex held. + */ +static int cgroup_drain_dying(struct cgroup *cgrp) + __releases(&cgroup_mutex) __acquires(&cgroup_mutex) +{ + struct css_task_iter it; + struct task_struct *task; + DEFINE_WAIT(wait); + + lockdep_assert_held(&cgroup_mutex); +retry: + if (!cgroup_is_populated(cgrp)) + return 0; + + /* Same iterator as cgroup.threads - if any task is visible, it's busy */ + css_task_iter_start(&cgrp->self, 0, &it); + task = css_task_iter_next(&it); + css_task_iter_end(&it); + + if (task) + return -EBUSY; + + /* + * All remaining tasks are PF_EXITING and will pass through + * cgroup_task_dead() shortly. Wait for a kick and retry. + * + * cgroup_is_populated() can't transition from false to true while + * we're holding cgroup_mutex, but the true to false transition + * happens under css_set_lock (via cgroup_task_dead()). We must + * retest and prepare_to_wait() under css_set_lock. Otherwise, the + * transition can happen between our first test and + * prepare_to_wait(), and we sleep with no one to wake us. + */ + spin_lock_irq(&css_set_lock); + if (!cgroup_is_populated(cgrp)) { + spin_unlock_irq(&css_set_lock); + return 0; + } + prepare_to_wait(&cgrp->dying_populated_waitq, &wait, + TASK_UNINTERRUPTIBLE); + spin_unlock_irq(&css_set_lock); + mutex_unlock(&cgroup_mutex); + schedule(); + finish_wait(&cgrp->dying_populated_waitq, &wait); + mutex_lock(&cgroup_mutex); + goto retry; +} + int cgroup_rmdir(struct kernfs_node *kn) { struct cgroup *cgrp; @@ -6233,9 +6304,12 @@ int cgroup_rmdir(struct kernfs_node *kn) if (!cgrp) return 0; - ret = cgroup_destroy_locked(cgrp); - if (!ret) - TRACE_CGROUP_PATH(rmdir, cgrp); + ret = cgroup_drain_dying(cgrp); + if (!ret) { + ret = cgroup_destroy_locked(cgrp); + if (!ret) + TRACE_CGROUP_PATH(rmdir, cgrp); + } cgroup_kn_unlock(kn); return ret; @@ -6995,6 +7069,7 @@ void cgroup_task_exit(struct task_struct *tsk) static void do_cgroup_task_dead(struct task_struct *tsk) { + struct cgrp_cset_link *link; struct css_set *cset; unsigned long flags; @@ -7008,6 +7083,11 @@ static void do_cgroup_task_dead(struct task_struct *tsk) if (thread_group_leader(tsk) && atomic_read(&tsk->signal->live)) list_add_tail(&tsk->cg_list, &cset->dying_tasks); + /* kick cgroup_drain_dying() waiters, see cgroup_rmdir() */ + list_for_each_entry(link, &cset->cgrp_links, cgrp_link) + if (waitqueue_active(&link->cgrp->dying_populated_waitq)) + wake_up(&link->cgrp->dying_populated_waitq); + if (dl_task(tsk)) dec_dl_tasks_cs(tsk); -- cgit v1.2.3 From 815980fe6dbb01ad4007e8b260a45617f598b76d Mon Sep 17 00:00:00 2001 From: Jonas Köppeler Date: Mon, 23 Mar 2026 18:49:20 +0100 Subject: net_sched: codel: fix stale state for empty flows in fq_codel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When codel_dequeue() finds an empty queue, it resets vars->dropping but does not reset vars->first_above_time. The reference CoDel algorithm (Nichols & Jacobson, ACM Queue 2012) resets both: dodeque_result codel_queue_t::dodeque(time_t now) { ... if (r.p == NULL) { first_above_time = 0; // <-- Linux omits this } ... } Note that codel_should_drop() does reset first_above_time when called with a NULL skb, but codel_dequeue() returns early before ever calling codel_should_drop() in the empty-queue case. The post-drop code paths do reach codel_should_drop(NULL) and correctly reset the timer, so a dropped packet breaks the cycle -- but the next delivered packet re-arms first_above_time and the cycle repeats. For sparse flows such as ICMP ping (one packet every 200ms-1s), the first packet arms first_above_time, the flow goes empty, and the second packet arrives after the interval has elapsed and gets dropped. The pattern repeats, producing sustained loss on flows that are not actually congested. Test: veth pair, fq_codel, BQL disabled, 30000 iptables rules in the consumer namespace (NAPI-64 cycle ~14ms, well above fq_codel's 5ms target), ping at 5 pps under UDP flood: Before fix: 26% ping packet loss After fix: 0% ping packet loss Fix by resetting first_above_time to zero in the empty-queue path of codel_dequeue(), matching the reference algorithm. Fixes: 76e3cc126bb2 ("codel: Controlled Delay AQM") Fixes: d068ca2ae2e6 ("codel: split into multiple files") Co-developed-by: Jesper Dangaard Brouer Signed-off-by: Jesper Dangaard Brouer Signed-off-by: Jonas Köppeler Reported-by: Chris Arges Tested-by: Jonas Köppeler Reviewed-by: Toke Høiland-Jørgensen Link: https://lore.kernel.org/all/20260318134826.1281205-7-hawk@kernel.org/ Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260323174920.253526-1-hawk@kernel.org Signed-off-by: Jakub Kicinski --- include/net/codel_impl.h | 1 + 1 file changed, 1 insertion(+) (limited to 'include') diff --git a/include/net/codel_impl.h b/include/net/codel_impl.h index 78a27ac73070..b2c359c6dd1b 100644 --- a/include/net/codel_impl.h +++ b/include/net/codel_impl.h @@ -158,6 +158,7 @@ static struct sk_buff *codel_dequeue(void *ctx, bool drop; if (!skb) { + vars->first_above_time = 0; vars->dropping = false; return skb; } -- cgit v1.2.3 From 2cdaff22ed26f1e619aa2b43f27bb84f2c6ef8f8 Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Wed, 25 Mar 2026 02:55:48 +0100 Subject: dma-mapping: add missing `inline` for `dma_free_attrs` Under an UML build for an upcoming series [1], I got `-Wstatic-in-inline` for `dma_free_attrs`: BINDGEN rust/bindings/bindings_generated.rs - due to target missing In file included from rust/helpers/helpers.c:59: rust/helpers/dma.c:17:2: warning: static function 'dma_free_attrs' is used in an inline function with external linkage [-Wstatic-in-inline] 17 | dma_free_attrs(dev, size, cpu_addr, dma_handle, attrs); | ^ rust/helpers/dma.c:12:1: note: use 'static' to give inline function 'rust_helper_dma_free_attrs' internal linkage 12 | __rust_helper void rust_helper_dma_free_attrs(struct device *dev, size_t size, | ^ | static The issue is that `dma_free_attrs` was not marked `inline` when it was introduced alongside the rest of the stubs. Thus mark it. Fixes: ed6ccf10f24b ("dma-mapping: properly stub out the DMA API for !CONFIG_HAS_DMA") Closes: https://lore.kernel.org/rust-for-linux/20260322194616.89847-1-ojeda@kernel.org/ [1] Signed-off-by: Miguel Ojeda Signed-off-by: Marek Szyprowski Link: https://lore.kernel.org/r/20260325015548.70912-1-ojeda@kernel.org --- include/linux/dma-mapping.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/linux/dma-mapping.h b/include/linux/dma-mapping.h index 482b919f040f..99ef042ecdb4 100644 --- a/include/linux/dma-mapping.h +++ b/include/linux/dma-mapping.h @@ -255,8 +255,8 @@ static inline void *dma_alloc_attrs(struct device *dev, size_t size, { return NULL; } -static void dma_free_attrs(struct device *dev, size_t size, void *cpu_addr, - dma_addr_t dma_handle, unsigned long attrs) +static inline void dma_free_attrs(struct device *dev, size_t size, + void *cpu_addr, dma_addr_t dma_handle, unsigned long attrs) { } static inline void *dmam_alloc_attrs(struct device *dev, size_t size, -- cgit v1.2.3 From b50dc1e54750a18265e7e465de38cd1c3c5ea543 Mon Sep 17 00:00:00 2001 From: Alexander Koskovich Date: Tue, 24 Mar 2026 11:48:09 +0000 Subject: drm/mipi-dsi: add RGB101010 pixel format Add MIPI_DSI_FMT_RGB101010 for 30 bit (10,10,10 RGB) pixel format, corresponding to the packed 30 bit pixel stream defined in MIPI DSI v1.3 Section 8.8.17. Reviewed-by: Dmitry Baryshkov Signed-off-by: Alexander Koskovich Patchwork: https://patchwork.freedesktop.org/patch/713714/ Link: https://lore.kernel.org/r/20260324-dsi-rgb101010-support-v5-1-ff6afc904115@pm.me [Acked by Maxime to be merged through msm-next on IRC on dri-devel] [DB: moved RGB101010 to the end of enum mipi_dsi_pixel_format] Signed-off-by: Dmitry Baryshkov --- include/drm/drm_mipi_dsi.h | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'include') diff --git a/include/drm/drm_mipi_dsi.h b/include/drm/drm_mipi_dsi.h index 3aba7b380c8d..2ab651a36115 100644 --- a/include/drm/drm_mipi_dsi.h +++ b/include/drm/drm_mipi_dsi.h @@ -144,6 +144,7 @@ enum mipi_dsi_pixel_format { MIPI_DSI_FMT_RGB666, MIPI_DSI_FMT_RGB666_PACKED, MIPI_DSI_FMT_RGB565, + MIPI_DSI_FMT_RGB101010, }; #define DSI_DEV_NAME_SIZE 20 @@ -235,6 +236,9 @@ extern const struct bus_type mipi_dsi_bus_type; static inline int mipi_dsi_pixel_format_to_bpp(enum mipi_dsi_pixel_format fmt) { switch (fmt) { + case MIPI_DSI_FMT_RGB101010: + return 30; + case MIPI_DSI_FMT_RGB888: case MIPI_DSI_FMT_RGB666: return 24; -- cgit v1.2.3 From c991ca3238410b611a2ce59adeca9b55850aff69 Mon Sep 17 00:00:00 2001 From: Shuming Fan Date: Wed, 25 Mar 2026 17:20:17 +0800 Subject: ASoC: SDCA: remove the max count of initialization table The number of the initialization table may exceed 2048. Therefore, this patch removes the limitation and allows the driver to allocate memory dynamically based on the size of the initialization table. Signed-off-by: Shuming Fan Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260325092017.3221640-1-shumingf@realtek.com Signed-off-by: Mark Brown --- include/sound/sdca_function.h | 5 ----- sound/soc/sdca/sdca_functions.c | 3 --- 2 files changed, 8 deletions(-) (limited to 'include') diff --git a/include/sound/sdca_function.h b/include/sound/sdca_function.h index 79bd5a7a0f88..0e871c786513 100644 --- a/include/sound/sdca_function.h +++ b/include/sound/sdca_function.h @@ -26,11 +26,6 @@ struct sdca_function_desc; */ #define SDCA_MAX_ENTITY_COUNT 128 -/* - * Sanity check on number of initialization writes, can be expanded if needed. - */ -#define SDCA_MAX_INIT_COUNT 2048 - /* * The Cluster IDs are 16-bit, so a maximum of 65535 Clusters per * function can be represented, however limit this to a slightly diff --git a/sound/soc/sdca/sdca_functions.c b/sound/soc/sdca/sdca_functions.c index e0ed593697ba..d27ffb25ad97 100644 --- a/sound/soc/sdca/sdca_functions.c +++ b/sound/soc/sdca/sdca_functions.c @@ -216,9 +216,6 @@ static int find_sdca_init_table(struct device *dev, } else if (num_init_writes % sizeof(*raw) != 0) { dev_err(dev, "%pfwP: init table size invalid\n", function_node); return -EINVAL; - } else if ((num_init_writes / sizeof(*raw)) > SDCA_MAX_INIT_COUNT) { - dev_err(dev, "%pfwP: maximum init table size exceeded\n", function_node); - return -EINVAL; } raw = kzalloc(num_init_writes, GFP_KERNEL); -- cgit v1.2.3 From 514c8451fc5829db3f022f0cb9018c06c570261d Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Thu, 19 Mar 2026 16:59:37 +0100 Subject: drm/mipi-dbi: Only modify planes on enabled CRTCs Use drm_atomic_helper_commit_tail_rpm() as commit tail to update the plane after enabling the CRTC. Then remove the plane-update code from mipi_dbi_enable_flush() and inline the remaining backlight code where necessary. Mipi-dbi's current commit tail drm_atomic_helper_commit_tail() first updates the plane and then enables the CRTC. But the CRTC enablement includes power management that prevents the initial plane update from working. Hence, each mipi-dbi driver includes a plane update in their CRTC enablement; in the form of mipi_dbi_enable_flush() or custom code. Using drm_atomic_helper_commit_tail_rpm() enables the CRTC before any plane updates. Hence the additional plane update can be removed from mipi_dbi_enable_flush() and a number of drivers. This leaves backlight_enable() in the helper, which can now be inlined into affected drivers. Drivers now enable the CRTC plus an optional backlight and then automatically update the plane. In the case of disabling the display, drm_atomic_helper_commit_tail_rpm() only disables the CRTC without touching the plane at all. Mipi-dbi's mipi_dbi_pipe_disable() already contains the necessary logic. Removing the plane update from the CRTC enablement will also help with converting mipi-dbi from simple-pipe helpers to regular atomic helpers. v3: - st7586: remove unused variable v2: - ili9225: remove unused variables (David) - st7586: remove unused variables (David) Signed-off-by: Thomas Zimmermann Acked-by: David Lechner Reviewed-by: Dmitry Baryshkov Link: https://patch.msgid.link/20260319160110.109610-2-tzimmermann@suse.de --- drivers/gpu/drm/drm_mipi_dbi.c | 44 +++++------------------------------ drivers/gpu/drm/sitronix/st7586.c | 11 --------- drivers/gpu/drm/sitronix/st7735r.c | 2 +- drivers/gpu/drm/tiny/hx8357d.c | 3 ++- drivers/gpu/drm/tiny/ili9163.c | 3 ++- drivers/gpu/drm/tiny/ili9225.c | 11 --------- drivers/gpu/drm/tiny/ili9341.c | 3 ++- drivers/gpu/drm/tiny/ili9486.c | 3 ++- drivers/gpu/drm/tiny/mi0283qt.c | 3 ++- drivers/gpu/drm/tiny/panel-mipi-dbi.c | 2 +- include/drm/drm_mipi_dbi.h | 3 --- 11 files changed, 18 insertions(+), 70 deletions(-) (limited to 'include') diff --git a/drivers/gpu/drm/drm_mipi_dbi.c b/drivers/gpu/drm/drm_mipi_dbi.c index 00482227a9cd..bb6cebc583be 100644 --- a/drivers/gpu/drm/drm_mipi_dbi.c +++ b/drivers/gpu/drm/drm_mipi_dbi.c @@ -368,44 +368,6 @@ void mipi_dbi_pipe_update(struct drm_simple_display_pipe *pipe, } EXPORT_SYMBOL(mipi_dbi_pipe_update); -/** - * mipi_dbi_enable_flush - MIPI DBI enable helper - * @dbidev: MIPI DBI device structure - * @crtc_state: CRTC state - * @plane_state: Plane state - * - * Flushes the whole framebuffer and enables the backlight. Drivers can use this - * in their &drm_simple_display_pipe_funcs->enable callback. - * - * Note: Drivers which don't use mipi_dbi_pipe_update() because they have custom - * framebuffer flushing, can't use this function since they both use the same - * flushing code. - */ -void mipi_dbi_enable_flush(struct mipi_dbi_dev *dbidev, - struct drm_crtc_state *crtc_state, - struct drm_plane_state *plane_state) -{ - struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(plane_state); - struct drm_framebuffer *fb = plane_state->fb; - struct drm_rect rect = { - .x1 = 0, - .x2 = fb->width, - .y1 = 0, - .y2 = fb->height, - }; - int idx; - - if (!drm_dev_enter(&dbidev->drm, &idx)) - return; - - mipi_dbi_fb_dirty(&shadow_plane_state->data[0], fb, &rect, - &shadow_plane_state->fmtcnv_state); - backlight_enable(dbidev->backlight); - - drm_dev_exit(idx); -} -EXPORT_SYMBOL(mipi_dbi_enable_flush); - static void mipi_dbi_blank(struct mipi_dbi_dev *dbidev) { struct drm_device *drm = &dbidev->drm; @@ -577,6 +539,10 @@ static int mipi_dbi_rotate_mode(struct drm_display_mode *mode, } } +static const struct drm_mode_config_helper_funcs mipi_dbi_mode_config_helper_funcs = { + .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm, +}; + static const struct drm_mode_config_funcs mipi_dbi_mode_config_funcs = { .fb_create = drm_gem_fb_create_with_dirty, .atomic_check = drm_atomic_helper_check, @@ -660,6 +626,8 @@ int mipi_dbi_dev_init_with_formats(struct mipi_dbi_dev *dbidev, drm->mode_config.max_width = dbidev->mode.hdisplay; drm->mode_config.min_height = dbidev->mode.vdisplay; drm->mode_config.max_height = dbidev->mode.vdisplay; + drm->mode_config.helper_private = &mipi_dbi_mode_config_helper_funcs; + dbidev->rotation = rotation; dbidev->pixel_format = formats[0]; if (formats[0] == DRM_FORMAT_RGB888) diff --git a/drivers/gpu/drm/sitronix/st7586.c b/drivers/gpu/drm/sitronix/st7586.c index b57ebf37a664..0fce12a09be0 100644 --- a/drivers/gpu/drm/sitronix/st7586.c +++ b/drivers/gpu/drm/sitronix/st7586.c @@ -174,15 +174,7 @@ static void st7586_pipe_enable(struct drm_simple_display_pipe *pipe, struct drm_plane_state *plane_state) { struct mipi_dbi_dev *dbidev = drm_to_mipi_dbi_dev(pipe->crtc.dev); - struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(plane_state); - struct drm_framebuffer *fb = plane_state->fb; struct mipi_dbi *dbi = &dbidev->dbi; - struct drm_rect rect = { - .x1 = 0, - .x2 = fb->width, - .y1 = 0, - .y2 = fb->height, - }; int idx, ret; u8 addr_mode; @@ -242,9 +234,6 @@ static void st7586_pipe_enable(struct drm_simple_display_pipe *pipe, msleep(100); - st7586_fb_dirty(&shadow_plane_state->data[0], fb, &rect, - &shadow_plane_state->fmtcnv_state); - mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_ON); out_exit: drm_dev_exit(idx); diff --git a/drivers/gpu/drm/sitronix/st7735r.c b/drivers/gpu/drm/sitronix/st7735r.c index c1f8228495f6..1a34c7ba460b 100644 --- a/drivers/gpu/drm/sitronix/st7735r.c +++ b/drivers/gpu/drm/sitronix/st7735r.c @@ -129,7 +129,7 @@ static void st7735r_pipe_enable(struct drm_simple_display_pipe *pipe, msleep(20); - mipi_dbi_enable_flush(dbidev, crtc_state, plane_state); + backlight_enable(dbidev->backlight); out_exit: drm_dev_exit(idx); } diff --git a/drivers/gpu/drm/tiny/hx8357d.c b/drivers/gpu/drm/tiny/hx8357d.c index 9f26aaca0bfa..5115be8854bb 100644 --- a/drivers/gpu/drm/tiny/hx8357d.c +++ b/drivers/gpu/drm/tiny/hx8357d.c @@ -177,7 +177,8 @@ out_enable: break; } mipi_dbi_command(dbi, MIPI_DCS_SET_ADDRESS_MODE, addr_mode); - mipi_dbi_enable_flush(dbidev, crtc_state, plane_state); + + backlight_enable(dbidev->backlight); out_exit: drm_dev_exit(idx); } diff --git a/drivers/gpu/drm/tiny/ili9163.c b/drivers/gpu/drm/tiny/ili9163.c index 7c154c008344..c616f56af5b5 100644 --- a/drivers/gpu/drm/tiny/ili9163.c +++ b/drivers/gpu/drm/tiny/ili9163.c @@ -96,7 +96,8 @@ out_enable: } addr_mode |= ILI9163_MADCTL_BGR; mipi_dbi_command(dbi, MIPI_DCS_SET_ADDRESS_MODE, addr_mode); - mipi_dbi_enable_flush(dbidev, crtc_state, plane_state); + + backlight_enable(dbidev->backlight); out_exit: drm_dev_exit(idx); } diff --git a/drivers/gpu/drm/tiny/ili9225.c b/drivers/gpu/drm/tiny/ili9225.c index d32538b1eb09..3eaf6b3a055a 100644 --- a/drivers/gpu/drm/tiny/ili9225.c +++ b/drivers/gpu/drm/tiny/ili9225.c @@ -184,16 +184,8 @@ static void ili9225_pipe_enable(struct drm_simple_display_pipe *pipe, struct drm_plane_state *plane_state) { struct mipi_dbi_dev *dbidev = drm_to_mipi_dbi_dev(pipe->crtc.dev); - struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(plane_state); - struct drm_framebuffer *fb = plane_state->fb; struct device *dev = pipe->crtc.dev->dev; struct mipi_dbi *dbi = &dbidev->dbi; - struct drm_rect rect = { - .x1 = 0, - .x2 = fb->width, - .y1 = 0, - .y2 = fb->height, - }; int ret, idx; u8 am_id; @@ -284,9 +276,6 @@ static void ili9225_pipe_enable(struct drm_simple_display_pipe *pipe, ili9225_command(dbi, ILI9225_DISPLAY_CONTROL_1, 0x1017); - ili9225_fb_dirty(&shadow_plane_state->data[0], fb, &rect, - &shadow_plane_state->fmtcnv_state); - out_exit: drm_dev_exit(idx); } diff --git a/drivers/gpu/drm/tiny/ili9341.c b/drivers/gpu/drm/tiny/ili9341.c index 2ab750cba505..972811564d6a 100644 --- a/drivers/gpu/drm/tiny/ili9341.c +++ b/drivers/gpu/drm/tiny/ili9341.c @@ -133,7 +133,8 @@ out_enable: } addr_mode |= ILI9341_MADCTL_BGR; mipi_dbi_command(dbi, MIPI_DCS_SET_ADDRESS_MODE, addr_mode); - mipi_dbi_enable_flush(dbidev, crtc_state, plane_state); + + backlight_enable(dbidev->backlight); out_exit: drm_dev_exit(idx); } diff --git a/drivers/gpu/drm/tiny/ili9486.c b/drivers/gpu/drm/tiny/ili9486.c index 1e411a0f4567..52b14f2cb0e1 100644 --- a/drivers/gpu/drm/tiny/ili9486.c +++ b/drivers/gpu/drm/tiny/ili9486.c @@ -155,7 +155,8 @@ static void waveshare_enable(struct drm_simple_display_pipe *pipe, } addr_mode |= ILI9486_MADCTL_BGR; mipi_dbi_command(dbi, MIPI_DCS_SET_ADDRESS_MODE, addr_mode); - mipi_dbi_enable_flush(dbidev, crtc_state, plane_state); + + backlight_enable(dbidev->backlight); out_exit: drm_dev_exit(idx); } diff --git a/drivers/gpu/drm/tiny/mi0283qt.c b/drivers/gpu/drm/tiny/mi0283qt.c index a063eff77624..f121e1a8a303 100644 --- a/drivers/gpu/drm/tiny/mi0283qt.c +++ b/drivers/gpu/drm/tiny/mi0283qt.c @@ -137,7 +137,8 @@ out_enable: } addr_mode |= ILI9341_MADCTL_BGR; mipi_dbi_command(dbi, MIPI_DCS_SET_ADDRESS_MODE, addr_mode); - mipi_dbi_enable_flush(dbidev, crtc_state, plane_state); + + backlight_enable(dbidev->backlight); out_exit: drm_dev_exit(idx); } diff --git a/drivers/gpu/drm/tiny/panel-mipi-dbi.c b/drivers/gpu/drm/tiny/panel-mipi-dbi.c index 82dfa169f762..4907945ab507 100644 --- a/drivers/gpu/drm/tiny/panel-mipi-dbi.c +++ b/drivers/gpu/drm/tiny/panel-mipi-dbi.c @@ -252,7 +252,7 @@ static void panel_mipi_dbi_enable(struct drm_simple_display_pipe *pipe, if (!ret) panel_mipi_dbi_commands_execute(dbi, dbidev->driver_private); - mipi_dbi_enable_flush(dbidev, crtc_state, plane_state); + backlight_enable(dbidev->backlight); out_exit: drm_dev_exit(idx); } diff --git a/include/drm/drm_mipi_dbi.h b/include/drm/drm_mipi_dbi.h index f45f9612c0bc..637be84d3d5a 100644 --- a/include/drm/drm_mipi_dbi.h +++ b/include/drm/drm_mipi_dbi.h @@ -176,9 +176,6 @@ enum drm_mode_status mipi_dbi_pipe_mode_valid(struct drm_simple_display_pipe *pi const struct drm_display_mode *mode); void mipi_dbi_pipe_update(struct drm_simple_display_pipe *pipe, struct drm_plane_state *old_state); -void mipi_dbi_enable_flush(struct mipi_dbi_dev *dbidev, - struct drm_crtc_state *crtc_state, - struct drm_plane_state *plan_state); void mipi_dbi_pipe_disable(struct drm_simple_display_pipe *pipe); int mipi_dbi_pipe_begin_fb_access(struct drm_simple_display_pipe *pipe, struct drm_plane_state *plane_state); -- cgit v1.2.3 From 7efb8fd7ca37125f01ec568a9e3f0c1548eb06ab Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Thu, 19 Mar 2026 16:59:38 +0100 Subject: drm/mipi-dbi: Support custom pipelines with drm_mipi_dbi_dev_init() Initialize the mipi-dbi device with drm_mipi_dbi_dev_init() without creating a modesetting pipeline. Will allow for mipi-dbi drivers without simple-display helpers. As the new helper is a DRM function, add the drm_ prefix. Mipi-dbi interfaces currently lack this. v3: - document tx_buf_size parameter (David) Signed-off-by: Thomas Zimmermann Acked-by: David Lechner Reviewed-by: Dmitry Baryshkov Link: https://patch.msgid.link/20260319160110.109610-3-tzimmermann@suse.de --- drivers/gpu/drm/drm_mipi_dbi.c | 80 ++++++++++++++++++++++++++++++------------ include/drm/drm_mipi_dbi.h | 4 +++ 2 files changed, 61 insertions(+), 23 deletions(-) (limited to 'include') diff --git a/drivers/gpu/drm/drm_mipi_dbi.c b/drivers/gpu/drm/drm_mipi_dbi.c index bb6cebc583be..bc5444ca7ac6 100644 --- a/drivers/gpu/drm/drm_mipi_dbi.c +++ b/drivers/gpu/drm/drm_mipi_dbi.c @@ -554,6 +554,59 @@ static const uint32_t mipi_dbi_formats[] = { DRM_FORMAT_XRGB8888, }; +/** + * drm_mipi_dbi_dev_init - MIPI DBI device initialization + * @dbidev: MIPI DBI device structure to initialize + * @mode: Hardware display mode + * @format: Hardware color format (DRM_FORMAT\_\*). + * @rotation: Initial rotation in degrees Counter Clock Wise + * @tx_buf_size: Allocate a transmit buffer of at least this size. + * + * Initializes a MIPI-DBI device. The minimum size of the transmit buffer + * in @tx_buf_size is optional. Pass 0 to allocate enough memory to transmit + * a single scanline of the display. + * + * Returns: + * Zero on success, negative error code on failure. + */ +int drm_mipi_dbi_dev_init(struct mipi_dbi_dev *dbidev, const struct drm_display_mode *mode, + u32 format, unsigned int rotation, size_t tx_buf_size) +{ + struct drm_device *drm = &dbidev->drm; + int ret; + + if (!dbidev->dbi.command) + return -EINVAL; + + if (!tx_buf_size) { + const struct drm_format_info *info = drm_format_info(format); + + tx_buf_size = drm_format_info_min_pitch(info, 0, mode->hdisplay) * + mode->vdisplay; + } + + dbidev->tx_buf = devm_kmalloc(drm->dev, tx_buf_size, GFP_KERNEL); + if (!dbidev->tx_buf) + return -ENOMEM; + + drm_mode_copy(&dbidev->mode, mode); + ret = mipi_dbi_rotate_mode(&dbidev->mode, rotation); + if (ret) { + drm_err(drm, "Illegal rotation value %u\n", rotation); + return -EINVAL; + } + + dbidev->rotation = rotation; + drm_dbg(drm, "rotation = %u\n", rotation); + + dbidev->pixel_format = format; + if (dbidev->pixel_format == DRM_FORMAT_RGB888) + dbidev->dbi.write_memory_bpw = 8; + + return 0; +} +EXPORT_SYMBOL(drm_mipi_dbi_dev_init); + /** * mipi_dbi_dev_init_with_formats - MIPI DBI device initialization with custom formats * @dbidev: MIPI DBI device structure to initialize @@ -590,24 +643,14 @@ int mipi_dbi_dev_init_with_formats(struct mipi_dbi_dev *dbidev, struct drm_device *drm = &dbidev->drm; int ret; - if (!dbidev->dbi.command) - return -EINVAL; + ret = drm_mipi_dbi_dev_init(dbidev, mode, formats[0], rotation, tx_buf_size); + if (ret) + return ret; ret = drmm_mode_config_init(drm); if (ret) return ret; - dbidev->tx_buf = devm_kmalloc(drm->dev, tx_buf_size, GFP_KERNEL); - if (!dbidev->tx_buf) - return -ENOMEM; - - drm_mode_copy(&dbidev->mode, mode); - ret = mipi_dbi_rotate_mode(&dbidev->mode, rotation); - if (ret) { - DRM_ERROR("Illegal rotation value %u\n", rotation); - return -EINVAL; - } - drm_connector_helper_add(&dbidev->connector, &mipi_dbi_connector_hfuncs); ret = drm_connector_init(drm, &dbidev->connector, &mipi_dbi_connector_funcs, DRM_MODE_CONNECTOR_SPI); @@ -628,13 +671,6 @@ int mipi_dbi_dev_init_with_formats(struct mipi_dbi_dev *dbidev, drm->mode_config.max_height = dbidev->mode.vdisplay; drm->mode_config.helper_private = &mipi_dbi_mode_config_helper_funcs; - dbidev->rotation = rotation; - dbidev->pixel_format = formats[0]; - if (formats[0] == DRM_FORMAT_RGB888) - dbidev->dbi.write_memory_bpw = 8; - - DRM_DEBUG_KMS("rotation = %u\n", rotation); - return 0; } EXPORT_SYMBOL(mipi_dbi_dev_init_with_formats); @@ -660,13 +696,11 @@ int mipi_dbi_dev_init(struct mipi_dbi_dev *dbidev, const struct drm_simple_display_pipe_funcs *funcs, const struct drm_display_mode *mode, unsigned int rotation) { - size_t bufsize = (u32)mode->vdisplay * mode->hdisplay * sizeof(u16); - dbidev->drm.mode_config.preferred_depth = 16; return mipi_dbi_dev_init_with_formats(dbidev, funcs, mipi_dbi_formats, ARRAY_SIZE(mipi_dbi_formats), mode, - rotation, bufsize); + rotation, 0); } EXPORT_SYMBOL(mipi_dbi_dev_init); diff --git a/include/drm/drm_mipi_dbi.h b/include/drm/drm_mipi_dbi.h index 637be84d3d5a..9c0e015dd929 100644 --- a/include/drm/drm_mipi_dbi.h +++ b/include/drm/drm_mipi_dbi.h @@ -164,6 +164,10 @@ static inline struct mipi_dbi_dev *drm_to_mipi_dbi_dev(struct drm_device *drm) int mipi_dbi_spi_init(struct spi_device *spi, struct mipi_dbi *dbi, struct gpio_desc *dc); + +int drm_mipi_dbi_dev_init(struct mipi_dbi_dev *dbidev, const struct drm_display_mode *mode, + u32 format, unsigned int rotation, size_t tx_buf_size); + int mipi_dbi_dev_init_with_formats(struct mipi_dbi_dev *dbidev, const struct drm_simple_display_pipe_funcs *funcs, const uint32_t *formats, unsigned int format_count, -- cgit v1.2.3 From 4a1affab9f9d816645b63bd642dccb49196e931d Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Thu, 19 Mar 2026 16:59:39 +0100 Subject: drm/mipi-dbi: Provide callbacks for atomic interfaces Refactor the existing simple-display callbacks such that they invoke helpers compatible with regular atomic modesetting. Allows for adding mipi-dbi drives that do not require simple-display helpers. Provide initializer macro for elements of the regular modesetting pipeline. These will be used by drivers to integrate mipi-dbi helpers. Also provide initializer macros for the plane formats. As the new helpers are DRM functions, add the drm_ prefix. Mipi-dbi interfaces currently lack this. v3: - fix uninitialized variable (David) - document public interfaces (David) - mention format macros in commit message (David) Signed-off-by: Thomas Zimmermann Acked-by: David Lechner Reviewed-by: Dmitry Baryshkov Link: https://patch.msgid.link/20260319160110.109610-4-tzimmermann@suse.de --- drivers/gpu/drm/drm_mipi_dbi.c | 203 +++++++++++++++++++++++++++++++---------- include/drm/drm_mipi_dbi.h | 86 +++++++++++++++++ 2 files changed, 242 insertions(+), 47 deletions(-) (limited to 'include') diff --git a/drivers/gpu/drm/drm_mipi_dbi.c b/drivers/gpu/drm/drm_mipi_dbi.c index bc5444ca7ac6..0374fd0c527d 100644 --- a/drivers/gpu/drm/drm_mipi_dbi.c +++ b/drivers/gpu/drm/drm_mipi_dbi.c @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -22,12 +23,9 @@ #include #include #include -#include -#include #include #include #include -#include #include #include