From 5fce563b639249bb5ed123421acb2bf62f3711fc Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Tue, 24 Feb 2026 10:09:31 +0000 Subject: [PATCH 001/562] fsl-mc: Remove MSI domain propagation to sub-devices Only the root device generates MSIs (it is the only one talking to the ITS), so propagating the domain is pretty pointless. Remove it. Reviewed-by: Ioana Ciornei Tested-by: Ioana Ciornei # LX2160ARDB, LS2088ARDB Tested-by: Sascha Bischoff Signed-off-by: Marc Zyngier Acked-by: Thomas Gleixner Link: https://lore.kernel.org/r/20260224100936.3752303-2-maz@kernel.org Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/bus/fsl-mc/fsl-mc-bus.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/bus/fsl-mc/fsl-mc-bus.c b/drivers/bus/fsl-mc/fsl-mc-bus.c index 221146e4860b..d54dd80c6503 100644 --- a/drivers/bus/fsl-mc/fsl-mc-bus.c +++ b/drivers/bus/fsl-mc/fsl-mc-bus.c @@ -828,14 +828,12 @@ int fsl_mc_device_add(struct fsl_mc_obj_desc *obj_desc, } else { /* * A non-DPRC object has to be a child of a DPRC, use the - * parent's ICID and interrupt domain. + * parent's ICID. */ mc_dev->icid = parent_mc_dev->icid; mc_dev->dma_mask = FSL_MC_DEFAULT_DMA_MASK; mc_dev->dev.dma_mask = &mc_dev->dma_mask; mc_dev->dev.coherent_dma_mask = mc_dev->dma_mask; - dev_set_msi_domain(&mc_dev->dev, - dev_get_msi_domain(&parent_mc_dev->dev)); } /* From 55319767ac5be2647c628bf536d4e8a2e048648b Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Tue, 24 Feb 2026 10:09:32 +0000 Subject: [PATCH 002/562] fsl-mc: Add minimal infrastructure to use platform MSI Add the tiny bit of infrastructure required to use platform MSI instead of the current hack. This means providing a write_msi_msg callback, as well as irq domain and devid retrieval helpers. Reviewed-by: Ioana Ciornei Tested-by: Ioana Ciornei # LX2160ARDB, LS2088ARDB Tested-by: Sascha Bischoff Signed-off-by: Marc Zyngier Acked-by: Thomas Gleixner Link: https://lore.kernel.org/r/20260224100936.3752303-3-maz@kernel.org Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/bus/fsl-mc/fsl-mc-msi.c | 50 ++++++++++++++++++++++++----- drivers/bus/fsl-mc/fsl-mc-private.h | 1 + include/linux/fsl/mc.h | 2 ++ 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/drivers/bus/fsl-mc/fsl-mc-msi.c b/drivers/bus/fsl-mc/fsl-mc-msi.c index 82cd69f7884c..c9f50969e88c 100644 --- a/drivers/bus/fsl-mc/fsl-mc-msi.c +++ b/drivers/bus/fsl-mc/fsl-mc-msi.c @@ -110,13 +110,8 @@ static void __fsl_mc_msi_write_msg(struct fsl_mc_device *mc_bus_dev, } } -/* - * NOTE: This function is invoked with interrupts disabled - */ -static void fsl_mc_msi_write_msg(struct irq_data *irq_data, - struct msi_msg *msg) +static void fsl_mc_write_msi_msg(struct msi_desc *msi_desc, struct msi_msg *msg) { - struct msi_desc *msi_desc = irq_data_get_msi_desc(irq_data); struct fsl_mc_device *mc_bus_dev = to_fsl_mc_device(msi_desc->dev); struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev); struct fsl_mc_device_irq *mc_dev_irq = @@ -130,6 +125,17 @@ static void fsl_mc_msi_write_msg(struct irq_data *irq_data, __fsl_mc_msi_write_msg(mc_bus_dev, mc_dev_irq, msi_desc); } +/* + * NOTE: This function is invoked with interrupts disabled + */ +static void fsl_mc_msi_write_msg(struct irq_data *irq_data, + struct msi_msg *msg) +{ + struct msi_desc *msi_desc = irq_data_get_msi_desc(irq_data); + + fsl_mc_write_msi_msg(msi_desc, msg); +} + static void fsl_mc_msi_update_chip_ops(struct msi_domain_info *info) { struct irq_chip *chip = info->chip; @@ -209,6 +215,20 @@ struct irq_domain *fsl_mc_find_msi_domain(struct device *dev) return msi_domain; } +struct irq_domain *fsl_mc_get_msi_parent(struct device *dev) +{ + struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); + struct device *root_dprc_dev; + struct device *bus_dev; + + fsl_mc_get_root_dprc(dev, &root_dprc_dev); + bus_dev = root_dprc_dev->parent; + + return (bus_dev->of_node ? + of_msi_get_domain(bus_dev, bus_dev->of_node, DOMAIN_BUS_NEXUS) : + iort_get_device_domain(bus_dev, mc_dev->icid, DOMAIN_BUS_NEXUS)); +} + int fsl_mc_msi_domain_alloc_irqs(struct device *dev, unsigned int irq_count) { int error = msi_setup_device_data(dev); @@ -220,8 +240,10 @@ int fsl_mc_msi_domain_alloc_irqs(struct device *dev, unsigned int irq_count) * NOTE: Calling this function will trigger the invocation of the * its_fsl_mc_msi_prepare() callback */ - error = msi_domain_alloc_irqs_range(dev, MSI_DEFAULT_DOMAIN, 0, irq_count - 1); - + if (!irq_domain_is_msi_parent(dev_get_msi_domain(dev))) + error = msi_domain_alloc_irqs_range(dev, MSI_DEFAULT_DOMAIN, 0, irq_count - 1); + else + error = platform_device_msi_init_and_alloc_irqs(dev, irq_count, fsl_mc_write_msi_msg); if (error) dev_err(dev, "Failed to allocate IRQs\n"); return error; @@ -231,3 +253,15 @@ void fsl_mc_msi_domain_free_irqs(struct device *dev) { msi_domain_free_irqs_all(dev, MSI_DEFAULT_DOMAIN); } + +u32 fsl_mc_get_msi_id(struct device *dev) +{ + struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); + struct device *root_dprc_dev; + + fsl_mc_get_root_dprc(dev, &root_dprc_dev); + + return (root_dprc_dev->parent->of_node ? + of_msi_xlate(dev, NULL, mc_dev->icid) : + iort_msi_map_id(dev, mc_dev->icid)); +} diff --git a/drivers/bus/fsl-mc/fsl-mc-private.h b/drivers/bus/fsl-mc/fsl-mc-private.h index beed4c53533d..44868f874fd6 100644 --- a/drivers/bus/fsl-mc/fsl-mc-private.h +++ b/drivers/bus/fsl-mc/fsl-mc-private.h @@ -642,6 +642,7 @@ int fsl_mc_msi_domain_alloc_irqs(struct device *dev, void fsl_mc_msi_domain_free_irqs(struct device *dev); struct irq_domain *fsl_mc_find_msi_domain(struct device *dev); +struct irq_domain *fsl_mc_get_msi_parent(struct device *dev); int __must_check fsl_create_mc_io(struct device *dev, phys_addr_t mc_portal_phys_addr, diff --git a/include/linux/fsl/mc.h b/include/linux/fsl/mc.h index 1da63f2d7040..fb9ad3186137 100644 --- a/include/linux/fsl/mc.h +++ b/include/linux/fsl/mc.h @@ -357,9 +357,11 @@ int mc_send_command(struct fsl_mc_io *mc_io, struct fsl_mc_command *cmd); #ifdef CONFIG_FSL_MC_BUS #define dev_is_fsl_mc(_dev) ((_dev)->bus == &fsl_mc_bus_type) +u32 fsl_mc_get_msi_id(struct device *dev); #else /* If fsl-mc bus is not present device cannot belong to fsl-mc bus */ #define dev_is_fsl_mc(_dev) (0) +#define fsl_mc_get_msi_id(_dev) (0) #endif /* Macro to check if a device is a container device */ From 9ff5d27c22d799d079674ffe91bf343c88bedea8 Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Tue, 24 Feb 2026 10:09:33 +0000 Subject: [PATCH 003/562] irqchip/gic-v3-its: Add fsl_mc device plumbing to the msi-parent handling Make the ITS code aware of fsl_mc devices by plumbing the devid retrieval primitive. Reviewed-by: Ioana Ciornei Tested-by: Ioana Ciornei # LX2160ARDB, LS2088ARDB Tested-by: Sascha Bischoff Signed-off-by: Marc Zyngier Acked-by: Thomas Gleixner Link: https://lore.kernel.org/r/20260224100936.3752303-4-maz@kernel.org Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/irqchip/irq-gic-its-msi-parent.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/irqchip/irq-gic-its-msi-parent.c b/drivers/irqchip/irq-gic-its-msi-parent.c index a832cdb2e697..d36b278ae66c 100644 --- a/drivers/irqchip/irq-gic-its-msi-parent.c +++ b/drivers/irqchip/irq-gic-its-msi-parent.c @@ -5,6 +5,7 @@ // Copyright (C) 2022 Intel #include +#include #include #include @@ -187,9 +188,11 @@ static int its_pmsi_prepare(struct irq_domain *domain, struct device *dev, { struct msi_domain_info *msi_info; u32 dev_id; - int ret; + int ret = 0; - if (dev->of_node) + if (dev_is_fsl_mc(dev)) + dev_id = fsl_mc_get_msi_id(dev); + else if (dev->of_node) ret = of_pmsi_get_msi_info(domain->parent, dev, &dev_id, NULL); else ret = iort_pmsi_get_msi_info(dev, &dev_id, NULL); From 838e857c6040f59fd586eb51262c31a71ac7d483 Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Tue, 24 Feb 2026 10:09:34 +0000 Subject: [PATCH 004/562] fsl-mc: Switch over to per-device platform MSI Obtain the msi-parent irqdomain instead of the fsl_mc domain, which magically engages the per-device infrastructure. Additionally, simplify the overly complicated error handling. Reviewed-by: Ioana Ciornei Tested-by: Ioana Ciornei # LX2160ARDB, LS2088ARDB Tested-by: Sascha Bischoff Signed-off-by: Marc Zyngier Acked-by: Thomas Gleixner Link: https://lore.kernel.org/r/20260224100936.3752303-5-maz@kernel.org Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/bus/fsl-mc/dprc-driver.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/drivers/bus/fsl-mc/dprc-driver.c b/drivers/bus/fsl-mc/dprc-driver.c index db67442addad..a85706826fa0 100644 --- a/drivers/bus/fsl-mc/dprc-driver.c +++ b/drivers/bus/fsl-mc/dprc-driver.c @@ -609,9 +609,8 @@ int dprc_setup(struct fsl_mc_device *mc_dev) { struct device *parent_dev = mc_dev->dev.parent; struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_dev); - struct irq_domain *mc_msi_domain; + struct irq_domain *mc_msi_domain = NULL; bool mc_io_created = false; - bool msi_domain_set = false; bool uapi_created = false; u16 major_ver, minor_ver; size_t region_size; @@ -652,14 +651,12 @@ int dprc_setup(struct fsl_mc_device *mc_dev) uapi_created = true; } - mc_msi_domain = fsl_mc_find_msi_domain(&mc_dev->dev); - if (!mc_msi_domain) { + mc_msi_domain = fsl_mc_get_msi_parent(&mc_dev->dev); + if (!mc_msi_domain) dev_warn(&mc_dev->dev, "WARNING: MC bus without interrupt support\n"); - } else { + else dev_set_msi_domain(&mc_dev->dev, mc_msi_domain); - msi_domain_set = true; - } error = dprc_open(mc_dev->mc_io, 0, mc_dev->obj_desc.id, &mc_dev->mc_handle); @@ -699,8 +696,7 @@ int dprc_setup(struct fsl_mc_device *mc_dev) (void)dprc_close(mc_dev->mc_io, 0, mc_dev->mc_handle); error_cleanup_msi_domain: - if (msi_domain_set) - dev_set_msi_domain(&mc_dev->dev, NULL); + dev_set_msi_domain(&mc_dev->dev, NULL); if (mc_io_created) { fsl_destroy_mc_io(mc_dev->mc_io); From 2c5b6443e6b6ad98ef29460c219b0a287bb3a554 Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Tue, 24 Feb 2026 10:09:35 +0000 Subject: [PATCH 005/562] fsl-mc: Remove legacy MSI implementation Get rid of most of the fsl_mc MSI infrastructure, which is now replaced by common code. Reviewed-by: Ioana Ciornei Tested-by: Ioana Ciornei # LX2160ARDB, LS2088ARDB Tested-by: Sascha Bischoff Signed-off-by: Marc Zyngier Acked-by: Thomas Gleixner Link: https://lore.kernel.org/r/20260224100936.3752303-6-maz@kernel.org Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/bus/fsl-mc/fsl-mc-msi.c | 166 +------------------ drivers/bus/fsl-mc/fsl-mc-private.h | 1 - drivers/irqchip/Kconfig | 6 - drivers/irqchip/Makefile | 1 - drivers/irqchip/irq-gic-v3-its-fsl-mc-msi.c | 168 -------------------- include/linux/fsl/mc.h | 4 - include/linux/irqdomain_defs.h | 1 - 7 files changed, 8 insertions(+), 339 deletions(-) delete mode 100644 drivers/irqchip/irq-gic-v3-its-fsl-mc-msi.c diff --git a/drivers/bus/fsl-mc/fsl-mc-msi.c b/drivers/bus/fsl-mc/fsl-mc-msi.c index c9f50969e88c..be38b43803de 100644 --- a/drivers/bus/fsl-mc/fsl-mc-msi.c +++ b/drivers/bus/fsl-mc/fsl-mc-msi.c @@ -15,53 +15,16 @@ #include "fsl-mc-private.h" -#ifdef GENERIC_MSI_DOMAIN_OPS -/* - * Generate a unique ID identifying the interrupt (only used within the MSI - * irqdomain. Combine the icid with the interrupt index. - */ -static irq_hw_number_t fsl_mc_domain_calc_hwirq(struct fsl_mc_device *dev, - struct msi_desc *desc) -{ - /* - * Make the base hwirq value for ICID*10000 so it is readable - * as a decimal value in /proc/interrupts. - */ - return (irq_hw_number_t)(desc->msi_index + (dev->icid * 10000)); -} - -static void fsl_mc_msi_set_desc(msi_alloc_info_t *arg, - struct msi_desc *desc) -{ - arg->desc = desc; - arg->hwirq = fsl_mc_domain_calc_hwirq(to_fsl_mc_device(desc->dev), - desc); -} -#else -#define fsl_mc_msi_set_desc NULL -#endif - -static void fsl_mc_msi_update_dom_ops(struct msi_domain_info *info) -{ - struct msi_domain_ops *ops = info->ops; - - if (!ops) - return; - - /* - * set_desc should not be set by the caller - */ - if (!ops->set_desc) - ops->set_desc = fsl_mc_msi_set_desc; -} - -static void __fsl_mc_msi_write_msg(struct fsl_mc_device *mc_bus_dev, - struct fsl_mc_device_irq *mc_dev_irq, - struct msi_desc *msi_desc) +static void fsl_mc_write_msi_msg(struct msi_desc *msi_desc, struct msi_msg *msg) { - int error; + struct fsl_mc_device *mc_bus_dev = to_fsl_mc_device(msi_desc->dev); + struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev); + struct fsl_mc_device_irq *mc_dev_irq = &mc_bus->irq_resources[msi_desc->msi_index]; struct fsl_mc_device *owner_mc_dev = mc_dev_irq->mc_dev; struct dprc_irq_cfg irq_cfg; + int error; + + msi_desc->msg = *msg; /* * msi_desc->msg.address is 0x0 when this function is invoked in @@ -110,111 +73,6 @@ static void __fsl_mc_msi_write_msg(struct fsl_mc_device *mc_bus_dev, } } -static void fsl_mc_write_msi_msg(struct msi_desc *msi_desc, struct msi_msg *msg) -{ - struct fsl_mc_device *mc_bus_dev = to_fsl_mc_device(msi_desc->dev); - struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev); - struct fsl_mc_device_irq *mc_dev_irq = - &mc_bus->irq_resources[msi_desc->msi_index]; - - msi_desc->msg = *msg; - - /* - * Program the MSI (paddr, value) pair in the device: - */ - __fsl_mc_msi_write_msg(mc_bus_dev, mc_dev_irq, msi_desc); -} - -/* - * NOTE: This function is invoked with interrupts disabled - */ -static void fsl_mc_msi_write_msg(struct irq_data *irq_data, - struct msi_msg *msg) -{ - struct msi_desc *msi_desc = irq_data_get_msi_desc(irq_data); - - fsl_mc_write_msi_msg(msi_desc, msg); -} - -static void fsl_mc_msi_update_chip_ops(struct msi_domain_info *info) -{ - struct irq_chip *chip = info->chip; - - if (!chip) - return; - - /* - * irq_write_msi_msg should not be set by the caller - */ - if (!chip->irq_write_msi_msg) - chip->irq_write_msi_msg = fsl_mc_msi_write_msg; -} - -/** - * fsl_mc_msi_create_irq_domain - Create a fsl-mc MSI interrupt domain - * @fwnode: Optional firmware node of the interrupt controller - * @info: MSI domain info - * @parent: Parent irq domain - * - * Updates the domain and chip ops and creates a fsl-mc MSI - * interrupt domain. - * - * Returns: - * A domain pointer or NULL in case of failure. - */ -struct irq_domain *fsl_mc_msi_create_irq_domain(struct fwnode_handle *fwnode, - struct msi_domain_info *info, - struct irq_domain *parent) -{ - struct irq_domain *domain; - - if (WARN_ON((info->flags & MSI_FLAG_LEVEL_CAPABLE))) - info->flags &= ~MSI_FLAG_LEVEL_CAPABLE; - if (info->flags & MSI_FLAG_USE_DEF_DOM_OPS) - fsl_mc_msi_update_dom_ops(info); - if (info->flags & MSI_FLAG_USE_DEF_CHIP_OPS) - fsl_mc_msi_update_chip_ops(info); - info->flags |= MSI_FLAG_ALLOC_SIMPLE_MSI_DESCS | MSI_FLAG_FREE_MSI_DESCS; - - domain = msi_create_irq_domain(fwnode, info, parent); - if (domain) - irq_domain_update_bus_token(domain, DOMAIN_BUS_FSL_MC_MSI); - - return domain; -} - -struct irq_domain *fsl_mc_find_msi_domain(struct device *dev) -{ - struct device *root_dprc_dev; - struct device *bus_dev; - struct irq_domain *msi_domain; - struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); - - fsl_mc_get_root_dprc(dev, &root_dprc_dev); - bus_dev = root_dprc_dev->parent; - - if (bus_dev->of_node) { - msi_domain = of_msi_map_get_device_domain(dev, - mc_dev->icid, - DOMAIN_BUS_FSL_MC_MSI); - - /* - * if the msi-map property is missing assume that all the - * child containers inherit the domain from the parent - */ - if (!msi_domain) - - msi_domain = of_msi_get_domain(bus_dev, - bus_dev->of_node, - DOMAIN_BUS_FSL_MC_MSI); - } else { - msi_domain = iort_get_device_domain(dev, mc_dev->icid, - DOMAIN_BUS_FSL_MC_MSI); - } - - return msi_domain; -} - struct irq_domain *fsl_mc_get_msi_parent(struct device *dev) { struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); @@ -232,18 +90,10 @@ struct irq_domain *fsl_mc_get_msi_parent(struct device *dev) int fsl_mc_msi_domain_alloc_irqs(struct device *dev, unsigned int irq_count) { int error = msi_setup_device_data(dev); - if (error) return error; - /* - * NOTE: Calling this function will trigger the invocation of the - * its_fsl_mc_msi_prepare() callback - */ - if (!irq_domain_is_msi_parent(dev_get_msi_domain(dev))) - error = msi_domain_alloc_irqs_range(dev, MSI_DEFAULT_DOMAIN, 0, irq_count - 1); - else - error = platform_device_msi_init_and_alloc_irqs(dev, irq_count, fsl_mc_write_msi_msg); + error = platform_device_msi_init_and_alloc_irqs(dev, irq_count, fsl_mc_write_msi_msg); if (error) dev_err(dev, "Failed to allocate IRQs\n"); return error; diff --git a/drivers/bus/fsl-mc/fsl-mc-private.h b/drivers/bus/fsl-mc/fsl-mc-private.h index 44868f874fd6..197edcc8cde4 100644 --- a/drivers/bus/fsl-mc/fsl-mc-private.h +++ b/drivers/bus/fsl-mc/fsl-mc-private.h @@ -641,7 +641,6 @@ int fsl_mc_msi_domain_alloc_irqs(struct device *dev, void fsl_mc_msi_domain_free_irqs(struct device *dev); -struct irq_domain *fsl_mc_find_msi_domain(struct device *dev); struct irq_domain *fsl_mc_get_msi_parent(struct device *dev); int __must_check fsl_create_mc_io(struct device *dev, diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index e755a2a05209..753e8fc3b916 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -51,12 +51,6 @@ config ARM_GIC_V3_ITS default ARM_GIC_V3 select IRQ_MSI_IOMMU -config ARM_GIC_V3_ITS_FSL_MC - bool - depends on ARM_GIC_V3_ITS - depends on FSL_MC_BUS - default ARM_GIC_V3_ITS - config ARM_GIC_V5 bool select IRQ_DOMAIN_HIERARCHY diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 26aa3b6ec99f..d5a28cee0d8e 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -35,7 +35,6 @@ obj-$(CONFIG_ARM_GIC_V2M) += irq-gic-v2m.o obj-$(CONFIG_ARM_GIC_V3) += irq-gic-v3.o irq-gic-v3-mbi.o irq-gic-common.o obj-$(CONFIG_ARM_GIC_ITS_PARENT) += irq-gic-its-msi-parent.o obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o irq-gic-v4.o -obj-$(CONFIG_ARM_GIC_V3_ITS_FSL_MC) += irq-gic-v3-its-fsl-mc-msi.o obj-$(CONFIG_ARM_GIC_V5) += irq-gic-v5.o irq-gic-v5-irs.o irq-gic-v5-its.o \ irq-gic-v5-iwb.o obj-$(CONFIG_HISILICON_IRQ_MBIGEN) += irq-mbigen.o diff --git a/drivers/irqchip/irq-gic-v3-its-fsl-mc-msi.c b/drivers/irqchip/irq-gic-v3-its-fsl-mc-msi.c deleted file mode 100644 index b5785472765a..000000000000 --- a/drivers/irqchip/irq-gic-v3-its-fsl-mc-msi.c +++ /dev/null @@ -1,168 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Freescale Management Complex (MC) bus driver MSI support - * - * Copyright (C) 2015-2016 Freescale Semiconductor, Inc. - * Author: German Rivera - * - */ - -#include -#include -#include -#include -#include -#include -#include - -static struct irq_chip its_msi_irq_chip = { - .name = "ITS-fMSI", - .irq_mask = irq_chip_mask_parent, - .irq_unmask = irq_chip_unmask_parent, - .irq_eoi = irq_chip_eoi_parent, - .irq_set_affinity = msi_domain_set_affinity -}; - -static u32 fsl_mc_msi_domain_get_msi_id(struct irq_domain *domain, - struct fsl_mc_device *mc_dev) -{ - struct device_node *of_node; - u32 out_id; - - of_node = irq_domain_get_of_node(domain); - out_id = of_node ? of_msi_xlate(&mc_dev->dev, &of_node, mc_dev->icid) : - iort_msi_map_id(&mc_dev->dev, mc_dev->icid); - - return out_id; -} - -static int its_fsl_mc_msi_prepare(struct irq_domain *msi_domain, - struct device *dev, - int nvec, msi_alloc_info_t *info) -{ - struct fsl_mc_device *mc_bus_dev; - struct msi_domain_info *msi_info; - - if (!dev_is_fsl_mc(dev)) - return -EINVAL; - - mc_bus_dev = to_fsl_mc_device(dev); - if (!(mc_bus_dev->flags & FSL_MC_IS_DPRC)) - return -EINVAL; - - /* - * Set the device Id to be passed to the GIC-ITS: - * - * NOTE: This device id corresponds to the IOMMU stream ID - * associated with the DPRC object (ICID). - */ - info->scratchpad[0].ul = fsl_mc_msi_domain_get_msi_id(msi_domain, - mc_bus_dev); - msi_info = msi_get_domain_info(msi_domain->parent); - - /* Allocate at least 32 MSIs, and always as a power of 2 */ - nvec = max_t(int, 32, roundup_pow_of_two(nvec)); - return msi_info->ops->msi_prepare(msi_domain->parent, dev, nvec, info); -} - -static struct msi_domain_ops its_fsl_mc_msi_ops __ro_after_init = { - .msi_prepare = its_fsl_mc_msi_prepare, -}; - -static struct msi_domain_info its_fsl_mc_msi_domain_info = { - .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS), - .ops = &its_fsl_mc_msi_ops, - .chip = &its_msi_irq_chip, -}; - -static const struct of_device_id its_device_id[] = { - { .compatible = "arm,gic-v3-its", }, - {}, -}; - -static void __init its_fsl_mc_msi_init_one(struct fwnode_handle *handle, - const char *name) -{ - struct irq_domain *parent; - struct irq_domain *mc_msi_domain; - - parent = irq_find_matching_fwnode(handle, DOMAIN_BUS_NEXUS); - if (!parent || !msi_get_domain_info(parent)) { - pr_err("%s: unable to locate ITS domain\n", name); - return; - } - - mc_msi_domain = fsl_mc_msi_create_irq_domain(handle, - &its_fsl_mc_msi_domain_info, - parent); - if (!mc_msi_domain) { - pr_err("%s: unable to create fsl-mc domain\n", name); - return; - } - - pr_info("fsl-mc MSI: %s domain created\n", name); -} - -#ifdef CONFIG_ACPI -static int __init -its_fsl_mc_msi_parse_madt(union acpi_subtable_headers *header, - const unsigned long end) -{ - struct acpi_madt_generic_translator *its_entry; - struct fwnode_handle *dom_handle; - const char *node_name; - int err = 0; - - its_entry = (struct acpi_madt_generic_translator *)header; - node_name = kasprintf(GFP_KERNEL, "ITS@0x%lx", - (long)its_entry->base_address); - - dom_handle = iort_find_domain_token(its_entry->translation_id); - if (!dom_handle) { - pr_err("%s: Unable to locate ITS domain handle\n", node_name); - err = -ENXIO; - goto out; - } - - its_fsl_mc_msi_init_one(dom_handle, node_name); - -out: - kfree(node_name); - return err; -} - - -static void __init its_fsl_mc_acpi_msi_init(void) -{ - acpi_table_parse_madt(ACPI_MADT_TYPE_GENERIC_TRANSLATOR, - its_fsl_mc_msi_parse_madt, 0); -} -#else -static inline void its_fsl_mc_acpi_msi_init(void) { } -#endif - -static void __init its_fsl_mc_of_msi_init(void) -{ - struct device_node *np; - - for (np = of_find_matching_node(NULL, its_device_id); np; - np = of_find_matching_node(np, its_device_id)) { - if (!of_device_is_available(np)) - continue; - if (!of_property_read_bool(np, "msi-controller")) - continue; - - its_fsl_mc_msi_init_one(of_fwnode_handle(np), - np->full_name); - } -} - -static int __init its_fsl_mc_msi_init(void) -{ - its_fsl_mc_of_msi_init(); - its_fsl_mc_acpi_msi_init(); - - return 0; -} - -early_initcall(its_fsl_mc_msi_init); diff --git a/include/linux/fsl/mc.h b/include/linux/fsl/mc.h index fb9ad3186137..9f671e87c80c 100644 --- a/include/linux/fsl/mc.h +++ b/include/linux/fsl/mc.h @@ -421,10 +421,6 @@ int __must_check fsl_mc_object_allocate(struct fsl_mc_device *mc_dev, void fsl_mc_object_free(struct fsl_mc_device *mc_adev); -struct irq_domain *fsl_mc_msi_create_irq_domain(struct fwnode_handle *fwnode, - struct msi_domain_info *info, - struct irq_domain *parent); - int __must_check fsl_mc_allocate_irqs(struct fsl_mc_device *mc_dev); void fsl_mc_free_irqs(struct fsl_mc_device *mc_dev); diff --git a/include/linux/irqdomain_defs.h b/include/linux/irqdomain_defs.h index 36653e2ee1c9..3a03bdfeeee9 100644 --- a/include/linux/irqdomain_defs.h +++ b/include/linux/irqdomain_defs.h @@ -17,7 +17,6 @@ enum irq_domain_bus_token { DOMAIN_BUS_PLATFORM_MSI, DOMAIN_BUS_NEXUS, DOMAIN_BUS_IPI, - DOMAIN_BUS_FSL_MC_MSI, DOMAIN_BUS_TI_SCI_INTA_MSI, DOMAIN_BUS_WAKEUP, DOMAIN_BUS_VMD_MSI, From eb9f171bd8c9f597d5e6f834beaecc66bfedc431 Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Tue, 24 Feb 2026 10:09:36 +0000 Subject: [PATCH 006/562] platform-msi: Remove stale comment The backward compatibility code for the previous incarnation of platform MSI was removed in e9894248994ca ("genirq/msi: Remove platform MSI leftovers"), but the comment about that removal is still present. Remove it. Reviewed-by: Ioana Ciornei Tested-by: Ioana Ciornei # LX2160ARDB, LS2088ARDB Tested-by: Sascha Bischoff Signed-off-by: Marc Zyngier Acked-by: Thomas Gleixner Link: https://lore.kernel.org/r/20260224100936.3752303-7-maz@kernel.org Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/base/platform-msi.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/base/platform-msi.c b/drivers/base/platform-msi.c index 70db08f3ac6f..69eed058eb20 100644 --- a/drivers/base/platform-msi.c +++ b/drivers/base/platform-msi.c @@ -61,10 +61,6 @@ static const struct msi_domain_template platform_msi_template = { * parent. The parent domain sets up the new domain. The domain has * a fixed size of @nvec. The domain is managed by devres and will * be removed when the device is removed. - * - * Note: For migration purposes this falls back to the original platform_msi code - * up to the point where all platforms have been converted to the MSI - * parent model. */ int platform_device_msi_init_and_alloc_irqs(struct device *dev, unsigned int nvec, irq_write_msi_msg_t write_msi_msg) From 79ef8bf8e69ff605de292ff22475647d9eb92f43 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Thu, 26 Feb 2026 14:09:42 -0800 Subject: [PATCH 007/562] virt: fsl_hypervisor: fix header kernel-doc warnings Correct struct member names to placate kernel-doc warnings: Warning: include/uapi/linux/fsl_hypervisor.h:148 struct member 'local_vaddr' not described in 'fsl_hv_ioctl_memcpy' Warning: include/uapi/linux/fsl_hypervisor.h:148 struct member 'remote_paddr' not described in 'fsl_hv_ioctl_memcpy' Fixes: 6db7199407ca ("drivers/virt: introduce Freescale hypervisor management driver") Signed-off-by: Randy Dunlap Link: https://lore.kernel.org/r/20260226220942.1035295-1-rdunlap@infradead.org Signed-off-by: Christophe Leroy (CS GROUP) --- include/uapi/linux/fsl_hypervisor.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/uapi/linux/fsl_hypervisor.h b/include/uapi/linux/fsl_hypervisor.h index 1e237fba951f..ab4388441e80 100644 --- a/include/uapi/linux/fsl_hypervisor.h +++ b/include/uapi/linux/fsl_hypervisor.h @@ -114,9 +114,9 @@ struct fsl_hv_ioctl_stop { * @target: the partition ID of the target partition, or -1 for this * partition * @reserved: reserved, must be set to 0 - * @local_addr: user-space virtual address of a buffer in the local + * @local_vaddr: user-space virtual address of a buffer in the local * partition - * @remote_addr: guest physical address of a buffer in the + * @remote_paddr: guest physical address of a buffer in the * remote partition * @count: the number of bytes to copy. Both the local and remote * buffers must be at least 'count' bytes long From a128e2f3df2894eb2531ba7463b36f08cccce878 Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Tue, 10 Mar 2026 00:25:30 +0800 Subject: [PATCH 008/562] soc: fsl: qe_ports_ic: Add missing cleanup on device removal Add a devm action handler to properly clean up the irq_domain and chained handler when the device is removed. Fixes: f0bcd784e1b7 ("soc: fsl: qe: Add an interrupt controller for QUICC Engine Ports") Signed-off-by: Felix Gu Link: https://lore.kernel.org/r/20260310-qe_ports_ic-v1-1-608293026561@gmail.com Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/soc/fsl/qe/qe_ports_ic.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/drivers/soc/fsl/qe/qe_ports_ic.c b/drivers/soc/fsl/qe/qe_ports_ic.c index 8e2107e2cde5..5e3fae19f314 100644 --- a/drivers/soc/fsl/qe/qe_ports_ic.c +++ b/drivers/soc/fsl/qe/qe_ports_ic.c @@ -17,6 +17,7 @@ struct qepic_data { void __iomem *reg; struct irq_domain *host; + int irq; }; static void qepic_mask(struct irq_data *d) @@ -92,11 +93,18 @@ static const struct irq_domain_ops qepic_host_ops = { .map = qepic_host_map, }; +static void qepic_remove(void *res) +{ + struct qepic_data *data = res; + + irq_set_chained_handler_and_data(data->irq, NULL, NULL); + irq_domain_remove(data->host); +} + static int qepic_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct qepic_data *data; - int irq; data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) @@ -106,17 +114,18 @@ static int qepic_probe(struct platform_device *pdev) if (IS_ERR(data->reg)) return PTR_ERR(data->reg); - irq = platform_get_irq(pdev, 0); - if (irq < 0) - return irq; + data->irq = platform_get_irq(pdev, 0); + if (data->irq < 0) + return data->irq; data->host = irq_domain_add_linear(dev->of_node, 32, &qepic_host_ops, data); if (!data->host) return -ENODEV; - irq_set_chained_handler_and_data(irq, qepic_cascade, data); + irq_set_chained_handler_and_data(data->irq, qepic_cascade, data); + + return devm_add_action_or_reset(dev, qepic_remove, data); - return 0; } static const struct of_device_id qepic_match[] = { From b5e9b6b37746c9de5ed8ac271bd6525abfe5d2eb Mon Sep 17 00:00:00 2001 From: "Christophe Leroy (CS GROUP)" Date: Tue, 24 Mar 2026 23:34:16 +0100 Subject: [PATCH 009/562] soc: fsl: qe_ports_ic: switch to irq_domain_create_linear() irq_domain_add_linear() is about to be removed, replace by the more generic irq_domain_create_linear(), see commit 42b8b16fe56c ("irqdomain: Drop irq_domain_add_*() functions") for details. Link: https://lore.kernel.org/r/a9de2f351ea71e4b794baaea8d9d790fbfac8d26.1774391374.git.chleroy@kernel.org Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/soc/fsl/qe/qe_ports_ic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/soc/fsl/qe/qe_ports_ic.c b/drivers/soc/fsl/qe/qe_ports_ic.c index 5e3fae19f314..9b0bba64e91e 100644 --- a/drivers/soc/fsl/qe/qe_ports_ic.c +++ b/drivers/soc/fsl/qe/qe_ports_ic.c @@ -118,7 +118,7 @@ static int qepic_probe(struct platform_device *pdev) if (data->irq < 0) return data->irq; - data->host = irq_domain_add_linear(dev->of_node, 32, &qepic_host_ops, data); + data->host = irq_domain_create_linear(dev_fwnode(dev), 32, &qepic_host_ops, data); if (!data->host) return -ENODEV; From ab8840460dbd706743ce14f5c1bbba2aa54a02bc Mon Sep 17 00:00:00 2001 From: Wang Jun <1742789905@qq.com> Date: Fri, 27 Mar 2026 08:12:25 +0800 Subject: [PATCH 010/562] soc: fsl: qe: panic on ioremap() failure in qe_reset() When ioremap() fails in qe_reset(), the global pointer qe_immr remains NULL, leading to a subsequent NULL pointer dereference when the pointer is accessed. Since this happens early in the boot process, a failure to map a few bytes of I/O memory indicates a fatal error from which the system cannot recover. Follow the same pattern as qe_sdma_init() and panic immediately when ioremap() fails. This avoids a silent NULL pointer dereference later and makes the error explicit. Fixes: 986585385131 ("[POWERPC] Add QUICC Engine (QE) infrastructure") Cc: stable@vger.kernel.org Signed-off-by: Wang Jun <1742789905@qq.com> Link: https://lore.kernel.org/r/tencent_FED49CF5331CC0C7910618883332A08E2606@qq.com [chleroy: Rearranged change to reduce churn] Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/soc/fsl/qe/qe.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/soc/fsl/qe/qe.c b/drivers/soc/fsl/qe/qe.c index 70b6eddb867b..3ecc4ce9cfa2 100644 --- a/drivers/soc/fsl/qe/qe.c +++ b/drivers/soc/fsl/qe/qe.c @@ -89,6 +89,9 @@ void qe_reset(void) if (qe_immr == NULL) qe_immr = ioremap(get_qe_base(), QE_IMMAP_SIZE); + if (!qe_immr) + panic("QE:ioremap failed!"); + qe_snums_init(); qe_issue_cmd(QE_RESET, QE_CR_SUBBLOCK_INVALID, From c99d8ac7b355cbbdbb3f4d8b9a9b46de05a6fee9 Mon Sep 17 00:00:00 2001 From: Ioana Ciornei Date: Wed, 1 Apr 2026 17:45:08 +0300 Subject: [PATCH 011/562] bus: fsl-mc: wait for the MC firmware to complete its boot There are use cases in which the Management Complex firmware boot process is started by the bootloader which does not wait for the boot to complete. This is mainly done in order to reduce the overall boot time of a DPAA2 based SoC. In this kind of circumstance, the fsl-mc bus driver needs to make sure that the MC firmware boot process is finished before proceeding to the usual operations such as interrogating the firmware to gather all existent DPAA2 objects, creating the fsl-mc devices on the bus etc. Add this kind of check early in the boot process of the fsl-mc bus and defer the probe in case the firmware is still in its boot process. Signed-off-by: Ioana Ciornei Link: https://lore.kernel.org/r/20260401144508.3062019-1-ioana.ciornei@nxp.com Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/bus/fsl-mc/fsl-mc-bus.c | 46 +++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/drivers/bus/fsl-mc/fsl-mc-bus.c b/drivers/bus/fsl-mc/fsl-mc-bus.c index d54dd80c6503..64d75eed0d34 100644 --- a/drivers/bus/fsl-mc/fsl-mc-bus.c +++ b/drivers/bus/fsl-mc/fsl-mc-bus.c @@ -66,6 +66,13 @@ struct fsl_mc_addr_translation_range { #define GCR1_P1_STOP BIT(31) #define GCR1_P2_STOP BIT(30) +#define FSL_MC_GSR 0x8 +#define FSL_MC_GSR_BOOT_DONE BIT(0) +#define FSL_MC_GSR_MCS_MASK GENMASK(7, 0) +#define FSL_MC_GSR_MCS_ERR_MASK GENMASK(7, 1) +#define FSL_MC_GSR_BC_MASK GENMASK(15, 8) +#define FSL_MC_GSR_BC_SHIFT 8 + #define FSL_MC_FAPR 0x28 #define MC_FAPR_PL BIT(18) #define MC_FAPR_BMT BIT(17) @@ -990,6 +997,41 @@ static int get_mc_addr_translation_ranges(struct device *dev, return 0; } +static u32 fsl_mc_read_gsr(struct fsl_mc *mc) +{ + return readl(mc->fsl_mc_regs + FSL_MC_GSR); +} + +static int fsl_mc_firmware_check(struct platform_device *pdev) +{ + struct fsl_mc *mc = platform_get_drvdata(pdev); + u32 gsr, boot_done, boot_code, mcs; + + gsr = fsl_mc_read_gsr(mc); + boot_code = (gsr & FSL_MC_GSR_BC_MASK) >> FSL_MC_GSR_BC_SHIFT; + if (boot_code == 0xDD) { + dev_err(&pdev->dev, + "fsl-mc: DPL processing was not started, DPAA2 will not work!\n"); + return -EOPNOTSUPP; + } + + boot_done = gsr & FSL_MC_GSR_BOOT_DONE; + if (!boot_done) { + dev_dbg(&pdev->dev, + "fsl-mc: DPL processing in progress, defer probe\n"); + return -EPROBE_DEFER; + } + + mcs = gsr & FSL_MC_GSR_MCS_MASK; + if (mcs & FSL_MC_GSR_MCS_ERR_MASK) { + dev_err(&pdev->dev, + "fsl-mc: MC boot completed with error 0x%x\n", mcs); + return -EINVAL; + } + + return 0; +} + /* * fsl_mc_bus_probe - callback invoked when the root MC bus is being * added @@ -1054,6 +1096,10 @@ static int fsl_mc_bus_probe(struct platform_device *pdev) mc->fsl_mc_regs + FSL_MC_GCR1); } + error = fsl_mc_firmware_check(pdev); + if (error) + return error; + /* * Get physical address of MC portal for the root DPRC: */ From 50d92732d10ead04f6533b2b8ba758f3dd70d6d9 Mon Sep 17 00:00:00 2001 From: Yu-Chun Lin Date: Tue, 5 May 2026 18:58:36 +0800 Subject: [PATCH 012/562] arm64: dts: realtek: Add pinctrl support for RTD1625 Add the pinctrl nodes for the Realtek RTD1625 SoC. Reviewed-by: Linus Walleij Signed-off-by: Yu-Chun Lin Link: https://lore.kernel.org/r/20260505105838.1014771-1-eleanor.lin@realtek.com Signed-off-by: Arnd Bergmann --- arch/arm64/boot/dts/realtek/kent.dtsi | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/arch/arm64/boot/dts/realtek/kent.dtsi b/arch/arm64/boot/dts/realtek/kent.dtsi index ae006ce24420..8d4293cd4c03 100644 --- a/arch/arm64/boot/dts/realtek/kent.dtsi +++ b/arch/arm64/boot/dts/realtek/kent.dtsi @@ -150,6 +150,26 @@ reg-shift = <2>; status = "disabled"; }; + + iso_pinctrl: pinctrl@4e000 { + compatible = "realtek,rtd1625-iso-pinctrl"; + reg = <0x4e000 0x1a4>; + }; + + main2_pinctrl: pinctrl@4f200 { + compatible = "realtek,rtd1625-main2-pinctrl"; + reg = <0x4f200 0x50>; + }; + + isom_pinctrl: pinctrl@146200 { + compatible = "realtek,rtd1625-isom-pinctrl"; + reg = <0x146200 0x34>; + }; + + ve4_pinctrl: pinctrl@14e000 { + compatible = "realtek,rtd1625-ve4-pinctrl"; + reg = <0x14e000 0x84>; + }; }; gic: interrupt-controller@ff100000 { From d2ab44b9ecf4ff4be22025c5f6f5cf4723403a66 Mon Sep 17 00:00:00 2001 From: Mohamed Ayman Date: Tue, 5 May 2026 11:36:07 +0200 Subject: [PATCH 013/562] ARM: dts: ixp4xx: use phandle-based GPIOs in mi424wr Convert remaining legacy integer GPIO specifiers to phandle-based descriptors in intel-ixp42x-actiontec-mi424wr.dtsi. All other GPIOs in this file already use &gpio0/&gpio1. These are the last remaining legacy users in the IXP4xx DTS files. Signed-off-by: Mohamed Ayman Link: https://lore.kernel.org/20260428191029.809462-1-mohamedaymanworkspace@gmail.com Signed-off-by: Linus Walleij Link: https://lore.kernel.org/r/20260505-ixp4xx-dts-v1-1-02f4a92a9697@kernel.org Signed-off-by: Arnd Bergmann --- .../boot/dts/intel/ixp/intel-ixp42x-actiontec-mi424wr.dtsi | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/arm/boot/dts/intel/ixp/intel-ixp42x-actiontec-mi424wr.dtsi b/arch/arm/boot/dts/intel/ixp/intel-ixp42x-actiontec-mi424wr.dtsi index 9b54e3c01a34..3043ae7232dd 100644 --- a/arch/arm/boot/dts/intel/ixp/intel-ixp42x-actiontec-mi424wr.dtsi +++ b/arch/arm/boot/dts/intel/ixp/intel-ixp42x-actiontec-mi424wr.dtsi @@ -195,19 +195,19 @@ pci-reset-hog { gpio-hog; - gpios = <7 GPIO_ACTIVE_HIGH>; + gpios = <&gpio0 7 GPIO_ACTIVE_HIGH>; output-high; line-name = "PCI reset"; }; pstn-relay-hog-1 { gpio-hog; - gpios = <11 GPIO_ACTIVE_HIGH>; + gpios = <&gpio0 11 GPIO_ACTIVE_HIGH>; output-low; line-name = "PSTN relay control 1"; }; pstn-relay-hog-2 { gpio-hog; - gpios = <12 GPIO_ACTIVE_HIGH>; + gpios = <&gpio0 12 GPIO_ACTIVE_HIGH>; output-low; line-name = "PSTN relay control 2"; }; From f0e3ab65a0a7997e6fbba7a9d0cf1ee06d09381c Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 7 May 2026 14:32:59 +0200 Subject: [PATCH 014/562] soc: document merges Signed-off-by: Arnd Bergmann --- arch/arm/arm-soc-for-next-contents.txt | 39 ++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 arch/arm/arm-soc-for-next-contents.txt diff --git a/arch/arm/arm-soc-for-next-contents.txt b/arch/arm/arm-soc-for-next-contents.txt new file mode 100644 index 000000000000..74bba327e77a --- /dev/null +++ b/arch/arm/arm-soc-for-next-contents.txt @@ -0,0 +1,39 @@ +soc/arm + patch + ARM: select legacy gpiolib interfaces where used + +soc/dt + patch + arm64: dts: realtek: Add pinctrl support for RTD1625 + ARM: dts: ixp4xx: use phandle-based GPIOs in mi424wr + +soc/drivers + +soc/defconfig + patch + arm64: defconfig: Move entries to match savedefconfig + arm64: defconfig: Drop unused legacy netfilter options + arm64: defconfig: Drop default or selected drivers + arm64: defconfig: Drop unused Ethernet vendors + arm64: defconfig: Switch Ethernet drivers to modules + defconfig/pinctrl + git://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-pinctrl tags/v7.2-qcom-pinctrl-defconfigs + patch + ARM: multi_v7_defconfig: Move entries to match savedefconfig + ARM: configs: Drop redundant I2C_DESIGNWARE_PLATFORM + ARM: configs: Drop redundant SND_ATMEL_SOC + ARM: multi_v7_defconfig: Cleanup redundant options + ARM: multi_v7_defconfig: Correct QCOM_RPMH and QCOM_RPMHPD + +soc/late + +arm/fixes + renesas/fixes-1 + git://git.kernel.org/pub/scm/linux/kernel/git/geert/renesas-devel tags/renesas-fixes-for-v7.1-tag1 + patch + ARM: integrator: Fix early initialization + MAINTAINERS: Add maintainers for ARM/REALTEK ARCHITECTURE + ARM: realtek: MAINTAINERS: Include pin controller drivers + ffa/fixes-2 + git://git.kernel.org/pub/scm/linux/kernel/git/sudeep.holla/linux tags/ffa-fixes-7.1-2 + From 8bb9429a291964ca7a2244f48a34e02bb862c95d Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 8 May 2026 15:36:35 +0200 Subject: [PATCH 015/562] soc: document merges Signed-off-by: Arnd Bergmann --- arch/arm/arm-soc-for-next-contents.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/arm/arm-soc-for-next-contents.txt b/arch/arm/arm-soc-for-next-contents.txt index 74bba327e77a..a338ff32632e 100644 --- a/arch/arm/arm-soc-for-next-contents.txt +++ b/arch/arm/arm-soc-for-next-contents.txt @@ -36,4 +36,8 @@ arm/fixes ARM: realtek: MAINTAINERS: Include pin controller drivers ffa/fixes-2 git://git.kernel.org/pub/scm/linux/kernel/git/sudeep.holla/linux tags/ffa-fixes-7.1-2 + patch + firmware: psci: Set pm_set_resume/suspend_via_firmware() for SYSTEM_SUSPEND + riscv/dt-fixes + https://git.kernel.org/pub/scm/linux/kernel/git/conor/linux tags/riscv-dt-fixes-for-v7.1-rc3 From b48ae6f3df2fe193b9afd57bb7c03bdc36dc60e7 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 20 May 2026 12:27:12 +0200 Subject: [PATCH 016/562] soc: document merges Signed-off-by: Arnd Bergmann --- arch/arm/arm-soc-for-next-contents.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/arch/arm/arm-soc-for-next-contents.txt b/arch/arm/arm-soc-for-next-contents.txt index a338ff32632e..6817d584bfa7 100644 --- a/arch/arm/arm-soc-for-next-contents.txt +++ b/arch/arm/arm-soc-for-next-contents.txt @@ -1,13 +1,23 @@ soc/arm patch ARM: select legacy gpiolib interfaces where used + arm64: Kconfig: drop unneeded dependency on OF_GPIO for ARCH_MVEBU soc/dt patch arm64: dts: realtek: Add pinctrl support for RTD1625 ARM: dts: ixp4xx: use phandle-based GPIOs in mi424wr + pxa1908/dt + https://codeberg.org/pxa1908-mainline/linux tags/pxa1908-dt-for-7.2 + renesas/dt + git://git.kernel.org/pub/scm/linux/kernel/git/geert/renesas-devel tags/renesas-dts-for-v7.2-tag1 + contains renesas/fixes-1 soc/drivers + fsl/soc-driver + https://git.kernel.org/pub/scm/linux/kernel/git/chleroy/linux tags/soc_fsl-7.1-2 + renesas/drivers + git://git.kernel.org/pub/scm/linux/kernel/git/geert/renesas-devel tags/renesas-drivers-for-v7.2-tag1 soc/defconfig patch @@ -24,6 +34,7 @@ soc/defconfig ARM: configs: Drop redundant SND_ATMEL_SOC ARM: multi_v7_defconfig: Cleanup redundant options ARM: multi_v7_defconfig: Correct QCOM_RPMH and QCOM_RPMHPD + arm64: defconfig: Enable PCI M.2 power sequencing driver soc/late From 8dbb80525730a16c7b38b07c2400df36e11ed605 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 20 May 2026 22:27:06 +0200 Subject: [PATCH 017/562] soc: document merges Signed-off-by: Arnd Bergmann --- arch/arm/arm-soc-for-next-contents.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/arch/arm/arm-soc-for-next-contents.txt b/arch/arm/arm-soc-for-next-contents.txt index 6817d584bfa7..6ed7086a7faf 100644 --- a/arch/arm/arm-soc-for-next-contents.txt +++ b/arch/arm/arm-soc-for-next-contents.txt @@ -2,6 +2,10 @@ soc/arm patch ARM: select legacy gpiolib interfaces where used arm64: Kconfig: drop unneeded dependency on OF_GPIO for ARCH_MVEBU + pxa/gpio + git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux tags/soc-pxa-gpio-for-v7.2 + zx/soc + https://gitlab.com/stefandoesinger/zx297520-kernel tags/zx29-plat-for-7.2 soc/dt patch @@ -12,6 +16,8 @@ soc/dt renesas/dt git://git.kernel.org/pub/scm/linux/kernel/git/geert/renesas-devel tags/renesas-dts-for-v7.2-tag1 contains renesas/fixes-1 + xz/dt + https://gitlab.com/stefandoesinger/zx297520-kernel tags/zx29-dts-for-7.2 soc/drivers fsl/soc-driver @@ -35,6 +41,8 @@ soc/defconfig ARM: multi_v7_defconfig: Cleanup redundant options ARM: multi_v7_defconfig: Correct QCOM_RPMH and QCOM_RPMHPD arm64: defconfig: Enable PCI M.2 power sequencing driver + cix/defconfig + git://git.kernel.org/pub/scm/linux/kernel/git/peter.chen/cix tags/cix-defconfig-v7.2-rc1 soc/late From 9318b3b22e833b5edcd378d28c64b4f88a801b7b Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 29 May 2026 00:01:52 +0200 Subject: [PATCH 018/562] soc: document merges Signed-off-by: Arnd Bergmann --- arch/arm/arm-soc-for-next-contents.txt | 27 +++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/arch/arm/arm-soc-for-next-contents.txt b/arch/arm/arm-soc-for-next-contents.txt index 6ed7086a7faf..298bd47edb1d 100644 --- a/arch/arm/arm-soc-for-next-contents.txt +++ b/arch/arm/arm-soc-for-next-contents.txt @@ -47,16 +47,21 @@ soc/defconfig soc/late arm/fixes - renesas/fixes-1 - git://git.kernel.org/pub/scm/linux/kernel/git/geert/renesas-devel tags/renesas-fixes-for-v7.1-tag1 + tee/fixes + git://git.kernel.org/pub/scm/linux/kernel/git/jenswi/linux-tee tags/tee-fixes-for-v7.1 + optee/fix + git://git.kernel.org/pub/scm/linux/kernel/git/jenswi/linux-tee tags/optee-fix-for-v7.1 + (471c18323dfdfe7844e193b896a9267ae23a1026) + git://git.kernel.org/pub/scm/linux/kernel/git/jenswi/linux-tee tags/qcomtee-fix-for-v7.1 + qcom/fixes + https://git.kernel.org/pub/scm/linux/kernel/git/qcom/linux tags/qcom-drivers-fixes-for-7.1 + qcom/dt-fixes + https://git.kernel.org/pub/scm/linux/kernel/git/qcom/linux tags/qcom-arm64-fixes-for-7.1 + (b73953af9bbd5c721c9d92b805a8aea8b0db74b1) + https://git.kernel.org/pub/scm/linux/kernel/git/qcom/linux tags/qcom-arm64-defconfig-fixes-for-7.1 patch - ARM: integrator: Fix early initialization - MAINTAINERS: Add maintainers for ARM/REALTEK ARCHITECTURE - ARM: realtek: MAINTAINERS: Include pin controller drivers - ffa/fixes-2 - git://git.kernel.org/pub/scm/linux/kernel/git/sudeep.holla/linux tags/ffa-fixes-7.1-2 - patch - firmware: psci: Set pm_set_resume/suspend_via_firmware() for SYSTEM_SUSPEND - riscv/dt-fixes - https://git.kernel.org/pub/scm/linux/kernel/git/conor/linux tags/riscv-dt-fixes-for-v7.1-rc3 + ARM: dts: gemini: Fix partition offsets + imx/fixes +imx/fixes-2 + git://git.kernel.org/pub/scm/linux/kernel/git/frank.li/linux tags/imx-soc-fixes-for-v7.1 From 9236274912338a7ff3854a7820815c5edb228604 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Sat, 30 May 2026 00:31:40 +0200 Subject: [PATCH 019/562] soc: document merges Signed-off-by: Arnd Bergmann --- arch/arm/arm-soc-for-next-contents.txt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/arch/arm/arm-soc-for-next-contents.txt b/arch/arm/arm-soc-for-next-contents.txt index 298bd47edb1d..d8dd5ce3ab3b 100644 --- a/arch/arm/arm-soc-for-next-contents.txt +++ b/arch/arm/arm-soc-for-next-contents.txt @@ -24,6 +24,17 @@ soc/drivers https://git.kernel.org/pub/scm/linux/kernel/git/chleroy/linux tags/soc_fsl-7.1-2 renesas/drivers git://git.kernel.org/pub/scm/linux/kernel/git/geert/renesas-devel tags/renesas-drivers-for-v7.2-tag1 + qcom/drivers + https://git.kernel.org/pub/scm/linux/kernel/git/qcom/linux tags/qcom-drivers-for-7.2 + firmware/scmi + git://git.kernel.org/pub/scm/linux/kernel/git/sudeep.holla/linux tags/scmi-updates-7.2 + firmware/ffa + git://git.kernel.org/pub/scm/linux/kernel/git/sudeep.holla/linux tags/ffa-updates-7.2 + contains ffa/fixes-2 + firmware/amdtee + git://git.kernel.org/pub/scm/linux/kernel/git/jenswi/linux-tee tags/amdtee-for-v7.2 + firmware/optee + git://git.kernel.org/pub/scm/linux/kernel/git/jenswi/linux-tee tags/optee-for-v7.2 soc/defconfig patch @@ -64,4 +75,8 @@ arm/fixes imx/fixes imx/fixes-2 git://git.kernel.org/pub/scm/linux/kernel/git/frank.li/linux tags/imx-soc-fixes-for-v7.1 + samsung/fixes + https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux tags/samsung-drivers-fixes-7.1-2 + memory/fixes + https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux-mem-ctrl tags/memory-controller-drv-fixes-7.1 From 972260ed6d56b0fba88c11709304275c3855fe3d Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 1 Jun 2026 22:20:03 +0200 Subject: [PATCH 020/562] soc: document merges Signed-off-by: Linus Walleij --- arch/arm/arm-soc-for-next-contents.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm/arm-soc-for-next-contents.txt b/arch/arm/arm-soc-for-next-contents.txt index d8dd5ce3ab3b..ead4970b8439 100644 --- a/arch/arm/arm-soc-for-next-contents.txt +++ b/arch/arm/arm-soc-for-next-contents.txt @@ -11,6 +11,8 @@ soc/dt patch arm64: dts: realtek: Add pinctrl support for RTD1625 ARM: dts: ixp4xx: use phandle-based GPIOs in mi424wr + cix/dt + git://git.kernel.org/pub/scm/linux/kernel/git/peter.chen/cix.git/ tags/cix-dt-v7.2-rc1 pxa1908/dt https://codeberg.org/pxa1908-mainline/linux tags/pxa1908-dt-for-7.2 renesas/dt From d386336244de1b00e489535fe88dfd314a570a74 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 1 Jun 2026 22:31:21 +0200 Subject: [PATCH 021/562] soc: document merges Signed-off-by: Linus Walleij --- arch/arm/arm-soc-for-next-contents.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/arm-soc-for-next-contents.txt b/arch/arm/arm-soc-for-next-contents.txt index ead4970b8439..1c36eaeea16e 100644 --- a/arch/arm/arm-soc-for-next-contents.txt +++ b/arch/arm/arm-soc-for-next-contents.txt @@ -6,6 +6,7 @@ soc/arm git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux tags/soc-pxa-gpio-for-v7.2 zx/soc https://gitlab.com/stefandoesinger/zx297520-kernel tags/zx29-plat-for-7.2 + https://gitlab.com/stefandoesinger/zx297520-kernel.git tags/zx29-docfix-for-7.2 soc/dt patch From 54704536be37e2d934749335563d730dad736ee7 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Mon, 1 Jun 2026 23:38:58 +0200 Subject: [PATCH 022/562] soc: document merges Signed-off-by: Arnd Bergmann --- arch/arm/arm-soc-for-next-contents.txt | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/arch/arm/arm-soc-for-next-contents.txt b/arch/arm/arm-soc-for-next-contents.txt index 1c36eaeea16e..695dced0833e 100644 --- a/arch/arm/arm-soc-for-next-contents.txt +++ b/arch/arm/arm-soc-for-next-contents.txt @@ -6,14 +6,15 @@ soc/arm git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux tags/soc-pxa-gpio-for-v7.2 zx/soc https://gitlab.com/stefandoesinger/zx297520-kernel tags/zx29-plat-for-7.2 - https://gitlab.com/stefandoesinger/zx297520-kernel.git tags/zx29-docfix-for-7.2 + xz29/docfix + https://gitlab.com/stefandoesinger/zx297520-kernel tags/zx29-docfix-for-7.2 + ep93xx/arm + https://git.kernel.org/pub/scm/linux/kernel/git/asv/linux tags/ep93xx-20260529 soc/dt patch arm64: dts: realtek: Add pinctrl support for RTD1625 ARM: dts: ixp4xx: use phandle-based GPIOs in mi424wr - cix/dt - git://git.kernel.org/pub/scm/linux/kernel/git/peter.chen/cix.git/ tags/cix-dt-v7.2-rc1 pxa1908/dt https://codeberg.org/pxa1908-mainline/linux tags/pxa1908-dt-for-7.2 renesas/dt @@ -21,6 +22,8 @@ soc/dt contains renesas/fixes-1 xz/dt https://gitlab.com/stefandoesinger/zx297520-kernel tags/zx29-dts-for-7.2 + (89d0ad7f3a60d3076da2d90ece8d78d0eb60bb91) + git://git.kernel.org/pub/scm/linux/kernel/git/peter.chen/cix tags/cix-dt-v7.2-rc1 soc/drivers fsl/soc-driver @@ -76,10 +79,13 @@ arm/fixes patch ARM: dts: gemini: Fix partition offsets imx/fixes -imx/fixes-2 git://git.kernel.org/pub/scm/linux/kernel/git/frank.li/linux tags/imx-soc-fixes-for-v7.1 samsung/fixes https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux tags/samsung-drivers-fixes-7.1-2 memory/fixes https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux-mem-ctrl tags/memory-controller-drv-fixes-7.1 + (765aaba18413a66f6c8fe8416336ca9b3dd98a79) + https://git.kernel.org/pub/scm/linux/kernel/git/at91/linux tags/at91-fixes-7.1 + (63838c323924fe4a78b2323bd45aa1030f72ca60) + git://git.kernel.org/pub/scm/linux/kernel/git/dinguyen/linux tags/socfpga_fix_for_v7.1 From 93726b22b0c19cac5546bbabe25e0b6cdf62f7e1 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Tue, 2 Jun 2026 10:58:22 +0200 Subject: [PATCH 023/562] soc: document merges Signed-off-by: Linus Walleij --- arch/arm/arm-soc-for-next-contents.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm/arm-soc-for-next-contents.txt b/arch/arm/arm-soc-for-next-contents.txt index 695dced0833e..8128adf70a0c 100644 --- a/arch/arm/arm-soc-for-next-contents.txt +++ b/arch/arm/arm-soc-for-next-contents.txt @@ -17,6 +17,8 @@ soc/dt ARM: dts: ixp4xx: use phandle-based GPIOs in mi424wr pxa1908/dt https://codeberg.org/pxa1908-mainline/linux tags/pxa1908-dt-for-7.2 + qcom/dt + https://git.kernel.org/pub/scm/linux/kernel/git/qcom/linux.git tags/qcom-arm64-for-7.2 renesas/dt git://git.kernel.org/pub/scm/linux/kernel/git/geert/renesas-devel tags/renesas-dts-for-v7.2-tag1 contains renesas/fixes-1 From 09debe5c1b2ecb4e154f95b0ffe1c069e2a71b4f Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Tue, 2 Jun 2026 13:36:10 +0200 Subject: [PATCH 024/562] soc: document merges Signed-off-by: Linus Walleij --- arch/arm/arm-soc-for-next-contents.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/arm/arm-soc-for-next-contents.txt b/arch/arm/arm-soc-for-next-contents.txt index 8128adf70a0c..a18e38d47415 100644 --- a/arch/arm/arm-soc-for-next-contents.txt +++ b/arch/arm/arm-soc-for-next-contents.txt @@ -22,6 +22,9 @@ soc/dt renesas/dt git://git.kernel.org/pub/scm/linux/kernel/git/geert/renesas-devel tags/renesas-dts-for-v7.2-tag1 contains renesas/fixes-1 + rockchip/dt + https://git.kernel.org/pub/scm/linux/kernel/git/mmind/linux-rockchip.git tags/v7.2-rockchip-dts32 + https://git.kernel.org/pub/scm/linux/kernel/git/mmind/linux-rockchip.git tags/v7.2-rockchip-dts64-1 xz/dt https://gitlab.com/stefandoesinger/zx297520-kernel tags/zx29-dts-for-7.2 (89d0ad7f3a60d3076da2d90ece8d78d0eb60bb91) From bba8a2d93a00a35271073f8be42331596f065058 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 3 Jun 2026 14:08:28 +0200 Subject: [PATCH 025/562] soc: document merges Signed-off-by: Linus Walleij --- arch/arm/arm-soc-for-next-contents.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm/arm-soc-for-next-contents.txt b/arch/arm/arm-soc-for-next-contents.txt index a18e38d47415..9583934487a3 100644 --- a/arch/arm/arm-soc-for-next-contents.txt +++ b/arch/arm/arm-soc-for-next-contents.txt @@ -15,6 +15,8 @@ soc/dt patch arm64: dts: realtek: Add pinctrl support for RTD1625 ARM: dts: ixp4xx: use phandle-based GPIOs in mi424wr + ixp4xx/dt + git://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-integrator.git tags/gemini-for-v7.2 pxa1908/dt https://codeberg.org/pxa1908-mainline/linux tags/pxa1908-dt-for-7.2 qcom/dt From 2266fd3eb39da752d1ee56e62134c5a67441fba3 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 3 Jun 2026 14:22:53 +0200 Subject: [PATCH 026/562] soc: document merges Signed-off-by: Linus Walleij --- arch/arm/arm-soc-for-next-contents.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/arch/arm/arm-soc-for-next-contents.txt b/arch/arm/arm-soc-for-next-contents.txt index 9583934487a3..20a0c28a01bd 100644 --- a/arch/arm/arm-soc-for-next-contents.txt +++ b/arch/arm/arm-soc-for-next-contents.txt @@ -27,6 +27,13 @@ soc/dt rockchip/dt https://git.kernel.org/pub/scm/linux/kernel/git/mmind/linux-rockchip.git tags/v7.2-rockchip-dts32 https://git.kernel.org/pub/scm/linux/kernel/git/mmind/linux-rockchip.git tags/v7.2-rockchip-dts64-1 + samsung/dt + https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux.git tags/samsung-dt64-7.2 + stm32/dt + git://git.kernel.org/pub/scm/linux/kernel/git/atorgue/stm32.git tags/stm32-dt-for-7.2-1 + tegra/dt + git://git.kernel.org/pub/scm/linux/kernel/git/tegra/linux.git tags/tegra-for-7.2-dt-bindings + git://git.kernel.org/pub/scm/linux/kernel/git/tegra/linux.git tags/tegra-for-7.2-arm-dt xz/dt https://gitlab.com/stefandoesinger/zx297520-kernel tags/zx29-dts-for-7.2 (89d0ad7f3a60d3076da2d90ece8d78d0eb60bb91) From f2afebe82898525d40a77c7ec6afe45bcfaf0fe7 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 3 Jun 2026 14:26:32 +0200 Subject: [PATCH 027/562] soc: document merges Signed-off-by: Linus Walleij --- arch/arm/arm-soc-for-next-contents.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm/arm-soc-for-next-contents.txt b/arch/arm/arm-soc-for-next-contents.txt index 20a0c28a01bd..2200acb37bcf 100644 --- a/arch/arm/arm-soc-for-next-contents.txt +++ b/arch/arm/arm-soc-for-next-contents.txt @@ -4,6 +4,8 @@ soc/arm arm64: Kconfig: drop unneeded dependency on OF_GPIO for ARCH_MVEBU pxa/gpio git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux tags/soc-pxa-gpio-for-v7.2 + samsung/soc + https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux.git tags/samsung-soc-7.2 zx/soc https://gitlab.com/stefandoesinger/zx297520-kernel tags/zx29-plat-for-7.2 xz29/docfix From d9a096d52405a7f084fef736a5ae45bc7fea5c0a Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 4 Jun 2026 10:43:15 +0200 Subject: [PATCH 028/562] soc: document merges Signed-off-by: Linus Walleij --- arch/arm/arm-soc-for-next-contents.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm/arm-soc-for-next-contents.txt b/arch/arm/arm-soc-for-next-contents.txt index 2200acb37bcf..4d40c5aeb165 100644 --- a/arch/arm/arm-soc-for-next-contents.txt +++ b/arch/arm/arm-soc-for-next-contents.txt @@ -57,6 +57,8 @@ soc/drivers git://git.kernel.org/pub/scm/linux/kernel/git/jenswi/linux-tee tags/amdtee-for-v7.2 firmware/optee git://git.kernel.org/pub/scm/linux/kernel/git/jenswi/linux-tee tags/optee-for-v7.2 + firmware/xilinx + https://github.com/Xilinx/linux-xlnx.git tags/zynqmp-soc-for-7.2 soc/defconfig patch From fb3fcb406b8882a91d1e51bae97101802fc991e1 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 4 Jun 2026 13:52:30 +0200 Subject: [PATCH 029/562] soc: document merges Signed-off-by: Linus Walleij --- arch/arm/arm-soc-for-next-contents.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm/arm-soc-for-next-contents.txt b/arch/arm/arm-soc-for-next-contents.txt index 4d40c5aeb165..f809a77b2018 100644 --- a/arch/arm/arm-soc-for-next-contents.txt +++ b/arch/arm/arm-soc-for-next-contents.txt @@ -31,6 +31,8 @@ soc/dt https://git.kernel.org/pub/scm/linux/kernel/git/mmind/linux-rockchip.git tags/v7.2-rockchip-dts64-1 samsung/dt https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux.git tags/samsung-dt64-7.2 + socfpga/dt + git://git.kernel.org/pub/scm/linux/kernel/git/dinguyen/linux.git tags/socfpga_dts_updates_for_v7.2 stm32/dt git://git.kernel.org/pub/scm/linux/kernel/git/atorgue/stm32.git tags/stm32-dt-for-7.2-1 tegra/dt From fb266bbe9aef17d547db468a1a7187b59ed33e9a Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Mon, 8 Jun 2026 09:41:04 +0200 Subject: [PATCH 030/562] riscv: defconfig: thead: enable PCA953X GPIO driver Enable PCA953X GPIO driver to properly probe Wifi pwrseq driver on LicheePi4a board. Reviewed-by: Drew Fustini Signed-off-by: Marek Szyprowski Signed-off-by: Drew Fustini --- arch/riscv/configs/defconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/riscv/configs/defconfig b/arch/riscv/configs/defconfig index c2c37327b987..9199fcca1926 100644 --- a/arch/riscv/configs/defconfig +++ b/arch/riscv/configs/defconfig @@ -173,6 +173,8 @@ CONFIG_PINCTRL_SOPHGO_SG2002=y CONFIG_GPIO_DWAPB=y CONFIG_GPIO_SIFIVE=y CONFIG_GPIO_SPACEMIT_K1=y +CONFIG_GPIO_PCA953X=m +CONFIG_GPIO_PCA953X_IRQ=y CONFIG_POWER_RESET_GPIO_RESTART=y CONFIG_SENSORS_SFCTEMP=m CONFIG_CPU_THERMAL=y From 1d516735dd08fad34c1b9a6f843c8b74f8b9558e Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 9 Jun 2026 13:45:48 +0200 Subject: [PATCH 031/562] soc: document merges Signed-off-by: Arnd Bergmann --- arch/arm/arm-soc-for-next-contents.txt | 81 ++++++++++++-------------- 1 file changed, 37 insertions(+), 44 deletions(-) diff --git a/arch/arm/arm-soc-for-next-contents.txt b/arch/arm/arm-soc-for-next-contents.txt index f809a77b2018..df5b20c99e49 100644 --- a/arch/arm/arm-soc-for-next-contents.txt +++ b/arch/arm/arm-soc-for-next-contents.txt @@ -4,44 +4,50 @@ soc/arm arm64: Kconfig: drop unneeded dependency on OF_GPIO for ARCH_MVEBU pxa/gpio git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux tags/soc-pxa-gpio-for-v7.2 - samsung/soc - https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux.git tags/samsung-soc-7.2 zx/soc https://gitlab.com/stefandoesinger/zx297520-kernel tags/zx29-plat-for-7.2 xz29/docfix https://gitlab.com/stefandoesinger/zx297520-kernel tags/zx29-docfix-for-7.2 ep93xx/arm https://git.kernel.org/pub/scm/linux/kernel/git/asv/linux tags/ep93xx-20260529 + tags/samsung-soc-7.2 + https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux tags/samsung-soc-7.2 + imx/soc + git://git.kernel.org/pub/scm/linux/kernel/git/frank.li/linux tags/imx-soc-updates-for-v7.2 + omap/soc + git://git.kernel.org/pub/scm/linux/kernel/git/khilman/linux-omap tags/omap-for-v7.2/soc-signed soc/dt patch arm64: dts: realtek: Add pinctrl support for RTD1625 ARM: dts: ixp4xx: use phandle-based GPIOs in mi424wr - ixp4xx/dt - git://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-integrator.git tags/gemini-for-v7.2 pxa1908/dt https://codeberg.org/pxa1908-mainline/linux tags/pxa1908-dt-for-7.2 - qcom/dt - https://git.kernel.org/pub/scm/linux/kernel/git/qcom/linux.git tags/qcom-arm64-for-7.2 renesas/dt git://git.kernel.org/pub/scm/linux/kernel/git/geert/renesas-devel tags/renesas-dts-for-v7.2-tag1 contains renesas/fixes-1 - rockchip/dt - https://git.kernel.org/pub/scm/linux/kernel/git/mmind/linux-rockchip.git tags/v7.2-rockchip-dts32 - https://git.kernel.org/pub/scm/linux/kernel/git/mmind/linux-rockchip.git tags/v7.2-rockchip-dts64-1 - samsung/dt - https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux.git tags/samsung-dt64-7.2 - socfpga/dt - git://git.kernel.org/pub/scm/linux/kernel/git/dinguyen/linux.git tags/socfpga_dts_updates_for_v7.2 - stm32/dt - git://git.kernel.org/pub/scm/linux/kernel/git/atorgue/stm32.git tags/stm32-dt-for-7.2-1 - tegra/dt - git://git.kernel.org/pub/scm/linux/kernel/git/tegra/linux.git tags/tegra-for-7.2-dt-bindings - git://git.kernel.org/pub/scm/linux/kernel/git/tegra/linux.git tags/tegra-for-7.2-arm-dt xz/dt https://gitlab.com/stefandoesinger/zx297520-kernel tags/zx29-dts-for-7.2 - (89d0ad7f3a60d3076da2d90ece8d78d0eb60bb91) + cix/dt git://git.kernel.org/pub/scm/linux/kernel/git/peter.chen/cix tags/cix-dt-v7.2-rc1 + qcom/dt + https://git.kernel.org/pub/scm/linux/kernel/git/qcom/linux tags/qcom-arm64-for-7.2 + rockchip/dt + https://git.kernel.org/pub/scm/linux/kernel/git/mmind/linux-rockchip tags/v7.2-rockchip-dts32 + qcom/dt64 + https://git.kernel.org/pub/scm/linux/kernel/git/mmind/linux-rockchip tags/v7.2-rockchip-dts64-1 + gemini/dt + git://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-integrator tags/gemini-for-v7.2 + tegra/dt-bindings + git://git.kernel.org/pub/scm/linux/kernel/git/tegra/linux tags/tegra-for-7.2-dt-bindings + tegra/dt + git://git.kernel.org/pub/scm/linux/kernel/git/tegra/linux tags/tegra-for-7.2-arm-dt + exynos/dt + https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux tags/samsung-dt64-7.2 + stm32/dt + git://git.kernel.org/pub/scm/linux/kernel/git/atorgue/stm32 tags/stm32-dt-for-7.2-1 + socfpga/dt + git://git.kernel.org/pub/scm/linux/kernel/git/dinguyen/linux tags/socfpga_dts_updates_for_v7.2 soc/drivers fsl/soc-driver @@ -60,7 +66,7 @@ soc/drivers firmware/optee git://git.kernel.org/pub/scm/linux/kernel/git/jenswi/linux-tee tags/optee-for-v7.2 firmware/xilinx - https://github.com/Xilinx/linux-xlnx.git tags/zynqmp-soc-for-7.2 + https://github.com/Xilinx/linux-xlnx tags/zynqmp-soc-for-7.2 soc/defconfig patch @@ -80,32 +86,19 @@ soc/defconfig arm64: defconfig: Enable PCI M.2 power sequencing driver cix/defconfig git://git.kernel.org/pub/scm/linux/kernel/git/peter.chen/cix tags/cix-defconfig-v7.2-rc1 + patch + ARM: configs: Drop duplicated CONFIG_EXT4_FS + ARM: multi_v7_defconfig: Enable dma-buf heaps + at91/defconfig + https://git.kernel.org/pub/scm/linux/kernel/git/at91/linux tags/at91-defconfig-7.2 + imx/defconfig + git://git.kernel.org/pub/scm/linux/kernel/git/frank.li/linux tags/imx-defconfig-7.2 soc/late arm/fixes - tee/fixes - git://git.kernel.org/pub/scm/linux/kernel/git/jenswi/linux-tee tags/tee-fixes-for-v7.1 - optee/fix - git://git.kernel.org/pub/scm/linux/kernel/git/jenswi/linux-tee tags/optee-fix-for-v7.1 - (471c18323dfdfe7844e193b896a9267ae23a1026) - git://git.kernel.org/pub/scm/linux/kernel/git/jenswi/linux-tee tags/qcomtee-fix-for-v7.1 - qcom/fixes - https://git.kernel.org/pub/scm/linux/kernel/git/qcom/linux tags/qcom-drivers-fixes-for-7.1 - qcom/dt-fixes - https://git.kernel.org/pub/scm/linux/kernel/git/qcom/linux tags/qcom-arm64-fixes-for-7.1 - (b73953af9bbd5c721c9d92b805a8aea8b0db74b1) - https://git.kernel.org/pub/scm/linux/kernel/git/qcom/linux tags/qcom-arm64-defconfig-fixes-for-7.1 - patch - ARM: dts: gemini: Fix partition offsets - imx/fixes - git://git.kernel.org/pub/scm/linux/kernel/git/frank.li/linux tags/imx-soc-fixes-for-v7.1 - samsung/fixes - https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux tags/samsung-drivers-fixes-7.1-2 - memory/fixes - https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux-mem-ctrl tags/memory-controller-drv-fixes-7.1 - (765aaba18413a66f6c8fe8416336ca9b3dd98a79) - https://git.kernel.org/pub/scm/linux/kernel/git/at91/linux tags/at91-fixes-7.1 - (63838c323924fe4a78b2323bd45aa1030f72ca60) - git://git.kernel.org/pub/scm/linux/kernel/git/dinguyen/linux tags/socfpga_fix_for_v7.1 + (75ef233975589d9a8c88bc8822a7c725c71ff650) + https://git.kernel.org/pub/scm/linux/kernel/git/conor/linux tags/riscv-soc-fixes-for-v7.1-rc7 + (6fc5666aa576c6c3bec256fa31d72653210319d5) + https://git.kernel.org/pub/scm/linux/kernel/git/mmind/linux-rockchip tags/v7.1-rockchip-arm32fixe From e59de2ce5407804096bb32185a490a3495711222 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 9 Jun 2026 14:32:05 +0200 Subject: [PATCH 032/562] soc: document merges Signed-off-by: Krzysztof Kozlowski --- arch/arm/arm-soc-for-next-contents.txt | 28 ++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/arch/arm/arm-soc-for-next-contents.txt b/arch/arm/arm-soc-for-next-contents.txt index df5b20c99e49..61b61b33e2c9 100644 --- a/arch/arm/arm-soc-for-next-contents.txt +++ b/arch/arm/arm-soc-for-next-contents.txt @@ -48,6 +48,26 @@ soc/dt git://git.kernel.org/pub/scm/linux/kernel/git/atorgue/stm32 tags/stm32-dt-for-7.2-1 socfpga/dt git://git.kernel.org/pub/scm/linux/kernel/git/dinguyen/linux tags/socfpga_dts_updates_for_v7.2 + mediatek/dt32 + ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/mediatek/linux tags/mtk-dts32-for-v7.2 + mediatek/dt64 + ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/mediatek/linux tags/mtk-dts64-for-v7.2 + renesas/dt-2 + https://git.kernel.org/pub/scm/linux/kernel/git/geert/renesas-devel tags/renesas-dts-for-v7.2-tag2 + tenstorrent/dt + https://git.kernel.org/pub/scm/linux/kernel/git/tenstorrent/linux tags/tenstorrent-dt-for-v7.2 + thead/dt + https://git.kernel.org/pub/scm/linux/kernel/git/fustini/linux tags/thead-dt-for-v7.2 + spacemit/dt + https://github.com/spacemit-com/linux tags/spacemit-dt-for-7.2-1 + amlogic/dt64 + ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/amlogic/linux tags/amlogic-arm64-dt-for-v7.2-v2 + nuvoton/dt64 + ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/bmc/linux tags/nuvoton-7.2-devicetree-0 + sophgo/dt + https://github.com/sophgo/linux tags/riscv-sophgo-dt-for-v7.2 + aspeed/dt32 + ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/bmc/linux tags/aspeed-7.2-devicetree-0 soc/drivers fsl/soc-driver @@ -67,6 +87,14 @@ soc/drivers git://git.kernel.org/pub/scm/linux/kernel/git/jenswi/linux-tee tags/optee-for-v7.2 firmware/xilinx https://github.com/Xilinx/linux-xlnx tags/zynqmp-soc-for-7.2 + mediatek/drivers + ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/mediatek/linux tags/mtk-soc-for-v7.2 + renesas/drivers-2 + https://git.kernel.org/pub/scm/linux/kernel/git/geert/renesas-devel tags/renesas-drivers-for-v7.2-tag2 + sunxi/drivers + ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/sunxi/linux tags/sunxi-drivers-for-7.2 + aspeed/drivers + ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/bmc/linux tags/aspeed-7.2-drivers-0 soc/defconfig patch From 839cce9606ae54f2cd828a5c2a1d7cb7e8a4b885 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 9 Jun 2026 17:42:27 +0200 Subject: [PATCH 033/562] soc: document m,erges Signed-off-by: Arnd Bergmann --- arch/arm/arm-soc-for-next-contents.txt | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/arch/arm/arm-soc-for-next-contents.txt b/arch/arm/arm-soc-for-next-contents.txt index 61b61b33e2c9..0bab0689d576 100644 --- a/arch/arm/arm-soc-for-next-contents.txt +++ b/arch/arm/arm-soc-for-next-contents.txt @@ -95,6 +95,26 @@ soc/drivers ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/sunxi/linux tags/sunxi-drivers-for-7.2 aspeed/drivers ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/bmc/linux tags/aspeed-7.2-drivers-0 + tegra/soc + git://git.kernel.org/pub/scm/linux/kernel/git/tegra/linux tags/tegra-for-7.2-soc + tegra/pmc + git://git.kernel.org/pub/scm/linux/kernel/git/tegra/linux tags/tegra-for-7.2-pmc + tegra/firmware + git://git.kernel.org/pub/scm/linux/kernel/git/tegra/linux tags/tegra-for-7.2-firmware + samsung/drivers + https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux tags/samsung-drivers-7.2 + contains samsung/fixes + drivers/memory + https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux-mem-ctrl tags/memory-controller-drv-7.2 + contains memory/fixes + broadcom/drivers + https://github.com/Broadcom/stblinux tags/arm-soc/for-7.2/drivers + ti/k3-drivers + https://git.kernel.org/pub/scm/linux/kernel/git/ti/linux tags/ti-driver-soc-for-v7.2 + drivers/cache + https://git.kernel.org/pub/scm/linux/kernel/git/conor/linux tags/cache-for-v7.2 + microchip/dt-bindings + https://git.kernel.org/pub/scm/linux/kernel/git/conor/linux tags/riscv-soc-drivers-for-v7.2 soc/defconfig patch @@ -121,6 +141,8 @@ soc/defconfig https://git.kernel.org/pub/scm/linux/kernel/git/at91/linux tags/at91-defconfig-7.2 imx/defconfig git://git.kernel.org/pub/scm/linux/kernel/git/frank.li/linux tags/imx-defconfig-7.2 + patch + arm64: configs: Update defconfig for AST2700 platform support soc/late From 47eb40f29444bb35c2876cd9df2699637c353185 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 9 Jun 2026 18:35:52 +0200 Subject: [PATCH 034/562] soc: document merges Signed-off-by: Arnd Bergmann --- arch/arm/arm-soc-for-next-contents.txt | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/arch/arm/arm-soc-for-next-contents.txt b/arch/arm/arm-soc-for-next-contents.txt index 0bab0689d576..6d76545ec3c8 100644 --- a/arch/arm/arm-soc-for-next-contents.txt +++ b/arch/arm/arm-soc-for-next-contents.txt @@ -68,6 +68,26 @@ soc/dt https://github.com/sophgo/linux tags/riscv-sophgo-dt-for-v7.2 aspeed/dt32 ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/bmc/linux tags/aspeed-7.2-devicetree-0 + tegra/dt64 + git://git.kernel.org/pub/scm/linux/kernel/git/tegra/linux tags/tegra-for-7.2-arm64-dt + broadcom/dt + https://github.com/Broadcom/stblinux tags/arm-soc/for-7.2/devicetree + ti/k3-dt + https://git.kernel.org/pub/scm/linux/kernel/git/ti/linux tags/ti-k3-dt-for-v7.2 + apple/dt + https://git.kernel.org/pub/scm/linux/kernel/git/sven/linux tags/apple-soc-dt-7.2 + imx/dt + git://git.kernel.org/pub/scm/linux/kernel/git/frank.li/linux tags/imx-dt-7.2 + hisi/dt + https://github.com/hisilicon/linux-hisi tags/hisi-arm64-dt-for-7.2 + rockchip/dt64 + https://git.kernel.org/pub/scm/linux/kernel/git/mmind/linux-rockchip tags/v7.2-rockchip-dts64-2 + microchip/riscv-dt + https://git.kernel.org/pub/scm/linux/kernel/git/conor/linux tags/riscv-dt-for-v7.2 + patch + dt-bindings: arm: aspeed: Add AST2700 board compatible + arm64: Kconfig: Add ASPEED SoC family Kconfig support + arm64: dts: aspeed: Add initial AST27xx SoC device tree soc/drivers fsl/soc-driver From de02cc4e855af652e4b5382e5d604169ca086dfa Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 9 Jun 2026 22:25:20 +0200 Subject: [PATCH 035/562] soc: document merges Signed-off-by: Arnd Bergmann --- arch/arm/arm-soc-for-next-contents.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/arch/arm/arm-soc-for-next-contents.txt b/arch/arm/arm-soc-for-next-contents.txt index 6d76545ec3c8..3b949b5f5599 100644 --- a/arch/arm/arm-soc-for-next-contents.txt +++ b/arch/arm/arm-soc-for-next-contents.txt @@ -16,6 +16,8 @@ soc/arm git://git.kernel.org/pub/scm/linux/kernel/git/frank.li/linux tags/imx-soc-updates-for-v7.2 omap/soc git://git.kernel.org/pub/scm/linux/kernel/git/khilman/linux-omap tags/omap-for-v7.2/soc-signed + mvebu/arm + git://git.kernel.org/pub/scm/linux/kernel/git/gclement/mvebu tags/mvebu-arm-7.2-1 soc/dt patch @@ -88,6 +90,12 @@ soc/dt dt-bindings: arm: aspeed: Add AST2700 board compatible arm64: Kconfig: Add ASPEED SoC family Kconfig support arm64: dts: aspeed: Add initial AST27xx SoC device tree + ti/omap-dt + git://git.kernel.org/pub/scm/linux/kernel/git/khilman/linux-omap tags/omap-for-v7.2/dt-signed + at91/dt + https://git.kernel.org/pub/scm/linux/kernel/git/at91/linux tags/at91-dt-7.2 + microchip/dt64 + https://git.kernel.org/pub/scm/linux/kernel/git/at91/linux tags/microchip-dt64-7.2 soc/drivers fsl/soc-driver From 9f1945a3e364cc8b40c424666995b21c5f0e59eb Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 11 Jun 2026 00:17:09 +0200 Subject: [PATCH 036/562] soc: document merges Signed-off-by: Arnd Bergmann --- arch/arm/arm-soc-for-next-contents.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arch/arm/arm-soc-for-next-contents.txt b/arch/arm/arm-soc-for-next-contents.txt index 3b949b5f5599..0e659e351262 100644 --- a/arch/arm/arm-soc-for-next-contents.txt +++ b/arch/arm/arm-soc-for-next-contents.txt @@ -18,6 +18,8 @@ soc/arm git://git.kernel.org/pub/scm/linux/kernel/git/khilman/linux-omap tags/omap-for-v7.2/soc-signed mvebu/arm git://git.kernel.org/pub/scm/linux/kernel/git/gclement/mvebu tags/mvebu-arm-7.2-1 + patch + ARM: remove the last few uses of do_bad_IRQ() soc/dt patch @@ -96,6 +98,10 @@ soc/dt https://git.kernel.org/pub/scm/linux/kernel/git/at91/linux tags/at91-dt-7.2 microchip/dt64 https://git.kernel.org/pub/scm/linux/kernel/git/at91/linux tags/microchip-dt64-7.2 + imx/dt64 + git://git.kernel.org/pub/scm/linux/kernel/git/frank.li/linux tags/imx-dt64-7.2 + imx/dt64-2 + git://git.kernel.org/pub/scm/linux/kernel/git/frank.li/linux tags/imx-dt64-7.2-part2 soc/drivers fsl/soc-driver From 42615efeed84624627d559d00ecc2e606a67bcb3 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 11 Jun 2026 23:20:51 +0200 Subject: [PATCH 037/562] soc: document merges Signed-off-by: Arnd Bergmann --- arch/arm/arm-soc-for-next-contents.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/arm/arm-soc-for-next-contents.txt b/arch/arm/arm-soc-for-next-contents.txt index 0e659e351262..472935c56fec 100644 --- a/arch/arm/arm-soc-for-next-contents.txt +++ b/arch/arm/arm-soc-for-next-contents.txt @@ -20,6 +20,7 @@ soc/arm git://git.kernel.org/pub/scm/linux/kernel/git/gclement/mvebu tags/mvebu-arm-7.2-1 patch ARM: remove the last few uses of do_bad_IRQ() + MAINTAINERS: Add Axiado reviewer and Maintainers soc/dt patch @@ -102,6 +103,10 @@ soc/dt git://git.kernel.org/pub/scm/linux/kernel/git/frank.li/linux tags/imx-dt64-7.2 imx/dt64-2 git://git.kernel.org/pub/scm/linux/kernel/git/frank.li/linux tags/imx-dt64-7.2-part2 + imx/dt-binding + git://git.kernel.org/pub/scm/linux/kernel/git/frank.li/linux tags/imx-binding-7.2 + sunxi/dt-2 + https://git.kernel.org/pub/scm/linux/kernel/git/sunxi/linux tags/sunxi-dt-for-7.2-2 soc/drivers fsl/soc-driver From 1a2494ceb09d0499025bd9f4b4372e7e1e7dc5a7 Mon Sep 17 00:00:00 2001 From: Jeff Chen Date: Tue, 28 Apr 2026 22:28:48 +0800 Subject: [PATCH 038/562] mmc: core: add NXP IW61x base ID and block size quirk The NXP IW61x series SDIO chipset identifies itself with a base card ID (0x0204) during the initial MMC bus scan, while the specific WLAN function reports a different ID (0x0205). To ensure that the MMC_QUIRK_BLKSZ_FOR_BYTE_MODE quirk is correctly inherited by all SDIO functions (including Wi-Fi), it must be attached to the base card ID at the core level. Add the SDIO_DEVICE_ID_NXP_IW61X_BASE definition and apply the required fixup in the SDIO quirk table. Signed-off-by: Jeff Chen --- drivers/mmc/core/quirks.h | 3 +++ include/linux/mmc/sdio_ids.h | 1 + 2 files changed, 4 insertions(+) diff --git a/drivers/mmc/core/quirks.h b/drivers/mmc/core/quirks.h index 940549d3b95d..ae3ece89d0aa 100644 --- a/drivers/mmc/core/quirks.h +++ b/drivers/mmc/core/quirks.h @@ -208,6 +208,9 @@ static const struct mmc_fixup __maybe_unused sdio_fixup_methods[] = { SDIO_FIXUP(SDIO_VENDOR_ID_MARVELL, SDIO_DEVICE_ID_MARVELL_8887_F0, add_limit_rate_quirk, 150000000), + SDIO_FIXUP(SDIO_VENDOR_ID_NXP, SDIO_DEVICE_ID_NXP_IW61X_BASE, + add_quirk, MMC_QUIRK_BLKSZ_FOR_BYTE_MODE), + END_FIXUP }; diff --git a/include/linux/mmc/sdio_ids.h b/include/linux/mmc/sdio_ids.h index 0685dd717e85..7dac5428afe0 100644 --- a/include/linux/mmc/sdio_ids.h +++ b/include/linux/mmc/sdio_ids.h @@ -118,6 +118,7 @@ #define SDIO_DEVICE_ID_MICROCHIP_WILC1000 0x5347 #define SDIO_VENDOR_ID_NXP 0x0471 +#define SDIO_DEVICE_ID_NXP_IW61X_BASE 0x0204 #define SDIO_DEVICE_ID_NXP_IW61X 0x0205 #define SDIO_VENDOR_ID_REALTEK 0x024c From 4c477f8bfc1a86c54a719cae475f7fa1973eba0f Mon Sep 17 00:00:00 2001 From: Jeff Chen Date: Fri, 5 Jun 2026 10:33:35 +0800 Subject: [PATCH 039/562] wifi: nxp: add nxpwifi driver for IW61x Add support for the NXP IW61x wireless devices. The nxpwifi driver implements a full-MAC design and integrates with cfg80211 for configuration and control, supporting both station (STA) and access point (AP) modes. The driver provides a firmware-based command/event interface using TLV messages, with the core handling command processing, event dispatching, and device lifecycle management. A SDIO transport layer is implemented to support IW61x devices. Key features include: - 802.11n/ac/ax (HT/VHT/HE) capability support - Scan, association, and connection management - Data path handling for TX/RX, including aggregation and reorder - WMM QoS support and traffic prioritization - 802.11h (DFS/TPC) support for regulatory compliance - cfg80211 integration for STA and AP operations - Debugfs and ethtool support - Wake-on-LAN support The driver translates cfg80211 configuration into firmware commands and implements required data path processing in software where needed. Signed-off-by: Jeff Chen --- MAINTAINERS | 7 + drivers/net/wireless/Kconfig | 1 + drivers/net/wireless/Makefile | 1 + drivers/net/wireless/nxp/Kconfig | 17 + drivers/net/wireless/nxp/Makefile | 3 + drivers/net/wireless/nxp/nxpwifi/11ac.c | 280 ++ drivers/net/wireless/nxp/nxpwifi/11ac.h | 33 + drivers/net/wireless/nxp/nxpwifi/11ax.c | 594 +++ drivers/net/wireless/nxp/nxpwifi/11ax.h | 73 + drivers/net/wireless/nxp/nxpwifi/11h.c | 339 ++ drivers/net/wireless/nxp/nxpwifi/11n.c | 837 ++++ drivers/net/wireless/nxp/nxpwifi/11n.h | 158 + drivers/net/wireless/nxp/nxpwifi/11n_aggr.c | 251 ++ drivers/net/wireless/nxp/nxpwifi/11n_aggr.h | 21 + .../net/wireless/nxp/nxpwifi/11n_rxreorder.c | 826 ++++ .../net/wireless/nxp/nxpwifi/11n_rxreorder.h | 71 + drivers/net/wireless/nxp/nxpwifi/Kconfig | 22 + drivers/net/wireless/nxp/nxpwifi/Makefile | 39 + drivers/net/wireless/nxp/nxpwifi/cfg.h | 1019 +++++ drivers/net/wireless/nxp/nxpwifi/cfg80211.c | 3931 +++++++++++++++++ drivers/net/wireless/nxp/nxpwifi/cfg80211.h | 18 + drivers/net/wireless/nxp/nxpwifi/cfp.c | 458 ++ drivers/net/wireless/nxp/nxpwifi/cmdevt.c | 1310 ++++++ drivers/net/wireless/nxp/nxpwifi/cmdevt.h | 122 + drivers/net/wireless/nxp/nxpwifi/debugfs.c | 1094 +++++ drivers/net/wireless/nxp/nxpwifi/ethtool.c | 58 + drivers/net/wireless/nxp/nxpwifi/fw.h | 2459 +++++++++++ drivers/net/wireless/nxp/nxpwifi/ie.c | 480 ++ drivers/net/wireless/nxp/nxpwifi/init.c | 607 +++ drivers/net/wireless/nxp/nxpwifi/join.c | 787 ++++ drivers/net/wireless/nxp/nxpwifi/main.c | 1673 +++++++ drivers/net/wireless/nxp/nxpwifi/main.h | 1427 ++++++ drivers/net/wireless/nxp/nxpwifi/scan.c | 2695 +++++++++++ drivers/net/wireless/nxp/nxpwifi/sdio.c | 2327 ++++++++++ drivers/net/wireless/nxp/nxpwifi/sdio.h | 340 ++ drivers/net/wireless/nxp/nxpwifi/sta_cfg.c | 1165 +++++ drivers/net/wireless/nxp/nxpwifi/sta_cmd.c | 3387 ++++++++++++++ drivers/net/wireless/nxp/nxpwifi/sta_event.c | 862 ++++ drivers/net/wireless/nxp/nxpwifi/sta_rx.c | 242 + drivers/net/wireless/nxp/nxpwifi/sta_tx.c | 190 + drivers/net/wireless/nxp/nxpwifi/txrx.c | 352 ++ drivers/net/wireless/nxp/nxpwifi/uap_cmd.c | 1256 ++++++ drivers/net/wireless/nxp/nxpwifi/uap_event.c | 488 ++ drivers/net/wireless/nxp/nxpwifi/uap_txrx.c | 478 ++ drivers/net/wireless/nxp/nxpwifi/util.c | 1381 ++++++ drivers/net/wireless/nxp/nxpwifi/util.h | 155 + drivers/net/wireless/nxp/nxpwifi/wmm.c | 1313 ++++++ drivers/net/wireless/nxp/nxpwifi/wmm.h | 77 + 48 files changed, 35724 insertions(+) create mode 100644 drivers/net/wireless/nxp/Kconfig create mode 100644 drivers/net/wireless/nxp/Makefile create mode 100644 drivers/net/wireless/nxp/nxpwifi/11ac.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/11ac.h create mode 100644 drivers/net/wireless/nxp/nxpwifi/11ax.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/11ax.h create mode 100644 drivers/net/wireless/nxp/nxpwifi/11h.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/11n.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/11n.h create mode 100644 drivers/net/wireless/nxp/nxpwifi/11n_aggr.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/11n_aggr.h create mode 100644 drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.h create mode 100644 drivers/net/wireless/nxp/nxpwifi/Kconfig create mode 100644 drivers/net/wireless/nxp/nxpwifi/Makefile create mode 100644 drivers/net/wireless/nxp/nxpwifi/cfg.h create mode 100644 drivers/net/wireless/nxp/nxpwifi/cfg80211.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/cfg80211.h create mode 100644 drivers/net/wireless/nxp/nxpwifi/cfp.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/cmdevt.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/cmdevt.h create mode 100644 drivers/net/wireless/nxp/nxpwifi/debugfs.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/ethtool.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/fw.h create mode 100644 drivers/net/wireless/nxp/nxpwifi/ie.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/init.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/join.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/main.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/main.h create mode 100644 drivers/net/wireless/nxp/nxpwifi/scan.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/sdio.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/sdio.h create mode 100644 drivers/net/wireless/nxp/nxpwifi/sta_cfg.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/sta_cmd.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/sta_event.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/sta_rx.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/sta_tx.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/txrx.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/uap_cmd.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/uap_event.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/uap_txrx.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/util.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/util.h create mode 100644 drivers/net/wireless/nxp/nxpwifi/wmm.c create mode 100644 drivers/net/wireless/nxp/nxpwifi/wmm.h diff --git a/MAINTAINERS b/MAINTAINERS index eb8cdcc76324..ab171a6a74e4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19331,6 +19331,13 @@ S: Maintained F: Documentation/devicetree/bindings/ptp/nxp,ptp-netc.yaml F: drivers/ptp/ptp_netc.c +NXP NXPWIFI WIRELESS DRIVER +M: Jeff Chen +R: Francesco Dolcini +L: linux-wireless@vger.kernel.org +S: Maintained +F: drivers/net/wireless/nxp/nxpwifi + NXP PF5300/PF5301/PF5302 PMIC REGULATOR DEVICE DRIVER M: Woodrow Douglass S: Maintained diff --git a/drivers/net/wireless/Kconfig b/drivers/net/wireless/Kconfig index c6599594dc99..4d7b81182925 100644 --- a/drivers/net/wireless/Kconfig +++ b/drivers/net/wireless/Kconfig @@ -27,6 +27,7 @@ source "drivers/net/wireless/intersil/Kconfig" source "drivers/net/wireless/marvell/Kconfig" source "drivers/net/wireless/mediatek/Kconfig" source "drivers/net/wireless/microchip/Kconfig" +source "drivers/net/wireless/nxp/Kconfig" source "drivers/net/wireless/purelifi/Kconfig" source "drivers/net/wireless/ralink/Kconfig" source "drivers/net/wireless/realtek/Kconfig" diff --git a/drivers/net/wireless/Makefile b/drivers/net/wireless/Makefile index e1c4141c6004..0c6b3cc719db 100644 --- a/drivers/net/wireless/Makefile +++ b/drivers/net/wireless/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_WLAN_VENDOR_INTERSIL) += intersil/ obj-$(CONFIG_WLAN_VENDOR_MARVELL) += marvell/ obj-$(CONFIG_WLAN_VENDOR_MEDIATEK) += mediatek/ obj-$(CONFIG_WLAN_VENDOR_MICROCHIP) += microchip/ +obj-$(CONFIG_WLAN_VENDOR_NXP) += nxp/ obj-$(CONFIG_WLAN_VENDOR_PURELIFI) += purelifi/ obj-$(CONFIG_WLAN_VENDOR_QUANTENNA) += quantenna/ obj-$(CONFIG_WLAN_VENDOR_RALINK) += ralink/ diff --git a/drivers/net/wireless/nxp/Kconfig b/drivers/net/wireless/nxp/Kconfig new file mode 100644 index 000000000000..68b32d4536e5 --- /dev/null +++ b/drivers/net/wireless/nxp/Kconfig @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0-only +config WLAN_VENDOR_NXP + bool "NXP devices" + default y + help + If you have a wireless card belonging to this class, say Y. + + Note that the answer to this question doesn't directly affect the + kernel: saying N will just cause the configurator to skip all the + questions about these cards. If you say Y, you will be asked for + your specific card in the following questions. + +if WLAN_VENDOR_NXP + +source "drivers/net/wireless/nxp/nxpwifi/Kconfig" + +endif # WLAN_VENDOR_NXP diff --git a/drivers/net/wireless/nxp/Makefile b/drivers/net/wireless/nxp/Makefile new file mode 100644 index 000000000000..27b41a0afdd2 --- /dev/null +++ b/drivers/net/wireless/nxp/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_NXPWIFI) += nxpwifi/ diff --git a/drivers/net/wireless/nxp/nxpwifi/11ac.c b/drivers/net/wireless/nxp/nxpwifi/11ac.c new file mode 100644 index 000000000000..117d06c35401 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/11ac.c @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi 802.11ac helpers + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "fw.h" +#include "main.h" +#include "11ac.h" + +/* Map VHT MCS/NSS to highest data rate (Mbps), long GI. */ +static const u16 max_rate_lgi_80MHZ[8][3] = { + {0x124, 0x15F, 0x186}, /* NSS = 1 */ + {0x249, 0x2BE, 0x30C}, /* NSS = 2 */ + {0x36D, 0x41D, 0x492}, /* NSS = 3 */ + {0x492, 0x57C, 0x618}, /* NSS = 4 */ + {0x5B6, 0x6DB, 0x79E}, /* NSS = 5 */ + {0x6DB, 0x83A, 0x0}, /* NSS = 6 */ + {0x7FF, 0x999, 0xAAA}, /* NSS = 7 */ + {0x924, 0xAF8, 0xC30} /* NSS = 8 */ +}; + +static const u16 max_rate_lgi_160MHZ[8][3] = { + {0x249, 0x2BE, 0x30C}, /* NSS = 1 */ + {0x492, 0x57C, 0x618}, /* NSS = 2 */ + {0x6DB, 0x83A, 0x0}, /* NSS = 3 */ + {0x924, 0xAF8, 0xC30}, /* NSS = 4 */ + {0xB6D, 0xDB6, 0xF3C}, /* NSS = 5 */ + {0xDB6, 0x1074, 0x1248}, /* NSS = 6 */ + {0xFFF, 0x1332, 0x1554}, /* NSS = 7 */ + {0x1248, 0x15F0, 0x1860} /* NSS = 8 */ +}; + +/* Convert 2-bit MCS map to highest long-GI VHT data rate. */ +static u16 +nxpwifi_convert_mcsmap_to_maxrate(struct nxpwifi_private *priv, + u16 bands, u16 mcs_map) +{ + u8 i, nss, mcs; + u16 max_rate = 0; + u32 usr_vht_cap_info = 0; + struct nxpwifi_adapter *adapter = priv->adapter; + + if (bands & BAND_AAC) + usr_vht_cap_info = adapter->usr_dot_11ac_dev_cap_a; + else + usr_vht_cap_info = adapter->usr_dot_11ac_dev_cap_bg; + + /* Find max supported NSS. */ + nss = 1; + for (i = 1; i <= 8; i++) { + mcs = GET_VHTNSSMCS(mcs_map, i); + if (mcs < IEEE80211_VHT_MCS_NOT_SUPPORTED) + nss = i; + } + mcs = GET_VHTNSSMCS(mcs_map, nss); + + /* If not supported, fall back to 0-9. */ + if (mcs == IEEE80211_VHT_MCS_NOT_SUPPORTED) + mcs = IEEE80211_VHT_MCS_SUPPORT_0_9; + + if (u32_get_bits(usr_vht_cap_info, IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK)) { + /* Support 160 MHz. */ + max_rate = max_rate_lgi_160MHZ[nss - 1][mcs]; + if (!max_rate) + /* MCS9 not supported in NSS6. */ + max_rate = max_rate_lgi_160MHZ[nss - 1][mcs - 1]; + } else { + max_rate = max_rate_lgi_80MHZ[nss - 1][mcs]; + if (!max_rate) + /* MCS9 not supported in NSS3. */ + max_rate = max_rate_lgi_80MHZ[nss - 1][mcs - 1]; + } + + return max_rate; +} + +static void +nxpwifi_fill_vht_cap_info(struct nxpwifi_private *priv, + struct ieee80211_vht_cap *vht_cap, u16 bands) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + if (bands & BAND_A) + vht_cap->vht_cap_info = + cpu_to_le32(adapter->usr_dot_11ac_dev_cap_a); + else + vht_cap->vht_cap_info = + cpu_to_le32(adapter->usr_dot_11ac_dev_cap_bg); +} + +void +nxpwifi_fill_vht_cap_tlv(struct nxpwifi_private *priv, + struct ieee80211_vht_cap *vht_cap, u16 bands) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u16 mcs_map_user, mcs_map_resp, mcs_map_result; + u16 mcs_user, mcs_resp, nss, tmp; + + /* Fill VHT capability info. */ + nxpwifi_fill_vht_cap_info(priv, vht_cap, bands); + + /* RX MCS set: min(user, AP). */ + mcs_map_user = GET_DEVRXMCSMAP(adapter->usr_dot_11ac_mcs_support); + mcs_map_resp = le16_to_cpu(vht_cap->supp_mcs.rx_mcs_map); + mcs_map_result = 0; + + for (nss = 1; nss <= 8; nss++) { + mcs_user = GET_VHTNSSMCS(mcs_map_user, nss); + mcs_resp = GET_VHTNSSMCS(mcs_map_resp, nss); + + if (mcs_user == IEEE80211_VHT_MCS_NOT_SUPPORTED || + mcs_resp == IEEE80211_VHT_MCS_NOT_SUPPORTED) + SET_VHTNSSMCS(mcs_map_result, nss, + IEEE80211_VHT_MCS_NOT_SUPPORTED); + else + SET_VHTNSSMCS(mcs_map_result, nss, + min(mcs_user, mcs_resp)); + } + + vht_cap->supp_mcs.rx_mcs_map = cpu_to_le16(mcs_map_result); + + tmp = nxpwifi_convert_mcsmap_to_maxrate(priv, bands, mcs_map_result); + vht_cap->supp_mcs.rx_highest = cpu_to_le16(tmp); + + /* TX MCS set: min(user, AP). */ + mcs_map_user = GET_DEVTXMCSMAP(adapter->usr_dot_11ac_mcs_support); + mcs_map_resp = le16_to_cpu(vht_cap->supp_mcs.tx_mcs_map); + mcs_map_result = 0; + + for (nss = 1; nss <= 8; nss++) { + mcs_user = GET_VHTNSSMCS(mcs_map_user, nss); + mcs_resp = GET_VHTNSSMCS(mcs_map_resp, nss); + if (mcs_user == IEEE80211_VHT_MCS_NOT_SUPPORTED || + mcs_resp == IEEE80211_VHT_MCS_NOT_SUPPORTED) + SET_VHTNSSMCS(mcs_map_result, nss, + IEEE80211_VHT_MCS_NOT_SUPPORTED); + else + SET_VHTNSSMCS(mcs_map_result, nss, + min(mcs_user, mcs_resp)); + } + + vht_cap->supp_mcs.tx_mcs_map = cpu_to_le16(mcs_map_result); + + tmp = nxpwifi_convert_mcsmap_to_maxrate(priv, bands, mcs_map_result); + vht_cap->supp_mcs.tx_highest = cpu_to_le16(tmp); +} + +int nxpwifi_cmd_append_11ac_tlv(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc, + u8 **buffer) +{ + struct nxpwifi_ie_types_vhtcap *vht_cap; + struct nxpwifi_ie_types_oper_mode_ntf *oper_ntf; + struct ieee_types_oper_mode_ntf *ieee_oper_ntf; + struct nxpwifi_ie_types_vht_oper *vht_op; + struct nxpwifi_adapter *adapter = priv->adapter; + u8 supp_chwd_set; + u32 usr_vht_cap_info; + int ret_len = 0; + + if (bss_desc->bss_band & BAND_A) + usr_vht_cap_info = adapter->usr_dot_11ac_dev_cap_a; + else + usr_vht_cap_info = adapter->usr_dot_11ac_dev_cap_bg; + + /* VHT Capabilities element. */ + if (bss_desc->bcn_vht_cap) { + vht_cap = (struct nxpwifi_ie_types_vhtcap *)*buffer; + memset(vht_cap, 0, sizeof(*vht_cap)); + vht_cap->header.type = cpu_to_le16(WLAN_EID_VHT_CAPABILITY); + vht_cap->header.len = + cpu_to_le16(sizeof(struct ieee80211_vht_cap)); + memcpy((u8 *)vht_cap + sizeof(struct nxpwifi_ie_types_header), + (u8 *)bss_desc->bcn_vht_cap, + le16_to_cpu(vht_cap->header.len)); + + nxpwifi_fill_vht_cap_tlv(priv, &vht_cap->vht_cap, + bss_desc->bss_band); + *buffer += sizeof(*vht_cap); + ret_len += sizeof(*vht_cap); + } + + /* VHT Operation element. */ + if (bss_desc->bcn_vht_oper) { + if (priv->bss_mode == NL80211_IFTYPE_STATION) { + vht_op = (struct nxpwifi_ie_types_vht_oper *)*buffer; + memset(vht_op, 0, sizeof(*vht_op)); + vht_op->header.type = + cpu_to_le16(WLAN_EID_VHT_OPERATION); + vht_op->header.len = cpu_to_le16(sizeof(*vht_op) - + sizeof(struct nxpwifi_ie_types_header)); + memcpy((u8 *)vht_op + + sizeof(struct nxpwifi_ie_types_header), + (u8 *)bss_desc->bcn_vht_oper, + le16_to_cpu(vht_op->header.len)); + + /* Negotiate channel width; keep peer's center freq. */ + supp_chwd_set = u32_get_bits(usr_vht_cap_info, + IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK); + + switch (supp_chwd_set) { + case 0: + vht_op->chan_width = + min_t(u8, IEEE80211_VHT_CHANWIDTH_80MHZ, + bss_desc->bcn_vht_oper->chan_width); + break; + case 1: + vht_op->chan_width = + min_t(u8, IEEE80211_VHT_CHANWIDTH_160MHZ, + bss_desc->bcn_vht_oper->chan_width); + break; + case 2: + vht_op->chan_width = + min_t(u8, IEEE80211_VHT_CHANWIDTH_80P80MHZ, + bss_desc->bcn_vht_oper->chan_width); + break; + default: + vht_op->chan_width = + IEEE80211_VHT_CHANWIDTH_USE_HT; + break; + } + + *buffer += sizeof(*vht_op); + ret_len += sizeof(*vht_op); + } + } + + /* Operating Mode Notification element. */ + if (bss_desc->oper_mode) { + ieee_oper_ntf = bss_desc->oper_mode; + oper_ntf = (void *)*buffer; + memset(oper_ntf, 0, sizeof(*oper_ntf)); + oper_ntf->header.type = cpu_to_le16(WLAN_EID_OPMODE_NOTIF); + oper_ntf->header.len = cpu_to_le16(sizeof(u8)); + oper_ntf->oper_mode = ieee_oper_ntf->oper_mode; + *buffer += sizeof(*oper_ntf); + ret_len += sizeof(*oper_ntf); + } + + return ret_len; +} + +int nxpwifi_cmd_11ac_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action, + struct nxpwifi_11ac_vht_cfg *cfg) +{ + struct host_cmd_11ac_vht_cfg *vhtcfg = &cmd->params.vht_cfg; + + cmd->command = cpu_to_le16(HOST_CMD_11AC_CFG); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_11ac_vht_cfg) + + S_DS_GEN); + vhtcfg->action = cpu_to_le16(cmd_action); + vhtcfg->band_config = cfg->band_config; + vhtcfg->misc_config = cfg->misc_config; + vhtcfg->cap_info = cpu_to_le32(cfg->cap_info); + vhtcfg->mcs_tx_set = cpu_to_le32(cfg->mcs_tx_set); + vhtcfg->mcs_rx_set = cpu_to_le32(cfg->mcs_rx_set); + + return 0; +} + +/* Initialize BlockAck parameters for 11ac. */ +void nxpwifi_set_11ac_ba_params(struct nxpwifi_private *priv) +{ + priv->add_ba_param.timeout = NXPWIFI_DEFAULT_BLOCK_ACK_TIMEOUT; + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + priv->add_ba_param.tx_win_size = + NXPWIFI_11AC_UAP_AMPDU_DEF_TXWINSIZE; + priv->add_ba_param.rx_win_size = + NXPWIFI_11AC_UAP_AMPDU_DEF_RXWINSIZE; + } else { + priv->add_ba_param.tx_win_size = + NXPWIFI_11AC_STA_AMPDU_DEF_TXWINSIZE; + priv->add_ba_param.rx_win_size = + NXPWIFI_11AC_STA_AMPDU_DEF_RXWINSIZE; + } +} diff --git a/drivers/net/wireless/nxp/nxpwifi/11ac.h b/drivers/net/wireless/nxp/nxpwifi/11ac.h new file mode 100644 index 000000000000..edc01b35d5b8 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/11ac.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * nxpwifi: 802.11ac (VHT) definitions + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_11AC_H_ +#define _NXPWIFI_11AC_H_ + +#define VHT_CFG_2GHZ BIT(0) +#define VHT_CFG_5GHZ BIT(1) + +enum vht_cfg_misc_config { + VHT_CAP_TX_OPERATION = 1, + VHT_CAP_ASSOCIATION, + VHT_CAP_UAP_ONLY +}; + +#define DEFAULT_VHT_MCS_SET 0xfffe +#define DISABLE_VHT_MCS_SET 0xffff + +#define VHT_BW_80_160_80P80 BIT(2) + +int nxpwifi_cmd_append_11ac_tlv(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc, + u8 **buffer); +int nxpwifi_cmd_11ac_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action, + struct nxpwifi_11ac_vht_cfg *cfg); +void nxpwifi_fill_vht_cap_tlv(struct nxpwifi_private *priv, + struct ieee80211_vht_cap *vht_cap, u16 bands); +#endif /* _NXPWIFI_11AC_H_ */ diff --git a/drivers/net/wireless/nxp/nxpwifi/11ax.c b/drivers/net/wireless/nxp/nxpwifi/11ax.c new file mode 100644 index 000000000000..cc47c435eb70 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/11ax.c @@ -0,0 +1,594 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* nxpwifi: 802.11ax (HE) support + * Copyright (C) 2011-2024 NXP + */ + +#include "cfg.h" +#include "fw.h" +#include "main.h" +#include "11ax.h" + +void nxpwifi_update_11ax_cap(struct nxpwifi_adapter *adapter, + struct hw_spec_extension *hw_he_cap) +{ + struct nxpwifi_private *priv; + struct nxpwifi_ie_types_he_cap *he_cap = NULL; + struct nxpwifi_ie_types_he_cap *user_he_cap = NULL; + u8 header_len = sizeof(struct nxpwifi_ie_types_header); + u16 data_len = le16_to_cpu(hw_he_cap->header.len); + bool he_cap_2g = false; + int i; + + if ((data_len + header_len) > sizeof(adapter->hw_he_cap)) { + nxpwifi_dbg(adapter, ERROR, + "hw_he_cap too big, len=%d\n", + data_len); + return; + } + + he_cap = (struct nxpwifi_ie_types_he_cap *)hw_he_cap; + + if (he_cap->he_phy_cap[0] & + (AX_2G_40MHZ_SUPPORT | AX_2G_20MHZ_SUPPORT)) { + adapter->hw_2g_he_cap_len = data_len + header_len; + memcpy(adapter->hw_2g_he_cap, (u8 *)hw_he_cap, + adapter->hw_2g_he_cap_len); + adapter->fw_bands |= BAND_GAX; + he_cap_2g = true; + nxpwifi_dbg_dump(adapter, CMD_D, "2.4G HE capability element ", + adapter->hw_2g_he_cap, + adapter->hw_2g_he_cap_len); + } else { + adapter->hw_he_cap_len = data_len + header_len; + memcpy(adapter->hw_he_cap, (u8 *)hw_he_cap, + adapter->hw_he_cap_len); + adapter->fw_bands |= BAND_AAX; + nxpwifi_dbg_dump(adapter, CMD_D, "5G HE capability element ", + adapter->hw_he_cap, + adapter->hw_he_cap_len); + } + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + + if (he_cap_2g) { + priv->user_2g_he_cap_len = adapter->hw_2g_he_cap_len; + memcpy(priv->user_2g_he_cap, adapter->hw_2g_he_cap, + sizeof(adapter->hw_2g_he_cap)); + user_he_cap = (struct nxpwifi_ie_types_he_cap *) + priv->user_2g_he_cap; + } else { + priv->user_he_cap_len = adapter->hw_he_cap_len; + memcpy(priv->user_he_cap, adapter->hw_he_cap, + sizeof(adapter->hw_he_cap)); + user_he_cap = (struct nxpwifi_ie_types_he_cap *) + priv->user_he_cap; + } + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) + user_he_cap->he_mac_cap[0] &= + ~HE_MAC_CAP_TWT_RESP_SUPPORT; + else + user_he_cap->he_mac_cap[0] &= + ~HE_MAC_CAP_TWT_REQ_SUPPORT; + } + + adapter->is_hw_11ax_capable = true; +} + +bool nxpwifi_11ax_bandconfig_allowed(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc) +{ + u16 bss_band = bss_desc->bss_band; + + if (bss_desc->disable_11n) + return false; + + if (bss_band & BAND_G) + return (priv->config_bands & BAND_GAX); + else if (bss_band & BAND_A) + return (priv->config_bands & BAND_AAX); + + return false; +} + +int nxpwifi_fill_he_cap_tlv(struct nxpwifi_private *priv, + struct nxpwifi_ie_types_he_cap *he_cap, + u16 bands) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_ie_types_he_cap *hw_he_cap = NULL; + u16 rx_nss, tx_nss; + u8 nss; + u16 cfg_value; + u16 hw_value; + int ret_len; + + if (bands & BAND_A) { + memcpy(he_cap, priv->user_he_cap, priv->user_he_cap_len); + hw_he_cap = (struct nxpwifi_ie_types_he_cap *)adapter->hw_he_cap; + ret_len = priv->user_he_cap_len; + } else { + memcpy(he_cap, priv->user_2g_he_cap, priv->user_2g_he_cap_len); + hw_he_cap = (struct nxpwifi_ie_types_he_cap *)adapter->hw_2g_he_cap; + ret_len = priv->user_2g_he_cap_len; + } + + if (bands & BAND_A) { + rx_nss = GET_RXMCSSUPP(adapter->user_htstream >> 8); + tx_nss = GET_TXMCSSUPP(adapter->user_htstream >> 8) & 0x0f; + } else { + rx_nss = GET_RXMCSSUPP(adapter->user_htstream); + tx_nss = GET_TXMCSSUPP(adapter->user_htstream) & 0x0f; + } + + for (nss = 1; nss <= 8; nss++) { + cfg_value = nxpwifi_get_he_nss_mcs(he_cap->rx_mcs_80, nss); + hw_value = nxpwifi_get_he_nss_mcs(hw_he_cap->rx_mcs_80, nss); + if (rx_nss != 0 && nss > rx_nss) + cfg_value = NO_NSS_SUPPORT; + if (hw_value == NO_NSS_SUPPORT || cfg_value == NO_NSS_SUPPORT) + nxpwifi_set_he_nss_mcs(&he_cap->rx_mcs_80, nss, + NO_NSS_SUPPORT); + else + nxpwifi_set_he_nss_mcs(&he_cap->rx_mcs_80, nss, + min(cfg_value, hw_value)); + } + + for (nss = 1; nss <= 8; nss++) { + cfg_value = nxpwifi_get_he_nss_mcs(he_cap->tx_mcs_80, nss); + hw_value = nxpwifi_get_he_nss_mcs(hw_he_cap->tx_mcs_80, nss); + if (tx_nss != 0 && nss > tx_nss) + cfg_value = NO_NSS_SUPPORT; + if (hw_value == NO_NSS_SUPPORT || cfg_value == NO_NSS_SUPPORT) + nxpwifi_set_he_nss_mcs(&he_cap->tx_mcs_80, nss, + NO_NSS_SUPPORT); + else + nxpwifi_set_he_nss_mcs(&he_cap->tx_mcs_80, nss, + min(cfg_value, hw_value)); + } + + return ret_len; +} + +int nxpwifi_cmd_append_11ax_tlv(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc, + u8 **buffer) +{ + struct nxpwifi_ie_types_he_cap *he_cap = NULL; + int ret_len; + + if (!bss_desc->bcn_he_cap) + return -EOPNOTSUPP; + + he_cap = (struct nxpwifi_ie_types_he_cap *)*buffer; + ret_len = nxpwifi_fill_he_cap_tlv(priv, he_cap, bss_desc->bss_band); + *buffer += ret_len; + + return ret_len; +} + +int nxpwifi_cmd_11ax_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action, + struct nxpwifi_11ax_he_cfg *ax_cfg) +{ + struct host_cmd_11ax_cfg *he_cfg = &cmd->params.ax_cfg; + u16 cmd_size; + struct nxpwifi_ie_types_header *header; + + cmd->command = cpu_to_le16(HOST_CMD_11AX_CFG); + cmd_size = sizeof(struct host_cmd_11ax_cfg) + S_DS_GEN; + + he_cfg->action = cpu_to_le16(cmd_action); + he_cfg->band_config = ax_cfg->band; + + if (ax_cfg->he_cap_cfg.len && + ax_cfg->he_cap_cfg.ext_id == WLAN_EID_EXT_HE_CAPABILITY) { + header = (struct nxpwifi_ie_types_header *)he_cfg->tlv; + header->type = cpu_to_le16(ax_cfg->he_cap_cfg.id); + header->len = cpu_to_le16(ax_cfg->he_cap_cfg.len); + memcpy(he_cfg->tlv + sizeof(*header), + &ax_cfg->he_cap_cfg.ext_id, + ax_cfg->he_cap_cfg.len); + cmd_size += (sizeof(*header) + ax_cfg->he_cap_cfg.len); + } + + cmd->size = cpu_to_le16(cmd_size); + + return 0; +} + +int nxpwifi_ret_11ax_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + struct nxpwifi_11ax_he_cfg *ax_cfg) +{ + struct host_cmd_11ax_cfg *he_cfg = &resp->params.ax_cfg; + struct nxpwifi_ie_types_header *header; + u16 left_len, tlv_type, tlv_len; + u8 ext_id; + struct nxpwifi_11ax_he_cap_cfg *he_cap = &ax_cfg->he_cap_cfg; + + left_len = le16_to_cpu(resp->size) - sizeof(*he_cfg) - S_DS_GEN; + header = (struct nxpwifi_ie_types_header *)he_cfg->tlv; + + while (left_len > sizeof(*header)) { + tlv_type = le16_to_cpu(header->type); + tlv_len = le16_to_cpu(header->len); + + if (tlv_type == TLV_TYPE_EXTENSION_ID) { + ext_id = *((u8 *)header + sizeof(*header) + 1); + if (ext_id == WLAN_EID_EXT_HE_CAPABILITY) { + he_cap->id = tlv_type; + he_cap->len = tlv_len; + memcpy((u8 *)&he_cap->ext_id, + (u8 *)header + sizeof(*header) + 1, + tlv_len); + if (he_cfg->band_config & BIT(1)) { + memcpy(priv->user_he_cap, + (u8 *)header, + sizeof(*header) + tlv_len); + priv->user_he_cap_len = + sizeof(*header) + tlv_len; + } else { + memcpy(priv->user_2g_he_cap, + (u8 *)header, + sizeof(*header) + tlv_len); + priv->user_2g_he_cap_len = + sizeof(*header) + tlv_len; + } + } + } + + left_len -= (sizeof(*header) + tlv_len); + header = (struct nxpwifi_ie_types_header *)((u8 *)header + + sizeof(*header) + + tlv_len); + } + + return 0; +} + +int nxpwifi_cmd_11ax_cmd(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action, + struct nxpwifi_11ax_cmd_cfg *ax_cmd) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_11ax_cmd *he_cmd = &cmd->params.ax_cmd; + u16 cmd_size; + struct nxpwifi_11ax_sr_cmd *sr_cmd; + struct nxpwifi_ie_types_data *tlv; + struct nxpwifi_11ax_beam_cmd *beam_cmd; + struct nxpwifi_11ax_htc_cmd *htc_cmd; + struct nxpwifi_11ax_txomi_cmd *txmoi_cmd; + struct nxpwifi_11ax_toltime_cmd *toltime_cmd; + struct nxpwifi_11ax_txop_cmd *txop_cmd; + struct nxpwifi_11ax_set_bsrp_cmd *set_bsrp_cmd; + struct nxpwifi_11ax_llde_cmd *llde_cmd; + + cmd->command = cpu_to_le16(HOST_CMD_11AX_CMD); + cmd_size = sizeof(struct host_cmd_11ax_cmd) + S_DS_GEN; + + he_cmd->action = cpu_to_le16(cmd_action); + he_cmd->sub_id = cpu_to_le16(ax_cmd->sub_id); + + switch (ax_cmd->sub_command) { + case NXPWIFI_11AXCMD_SR_SUBID: + sr_cmd = (struct nxpwifi_11ax_sr_cmd *)&ax_cmd->param; + + tlv = (struct nxpwifi_ie_types_data *)he_cmd->val; + tlv->header.type = cpu_to_le16(sr_cmd->type); + tlv->header.len = cpu_to_le16(sr_cmd->len); + memcpy(tlv->data, sr_cmd->param.obss_pd_offset.offset, + sr_cmd->len); + cmd_size += (sizeof(tlv->header) + sr_cmd->len); + break; + case NXPWIFI_11AXCMD_BEAM_SUBID: + beam_cmd = (struct nxpwifi_11ax_beam_cmd *)&ax_cmd->param; + + he_cmd->val[0] = beam_cmd->value; + cmd_size += sizeof(*beam_cmd); + break; + case NXPWIFI_11AXCMD_HTC_SUBID: + htc_cmd = (struct nxpwifi_11ax_htc_cmd *)&ax_cmd->param; + + he_cmd->val[0] = htc_cmd->value; + cmd_size += sizeof(*htc_cmd); + break; + case NXPWIFI_11AXCMD_TXOMI_SUBID: + txmoi_cmd = (struct nxpwifi_11ax_txomi_cmd *)&ax_cmd->param; + + memcpy((void *)he_cmd->val, txmoi_cmd, sizeof(*txmoi_cmd)); + cmd_size += sizeof(*txmoi_cmd); + break; + case NXPWIFI_11AXCMD_OBSS_TOLTIME_SUBID: + toltime_cmd = (struct nxpwifi_11ax_toltime_cmd *)&ax_cmd->param; + + memcpy(he_cmd->val, &toltime_cmd->tol_time, + sizeof(toltime_cmd->tol_time)); + cmd_size += sizeof(*toltime_cmd); + break; + case NXPWIFI_11AXCMD_TXOPRTS_SUBID: + txop_cmd = (struct nxpwifi_11ax_txop_cmd *)&ax_cmd->param; + + memcpy(he_cmd->val, &txop_cmd->rts_thres, + sizeof(txop_cmd->rts_thres)); + cmd_size += sizeof(*txop_cmd); + break; + case NXPWIFI_11AXCMD_SET_BSRP_SUBID: + set_bsrp_cmd = (struct nxpwifi_11ax_set_bsrp_cmd *)&ax_cmd->param; + + he_cmd->val[0] = set_bsrp_cmd->value; + cmd_size += sizeof(*set_bsrp_cmd); + break; + case NXPWIFI_11AXCMD_LLDE_SUBID: + llde_cmd = (struct nxpwifi_11ax_llde_cmd *)&ax_cmd->param; + + memcpy((void *)he_cmd->val, llde_cmd, sizeof(*llde_cmd)); + cmd_size += sizeof(*llde_cmd); + break; + default: + nxpwifi_dbg(adapter, ERROR, + "%s: Unknown sub command: %d\n", + __func__, ax_cmd->sub_command); + return -EINVAL; + } + + cmd->size = cpu_to_le16(cmd_size); + + return 0; +} + +int nxpwifi_ret_11ax_cmd(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + struct nxpwifi_11ax_cmd_cfg *ax_cmd) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_11ax_cmd *he_cmd = &resp->params.ax_cmd; + struct nxpwifi_ie_types_data *tlv; + + ax_cmd->sub_id = le16_to_cpu(he_cmd->sub_id); + + switch (ax_cmd->sub_command) { + case NXPWIFI_11AXCMD_SR_SUBID: + tlv = (struct nxpwifi_ie_types_data *)he_cmd->val; + memcpy(ax_cmd->param.sr_cfg.param.obss_pd_offset.offset, + tlv->data, + ax_cmd->param.sr_cfg.len); + break; + case NXPWIFI_11AXCMD_BEAM_SUBID: + ax_cmd->param.beam_cfg.value = *he_cmd->val; + break; + case NXPWIFI_11AXCMD_HTC_SUBID: + ax_cmd->param.htc_cfg.value = *he_cmd->val; + break; + case NXPWIFI_11AXCMD_TXOMI_SUBID: + memcpy(&ax_cmd->param.txomi_cfg, + he_cmd->val, sizeof(ax_cmd->param.txomi_cfg)); + break; + case NXPWIFI_11AXCMD_OBSS_TOLTIME_SUBID: + memcpy(&ax_cmd->param.toltime_cfg.tol_time, + he_cmd->val, sizeof(ax_cmd->param.toltime_cfg)); + break; + case NXPWIFI_11AXCMD_TXOPRTS_SUBID: + memcpy(&ax_cmd->param.txop_cfg.rts_thres, + he_cmd->val, sizeof(ax_cmd->param.txop_cfg)); + break; + case NXPWIFI_11AXCMD_SET_BSRP_SUBID: + ax_cmd->param.setbsrp_cfg.value = *he_cmd->val; + break; + case NXPWIFI_11AXCMD_LLDE_SUBID: + memcpy(&ax_cmd->param.llde_cfg, + he_cmd->val, sizeof(ax_cmd->param.llde_cfg)); + break; + default: + nxpwifi_dbg(adapter, ERROR, + "%s: Unknown sub command: %d\n", + __func__, ax_cmd->sub_command); + return -EINVAL; + } + + return 0; +} + +static u8 nxpwifi_is_ap_11ax_twt_supported(struct nxpwifi_bssdescriptor *bss_desc) +{ + struct element *ext_cap; + + if (!bss_desc->bcn_he_cap) + return false; + if (!(bss_desc->bcn_he_cap->mac_cap_info[0] & HE_MAC_CAP_TWT_RESP_SUPPORT)) + return false; + if (!bss_desc->bcn_ext_cap) + return false; + ext_cap = (struct element *)bss_desc->bcn_ext_cap; + + if (!(ext_cap->data[9] & WLAN_EXT_CAPA10_TWT_RESPONDER_SUPPORT)) + return false; + return true; +} + +bool nxpwifi_is_11ax_twt_supported(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc) +{ + struct nxpwifi_ie_types_he_cap *user_he_cap; + struct nxpwifi_ie_types_he_cap *hw_he_cap; + + if (bss_desc && (!nxpwifi_is_ap_11ax_twt_supported(bss_desc))) { + nxpwifi_dbg(priv->adapter, MSG, + "AP don't support twt feature\n"); + return false; + } + + if (bss_desc->bss_band & BAND_A) { + hw_he_cap = (struct nxpwifi_ie_types_he_cap *) + priv->adapter->hw_he_cap; + user_he_cap = (struct nxpwifi_ie_types_he_cap *) + priv->user_he_cap; + } else { + hw_he_cap = (struct nxpwifi_ie_types_he_cap *) + priv->adapter->hw_2g_he_cap; + user_he_cap = (struct nxpwifi_ie_types_he_cap *) + priv->user_2g_he_cap; + } + + if (!(hw_he_cap->he_mac_cap[0] & HE_MAC_CAP_TWT_REQ_SUPPORT)) { + nxpwifi_dbg(priv->adapter, MSG, + "FW don't support TWT\n"); + return false; + } + + if (!(user_he_cap->he_mac_cap[0] & HE_MAC_CAP_TWT_REQ_SUPPORT)) { + nxpwifi_dbg(priv->adapter, MSG, + "USER HE_MAC_CAP don't support TWT\n"); + return false; + } + + return true; +} + +u8 nxpwifi_is_sta_11ax_twt_req_supported(struct nxpwifi_private *priv) +{ + struct nxpwifi_ie_types_he_cap *user_he_cap; + u8 ret = 0; + + if (ISSUPP_11AXENABLED(priv->adapter->fw_cap_ext) && + (priv->config_bands & BAND_GAX || priv->config_bands & BAND_AAX)) { + if (priv->config_bands & BAND_AAX) + user_he_cap = (struct nxpwifi_ie_types_he_cap *)priv->user_he_cap; + else + user_he_cap = (struct nxpwifi_ie_types_he_cap *)priv->user_2g_he_cap; + ret = user_he_cap->he_mac_cap[0] & HE_MAC_CAP_TWT_REQ_SUPPORT; + } + + return ret; +} + +int nxpwifi_cmd_twt_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action, + struct nxpwifi_twt_cfg *twt_cfg) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_twt_cfg *twt_cfg_cmd = &cmd->params.twt_cfg; + struct nxpwifi_twt_setup *twt_setup; + struct nxpwifi_twt_teardown *twt_teardown; + struct nxpwifi_twt_report *twt_report; + struct nxpwifi_twt_information *twt_information; + struct nxpwifi_btwt_ap_config *btwt_ap_config; + u8 i; + u16 cmd_size; + + cmd->command = cpu_to_le16(HOST_CMD_TWT_CFG); + cmd_size = sizeof(struct host_cmd_twt_cfg) + S_DS_GEN; + + twt_cfg_cmd->action = cpu_to_le16(cmd_action); + twt_cfg_cmd->sub_id = cpu_to_le16(twt_cfg->sub_id); + + switch (twt_cfg->sub_id) { + case NXPWIFI_11AX_TWT_SETUP_SUBID: + twt_setup = (struct nxpwifi_twt_setup *) + twt_cfg_cmd->val; + + memset(twt_setup, 0x00, sizeof(struct nxpwifi_twt_setup)); + twt_setup->implicit = twt_cfg->param.twt_setup.implicit; + twt_setup->announced = twt_cfg->param.twt_setup.announced; + twt_setup->trigger_enabled = twt_cfg->param.twt_setup.trigger_enabled; + twt_setup->twt_info_disabled = twt_cfg->param.twt_setup.twt_info_disabled; + twt_setup->negotiation_type = twt_cfg->param.twt_setup.negotiation_type; + twt_setup->twt_wakeup_duration = + twt_cfg->param.twt_setup.twt_wakeup_duration; + twt_setup->flow_identifier = twt_cfg->param.twt_setup.flow_identifier; + twt_setup->hard_constraint = twt_cfg->param.twt_setup.hard_constraint; + twt_setup->twt_exponent = twt_cfg->param.twt_setup.twt_exponent; + twt_setup->twt_mantissa = twt_cfg->param.twt_setup.twt_mantissa; + twt_setup->twt_request = twt_cfg->param.twt_setup.twt_request; + twt_setup->bcn_miss_threshold = twt_cfg->param.twt_setup.bcn_miss_threshold; + cmd_size += sizeof(struct nxpwifi_twt_setup); + break; + case NXPWIFI_11AX_TWT_TEARDOWN_SUBID: + twt_teardown = (struct nxpwifi_twt_teardown *) + twt_cfg_cmd->val; + memset(twt_teardown, 0x00, + sizeof(struct nxpwifi_twt_teardown)); + twt_teardown->flow_identifier = + twt_cfg->param.twt_teardown.flow_identifier; + twt_teardown->negotiation_type = + twt_cfg->param.twt_teardown.negotiation_type; + twt_teardown->teardown_all_twt = + twt_cfg->param.twt_teardown.teardown_all_twt; + cmd_size += sizeof(struct nxpwifi_twt_teardown); + break; + case NXPWIFI_11AX_TWT_REPORT_SUBID: + twt_report = (struct nxpwifi_twt_report *) + twt_cfg_cmd->val; + memset(twt_report, 0x00, sizeof(struct nxpwifi_twt_report)); + twt_report->type = twt_cfg->param.twt_report.type; + cmd_size += sizeof(struct nxpwifi_twt_report); + break; + case NXPWIFI_11AX_TWT_INFORMATION_SUBID: + twt_information = (struct nxpwifi_twt_information *) + twt_cfg_cmd->val; + memset(twt_information, 0x00, + sizeof(struct nxpwifi_twt_information)); + twt_information->flow_identifier = + twt_cfg->param.twt_information.flow_identifier; + twt_information->suspend_duration = + twt_cfg->param.twt_information.suspend_duration; + cmd_size += sizeof(struct nxpwifi_twt_information); + break; + case NXPWIFI_11AX_BTWT_AP_CONFIG_SUBID: + btwt_ap_config = (struct nxpwifi_btwt_ap_config *) + twt_cfg_cmd->val; + memset(btwt_ap_config, 0x00, + sizeof(struct nxpwifi_btwt_ap_config)); + btwt_ap_config->ap_bcast_bet_sta_wait = + twt_cfg->param.btwt_ap_config.ap_bcast_bet_sta_wait; + btwt_ap_config->ap_bcast_offset = + twt_cfg->param.btwt_ap_config.ap_bcast_offset; + btwt_ap_config->bcast_twtli = + twt_cfg->param.btwt_ap_config.bcast_twtli; + btwt_ap_config->count = + twt_cfg->param.btwt_ap_config.count; + for (i = 0; i < BTWT_AGREEMENT_MAX; i++) { + btwt_ap_config->btwt_sets[i].btwt_id = + twt_cfg->param.btwt_ap_config.btwt_sets[i].btwt_id; + btwt_ap_config->btwt_sets[i].ap_bcast_mantissa = + twt_cfg->param.btwt_ap_config.btwt_sets[i].ap_bcast_mantissa; + btwt_ap_config->btwt_sets[i].ap_bcast_exponent = + twt_cfg->param.btwt_ap_config.btwt_sets[i].ap_bcast_exponent; + btwt_ap_config->btwt_sets[i].nominalwake = + twt_cfg->param.btwt_ap_config.btwt_sets[i].nominalwake; + } + + cmd_size += sizeof(struct nxpwifi_btwt_ap_config); + break; + default: + nxpwifi_dbg(adapter, ERROR, + "Unknown sub id: %d\n", twt_cfg->sub_id); + return -EINVAL; + } + + cmd->size = cpu_to_le16(cmd_size); + + return 0; +} + +int nxpwifi_ret_twt_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + struct nxpwifi_twt_cfg *twt_cfg) +{ + struct host_cmd_twt_cfg *twt_cfg_cmd = &resp->params.twt_cfg; + u16 action; + + action = le16_to_cpu(twt_cfg_cmd->action); + twt_cfg->sub_id = le16_to_cpu(twt_cfg_cmd->sub_id); + + if (action == HOST_ACT_GEN_GET && + twt_cfg->sub_id == NXPWIFI_11AX_TWT_REPORT_SUBID) { + struct nxpwifi_twt_report *twt_report = + (struct nxpwifi_twt_report *)twt_cfg_cmd->val; + + memcpy(&twt_cfg->param.twt_report, twt_report, sizeof(struct nxpwifi_twt_report)); + } + + return 0; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/11ax.h b/drivers/net/wireless/nxp/nxpwifi/11ax.h new file mode 100644 index 000000000000..2eda69f19763 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/11ax.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * nxpwifi: 802.11ax support + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_11AX_H_ +#define _NXPWIFI_11AX_H_ + +/* device support 2.4G 40MHZ */ +#define AX_2G_40MHZ_SUPPORT BIT(1) +/* device support 2.4G 242 tone RUs */ +#define AX_2G_20MHZ_SUPPORT BIT(5) + +/* Get HE MCS map code for n spatial streams (0..3). */ +static inline u16 +nxpwifi_get_he_nss_mcs(__le16 mcs_map_set, int nss) { + return ((le16_to_cpu(mcs_map_set) >> (2 * (nss - 1))) & 0x3); +} + +static inline void +nxpwifi_set_he_nss_mcs(__le16 *mcs_map_set, int nss, int value) { + u16 temp; + + temp = le16_to_cpu(*mcs_map_set); + temp |= ((value & 0x3) << (2 * (nss - 1))); + *mcs_map_set = cpu_to_le16(temp); +} + +bool nxpwifi_is_11ax_twt_supported(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc); + +void nxpwifi_update_11ax_cap(struct nxpwifi_adapter *adapter, + struct hw_spec_extension *hw_he_cap); + +bool nxpwifi_11ax_bandconfig_allowed(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc); + +int nxpwifi_cmd_append_11ax_tlv(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc, + u8 **buffer); + +int nxpwifi_fill_he_cap_tlv(struct nxpwifi_private *priv, + struct nxpwifi_ie_types_he_cap *he_cap, + u16 bands); +int nxpwifi_cmd_11ax_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action, + struct nxpwifi_11ax_he_cfg *ax_cfg); + +int nxpwifi_ret_11ax_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + struct nxpwifi_11ax_he_cfg *ax_cfg); + +int nxpwifi_cmd_11ax_cmd(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action, + struct nxpwifi_11ax_cmd_cfg *ax_cmd); + +int nxpwifi_ret_11ax_cmd(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + struct nxpwifi_11ax_cmd_cfg *ax_cmd); + +int nxpwifi_cmd_twt_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action, + struct nxpwifi_twt_cfg *twt_cfg); + +int nxpwifi_ret_twt_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + struct nxpwifi_twt_cfg *twt_cfg); + +u8 nxpwifi_is_sta_11ax_twt_req_supported(struct nxpwifi_private *priv); + +#endif /* _NXPWIFI_11AX_H_ */ diff --git a/drivers/net/wireless/nxp/nxpwifi/11h.c b/drivers/net/wireless/nxp/nxpwifi/11h.c new file mode 100644 index 000000000000..058c319ff910 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/11h.c @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: 802.11h helpers + * + * Copyright 2011-2024 NXP + */ + +#include "main.h" +#include "cmdevt.h" +#include "fw.h" +#include "cfg80211.h" + +void nxpwifi_init_11h_params(struct nxpwifi_private *priv) +{ + priv->state_11h.is_11h_enabled = true; + priv->state_11h.is_11h_active = false; +} + +int nxpwifi_is_11h_active(struct nxpwifi_private *priv) +{ + return priv->state_11h.is_11h_active; +} + +/* appends 11h info to a buffer while joining an infrastructure BSS */ +static void +nxpwifi_11h_process_infra_join(struct nxpwifi_private *priv, u8 **buffer, + struct nxpwifi_bssdescriptor *bss_desc) +{ + struct nxpwifi_ie_types_header *ie_header; + struct nxpwifi_ie_types_pwr_capability *cap; + struct nxpwifi_ie_types_local_pwr_constraint *constraint; + struct ieee80211_supported_band *sband; + u8 radio_type; + int i; + + if (!buffer || !(*buffer)) + return; + + radio_type = nxpwifi_band_to_radio_type((u8)bss_desc->bss_band); + sband = priv->wdev.wiphy->bands[radio_type]; + + cap = (struct nxpwifi_ie_types_pwr_capability *)*buffer; + cap->header.type = cpu_to_le16(WLAN_EID_PWR_CAPABILITY); + cap->header.len = cpu_to_le16(2); + cap->min_pwr = 0; + cap->max_pwr = 0; + *buffer += sizeof(*cap); + + constraint = (struct nxpwifi_ie_types_local_pwr_constraint *)*buffer; + constraint->header.type = cpu_to_le16(WLAN_EID_PWR_CONSTRAINT); + constraint->header.len = cpu_to_le16(2); + constraint->chan = bss_desc->channel; + constraint->constraint = bss_desc->local_constraint; + *buffer += sizeof(*constraint); + + ie_header = (struct nxpwifi_ie_types_header *)*buffer; + ie_header->type = cpu_to_le16(TLV_TYPE_PASSTHROUGH); + ie_header->len = cpu_to_le16(2 * sband->n_channels + 2); + *buffer += sizeof(*ie_header); + *(*buffer)++ = WLAN_EID_SUPPORTED_CHANNELS; + *(*buffer)++ = 2 * sband->n_channels; + for (i = 0; i < sband->n_channels; i++) { + u32 center_freq; + + center_freq = sband->channels[i].center_freq; + *(*buffer)++ = ieee80211_frequency_to_channel(center_freq); + *(*buffer)++ = 1; /* one channel in the subband */ + } +} + +/* Enable or disable the 11h extensions in the firmware */ +int nxpwifi_11h_activate(struct nxpwifi_private *priv, bool flag) +{ + u32 enable = flag; + + /* enable master mode radar detection on AP interface */ + if ((GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) && enable) + enable |= NXPWIFI_MASTER_RADAR_DET_MASK; + + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_SNMP_MIB, + HOST_ACT_GEN_SET, DOT11H_I, &enable, true); +} + +/* + * Process TLV buffer for a pending BSS join. Enable 11h in firmware when the + * network advertises spectrum management, and add required TLVs based on the + * BSS's 11h capability. + */ +void nxpwifi_11h_process_join(struct nxpwifi_private *priv, u8 **buffer, + struct nxpwifi_bssdescriptor *bss_desc) +{ + if (bss_desc->sensed_11h) { + /* Activate 11h functions in firmware, turns on capability bit */ + nxpwifi_11h_activate(priv, true); + priv->state_11h.is_11h_active = true; + bss_desc->cap_info_bitmap |= WLAN_CAPABILITY_SPECTRUM_MGMT; + nxpwifi_11h_process_infra_join(priv, buffer, bss_desc); + } else { + /* Deactivate 11h functions in the firmware */ + nxpwifi_11h_activate(priv, false); + priv->state_11h.is_11h_active = false; + bss_desc->cap_info_bitmap &= ~WLAN_CAPABILITY_SPECTRUM_MGMT; + } +} + +/* + * DFS CAC work function. This delayed work emits CAC finished event for cfg80211 + * if CAC was started earlier + */ +void nxpwifi_dfs_cac_work(struct wiphy *wiphy, struct wiphy_work *work) +{ + struct cfg80211_chan_def chandef; + struct wiphy_delayed_work *delayed_work = + container_of(work, struct wiphy_delayed_work, work); + struct nxpwifi_private *priv = container_of(delayed_work, + struct nxpwifi_private, + dfs_cac_work); + + chandef = priv->dfs_chandef; + if (priv->wdev.links[0].cac_started) { + nxpwifi_dbg(priv->adapter, MSG, + "CAC timer finished; No radar detected\n"); + cfg80211_cac_event(priv->netdev, &chandef, + NL80211_RADAR_CAC_FINISHED, + GFP_KERNEL, 0); + } +} + +/* prepares channel report request command to FW for starting radar detection */ +int nxpwifi_cmd_issue_chan_report_request(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + void *data_buf) +{ + struct host_cmd_ds_chan_rpt_req *cr_req = &cmd->params.chan_rpt_req; + struct nxpwifi_radar_params *radar_params = (void *)data_buf; + u16 size; + + cmd->command = cpu_to_le16(HOST_CMD_CHAN_REPORT_REQUEST); + size = S_DS_GEN; + + cr_req->chan_desc.start_freq = cpu_to_le16(NXPWIFI_A_BAND_START_FREQ); + nxpwifi_convert_chan_to_band_cfg(priv, + &cr_req->chan_desc.band_cfg, + radar_params->chandef); + cr_req->chan_desc.chan_num = radar_params->chandef->chan->hw_value; + cr_req->msec_dwell_time = cpu_to_le32(radar_params->cac_time_ms); + size += sizeof(*cr_req); + + if (radar_params->cac_time_ms) { + struct nxpwifi_ie_types_chan_rpt_data *rpt; + + rpt = (struct nxpwifi_ie_types_chan_rpt_data *)((u8 *)cmd + size); + rpt->header.type = cpu_to_le16(TLV_TYPE_CHANRPT_11H_BASIC); + rpt->header.len = cpu_to_le16(sizeof(u8)); + rpt->meas_rpt_map = 1 << MEAS_RPT_MAP_RADAR_SHIFT_BIT; + size += sizeof(*rpt); + + nxpwifi_dbg(priv->adapter, MSG, + "11h: issuing DFS Radar check for channel=%d\n", + radar_params->chandef->chan->hw_value); + } else { + nxpwifi_dbg(priv->adapter, MSG, "cancelling CAC\n"); + } + + cmd->size = cpu_to_le16(size); + + return 0; +} + +int nxpwifi_stop_radar_detection(struct nxpwifi_private *priv, + struct cfg80211_chan_def *chandef) +{ + struct nxpwifi_radar_params radar_params; + + memset(&radar_params, 0, sizeof(struct nxpwifi_radar_params)); + radar_params.chandef = chandef; + radar_params.cac_time_ms = 0; + + return nxpwifi_send_cmd(priv, HOST_CMD_CHAN_REPORT_REQUEST, + HOST_ACT_GEN_SET, 0, &radar_params, true); +} + +/* Abort ongoing CAC when stopping AP operations or during unload */ +void nxpwifi_abort_cac(struct nxpwifi_private *priv) +{ + if (priv->wdev.links[0].cac_started) { + if (nxpwifi_stop_radar_detection(priv, &priv->dfs_chandef)) + nxpwifi_dbg(priv->adapter, ERROR, + "failed to stop CAC in FW\n"); + nxpwifi_dbg(priv->adapter, MSG, + "Aborting delayed work for CAC.\n"); + wiphy_delayed_work_cancel(priv->adapter->wiphy, &priv->dfs_cac_work); + cfg80211_cac_event(priv->netdev, &priv->dfs_chandef, + NL80211_RADAR_CAC_ABORTED, GFP_KERNEL, 0); + } +} + +/* + * handles channel report event from FW during CAC period. If radar is detected + * during CAC, driver indicates the same to cfg80211 and also cancels ongoing + * delayed work + */ +int nxpwifi_11h_handle_chanrpt_ready(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct host_cmd_ds_chan_rpt_event *rpt_event; + struct nxpwifi_ie_types_chan_rpt_data *rpt; + u16 event_len, tlv_len; + + rpt_event = (void *)(skb->data + sizeof(u32)); + event_len = skb->len - (sizeof(struct host_cmd_ds_chan_rpt_event) + + sizeof(u32)); + + if (le32_to_cpu(rpt_event->result) != HOST_RESULT_OK) { + nxpwifi_dbg(priv->adapter, ERROR, + "Error in channel report event\n"); + return -EINVAL; + } + + while (event_len >= sizeof(struct nxpwifi_ie_types_header)) { + rpt = (void *)&rpt_event->tlvbuf; + tlv_len = le16_to_cpu(rpt->header.len); + + switch (le16_to_cpu(rpt->header.type)) { + case TLV_TYPE_CHANRPT_11H_BASIC: + if (rpt->meas_rpt_map & MEAS_RPT_MAP_RADAR_MASK) { + nxpwifi_dbg(priv->adapter, MSG, + "RADAR Detected on channel %d!\n", + priv->dfs_chandef.chan->hw_value); + + wiphy_delayed_work_cancel(priv->adapter->wiphy, + &priv->dfs_cac_work); + cfg80211_cac_event(priv->netdev, + &priv->dfs_chandef, + NL80211_RADAR_CAC_ABORTED, + GFP_KERNEL, 0); + cfg80211_radar_event(priv->adapter->wiphy, + &priv->dfs_chandef, + GFP_KERNEL); + } + break; + default: + break; + } + + event_len -= (tlv_len + sizeof(rpt->header)); + } + + return 0; +} + +/* Handler for radar detected event from FW */ +int nxpwifi_11h_handle_radar_detected(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct nxpwifi_radar_det_event *rdr_event; + + rdr_event = (void *)(skb->data + sizeof(u32)); + + nxpwifi_dbg(priv->adapter, MSG, + "radar detected; indicating kernel\n"); + + if (priv->wdev.links[0].cac_started) { + if (nxpwifi_stop_radar_detection(priv, &priv->dfs_chandef)) + nxpwifi_dbg(priv->adapter, ERROR, + "Failed to stop CAC in FW\n"); + wiphy_delayed_work_cancel(priv->adapter->wiphy, &priv->dfs_cac_work); + cfg80211_cac_event(priv->netdev, &priv->dfs_chandef, + NL80211_RADAR_CAC_ABORTED, GFP_KERNEL, 0); + } + cfg80211_radar_event(priv->adapter->wiphy, &priv->dfs_chandef, + GFP_KERNEL); + nxpwifi_dbg(priv->adapter, MSG, "regdomain: %d\n", + rdr_event->reg_domain); + nxpwifi_dbg(priv->adapter, MSG, "radar detection type: %d\n", + rdr_event->det_type); + + return 0; +} + +/* + * work function for channel switch handling. takes care of updating new channel + * definitin to bss config structure, restart AP and indicate channel switch + * success to cfg80211 + */ +void nxpwifi_dfs_chan_sw_work(struct wiphy *wiphy, struct wiphy_work *work) +{ + struct nxpwifi_uap_bss_param *bss_cfg; + struct wiphy_delayed_work *delayed_work = + container_of(work, struct wiphy_delayed_work, work); + struct nxpwifi_private *priv = container_of(delayed_work, + struct nxpwifi_private, + dfs_chan_sw_work); + struct nxpwifi_adapter *adapter = priv->adapter; + + if (nxpwifi_del_mgmt_ies(priv)) + nxpwifi_dbg(priv->adapter, ERROR, + "Failed to delete mgmt IEs!\n"); + + bss_cfg = &priv->bss_cfg; + if (!bss_cfg->beacon_period) { + nxpwifi_dbg(adapter, ERROR, + "channel switch: AP already stopped\n"); + return; + } + + if (nxpwifi_send_cmd(priv, HOST_CMD_UAP_BSS_STOP, + HOST_ACT_GEN_SET, 0, NULL, true)) { + nxpwifi_dbg(adapter, ERROR, + "channel switch: Failed to stop the BSS\n"); + return; + } + + if (nxpwifi_cfg80211_change_beacon(adapter->wiphy, priv->netdev, + &priv->ap_update_info)) { + nxpwifi_dbg(adapter, ERROR, + "channel switch: Failed to set beacon\n"); + return; + } + + nxpwifi_uap_set_channel(priv, bss_cfg, priv->dfs_chandef); + + if (nxpwifi_config_start_uap(priv, bss_cfg)) { + nxpwifi_dbg(adapter, ERROR, + "Failed to start AP after channel switch\n"); + return; + } + + nxpwifi_dbg(adapter, MSG, + "indicating channel switch completion to kernel\n"); + + cfg80211_ch_switch_notify(priv->netdev, &priv->dfs_chandef, 0); + + if (priv->uap_stop_tx) { + netif_carrier_on(priv->netdev); + nxpwifi_wake_up_net_dev_queue(priv->netdev, adapter); + priv->uap_stop_tx = false; + } +} diff --git a/drivers/net/wireless/nxp/nxpwifi/11n.c b/drivers/net/wireless/nxp/nxpwifi/11n.c new file mode 100644 index 000000000000..e46c5053d509 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/11n.c @@ -0,0 +1,837 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi 802.11n helpers + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cmdevt.h" +#include "wmm.h" +#include "11n.h" +#include "11ax.h" + +/* + * Fills HT capability information field, AMPDU Parameters field, HT extended + * capability field, and supported MCS set fields. + * + * HT capability information field, AMPDU Parameters field, supported MCS set + * fields are retrieved from cfg80211 stack + * + * RD responder bit to set to clear in the extended capability header. + */ +int nxpwifi_fill_cap_info(struct nxpwifi_private *priv, u8 radio_type, + struct ieee80211_ht_cap *ht_cap) +{ + u16 ht_cap_info; + u16 bcn_ht_cap = le16_to_cpu(ht_cap->cap_info); + u16 ht_ext_cap = le16_to_cpu(ht_cap->extended_ht_cap_info); + struct ieee80211_supported_band *sband = + priv->wdev.wiphy->bands[radio_type]; + + if (WARN_ON_ONCE(!sband)) { + nxpwifi_dbg(priv->adapter, ERROR, "Invalid radio type!\n"); + return -EINVAL; + } + + ht_cap->ampdu_params_info = + (AMPDU_FACTOR_64K & IEEE80211_HT_AMPDU_PARM_FACTOR) | + ((priv->adapter->hw_mpdu_density << + IEEE80211_HT_AMPDU_PARM_DENSITY_SHIFT) & + IEEE80211_HT_AMPDU_PARM_DENSITY); + + memcpy((u8 *)&ht_cap->mcs, &sband->ht_cap.mcs, + sizeof(sband->ht_cap.mcs)); + + if (priv->bss_mode == NL80211_IFTYPE_STATION || + (sband->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 && + priv->adapter->sec_chan_offset != IEEE80211_HT_PARAM_CHA_SEC_NONE)) + /* Set MCS32 for infra mode or ad-hoc mode with 40MHz support */ + SETHT_MCS32(ht_cap->mcs.rx_mask); + + /* Clear RD responder bit */ + ht_ext_cap &= ~IEEE80211_HT_EXT_CAP_RD_RESPONDER; + + ht_cap_info = sband->ht_cap.cap; + if (bcn_ht_cap) { + if (!(bcn_ht_cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40)) + ht_cap_info &= ~IEEE80211_HT_CAP_SUP_WIDTH_20_40; + if (!(bcn_ht_cap & IEEE80211_HT_CAP_SGI_40)) + ht_cap_info &= ~IEEE80211_HT_CAP_SGI_40; + if (!(bcn_ht_cap & IEEE80211_HT_CAP_40MHZ_INTOLERANT)) + ht_cap_info &= ~IEEE80211_HT_CAP_40MHZ_INTOLERANT; + } + ht_cap->cap_info = cpu_to_le16(ht_cap_info); + ht_cap->extended_ht_cap_info = cpu_to_le16(ht_ext_cap); + + if (ISSUPP_BEAMFORMING(priv->adapter->hw_dot_11n_dev_cap)) + ht_cap->tx_BF_cap_info = cpu_to_le32(NXPWIFI_DEF_11N_TX_BF_CAP); + + return 0; +} + +/* Return BA stream entry that matches the requested status. */ +static struct nxpwifi_tx_ba_stream_tbl * +nxpwifi_get_ba_status(struct nxpwifi_private *priv, int tid, + enum nxpwifi_ba_status ba_status) +{ + struct nxpwifi_tx_ba_stream_tbl *tx_ba_tsr_tbl, *found = NULL; + + guard(rcu)(); + list_for_each_entry_rcu(tx_ba_tsr_tbl, &priv->tx_ba_stream_tbl_ptr[tid], list) { + if (tx_ba_tsr_tbl->ba_status == ba_status) { + found = tx_ba_tsr_tbl; + break; + } + } + return found; +} + +/* Handle DELBA command response (recreate or continue ADDBA as needed). */ +int nxpwifi_ret_11n_delba(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp) +{ + int tid; + struct nxpwifi_tx_ba_stream_tbl *tx_ba_tbl; + struct host_cmd_ds_11n_delba *del_ba = &resp->params.del_ba; + u16 del_ba_param_set = le16_to_cpu(del_ba->del_ba_param_set); + + tid = del_ba_param_set >> DELBA_TID_POS; + if (del_ba->del_result == BA_RESULT_SUCCESS) { + nxpwifi_del_ba_tbl(priv, tid, del_ba->peer_mac_addr, + TYPE_DELBA_SENT, + INITIATOR_BIT(del_ba_param_set)); + + tx_ba_tbl = nxpwifi_get_ba_status(priv, tid, BA_SETUP_INPROGRESS); + if (tx_ba_tbl) + nxpwifi_send_addba(priv, tx_ba_tbl->tid, + tx_ba_tbl->ra); + } else { + /* + * In case of failure, recreate the deleted stream in case + * we initiated the DELBA + */ + if (!INITIATOR_BIT(del_ba_param_set)) + return 0; + + nxpwifi_create_ba_tbl(priv, del_ba->peer_mac_addr, tid, + BA_SETUP_INPROGRESS); + + tx_ba_tbl = nxpwifi_get_ba_status(priv, tid, BA_SETUP_INPROGRESS); + + if (tx_ba_tbl) + nxpwifi_del_ba_tbl(priv, tx_ba_tbl->tid, tx_ba_tbl->ra, + TYPE_DELBA_SENT, true); + } + + return 0; +} + +/* Handle ADDBA response; delete BA stream on failure. */ +int nxpwifi_ret_11n_addba_req(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp) +{ + int tid, tid_down; + struct host_cmd_ds_11n_addba_rsp *add_ba_rsp = &resp->params.add_ba_rsp; + struct nxpwifi_tx_ba_stream_tbl *tx_ba_tbl; + struct nxpwifi_ra_list_tbl *ra_list; + u16 block_ack_param_set = le16_to_cpu(add_ba_rsp->block_ack_param_set); + + add_ba_rsp->ssn = cpu_to_le16((le16_to_cpu(add_ba_rsp->ssn)) + & SSN_MASK); + + tid = u16_get_bits(block_ack_param_set, IEEE80211_ADDBA_PARAM_TID_MASK); + + tid_down = nxpwifi_wmm_downgrade_tid(priv, tid); + ra_list = nxpwifi_wmm_get_ralist_node(priv, tid_down, + add_ba_rsp->peer_mac_addr); + if (le16_to_cpu(add_ba_rsp->status_code) != BA_RESULT_SUCCESS) { + if (ra_list) { + ra_list->ba_status = BA_SETUP_NONE; + ra_list->amsdu_in_ampdu = false; + } + nxpwifi_del_ba_tbl(priv, tid, add_ba_rsp->peer_mac_addr, + TYPE_DELBA_SENT, true); + if (add_ba_rsp->add_rsp_result != BA_RESULT_TIMEOUT) + priv->aggr_prio_tbl[tid].ampdu_ap = + BA_STREAM_NOT_ALLOWED; + return 0; + } + + guard(rcu)(); + tx_ba_tbl = nxpwifi_get_ba_tbl(priv, tid, add_ba_rsp->peer_mac_addr); + if (tx_ba_tbl) { + nxpwifi_dbg(priv->adapter, EVENT, "info: BA stream complete\n"); + tx_ba_tbl->ba_status = BA_SETUP_COMPLETE; + if ((block_ack_param_set & IEEE80211_ADDBA_PARAM_AMSDU_MASK) && + priv->add_ba_param.tx_amsdu && + priv->aggr_prio_tbl[tid].amsdu != BA_STREAM_NOT_ALLOWED) + tx_ba_tbl->amsdu = true; + else + tx_ba_tbl->amsdu = false; + if (ra_list) { + ra_list->amsdu_in_ampdu = tx_ba_tbl->amsdu; + ra_list->ba_status = BA_SETUP_COMPLETE; + } + } else { + nxpwifi_dbg(priv->adapter, ERROR, "BA stream not created\n"); + } + + return 0; +} + +/* + * Reconfigure Tx buffer command. + * Set command ID/action/size; set Tx buffer size on SET; ensure little-endian. + */ +int nxpwifi_cmd_recfg_tx_buf(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, int cmd_action, + u16 *buf_size) +{ + struct host_cmd_ds_txbuf_cfg *tx_buf = &cmd->params.tx_buf; + u16 action = (u16)cmd_action; + + cmd->command = cpu_to_le16(HOST_CMD_RECONFIGURE_TX_BUFF); + cmd->size = + cpu_to_le16(sizeof(struct host_cmd_ds_txbuf_cfg) + S_DS_GEN); + tx_buf->action = cpu_to_le16(action); + switch (action) { + case HOST_ACT_GEN_SET: + nxpwifi_dbg(priv->adapter, CMD, + "cmd: set tx_buf=%d\n", *buf_size); + tx_buf->buff_size = cpu_to_le16(*buf_size); + break; + case HOST_ACT_GEN_GET: + default: + tx_buf->buff_size = 0; + break; + } + return 0; +} + +/* + * AMSDU aggregation control command. + * Set ID/action/size; set AMSDU params on SET; ensure little-endian. + */ +int nxpwifi_cmd_amsdu_aggr_ctrl(struct host_cmd_ds_command *cmd, + int cmd_action, + struct nxpwifi_ds_11n_amsdu_aggr_ctrl *aa_ctrl) +{ + struct host_cmd_ds_amsdu_aggr_ctrl *amsdu_ctrl = + &cmd->params.amsdu_aggr_ctrl; + u16 action = (u16)cmd_action; + + cmd->command = cpu_to_le16(HOST_CMD_AMSDU_AGGR_CTRL); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_amsdu_aggr_ctrl) + + S_DS_GEN); + amsdu_ctrl->action = cpu_to_le16(action); + switch (action) { + case HOST_ACT_GEN_SET: + amsdu_ctrl->enable = cpu_to_le16(aa_ctrl->enable); + amsdu_ctrl->curr_buf_size = 0; + break; + case HOST_ACT_GEN_GET: + default: + amsdu_ctrl->curr_buf_size = 0; + break; + } + return 0; +} + +/* + * 11n configuration command. + * Set action, HT Tx capability/info, and misc config when 11ac HW is present. + */ +int nxpwifi_cmd_11n_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action, + struct nxpwifi_ds_11n_tx_cfg *txcfg) +{ + struct host_cmd_ds_11n_cfg *htcfg = &cmd->params.htcfg; + + cmd->command = cpu_to_le16(HOST_CMD_11N_CFG); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_11n_cfg) + S_DS_GEN); + htcfg->action = cpu_to_le16(cmd_action); + htcfg->ht_tx_cap = cpu_to_le16(txcfg->tx_htcap); + htcfg->ht_tx_info = cpu_to_le16(txcfg->tx_htinfo); + + if (priv->adapter->is_hw_11ac_capable) + htcfg->misc_config = cpu_to_le16(txcfg->misc_config); + + return 0; +} + +/* + * Append 11n TLVs to the caller-owned buffer. + * Caller allocates space; no size checks here. + * May add: HT Cap, HT Operation + channel list, 20/40 BSS Coexistence, + * and Extended Capabilities (HS2/TWT bits when applicable). + */ +int +nxpwifi_cmd_append_11n_tlv(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc, + u8 **buffer) +{ + struct nxpwifi_ie_types_htcap *ht_cap; + struct nxpwifi_ie_types_chan_list_param_set *chan_list; + struct nxpwifi_chan_scan_param_set *chan_param; + struct nxpwifi_ie_types_2040bssco *bss_co_2040; + struct nxpwifi_ie_types_extcap *ext_cap; + int ret_len = 0; + struct ieee80211_supported_band *sband; + struct element *hdr; + u8 radio_type; + + if (!buffer || !*buffer) + return ret_len; + + radio_type = nxpwifi_band_to_radio_type((u8)bss_desc->bss_band); + sband = priv->wdev.wiphy->bands[radio_type]; + + if (bss_desc->bcn_ht_cap) { + ht_cap = (struct nxpwifi_ie_types_htcap *)*buffer; + memset(ht_cap, 0, sizeof(struct nxpwifi_ie_types_htcap)); + ht_cap->header.type = cpu_to_le16(WLAN_EID_HT_CAPABILITY); + ht_cap->header.len = + cpu_to_le16(sizeof(struct ieee80211_ht_cap)); + memcpy((u8 *)ht_cap + sizeof(struct nxpwifi_ie_types_header), + (u8 *)bss_desc->bcn_ht_cap, + le16_to_cpu(ht_cap->header.len)); + + nxpwifi_fill_cap_info(priv, radio_type, &ht_cap->ht_cap); + /* Update HT40 capability from current channel. */ + if (bss_desc->bcn_ht_oper) { + u8 ht_param = bss_desc->bcn_ht_oper->ht_param; + u8 radio = + nxpwifi_band_to_radio_type(bss_desc->bss_band); + int freq = + ieee80211_channel_to_frequency(bss_desc->channel, + radio); + struct ieee80211_channel *chan = + ieee80211_get_channel(priv->adapter->wiphy, freq); + + switch (ht_param & IEEE80211_HT_PARAM_CHA_SEC_OFFSET) { + case IEEE80211_HT_PARAM_CHA_SEC_ABOVE: + if (chan->flags & IEEE80211_CHAN_NO_HT40PLUS) { + ht_cap->ht_cap.cap_info &= + cpu_to_le16 + (~IEEE80211_HT_CAP_SUP_WIDTH_20_40); + ht_cap->ht_cap.cap_info &= + cpu_to_le16(~IEEE80211_HT_CAP_SGI_40); + } + break; + case IEEE80211_HT_PARAM_CHA_SEC_BELOW: + if (chan->flags & IEEE80211_CHAN_NO_HT40MINUS) { + ht_cap->ht_cap.cap_info &= + cpu_to_le16 + (~IEEE80211_HT_CAP_SUP_WIDTH_20_40); + ht_cap->ht_cap.cap_info &= + cpu_to_le16(~IEEE80211_HT_CAP_SGI_40); + } + break; + } + } + + *buffer += sizeof(struct nxpwifi_ie_types_htcap); + ret_len += sizeof(struct nxpwifi_ie_types_htcap); + } + + if (bss_desc->bcn_ht_oper) { + chan_list = + (struct nxpwifi_ie_types_chan_list_param_set *)*buffer; + chan_param = chan_list->chan_scan_param; + memset(chan_list, 0, struct_size(chan_list, chan_scan_param, 1)); + chan_list->header.type = cpu_to_le16(TLV_TYPE_CHANLIST); + chan_list->header.len = cpu_to_le16(sizeof(*chan_param)); + chan_param->chan_number = bss_desc->bcn_ht_oper->primary_chan; + chan_param->band_cfg = + nxpwifi_band_to_radio_type((u8)bss_desc->bss_band); + + if (ISSUPP_11ACENABLED(priv->adapter->fw_cap_info) && + bss_desc->bcn_vht_oper && + bss_desc->bcn_vht_oper->chan_width == + IEEE80211_VHT_CHANWIDTH_80MHZ) { + SET_SECONDARYCHAN(chan_param->band_cfg, + (bss_desc->bcn_ht_oper->ht_param & + IEEE80211_HT_PARAM_CHA_SEC_OFFSET)); + chan_param->band_cfg |= + ((CHAN_BW_80MHZ << + BAND_CFG_CHAN_WIDTH_SHIFT_BIT) & + BAND_CFG_CHAN_WIDTH_MASK); + } else if (sband->ht_cap.cap & + IEEE80211_HT_CAP_SUP_WIDTH_20_40 && + bss_desc->bcn_ht_oper->ht_param & + IEEE80211_HT_PARAM_CHAN_WIDTH_ANY) { + SET_SECONDARYCHAN(chan_param->band_cfg, + (bss_desc->bcn_ht_oper->ht_param & + IEEE80211_HT_PARAM_CHA_SEC_OFFSET)); + chan_param->band_cfg |= + ((CHAN_BW_40MHZ << + BAND_CFG_CHAN_WIDTH_SHIFT_BIT) & + BAND_CFG_CHAN_WIDTH_MASK); + } + + *buffer += struct_size(chan_list, chan_scan_param, 1); + ret_len += struct_size(chan_list, chan_scan_param, 1); + } + + if (bss_desc->bcn_bss_co_2040) { + bss_co_2040 = (struct nxpwifi_ie_types_2040bssco *)*buffer; + memset(bss_co_2040, 0, + sizeof(struct nxpwifi_ie_types_2040bssco)); + bss_co_2040->header.type = cpu_to_le16(WLAN_EID_BSS_COEX_2040); + bss_co_2040->header.len = + cpu_to_le16(sizeof(bss_co_2040->bss_co_2040)); + + memcpy((u8 *)bss_co_2040 + + sizeof(struct nxpwifi_ie_types_header), + bss_desc->bcn_bss_co_2040 + + sizeof(struct element), + le16_to_cpu(bss_co_2040->header.len)); + + *buffer += sizeof(struct nxpwifi_ie_types_2040bssco); + ret_len += sizeof(struct nxpwifi_ie_types_2040bssco); + } + + if (bss_desc->bcn_ext_cap) { + u8 *ext_capab; + + hdr = (void *)bss_desc->bcn_ext_cap; + + ext_capab = (u8 *)cfg80211_find_ie(WLAN_EID_EXT_CAPABILITY, priv->gen_ie_buf, + priv->gen_ie_buf_len); + if (ext_capab) { + ext_capab += 2; + } else { + ext_cap = (struct nxpwifi_ie_types_extcap *)*buffer; + memset(ext_cap, 0, sizeof(struct nxpwifi_ie_types_extcap) + hdr->datalen); + ext_cap->header.type = cpu_to_le16(WLAN_EID_EXT_CAPABILITY); + ext_cap->header.len = cpu_to_le16(hdr->datalen); + ext_capab = ext_cap->ext_capab; + *buffer += sizeof(struct nxpwifi_ie_types_extcap) + hdr->datalen; + ret_len += sizeof(struct nxpwifi_ie_types_extcap) + hdr->datalen; + } + + if (hdr->datalen > 3 && + ext_capab[3] & WLAN_EXT_CAPA4_INTERWORKING_ENABLED) + priv->hs2_enabled = true; + else + priv->hs2_enabled = false; + + if (nxpwifi_is_11ax_twt_supported(priv, bss_desc)) + ext_capab[9] |= + WLAN_EXT_CAPA10_TWT_REQUESTER_SUPPORT; + } + return ret_len; +} + +/* Check if pointer is a valid Tx BA stream entry. */ +static bool +nxpwifi_is_tx_ba_stream_ptr_valid(struct nxpwifi_private *priv, + struct nxpwifi_tx_ba_stream_tbl *tx_tbl_ptr) +{ + struct nxpwifi_tx_ba_stream_tbl *tx_ba_tsr_tbl; + bool ret = false; + int tid; + + tid = tx_tbl_ptr->tid; + guard(rcu)(); + list_for_each_entry_rcu(tx_ba_tsr_tbl, &priv->tx_ba_stream_tbl_ptr[tid], list) { + if (tx_ba_tsr_tbl == tx_tbl_ptr) { + ret = true; + break; + } + } + return ret; +} + +/* Delete a Tx BA stream entry (after validating pointer). */ +void +nxpwifi_11n_delete_tx_ba_stream_tbl_entry(struct nxpwifi_private *priv, + struct nxpwifi_tx_ba_stream_tbl *tbl) +{ + if (!tbl && nxpwifi_is_tx_ba_stream_ptr_valid(priv, tbl)) + return; + + nxpwifi_dbg(priv->adapter, INFO, + "info: tx_ba_tsr_tbl %p\n", tbl); + + list_del_rcu(&tbl->list); + kfree_rcu(tbl, rcu); +} + +/* Delete all entries in Tx BA stream table. */ +void nxpwifi_11n_delete_all_tx_ba_stream_tbl(struct nxpwifi_private *priv) +{ + int i; + struct nxpwifi_tx_ba_stream_tbl *del_tbl_ptr, *tmp_node; + + for (i = 0; i < MAX_NUM_TID; i++) { + spin_lock_bh(&priv->tx_ba_stream_tbl_lock[i]); + list_for_each_entry_safe(del_tbl_ptr, tmp_node, + &priv->tx_ba_stream_tbl_ptr[i], list) + nxpwifi_11n_delete_tx_ba_stream_tbl_entry(priv, del_tbl_ptr); + spin_unlock_bh(&priv->tx_ba_stream_tbl_lock[i]); + + INIT_LIST_HEAD(&priv->tx_ba_stream_tbl_ptr[i]); + + priv->aggr_prio_tbl[i].ampdu_ap = + priv->aggr_prio_tbl[i].ampdu_user; + } +} + +/* Return BA stream entry for given RA/TID. */ +struct nxpwifi_tx_ba_stream_tbl * +nxpwifi_get_ba_tbl(struct nxpwifi_private *priv, int tid, u8 *ra) +{ + struct nxpwifi_tx_ba_stream_tbl *tx_ba_tsr_tbl = NULL; + + list_for_each_entry_rcu(tx_ba_tsr_tbl, &priv->tx_ba_stream_tbl_ptr[tid], list) { + if (ether_addr_equal_unaligned(tx_ba_tsr_tbl->ra, ra) && + tx_ba_tsr_tbl->tid == tid) + return tx_ba_tsr_tbl; + } + return NULL; +} + +/* Create Tx BA stream entry for given RA/TID. */ +void nxpwifi_create_ba_tbl(struct nxpwifi_private *priv, u8 *ra, int tid, + enum nxpwifi_ba_status ba_status) +{ + struct nxpwifi_tx_ba_stream_tbl *new_node; + struct nxpwifi_ra_list_tbl *ra_list; + int tid_down; + struct nxpwifi_tx_ba_stream_tbl *tx_ba_tbl; + + guard(rcu)(); + tx_ba_tbl = nxpwifi_get_ba_tbl(priv, tid, ra); + + if (!tx_ba_tbl) { + new_node = kzalloc_obj(*new_node, GFP_ATOMIC); + if (!new_node) + return; + + tid_down = nxpwifi_wmm_downgrade_tid(priv, tid); + ra_list = nxpwifi_wmm_get_ralist_node(priv, tid_down, ra); + if (ra_list) { + ra_list->ba_status = ba_status; + ra_list->amsdu_in_ampdu = false; + } + INIT_LIST_HEAD(&new_node->list); + + new_node->tid = tid; + new_node->ba_status = ba_status; + memcpy(new_node->ra, ra, ETH_ALEN); + + spin_lock_bh(&priv->tx_ba_stream_tbl_lock[tid]); + list_add_tail_rcu(&new_node->list, &priv->tx_ba_stream_tbl_ptr[tid]); + spin_unlock_bh(&priv->tx_ba_stream_tbl_lock[tid]); + } +} + +/* Send ADDBA request to the given TID/RA. */ +int nxpwifi_send_addba(struct nxpwifi_private *priv, int tid, u8 *peer_mac) +{ + struct host_cmd_ds_11n_addba_req add_ba_req; + u32 tx_win_size = priv->add_ba_param.tx_win_size; + static u8 dialog_tok; + u16 block_ack_param_set; + + nxpwifi_dbg(priv->adapter, CMD, "cmd: %s: tid %d\n", __func__, tid); + + memset(&add_ba_req, 0, sizeof(add_ba_req)); + + block_ack_param_set = (u16)((tid << BLOCKACKPARAM_TID_POS) | + tx_win_size << BLOCKACKPARAM_WINSIZE_POS | + IMMEDIATE_BLOCK_ACK); + + /* enable AMSDU inside AMPDU */ + if (priv->add_ba_param.tx_amsdu && + priv->aggr_prio_tbl[tid].amsdu != BA_STREAM_NOT_ALLOWED) + block_ack_param_set |= IEEE80211_ADDBA_PARAM_AMSDU_MASK; + + add_ba_req.block_ack_param_set = cpu_to_le16(block_ack_param_set); + add_ba_req.block_ack_tmo = cpu_to_le16((u16)priv->add_ba_param.timeout); + + ++dialog_tok; + + if (dialog_tok == 0) + dialog_tok = 1; + + add_ba_req.dialog_token = dialog_tok; + memcpy(&add_ba_req.peer_mac_addr, peer_mac, ETH_ALEN); + + /* We don't wait for the response of this command */ + return nxpwifi_send_cmd(priv, HOST_CMD_11N_ADDBA_REQ, + 0, 0, &add_ba_req, false); +} + +/* Send DELBA request to the given TID/RA. */ +int nxpwifi_send_delba(struct nxpwifi_private *priv, int tid, u8 *peer_mac, + int initiator) +{ + struct host_cmd_ds_11n_delba delba; + u16 del_ba_param_set; + + memset(&delba, 0, sizeof(delba)); + + del_ba_param_set = tid << DELBA_TID_POS; + + if (initiator) + del_ba_param_set |= IEEE80211_DELBA_PARAM_INITIATOR_MASK; + else + del_ba_param_set &= ~IEEE80211_DELBA_PARAM_INITIATOR_MASK; + + delba.del_ba_param_set = cpu_to_le16(del_ba_param_set); + memcpy(&delba.peer_mac_addr, peer_mac, ETH_ALEN); + + /* We don't wait for the response of this command */ + return nxpwifi_send_cmd(priv, HOST_CMD_11N_DELBA, + HOST_ACT_GEN_SET, 0, &delba, false); +} + +/* Send DELBA to specific TID. */ +void nxpwifi_11n_delba(struct nxpwifi_private *priv, int tid) +{ + struct nxpwifi_rx_reorder_tbl *rx_reor_tbl_ptr; + u8 ta[ETH_ALEN]; + bool found = false; + + rcu_read_lock(); + list_for_each_entry_rcu(rx_reor_tbl_ptr, &priv->rx_reorder_tbl_ptr[tid], list) { + if (rx_reor_tbl_ptr->tid == tid) { + memcpy(ta, rx_reor_tbl_ptr->ta, ETH_ALEN); + found = true; + break; + } + } + rcu_read_unlock(); + + if (found) { + nxpwifi_dbg(priv->adapter, INFO, + "Send delba to tid=%d, %pM\n", tid, ta); + nxpwifi_send_delba(priv, tid, ta, 0); + } +} + +/* Handle DELBA event; remove BA stream. */ +void nxpwifi_11n_delete_ba_stream(struct nxpwifi_private *priv, u8 *del_ba) +{ + struct host_cmd_ds_11n_delba *cmd_del_ba = + (struct host_cmd_ds_11n_delba *)del_ba; + u16 del_ba_param_set = le16_to_cpu(cmd_del_ba->del_ba_param_set); + int tid; + + tid = del_ba_param_set >> DELBA_TID_POS; + + nxpwifi_del_ba_tbl(priv, tid, cmd_del_ba->peer_mac_addr, + TYPE_DELBA_RECEIVE, INITIATOR_BIT(del_ba_param_set)); +} + +/* Retrieve Rx reordering table. */ +int nxpwifi_get_rx_reorder_tbl(struct nxpwifi_private *priv, + struct nxpwifi_ds_rx_reorder_tbl *buf) +{ + int i, j; + struct nxpwifi_ds_rx_reorder_tbl *rx_reo_tbl = buf; + struct nxpwifi_rx_reorder_tbl *rx_reorder_tbl_ptr; + int count = 0; + + guard(rcu)(); + for (j = 0; j < MAX_NUM_TID; j++) { + list_for_each_entry_rcu(rx_reorder_tbl_ptr, + &priv->rx_reorder_tbl_ptr[j], + list) { + rx_reo_tbl->tid = (u16)rx_reorder_tbl_ptr->tid; + memcpy(rx_reo_tbl->ta, rx_reorder_tbl_ptr->ta, ETH_ALEN); + rx_reo_tbl->start_win = rx_reorder_tbl_ptr->start_win; + rx_reo_tbl->win_size = rx_reorder_tbl_ptr->win_size; + for (i = 0; i < rx_reorder_tbl_ptr->win_size; ++i) { + if (rx_reorder_tbl_ptr->rx_reorder_ptr[i]) + rx_reo_tbl->buffer[i] = true; + else + rx_reo_tbl->buffer[i] = false; + } + rx_reo_tbl++; + count++; + + if (count >= NXPWIFI_MAX_RX_BASTREAM_SUPPORTED) + return count; + } + } + + return count; +} + +/* Retrieve Tx BA stream table. */ +int nxpwifi_get_tx_ba_stream_tbl(struct nxpwifi_private *priv, + struct nxpwifi_ds_tx_ba_stream_tbl *buf) +{ + struct nxpwifi_tx_ba_stream_tbl *tx_ba_tsr_tbl; + struct nxpwifi_ds_tx_ba_stream_tbl *rx_reo_tbl = buf; + int count = 0; + int i; + + guard(rcu)(); + for (i = 0; i < MAX_NUM_TID; i++) { + list_for_each_entry_rcu(tx_ba_tsr_tbl, &priv->tx_ba_stream_tbl_ptr[i], list) { + rx_reo_tbl->tid = (u16)tx_ba_tsr_tbl->tid; + nxpwifi_dbg(priv->adapter, DATA, "data: %s tid=%d\n", + __func__, rx_reo_tbl->tid); + memcpy(rx_reo_tbl->ra, tx_ba_tsr_tbl->ra, ETH_ALEN); + rx_reo_tbl->amsdu = tx_ba_tsr_tbl->amsdu; + rx_reo_tbl++; + count++; + if (count >= NXPWIFI_MAX_TX_BASTREAM_SUPPORTED) + return count; + } + } + + return count; +} + +/* Delete Tx BA stream entry by RA. */ +void nxpwifi_del_tx_ba_stream_tbl_by_ra(struct nxpwifi_private *priv, u8 *ra) +{ + struct nxpwifi_tx_ba_stream_tbl *tbl; + int i; + + if (!ra) + return; + + for (i = 0; i < MAX_NUM_TID; i++) { + spin_lock_bh(&priv->tx_ba_stream_tbl_lock[i]); + list_for_each_entry_rcu(tbl, &priv->tx_ba_stream_tbl_ptr[i], list) + if (!memcmp(tbl->ra, ra, ETH_ALEN)) + nxpwifi_11n_delete_tx_ba_stream_tbl_entry(priv, tbl); + + spin_unlock_bh(&priv->tx_ba_stream_tbl_lock[i]); + } +} + +/* Initialize BlockAck parameters. */ +void nxpwifi_set_ba_params(struct nxpwifi_private *priv) +{ + priv->add_ba_param.timeout = NXPWIFI_DEFAULT_BLOCK_ACK_TIMEOUT; + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + priv->add_ba_param.tx_win_size = + NXPWIFI_UAP_AMPDU_DEF_TXWINSIZE; + priv->add_ba_param.rx_win_size = + NXPWIFI_UAP_AMPDU_DEF_RXWINSIZE; + } else { + priv->add_ba_param.tx_win_size = + NXPWIFI_STA_AMPDU_DEF_TXWINSIZE; + priv->add_ba_param.rx_win_size = + NXPWIFI_STA_AMPDU_DEF_RXWINSIZE; + } + + priv->add_ba_param.tx_amsdu = true; + priv->add_ba_param.rx_amsdu = true; +} + +u8 nxpwifi_get_sec_chan_offset(int chan) +{ + u8 sec_offset; + + switch (chan) { + case 36: + case 44: + case 52: + case 60: + case 100: + case 108: + case 116: + case 124: + case 132: + case 140: + case 149: + case 157: + case 173: + sec_offset = IEEE80211_HT_PARAM_CHA_SEC_ABOVE; + break; + case 40: + case 48: + case 56: + case 64: + case 104: + case 112: + case 120: + case 128: + case 136: + case 144: + case 153: + case 161: + case 169: + case 177: + sec_offset = IEEE80211_HT_PARAM_CHA_SEC_BELOW; + break; + case 165: + default: + sec_offset = IEEE80211_HT_PARAM_CHA_SEC_NONE; + break; + } + + return sec_offset; +} + +/* Send DELBA to entries in the Tx BA stream table. */ +static void +nxpwifi_send_delba_txbastream_tbl(struct nxpwifi_private *priv, u8 tid) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_tx_ba_stream_tbl *tx_ba_stream_tbl_ptr; + + guard(rcu)(); + list_for_each_entry_rcu(tx_ba_stream_tbl_ptr, + &priv->tx_ba_stream_tbl_ptr[tid], list) { + if (tx_ba_stream_tbl_ptr->ba_status == BA_SETUP_COMPLETE) { + if (tid == tx_ba_stream_tbl_ptr->tid) { + nxpwifi_dbg(adapter, INFO, + "Tx:Send delba to tid=%d, %pM\n", tid, + tx_ba_stream_tbl_ptr->ra); + nxpwifi_send_delba(priv, + tx_ba_stream_tbl_ptr->tid, + tx_ba_stream_tbl_ptr->ra, 1); + break; + } + } + } +} + +/* + * Update tx_win_size for all interfaces and send DELBA when it changes. + */ +void nxpwifi_update_ampdu_txwinsize(struct nxpwifi_adapter *adapter) +{ + u8 i, j; + u32 tx_win_size; + struct nxpwifi_private *priv; + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + tx_win_size = priv->add_ba_param.tx_win_size; + + if (priv->bss_type == NXPWIFI_BSS_TYPE_STA) + priv->add_ba_param.tx_win_size = + NXPWIFI_STA_AMPDU_DEF_TXWINSIZE; + + if (priv->bss_type == NXPWIFI_BSS_TYPE_UAP) + priv->add_ba_param.tx_win_size = + NXPWIFI_UAP_AMPDU_DEF_TXWINSIZE; + + if (adapter->coex_win_size) { + if (adapter->coex_tx_win_size) + priv->add_ba_param.tx_win_size = + adapter->coex_tx_win_size; + } + + if (tx_win_size != priv->add_ba_param.tx_win_size) { + if (!priv->media_connected) + continue; + for (j = 0; j < MAX_NUM_TID; j++) + nxpwifi_send_delba_txbastream_tbl(priv, j); + } + } +} diff --git a/drivers/net/wireless/nxp/nxpwifi/11n.h b/drivers/net/wireless/nxp/nxpwifi/11n.h new file mode 100644 index 000000000000..039c45993f07 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/11n.h @@ -0,0 +1,158 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * nxpwifi: 802.11n support + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_11N_H_ +#define _NXPWIFI_11N_H_ + +#include "11n_aggr.h" +#include "11n_rxreorder.h" +#include "wmm.h" + +int nxpwifi_ret_11n_delba(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp); +int nxpwifi_ret_11n_addba_req(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp); +int nxpwifi_cmd_11n_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action, + struct nxpwifi_ds_11n_tx_cfg *txcfg); +int nxpwifi_cmd_append_11n_tlv(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc, + u8 **buffer); +int nxpwifi_fill_cap_info(struct nxpwifi_private *priv, u8 radio_type, + struct ieee80211_ht_cap *ht_cap); +int nxpwifi_set_get_11n_htcap_cfg(struct nxpwifi_private *priv, + u16 action, int *htcap_cfg); +void nxpwifi_11n_delete_tx_ba_stream_tbl_entry(struct nxpwifi_private *priv, + struct nxpwifi_tx_ba_stream_tbl + *tx_tbl); +void nxpwifi_11n_delete_all_tx_ba_stream_tbl(struct nxpwifi_private *priv); +struct nxpwifi_tx_ba_stream_tbl *nxpwifi_get_ba_tbl(struct nxpwifi_private + *priv, int tid, u8 *ra); +void nxpwifi_create_ba_tbl(struct nxpwifi_private *priv, u8 *ra, int tid, + enum nxpwifi_ba_status ba_status); +int nxpwifi_send_addba(struct nxpwifi_private *priv, int tid, u8 *peer_mac); +int nxpwifi_send_delba(struct nxpwifi_private *priv, int tid, u8 *peer_mac, + int initiator); +void nxpwifi_11n_delete_ba_stream(struct nxpwifi_private *priv, u8 *del_ba); +int nxpwifi_get_rx_reorder_tbl(struct nxpwifi_private *priv, + struct nxpwifi_ds_rx_reorder_tbl *buf); +int nxpwifi_get_tx_ba_stream_tbl(struct nxpwifi_private *priv, + struct nxpwifi_ds_tx_ba_stream_tbl *buf); +int nxpwifi_cmd_recfg_tx_buf(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + int cmd_action, u16 *buf_size); +int nxpwifi_cmd_amsdu_aggr_ctrl(struct host_cmd_ds_command *cmd, + int cmd_action, + struct nxpwifi_ds_11n_amsdu_aggr_ctrl *aa_ctrl); +void nxpwifi_del_tx_ba_stream_tbl_by_ra(struct nxpwifi_private *priv, u8 *ra); +u8 nxpwifi_get_sec_chan_offset(int chan); + +static inline bool +nxpwifi_is_station_ampdu_allowed(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ptr, int tid) +{ + struct nxpwifi_sta_node *node; + + guard(rcu)(); + node = nxpwifi_get_sta_entry(priv, ptr->ra); + if (unlikely(!node)) + return false; + + if (node->ampdu_sta[tid] == BA_STREAM_NOT_ALLOWED) + return false; + + return true; +} + +/* Check if AMPDU is allowed for the given TID. */ +static inline bool +nxpwifi_is_ampdu_allowed(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ptr, int tid) +{ + if (is_broadcast_ether_addr(ptr->ra)) + return false; + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) + return nxpwifi_is_station_ampdu_allowed(priv, ptr, tid); + + return priv->aggr_prio_tbl[tid].ampdu_ap != BA_STREAM_NOT_ALLOWED; +} + +/* Check if AMSDU is allowed for the given TID. */ +static inline bool +nxpwifi_is_amsdu_allowed(struct nxpwifi_private *priv, int tid) +{ + bool amsdu_enabled = priv->aggr_prio_tbl[tid].amsdu != BA_STREAM_NOT_ALLOWED; + bool rate_ok = priv->is_data_rate_auto || !(priv->bitmap_rates[2] & 0x03); + + return amsdu_enabled && rate_ok; +} + +/* Check if there is available space for a new BA stream. */ +static inline bool +nxpwifi_space_avail_for_new_ba_stream(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_private *priv; + u8 i, j; + size_t ba_stream_num = 0; + size_t ba_stream_max = NXPWIFI_MAX_TX_BASTREAM_SUPPORTED; + + if (adapter->fw_api_ver == NXPWIFI_FW_V15) { + ba_stream_max = GETSUPP_TXBASTREAMS(adapter->hw_dot_11n_dev_cap); + if (!ba_stream_max) + ba_stream_max = NXPWIFI_MAX_TX_BASTREAM_SUPPORTED; + } + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + for (j = 0; j < MAX_NUM_TID; j++) + ba_stream_num += list_count_nodes(&priv->tx_ba_stream_tbl_ptr[j]); + } + + return ba_stream_num < ba_stream_max; +} + +/* Find the Tx BA stream to delete and return its TID and RA. */ +static inline bool +nxpwifi_find_stream_to_delete(struct nxpwifi_private *priv, int ptr_tid, + int *ptid, u8 *ra) +{ + int search_tid = priv->aggr_prio_tbl[ptr_tid].ampdu_user; + bool found = false; + struct nxpwifi_tx_ba_stream_tbl *tx_tbl; + int candidate_tid; + + spin_lock_bh(&priv->tx_ba_stream_tbl_lock[ptr_tid]); + + list_for_each_entry(tx_tbl, &priv->tx_ba_stream_tbl_ptr[ptr_tid], list) { + candidate_tid = priv->aggr_prio_tbl[tx_tbl->tid].ampdu_user; + + if (search_tid > candidate_tid) { + search_tid = candidate_tid; + *ptid = tx_tbl->tid; + memcpy(ra, tx_tbl->ra, ETH_ALEN); + found = true; + } + } + + spin_unlock_bh(&priv->tx_ba_stream_tbl_lock[ptr_tid]); + + return found; +} + +/* Check whether the associated station is 11n enabled. */ +static inline int nxpwifi_is_sta_11n_enabled(struct nxpwifi_private *priv, + struct nxpwifi_sta_node *node) +{ + if (!node || (priv->bss_role == NXPWIFI_BSS_ROLE_UAP && + !priv->ap_11n_enabled)) + return 0; + + return node->is_11n_enabled; +} + +#endif /* !_NXPWIFI_11N_H_ */ diff --git a/drivers/net/wireless/nxp/nxpwifi/11n_aggr.c b/drivers/net/wireless/nxp/nxpwifi/11n_aggr.c new file mode 100644 index 000000000000..be7080f2a6ce --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/11n_aggr.c @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: 802.11n Aggregation + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "wmm.h" +#include "11n.h" +#include "11n_aggr.h" + +/* + * Build an AMSDU subframe for aggregation, with fields + * (DA | SA | Length | SNAP header | MSDU), and compute padding + * to align the subframe to a 4-byte boundary. + */ +static int nxpwifi_11n_form_amsdu_pkt(struct sk_buff *skb_aggr, + struct sk_buff *skb_src, int *pad) + +{ + int dt_offset; + struct rfc_1042_hdr snap = { + 0xaa, /* LLC DSAP */ + 0xaa, /* LLC SSAP */ + 0x03, /* LLC CTRL */ + {0x00, 0x00, 0x00}, /* SNAP OUI */ + 0x0000 /* SNAP type */ + /* This field will be overwritten later with ethertype */ + }; + struct tx_packet_hdr *tx_header; + + tx_header = skb_put(skb_aggr, sizeof(*tx_header)); + + /* Copy DA and SA */ + dt_offset = 2 * ETH_ALEN; + memcpy(&tx_header->eth803_hdr, skb_src->data, dt_offset); + + /* Copy SNAP header */ + snap.snap_type = ((struct ethhdr *)skb_src->data)->h_proto; + + dt_offset += sizeof(__be16); + + memcpy(&tx_header->rfc1042_hdr, &snap, sizeof(struct rfc_1042_hdr)); + + skb_pull(skb_src, dt_offset); + + /* Update Length field */ + tx_header->eth803_hdr.h_proto = htons(skb_src->len + LLC_SNAP_LEN); + + /* Add payload */ + skb_put_data(skb_aggr, skb_src->data, skb_src->len); + + /* Add padding for new MSDU to start from 4 byte boundary */ + *pad = (4 - ((unsigned long)skb_aggr->tail & 0x3)) % 4; + + return skb_aggr->len + *pad; +} + +/* + * Adds TxPD to AMSDU header. Each AMSDU packet will contain one TxPD at the + * beginning, followed by multiple AMSDU subframes + */ +static void +nxpwifi_11n_form_amsdu_txpd(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct txpd *local_tx_pd; + + skb_push(skb, sizeof(*local_tx_pd)); + + local_tx_pd = (struct txpd *)skb->data; + memset(local_tx_pd, 0, sizeof(struct txpd)); + + /* Original priority has been overwritten */ + local_tx_pd->priority = (u8)skb->priority; + local_tx_pd->pkt_delay_2ms = + nxpwifi_wmm_compute_drv_pkt_delay(priv, skb); + local_tx_pd->bss_num = priv->bss_num; + local_tx_pd->bss_type = priv->bss_type; + /* Always zero as the data is followed by struct txpd */ + local_tx_pd->tx_pkt_offset = cpu_to_le16(sizeof(struct txpd)); + local_tx_pd->tx_pkt_type = cpu_to_le16(PKT_TYPE_AMSDU); + local_tx_pd->tx_pkt_length = cpu_to_le16(skb->len - + sizeof(*local_tx_pd)); + + if (local_tx_pd->tx_control == 0) + /* TxCtrl set by user or default */ + local_tx_pd->tx_control = cpu_to_le32(priv->pkt_tx_ctrl); + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA && + priv->adapter->pps_uapsd_mode) { + if (nxpwifi_check_last_packet_indication(priv)) { + priv->adapter->tx_lock_flag = true; + local_tx_pd->flags = + NXPWIFI_TxPD_POWER_MGMT_LAST_PACKET; + } + } +} + +/* + * Build an aggregated MSDU packet by encapsulating buffers from the RA + * list as AMSDU subframes and concatenating them. A TxPD is prepended + * before transmission to form the final AMSDU packet. + */ +int +nxpwifi_11n_aggregate_pkt(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *pra_list, + int ptrindex) + __releases(&priv->wmm.ra_list_spinlock) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct sk_buff *skb_aggr, *skb_src; + struct nxpwifi_txinfo *tx_info_aggr, *tx_info_src; + int pad = 0, aggr_num = 0, ret; + struct nxpwifi_tx_param tx_param; + struct txpd *ptx_pd = NULL; + int headroom = adapter->intf_hdr_len; + + skb_src = skb_peek(&pra_list->skb_head); + if (!skb_src) { + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + return 0; + } + + tx_info_src = NXPWIFI_SKB_TXCB(skb_src); + skb_aggr = nxpwifi_alloc_dma_align_buf(adapter->tx_buf_size, + GFP_ATOMIC); + if (!skb_aggr) { + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + return -ENOMEM; + } + + /* + * skb_aggr->data already 64 byte align, just reserve bus interface + * header and txpd. + */ + skb_reserve(skb_aggr, headroom + sizeof(struct txpd)); + tx_info_aggr = NXPWIFI_SKB_TXCB(skb_aggr); + + memset(tx_info_aggr, 0, sizeof(*tx_info_aggr)); + tx_info_aggr->bss_type = tx_info_src->bss_type; + tx_info_aggr->bss_num = tx_info_src->bss_num; + + tx_info_aggr->flags |= NXPWIFI_BUF_FLAG_AGGR_PKT; + skb_aggr->priority = skb_src->priority; + skb_aggr->tstamp = skb_src->tstamp; + + do { + /* Check if AMSDU can accommodate this MSDU */ + if ((skb_aggr->len + skb_src->len + LLC_SNAP_LEN) > + adapter->tx_buf_size) + break; + + skb_src = skb_dequeue(&pra_list->skb_head); + pra_list->total_pkt_count--; + atomic_dec(&priv->wmm.tx_pkts_queued); + aggr_num++; + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + nxpwifi_11n_form_amsdu_pkt(skb_aggr, skb_src, &pad); + + nxpwifi_write_data_complete(adapter, skb_src, 0, 0); + + spin_lock_bh(&priv->wmm.ra_list_spinlock); + + if (!nxpwifi_is_ralist_valid(priv, pra_list, ptrindex)) { + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + return -ENOENT; + } + + if (skb_tailroom(skb_aggr) < pad) { + pad = 0; + break; + } + skb_put(skb_aggr, pad); + + skb_src = skb_peek(&pra_list->skb_head); + + } while (skb_src); + + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + + /* Last AMSDU packet does not need padding */ + skb_trim(skb_aggr, skb_aggr->len - pad); + + /* Form AMSDU */ + nxpwifi_11n_form_amsdu_txpd(priv, skb_aggr); + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) + ptx_pd = (struct txpd *)skb_aggr->data; + + skb_push(skb_aggr, headroom); + tx_info_aggr->aggr_num = aggr_num * 2; + if (adapter->data_sent || adapter->tx_lock_flag) { + atomic_add(aggr_num * 2, &adapter->tx_queued); + skb_queue_tail(&adapter->tx_data_q, skb_aggr); + return 0; + } + + if (skb_src) + tx_param.next_pkt_len = skb_src->len + sizeof(struct txpd); + else + tx_param.next_pkt_len = 0; + + ret = adapter->if_ops.host_to_card(adapter, NXPWIFI_TYPE_DATA, + skb_aggr, &tx_param); + + switch (ret) { + case -EBUSY: + spin_lock_bh(&priv->wmm.ra_list_spinlock); + if (!nxpwifi_is_ralist_valid(priv, pra_list, ptrindex)) { + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + nxpwifi_write_data_complete(adapter, skb_aggr, 1, -1); + return -EINVAL; + } + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA && + adapter->pps_uapsd_mode && adapter->tx_lock_flag) { + priv->adapter->tx_lock_flag = false; + if (ptx_pd) + ptx_pd->flags = 0; + } + + skb_queue_tail(&pra_list->skb_head, skb_aggr); + + pra_list->total_pkt_count++; + + atomic_inc(&priv->wmm.tx_pkts_queued); + + tx_info_aggr->flags |= NXPWIFI_BUF_FLAG_REQUEUED_PKT; + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + nxpwifi_dbg(adapter, ERROR, "data: -EBUSY is returned\n"); + break; + case -EINPROGRESS: + break; + case 0: + nxpwifi_write_data_complete(adapter, skb_aggr, 1, ret); + break; + default: + nxpwifi_dbg(adapter, ERROR, "%s: host_to_card failed: %#x\n", + __func__, ret); + adapter->dbg.num_tx_host_to_card_failure++; + nxpwifi_write_data_complete(adapter, skb_aggr, 1, ret); + break; + } + if (ret != -EBUSY) + nxpwifi_rotate_priolists(priv, pra_list, ptrindex); + + return 0; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/11n_aggr.h b/drivers/net/wireless/nxp/nxpwifi/11n_aggr.h new file mode 100644 index 000000000000..be9f0f8f4e48 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/11n_aggr.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * NXP Wireless LAN device driver: 802.11n Aggregation + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_11N_AGGR_H_ +#define _NXPWIFI_11N_AGGR_H_ + +#define PKT_TYPE_AMSDU 0xE6 +#define MIN_NUM_AMSDU 2 + +int nxpwifi_11n_deaggregate_pkt(struct nxpwifi_private *priv, + struct sk_buff *skb); +int nxpwifi_11n_aggregate_pkt(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ptr, + int ptr_index) + __releases(&priv->wmm.ra_list_spinlock); + +#endif /* !_NXPWIFI_11N_AGGR_H_ */ diff --git a/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c b/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c new file mode 100644 index 000000000000..c5819f89b08c --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c @@ -0,0 +1,826 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: 802.11n RX Re-ordering + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cmdevt.h" +#include "wmm.h" +#include "11n.h" +#include "11n_rxreorder.h" +/* Dispatch A-MSDU to stack. */ +static int nxpwifi_11n_dispatch_amsdu_pkt(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct rxpd *local_rx_pd = (struct rxpd *)(skb->data); + int ret; + + if (le16_to_cpu(local_rx_pd->rx_pkt_type) == PKT_TYPE_AMSDU) { + struct sk_buff_head list; + struct sk_buff *rx_skb; + + __skb_queue_head_init(&list); + + skb_pull(skb, le16_to_cpu(local_rx_pd->rx_pkt_offset)); + skb_trim(skb, le16_to_cpu(local_rx_pd->rx_pkt_length)); + + ieee80211_amsdu_to_8023s(skb, &list, priv->curr_addr, + priv->wdev.iftype, 0, NULL, NULL, false); + + while (!skb_queue_empty(&list)) { + rx_skb = __skb_dequeue(&list); + + if (priv->bss_role == NXPWIFI_BSS_ROLE_UAP) + ret = nxpwifi_uap_recv_packet(priv, rx_skb); + else + ret = nxpwifi_recv_packet(priv, rx_skb); + if (ret) + nxpwifi_dbg(priv->adapter, ERROR, + "Rx of A-MSDU failed"); + } + return 0; + } + + return -EINVAL; +} + +/* Process RX packet and forward to stack. */ +static int nxpwifi_11n_dispatch_pkt(struct nxpwifi_private *priv, + struct sk_buff *payload) +{ + int ret; + + if (!payload) { + nxpwifi_dbg(priv->adapter, INFO, "info: fw drop data\n"); + return 0; + } + + ret = nxpwifi_11n_dispatch_amsdu_pkt(priv, payload); + if (!ret) + return 0; + + if (priv->bss_role == NXPWIFI_BSS_ROLE_UAP) + return nxpwifi_handle_uap_rx_forward(priv, payload); + + return nxpwifi_process_rx_packet(priv, payload); +} + +/* Dispatch packets up to start_win. */ +static void +nxpwifi_11n_dispatch_pkt_until_start_win(struct nxpwifi_private *priv, + struct nxpwifi_rx_reorder_tbl *tbl, + int start_win) +{ + struct sk_buff_head list; + struct sk_buff *skb; + int pkt_to_send, i, tid; + + tid = tbl->tid; + __skb_queue_head_init(&list); + spin_lock_bh(&priv->rx_reorder_tbl_lock[tid]); + + pkt_to_send = (start_win > tbl->start_win) ? + min((start_win - tbl->start_win), tbl->win_size) : + tbl->win_size; + + for (i = 0; i < pkt_to_send; ++i) { + if (tbl->rx_reorder_ptr[i]) { + skb = tbl->rx_reorder_ptr[i]; + __skb_queue_tail(&list, skb); + tbl->rx_reorder_ptr[i] = NULL; + } + } + + /* Simulate circular buffer via rotation. */ + for (i = 0; i < tbl->win_size - pkt_to_send; ++i) { + tbl->rx_reorder_ptr[i] = tbl->rx_reorder_ptr[pkt_to_send + i]; + tbl->rx_reorder_ptr[pkt_to_send + i] = NULL; + } + + tbl->start_win = start_win; + spin_unlock_bh(&priv->rx_reorder_tbl_lock[tid]); + + while ((skb = __skb_dequeue(&list))) + nxpwifi_11n_dispatch_pkt(priv, skb); +} + +/* Dispatch packets until a hole is found. */ +static void +nxpwifi_11n_scan_and_dispatch(struct nxpwifi_private *priv, + struct nxpwifi_rx_reorder_tbl *tbl) +{ + struct sk_buff_head list; + struct sk_buff *skb; + int i, j, xchg, tid; + + tid = tbl->tid; + __skb_queue_head_init(&list); + spin_lock_bh(&priv->rx_reorder_tbl_lock[tid]); + + for (i = 0; i < tbl->win_size; ++i) { + if (!tbl->rx_reorder_ptr[i]) + break; + skb = tbl->rx_reorder_ptr[i]; + __skb_queue_tail(&list, skb); + tbl->rx_reorder_ptr[i] = NULL; + } + + /* Simulate circular buffer via rotation. */ + if (i > 0) { + xchg = tbl->win_size - i; + for (j = 0; j < xchg; ++j) { + tbl->rx_reorder_ptr[j] = tbl->rx_reorder_ptr[i + j]; + tbl->rx_reorder_ptr[i + j] = NULL; + } + } + tbl->start_win = (tbl->start_win + i) & (MAX_TID_VALUE - 1); + + spin_unlock_bh(&priv->rx_reorder_tbl_lock[tid]); + + while ((skb = __skb_dequeue(&list))) + nxpwifi_11n_dispatch_pkt(priv, skb); +} + +/* Delete RX reorder entry and flush pending packets. */ +static void +nxpwifi_del_rx_reorder_entry(struct nxpwifi_private *priv, + struct nxpwifi_rx_reorder_tbl *tbl) +{ + int start_win, tid; + + if (!tbl) + return; + + tid = tbl->tid; + + atomic_set(&priv->adapter->rx_ba_teardown_pending, 1); + flush_workqueue(priv->adapter->rx_workqueue); + + start_win = (tbl->start_win + tbl->win_size) & (MAX_TID_VALUE - 1); + nxpwifi_11n_dispatch_pkt_until_start_win(priv, tbl, start_win); + + timer_delete_sync(&tbl->timer_context.timer); + tbl->timer_context.timer_is_set = false; + + spin_lock_bh(&priv->rx_reorder_tbl_lock[tid]); + list_del_rcu(&tbl->list); + spin_unlock_bh(&priv->rx_reorder_tbl_lock[tid]); + + kfree(tbl->rx_reorder_ptr); + kfree_rcu(tbl, rcu); + + atomic_set(&priv->adapter->rx_ba_teardown_pending, 0); +} + +/* Lookup RX reorder entry by TID/TA. */ +struct nxpwifi_rx_reorder_tbl * +nxpwifi_11n_get_rx_reorder_tbl(struct nxpwifi_private *priv, int tid, u8 *ta) +{ + struct nxpwifi_rx_reorder_tbl *tbl, *found = NULL; + + guard(rcu)(); + + list_for_each_entry_rcu(tbl, &priv->rx_reorder_tbl_ptr[tid], list) { + if (!memcmp(tbl->ta, ta, ETH_ALEN) && tbl->tid == tid) { + found = tbl; + break; + } + } + + return found; +} + +/* Delete RX reorder entries by TA. */ +void nxpwifi_11n_del_rx_reorder_tbl_by_ta(struct nxpwifi_private *priv, u8 *ta) +{ + struct nxpwifi_rx_reorder_tbl *tbl, *tmp; + LIST_HEAD(to_delete); + int i; + + if (!ta) + return; + + for (i = 0; i < MAX_NUM_TID; i++) { + guard(rcu)(); + list_for_each_entry_rcu(tbl, &priv->rx_reorder_tbl_ptr[i], list) { + if (!memcmp(tbl->ta, ta, ETH_ALEN)) { + INIT_LIST_HEAD(&tbl->tmp_list); + list_add_tail(&tbl->tmp_list, &to_delete); + } + } + + list_for_each_entry_safe(tbl, tmp, &to_delete, tmp_list) + nxpwifi_del_rx_reorder_entry(priv, tbl); + + INIT_LIST_HEAD(&to_delete); + } +} + +/* Find last buffered sequence index. */ +static int +nxpwifi_11n_find_last_seq_num(struct reorder_tmr_cnxt *ctx) +{ + struct nxpwifi_rx_reorder_tbl *rx_reorder_tbl_ptr = ctx->ptr; + int i; + + guard(rcu)(); + for (i = rx_reorder_tbl_ptr->win_size - 1; i >= 0; --i) { + if (rx_reorder_tbl_ptr->rx_reorder_ptr[i]) + return i; + } + + return -EINVAL; +} + +/* Flush and dispatch buffered packets on timer. */ +static void +nxpwifi_flush_data(struct timer_list *t) +{ + struct reorder_tmr_cnxt *ctx = + timer_container_of(ctx, t, timer); + int start_win, seq_num; + + ctx->timer_is_set = false; + seq_num = nxpwifi_11n_find_last_seq_num(ctx); + + if (seq_num < 0) + return; + + nxpwifi_dbg(ctx->priv->adapter, INFO, "info: flush data %d\n", seq_num); + start_win = (ctx->ptr->start_win + seq_num + 1) & (MAX_TID_VALUE - 1); + nxpwifi_11n_dispatch_pkt_until_start_win(ctx->priv, ctx->ptr, + start_win); +} + +/* Create RX reorder entry (TID/TA, SSN, winsize, timer). */ +static void +nxpwifi_11n_create_rx_reorder_tbl(struct nxpwifi_private *priv, u8 *ta, + int tid, int win_size, int seq_num) +{ + int i; + struct nxpwifi_rx_reorder_tbl *tbl, *new_node; + u16 last_seq = 0; + struct nxpwifi_sta_node *node; + + /* Existing TID/TA: flush and move window to SSN. */ + tbl = nxpwifi_11n_get_rx_reorder_tbl(priv, tid, ta); + if (tbl) { + nxpwifi_11n_dispatch_pkt_until_start_win(priv, tbl, seq_num); + return; + } + /* if !tbl then create one */ + new_node = kzalloc_obj(*new_node, GFP_KERNEL); + if (!new_node) + return; + + INIT_LIST_HEAD(&new_node->list); + new_node->tid = tid; + memcpy(new_node->ta, ta, ETH_ALEN); + new_node->start_win = seq_num; + new_node->init_win = seq_num; + new_node->flags = 0; + + if (nxpwifi_queuing_ra_based(priv)) { + if (priv->bss_role == NXPWIFI_BSS_ROLE_UAP) { + guard(rcu)(); + node = nxpwifi_get_sta_entry(priv, ta); + if (node) + last_seq = node->rx_seq[tid]; + } + } else { + guard(rcu)(); + node = nxpwifi_get_sta_entry(priv, ta); + if (node) + last_seq = node->rx_seq[tid]; + else + last_seq = priv->rx_seq[tid]; + } + + nxpwifi_dbg(priv->adapter, INFO, + "info: last_seq=%d start_win=%d\n", + last_seq, new_node->start_win); + + if (last_seq != NXPWIFI_DEF_11N_RX_SEQ_NUM && + last_seq >= new_node->start_win) { + new_node->start_win = last_seq + 1; + new_node->flags |= RXREOR_INIT_WINDOW_SHIFT; + } + + new_node->win_size = win_size; + + new_node->rx_reorder_ptr = kcalloc(win_size, sizeof(void *), + GFP_KERNEL); + if (!new_node->rx_reorder_ptr) { + kfree(new_node); + nxpwifi_dbg(priv->adapter, ERROR, + "%s: failed to alloc reorder_ptr\n", __func__); + return; + } + + new_node->timer_context.ptr = new_node; + new_node->timer_context.priv = priv; + new_node->timer_context.timer_is_set = false; + + timer_setup(&new_node->timer_context.timer, nxpwifi_flush_data, 0); + + for (i = 0; i < win_size; ++i) + new_node->rx_reorder_ptr[i] = NULL; + + spin_lock_bh(&priv->rx_reorder_tbl_lock[tid]); + list_add_tail_rcu(&new_node->list, &priv->rx_reorder_tbl_ptr[tid]); + spin_unlock_bh(&priv->rx_reorder_tbl_lock[tid]); +} + +static void +nxpwifi_11n_rxreorder_timer_restart(struct nxpwifi_rx_reorder_tbl *tbl) +{ + u32 min_flush_time; + + if (tbl->win_size >= NXPWIFI_BA_WIN_SIZE_32) + min_flush_time = MIN_FLUSH_TIMER_15_MS; + else + min_flush_time = MIN_FLUSH_TIMER_MS; + + mod_timer(&tbl->timer_context.timer, + jiffies + msecs_to_jiffies(min_flush_time * tbl->win_size)); + + tbl->timer_context.timer_is_set = true; +} + +/* Prepare ADDBA request. */ +int nxpwifi_cmd_11n_addba_req(struct host_cmd_ds_command *cmd, void *data_buf) +{ + struct host_cmd_ds_11n_addba_req *add_ba_req = &cmd->params.add_ba_req; + + cmd->command = cpu_to_le16(HOST_CMD_11N_ADDBA_REQ); + cmd->size = cpu_to_le16(sizeof(*add_ba_req) + S_DS_GEN); + memcpy(add_ba_req, data_buf, sizeof(*add_ba_req)); + + return 0; +} + +/* Prepare ADDBA response and create RX reorder table. */ +int nxpwifi_cmd_11n_addba_rsp_gen(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + struct host_cmd_ds_11n_addba_req + *cmd_addba_req) +{ + struct host_cmd_ds_11n_addba_rsp *add_ba_rsp = &cmd->params.add_ba_rsp; + u32 rx_win_size = priv->add_ba_param.rx_win_size; + u8 tid; + int win_size; + u16 block_ack_param_set; + + cmd->command = cpu_to_le16(HOST_CMD_11N_ADDBA_RSP); + cmd->size = cpu_to_le16(sizeof(*add_ba_rsp) + S_DS_GEN); + + memcpy(add_ba_rsp->peer_mac_addr, cmd_addba_req->peer_mac_addr, + ETH_ALEN); + add_ba_rsp->dialog_token = cmd_addba_req->dialog_token; + add_ba_rsp->block_ack_tmo = cmd_addba_req->block_ack_tmo; + add_ba_rsp->ssn = cmd_addba_req->ssn; + + block_ack_param_set = le16_to_cpu(cmd_addba_req->block_ack_param_set); + tid = (block_ack_param_set & IEEE80211_ADDBA_PARAM_TID_MASK) + >> BLOCKACKPARAM_TID_POS; + add_ba_rsp->status_code = cpu_to_le16(ADDBA_RSP_STATUS_ACCEPT); + block_ack_param_set &= ~IEEE80211_ADDBA_PARAM_BUF_SIZE_MASK; + + /* If we don't support AMSDU inside AMPDU, reset the bit */ + if (!priv->add_ba_param.rx_amsdu || + priv->aggr_prio_tbl[tid].amsdu == BA_STREAM_NOT_ALLOWED) + block_ack_param_set &= ~IEEE80211_ADDBA_PARAM_AMSDU_MASK; + block_ack_param_set |= rx_win_size << BLOCKACKPARAM_WINSIZE_POS; + add_ba_rsp->block_ack_param_set = cpu_to_le16(block_ack_param_set); + win_size = (le16_to_cpu(add_ba_rsp->block_ack_param_set) + & IEEE80211_ADDBA_PARAM_BUF_SIZE_MASK) + >> BLOCKACKPARAM_WINSIZE_POS; + cmd_addba_req->block_ack_param_set = cpu_to_le16(block_ack_param_set); + + nxpwifi_11n_create_rx_reorder_tbl(priv, cmd_addba_req->peer_mac_addr, + tid, win_size, + le16_to_cpu(cmd_addba_req->ssn)); + return 0; +} + +/* Prepare DELBA command. */ +int nxpwifi_cmd_11n_delba(struct host_cmd_ds_command *cmd, void *data_buf) +{ + struct host_cmd_ds_11n_delba *del_ba = &cmd->params.del_ba; + + cmd->command = cpu_to_le16(HOST_CMD_11N_DELBA); + cmd->size = cpu_to_le16(sizeof(*del_ba) + S_DS_GEN); + memcpy(del_ba, data_buf, sizeof(*del_ba)); + + return 0; +} + +/* Decide and perform RX reordering for a packet. */ +int nxpwifi_11n_rx_reorder_pkt(struct nxpwifi_private *priv, + u16 seq_num, u16 tid, + u8 *ta, u8 pkt_type, void *payload) +{ + struct nxpwifi_rx_reorder_tbl *tbl; + int prev_start_win, start_win, end_win, win_size; + u16 pkt_index; + bool init_window_shift = false; + int ret = 0; + + tbl = nxpwifi_11n_get_rx_reorder_tbl(priv, tid, ta); + if (!tbl) { + if (pkt_type != PKT_TYPE_BAR) + nxpwifi_11n_dispatch_pkt(priv, payload); + return ret; + } + + if (pkt_type == PKT_TYPE_AMSDU && !tbl->amsdu) { + nxpwifi_11n_dispatch_pkt(priv, payload); + return ret; + } + + start_win = tbl->start_win; + prev_start_win = start_win; + win_size = tbl->win_size; + end_win = ((start_win + win_size) - 1) & (MAX_TID_VALUE - 1); + if (tbl->flags & RXREOR_INIT_WINDOW_SHIFT) { + init_window_shift = true; + tbl->flags &= ~RXREOR_INIT_WINDOW_SHIFT; + } + + if (tbl->flags & RXREOR_FORCE_NO_DROP) { + nxpwifi_dbg(priv->adapter, INFO, + "RXREOR_FORCE_NO_DROP when HS is activated\n"); + tbl->flags &= ~RXREOR_FORCE_NO_DROP; + } else if (init_window_shift && seq_num < start_win && + seq_num >= tbl->init_win) { + nxpwifi_dbg(priv->adapter, INFO, + "Sender TID sequence number reset %d->%d for SSN %d\n", + start_win, seq_num, tbl->init_win); + start_win = seq_num; + tbl->start_win = start_win; + end_win = ((start_win + win_size) - 1) & (MAX_TID_VALUE - 1); + } else { + /* Drop packet if seq_num < start_win. */ + if ((start_win + TWOPOW11) > (MAX_TID_VALUE - 1)) { + if (seq_num >= ((start_win + TWOPOW11) & + (MAX_TID_VALUE - 1)) && + seq_num < start_win) { + ret = -EINVAL; + goto done; + } + } else if ((seq_num < start_win) || + (seq_num >= (start_win + TWOPOW11))) { + ret = -EINVAL; + goto done; + } + } + + /* Adjust seq_num for BAR (WinStart = seq_num). */ + if (pkt_type == PKT_TYPE_BAR) + seq_num = ((seq_num + win_size) - 1) & (MAX_TID_VALUE - 1); + + if ((end_win < start_win && + seq_num < start_win && seq_num > end_win) || + (end_win > start_win && (seq_num > end_win || + seq_num < start_win))) { + end_win = seq_num; + if (((end_win - win_size) + 1) >= 0) + start_win = (end_win - win_size) + 1; + else + start_win = (MAX_TID_VALUE - (win_size - end_win)) + 1; + nxpwifi_11n_dispatch_pkt_until_start_win(priv, tbl, start_win); + } + + if (pkt_type != PKT_TYPE_BAR) { + if (seq_num >= start_win) + pkt_index = seq_num - start_win; + else + pkt_index = (seq_num + MAX_TID_VALUE) - start_win; + + if (tbl->rx_reorder_ptr[pkt_index]) { + ret = -EINVAL; + goto done; + } + + tbl->rx_reorder_ptr[pkt_index] = payload; + } + + /* Dispatch sequentially until a hole; update start_win. */ + nxpwifi_11n_scan_and_dispatch(priv, tbl); + +done: + if (!tbl->timer_context.timer_is_set || + prev_start_win != tbl->start_win) + nxpwifi_11n_rxreorder_timer_restart(tbl); + return ret; +} + +/* Delete BA entry for TID/TA. */ +void +nxpwifi_del_ba_tbl(struct nxpwifi_private *priv, int tid, u8 *peer_mac, + u8 type, int initiator) +{ + struct nxpwifi_rx_reorder_tbl *tbl; + struct nxpwifi_tx_ba_stream_tbl *ptx_tbl; + struct nxpwifi_ra_list_tbl *ra_list; + u8 cleanup_rx_reorder_tbl; + int tid_down; + + if (type == TYPE_DELBA_RECEIVE) + cleanup_rx_reorder_tbl = (initiator) ? true : false; + else + cleanup_rx_reorder_tbl = (initiator) ? false : true; + + nxpwifi_dbg(priv->adapter, EVENT, "event: DELBA: %pM tid=%d initiator=%d\n", + peer_mac, tid, initiator); + + if (cleanup_rx_reorder_tbl) { + tbl = nxpwifi_11n_get_rx_reorder_tbl(priv, tid, peer_mac); + if (!tbl) { + nxpwifi_dbg(priv->adapter, EVENT, + "event: TID, TA not found in table\n"); + return; + } + nxpwifi_del_rx_reorder_entry(priv, tbl); + } else { + guard(rcu)(); + ptx_tbl = nxpwifi_get_ba_tbl(priv, tid, peer_mac); + + if (!ptx_tbl) { + nxpwifi_dbg(priv->adapter, EVENT, + "event: TID, RA not found in table\n"); + return; + } + + tid_down = nxpwifi_wmm_downgrade_tid(priv, tid); + ra_list = nxpwifi_wmm_get_ralist_node(priv, tid_down, peer_mac); + if (ra_list) { + ra_list->amsdu_in_ampdu = false; + ra_list->ba_status = BA_SETUP_NONE; + } + spin_lock_bh(&priv->tx_ba_stream_tbl_lock[tid]); + nxpwifi_11n_delete_tx_ba_stream_tbl_entry(priv, ptx_tbl); + spin_unlock_bh(&priv->tx_ba_stream_tbl_lock[tid]); + } +} + +/* Handle ADDBA response. */ +int nxpwifi_ret_11n_addba_resp(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp) +{ + struct host_cmd_ds_11n_addba_rsp *add_ba_rsp = &resp->params.add_ba_rsp; + int tid, win_size; + struct nxpwifi_rx_reorder_tbl *tbl; + u16 block_ack_param_set; + + block_ack_param_set = le16_to_cpu(add_ba_rsp->block_ack_param_set); + + tid = (block_ack_param_set & IEEE80211_ADDBA_PARAM_TID_MASK) + >> BLOCKACKPARAM_TID_POS; + /* Check if we had rejected the ADDBA, if yes then do not create the stream */ + if (le16_to_cpu(add_ba_rsp->status_code) != BA_RESULT_SUCCESS) { + nxpwifi_dbg(priv->adapter, ERROR, "ADDBA RSP: failed %pM tid=%d)\n", + add_ba_rsp->peer_mac_addr, tid); + + tbl = nxpwifi_11n_get_rx_reorder_tbl(priv, tid, + add_ba_rsp->peer_mac_addr); + if (tbl) + nxpwifi_del_rx_reorder_entry(priv, tbl); + + return 0; + } + + win_size = (block_ack_param_set & IEEE80211_ADDBA_PARAM_BUF_SIZE_MASK) + >> BLOCKACKPARAM_WINSIZE_POS; + + tbl = nxpwifi_11n_get_rx_reorder_tbl(priv, tid, + add_ba_rsp->peer_mac_addr); + if (tbl) { + if ((block_ack_param_set & IEEE80211_ADDBA_PARAM_AMSDU_MASK) && + priv->add_ba_param.rx_amsdu && + priv->aggr_prio_tbl[tid].amsdu != BA_STREAM_NOT_ALLOWED) + tbl->amsdu = true; + else + tbl->amsdu = false; + } + + nxpwifi_dbg(priv->adapter, CMD, + "cmd: ADDBA RSP: %pM tid=%d ssn=%d win_size=%d\n", + add_ba_rsp->peer_mac_addr, tid, add_ba_rsp->ssn, win_size); + + return 0; +} + +/* Handle BA stream timeout: send DELBA. */ +void nxpwifi_11n_ba_stream_timeout(struct nxpwifi_private *priv, + struct host_cmd_ds_11n_batimeout *event) +{ + struct host_cmd_ds_11n_delba delba; + + memset(&delba, 0, sizeof(struct host_cmd_ds_11n_delba)); + memcpy(delba.peer_mac_addr, event->peer_mac_addr, ETH_ALEN); + + delba.del_ba_param_set |= + cpu_to_le16((u16)event->tid << DELBA_TID_POS); + delba.del_ba_param_set |= + cpu_to_le16((u16)event->origninator << DELBA_INITIATOR_POS); + delba.reason_code = cpu_to_le16(WLAN_REASON_QSTA_TIMEOUT); + nxpwifi_send_cmd(priv, HOST_CMD_11N_DELBA, 0, 0, &delba, false); +} + +/* Cleanup all RX reorder entries. */ +void nxpwifi_11n_cleanup_reorder_tbl(struct nxpwifi_private *priv) +{ + struct nxpwifi_rx_reorder_tbl *del_tbl_ptr, *tmp_node; + LIST_HEAD(to_delete_list); + int i; + + for (i = 0; i < MAX_NUM_TID; i++) { + spin_lock_bh(&priv->rx_reorder_tbl_lock[i]); + list_splice_init(&priv->rx_reorder_tbl_ptr[i], &to_delete_list); + spin_unlock_bh(&priv->rx_reorder_tbl_lock[i]); + + list_for_each_entry_safe(del_tbl_ptr, tmp_node, &to_delete_list, list) + nxpwifi_del_rx_reorder_entry(priv, del_tbl_ptr); + + INIT_LIST_HEAD(&to_delete_list); + } + + nxpwifi_reset_11n_rx_seq_num(priv); +} + +/* Update flags for all RX reorder tables. */ +void nxpwifi_update_rxreor_flags(struct nxpwifi_adapter *adapter, u8 flags) +{ + struct nxpwifi_private *priv; + struct nxpwifi_rx_reorder_tbl *tbl; + int i, j; + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + + for (j = 0; j < MAX_NUM_TID; j++) { + spin_lock_bh(&priv->rx_reorder_tbl_lock[j]); + list_for_each_entry_rcu(tbl, &priv->rx_reorder_tbl_ptr[j], list) + tbl->flags = flags; + spin_unlock_bh(&priv->rx_reorder_tbl_lock[j]); + } + } +} + +/* Update RX window size based on coex flag. */ +static void nxpwifi_update_ampdu_rxwinsize(struct nxpwifi_adapter *adapter, + bool coex_flag) +{ + u8 i, j; + u32 rx_win_size; + struct nxpwifi_private *priv; + + nxpwifi_dbg(adapter, INFO, "Update rxwinsize %d\n", coex_flag); + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + rx_win_size = priv->add_ba_param.rx_win_size; + if (coex_flag) { + if (priv->bss_type == NXPWIFI_BSS_TYPE_STA) + priv->add_ba_param.rx_win_size = + NXPWIFI_STA_COEX_AMPDU_DEF_RXWINSIZE; + if (priv->bss_type == NXPWIFI_BSS_TYPE_UAP) + priv->add_ba_param.rx_win_size = + NXPWIFI_UAP_COEX_AMPDU_DEF_RXWINSIZE; + } else { + if (priv->bss_type == NXPWIFI_BSS_TYPE_STA) + priv->add_ba_param.rx_win_size = + NXPWIFI_STA_AMPDU_DEF_RXWINSIZE; + if (priv->bss_type == NXPWIFI_BSS_TYPE_UAP) + priv->add_ba_param.rx_win_size = + NXPWIFI_UAP_AMPDU_DEF_RXWINSIZE; + } + + if (adapter->coex_win_size && adapter->coex_rx_win_size) + priv->add_ba_param.rx_win_size = + adapter->coex_rx_win_size; + + if (rx_win_size != priv->add_ba_param.rx_win_size) { + if (!priv->media_connected) + continue; + for (j = 0; j < MAX_NUM_TID; j++) + nxpwifi_11n_delba(priv, j); + } + } +} + +/* Check coex for RX BA. */ +void nxpwifi_coex_ampdu_rxwinsize(struct nxpwifi_adapter *adapter) +{ + u8 i; + struct nxpwifi_private *priv; + u8 count = 0; + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) { + if (priv->media_connected) + count++; + } + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + if (priv->bss_started) + count++; + } + if (count >= NXPWIFI_BSS_COEX_COUNT) + break; + } + if (count >= NXPWIFI_BSS_COEX_COUNT) + nxpwifi_update_ampdu_rxwinsize(adapter, true); + else + nxpwifi_update_ampdu_rxwinsize(adapter, false); +} + +/* Handle RXBA sync event. */ +void nxpwifi_11n_rxba_sync_event(struct nxpwifi_private *priv, + u8 *event_buf, u16 len) +{ + struct nxpwifi_ie_types_rxba_sync *tlv_rxba = (void *)event_buf; + u16 tlv_type, tlv_len; + struct nxpwifi_rx_reorder_tbl *rx_reor_tbl_ptr; + u8 i, j; + u16 seq_num, tlv_seq_num, tlv_bitmap_len; + int tlv_buf_left = len; + int ret; + u8 *tmp; + + nxpwifi_dbg_dump(priv->adapter, EVT_D, "RXBA_SYNC event:", + event_buf, len); + while (tlv_buf_left > sizeof(*tlv_rxba)) { + tlv_type = le16_to_cpu(tlv_rxba->header.type); + tlv_len = le16_to_cpu(tlv_rxba->header.len); + if (size_add(sizeof(tlv_rxba->header), tlv_len) > tlv_buf_left) { + nxpwifi_dbg(priv->adapter, WARN, + "TLV size (%zu) overflows event_buf buf_left=%d\n", + size_add(sizeof(tlv_rxba->header), tlv_len), + tlv_buf_left); + return; + } + + if (tlv_type != TLV_TYPE_RXBA_SYNC) { + nxpwifi_dbg(priv->adapter, ERROR, + "Wrong TLV id=0x%x\n", tlv_type); + return; + } + + tlv_seq_num = le16_to_cpu(tlv_rxba->seq_num); + tlv_bitmap_len = le16_to_cpu(tlv_rxba->bitmap_len); + if (size_add(sizeof(*tlv_rxba), tlv_bitmap_len) > tlv_buf_left) { + nxpwifi_dbg(priv->adapter, WARN, + "TLV size (%zu) overflows event_buf buf_left=%d\n", + size_add(sizeof(*tlv_rxba), tlv_bitmap_len), + tlv_buf_left); + return; + } + + nxpwifi_dbg(priv->adapter, INFO, + "%pM tid=%d seq_num=%d bitmap_len=%d\n", + tlv_rxba->mac, tlv_rxba->tid, tlv_seq_num, + tlv_bitmap_len); + + rx_reor_tbl_ptr = + nxpwifi_11n_get_rx_reorder_tbl(priv, tlv_rxba->tid, + tlv_rxba->mac); + if (!rx_reor_tbl_ptr) { + nxpwifi_dbg(priv->adapter, ERROR, + "Can not find rx_reorder_tbl!"); + return; + } + + for (i = 0; i < tlv_bitmap_len; i++) { + for (j = 0 ; j < 8; j++) { + if (tlv_rxba->bitmap[i] & (1 << j)) { + seq_num = (MAX_TID_VALUE - 1) & + (tlv_seq_num + i * 8 + j); + + nxpwifi_dbg(priv->adapter, ERROR, + "drop packet,seq=%d\n", + seq_num); + + ret = nxpwifi_11n_rx_reorder_pkt + (priv, seq_num, tlv_rxba->tid, + tlv_rxba->mac, 0, NULL); + + if (ret) + nxpwifi_dbg(priv->adapter, + ERROR, + "Fail to drop packet"); + } + } + } + + tlv_buf_left -= (sizeof(tlv_rxba->header) + tlv_len); + tmp = (u8 *)tlv_rxba + sizeof(tlv_rxba->header) + tlv_len; + tlv_rxba = (struct nxpwifi_ie_types_rxba_sync *)tmp; + } +} diff --git a/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.h b/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.h new file mode 100644 index 000000000000..db95d9db5d1f --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * NXP Wireless LAN device driver: 802.11n RX Re-ordering + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_11N_RXREORDER_H_ +#define _NXPWIFI_11N_RXREORDER_H_ + +#define MIN_FLUSH_TIMER_MS 50 +#define MIN_FLUSH_TIMER_15_MS 15 +#define NXPWIFI_BA_WIN_SIZE_32 32 + +#define PKT_TYPE_BAR 0xE7 +#define MAX_TID_VALUE (2 << 11) +#define TWOPOW11 (2 << 10) + +#define BLOCKACKPARAM_TID_POS 2 +#define BLOCKACKPARAM_WINSIZE_POS 6 +#define DELBA_TID_POS 12 +#define DELBA_INITIATOR_POS 11 +#define TYPE_DELBA_SENT 1 +#define TYPE_DELBA_RECEIVE 2 +#define IMMEDIATE_BLOCK_ACK 0x2 + +#define ADDBA_RSP_STATUS_ACCEPT 0 + +#define NXPWIFI_DEF_11N_RX_SEQ_NUM 0xffff +#define BA_SETUP_MAX_PACKET_THRESHOLD 16 +#define BA_SETUP_PACKET_OFFSET 16 + +enum nxpwifi_rxreor_flags { + RXREOR_FORCE_NO_DROP = 1 << 0, + RXREOR_INIT_WINDOW_SHIFT = 1 << 1, +}; + +static inline void nxpwifi_reset_11n_rx_seq_num(struct nxpwifi_private *priv) +{ + memset(priv->rx_seq, 0xff, sizeof(priv->rx_seq)); +} + +int nxpwifi_11n_rx_reorder_pkt(struct nxpwifi_private *priv, + u16 seq_num, + u16 tid, u8 *ta, + u8 pkttype, void *payload); +void nxpwifi_del_ba_tbl(struct nxpwifi_private *priv, int tid, + u8 *peer_mac, u8 type, int initiator); +void nxpwifi_11n_ba_stream_timeout(struct nxpwifi_private *priv, + struct host_cmd_ds_11n_batimeout *event); +int nxpwifi_ret_11n_addba_resp(struct nxpwifi_private *priv, + struct host_cmd_ds_command + *resp); +int nxpwifi_cmd_11n_delba(struct host_cmd_ds_command *cmd, + void *data_buf); +int nxpwifi_cmd_11n_addba_rsp_gen(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + struct host_cmd_ds_11n_addba_req + *cmd_addba_req); +int nxpwifi_cmd_11n_addba_req(struct host_cmd_ds_command *cmd, + void *data_buf); +void nxpwifi_11n_cleanup_reorder_tbl(struct nxpwifi_private *priv); +struct nxpwifi_rx_reorder_tbl * +nxpwifi_11n_get_rxreorder_tbl(struct nxpwifi_private *priv, int tid, u8 *ta); +struct nxpwifi_rx_reorder_tbl * +nxpwifi_11n_get_rx_reorder_tbl(struct nxpwifi_private *priv, int tid, u8 *ta); +void nxpwifi_11n_del_rx_reorder_tbl_by_ta(struct nxpwifi_private *priv, u8 *ta); +void nxpwifi_update_rxreor_flags(struct nxpwifi_adapter *adapter, u8 flags); +void nxpwifi_11n_rxba_sync_event(struct nxpwifi_private *priv, + u8 *event_buf, u16 len); +#endif /* _NXPWIFI_11N_RXREORDER_H_ */ diff --git a/drivers/net/wireless/nxp/nxpwifi/Kconfig b/drivers/net/wireless/nxp/nxpwifi/Kconfig new file mode 100644 index 000000000000..3637068574b8 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/Kconfig @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: GPL-2.0-only +config NXPWIFI + tristate "NXP WiFi Driver" + depends on CFG80211 + help + This adds support for wireless adapters based on NXP + 802.11n/ac chipsets. + + If you choose to build it as a module, it will be called + nxpwifi. + +config NXPWIFI_SDIO + tristate "NXP WiFi Driver for IW61x" + depends on NXPWIFI && MMC + select FW_LOADER + select WANT_DEV_COREDUMP + help + This adds support for wireless adapters based on NXP + IW61x interface. + + If you choose to build it as a module, it will be called + nxpwifi_sdio. diff --git a/drivers/net/wireless/nxp/nxpwifi/Makefile b/drivers/net/wireless/nxp/nxpwifi/Makefile new file mode 100644 index 000000000000..8f581429f28d --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/Makefile @@ -0,0 +1,39 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Copyright 2011-2020 NXP +# + + +nxpwifi-y += main.o +nxpwifi-y += init.o +nxpwifi-y += cfp.o +nxpwifi-y += cmdevt.o +nxpwifi-y += util.o +nxpwifi-y += txrx.o +nxpwifi-y += wmm.o +nxpwifi-y += 11n.o +nxpwifi-y += 11ac.o +nxpwifi-y += 11ax.o +nxpwifi-y += 11n_aggr.o +nxpwifi-y += 11n_rxreorder.o +nxpwifi-y += scan.o +nxpwifi-y += join.o +nxpwifi-y += sta_cfg.o +nxpwifi-y += sta_cmd.o +nxpwifi-y += uap_cmd.o +nxpwifi-y += ie.o +nxpwifi-y += sta_event.o +nxpwifi-y += uap_event.o +nxpwifi-y += sta_tx.o +nxpwifi-y += sta_rx.o +nxpwifi-y += uap_txrx.o +nxpwifi-y += cfg80211.o +nxpwifi-y += ethtool.o +nxpwifi-y += 11h.o +nxpwifi-$(CONFIG_DEBUG_FS) += debugfs.o +obj-$(CONFIG_NXPWIFI) += nxpwifi.o + +nxpwifi_sdio-y += sdio.o +obj-$(CONFIG_NXPWIFI_SDIO) += nxpwifi_sdio.o + +ccflags-y += -D__CHECK_ENDIAN diff --git a/drivers/net/wireless/nxp/nxpwifi/cfg.h b/drivers/net/wireless/nxp/nxpwifi/cfg.h new file mode 100644 index 000000000000..8627a3372978 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/cfg.h @@ -0,0 +1,1019 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * NXP Wireless LAN device driver: ioctl data structures & APIs + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_CFG_H_ +#define _NXPWIFI_CFG_H_ + +#include +#include +#include +#include +#include + +#define NUM_WEP_KEYS 4 + +#define NXPWIFI_BSS_COEX_COUNT 2 +#define NXPWIFI_MAX_BSS_NUM (3) + +#define NXPWIFI_MAX_CSA_COUNTERS 5 + +#define NXPWIFI_DMA_ALIGN_SZ 64 +#define NXPWIFI_RX_HEADROOM 64 +#define MAX_TXPD_SZ 32 +#define INTF_HDR_ALIGN 4 +/* special FW 4 address management header */ +#define NXPWIFI_MIN_DATA_HEADER_LEN (NXPWIFI_DMA_ALIGN_SZ + INTF_HDR_ALIGN + \ + MAX_TXPD_SZ) + +#define NXPWIFI_MGMT_FRAME_HEADER_SIZE 8 /* sizeof(pkt_type) + * + sizeof(tx_control) + */ + +#define FRMCTL_LEN 2 +#define DURATION_LEN 2 +#define SEQCTL_LEN 2 +#define NXPWIFI_MGMT_HEADER_LEN (FRMCTL_LEN + FRMCTL_LEN + ETH_ALEN + \ + ETH_ALEN + ETH_ALEN + SEQCTL_LEN + ETH_ALEN) + +#define AUTH_ALG_LEN 2 +#define AUTH_TRANSACTION_LEN 2 +#define AUTH_STATUS_LEN 2 +#define NXPWIFI_AUTH_BODY_LEN (AUTH_ALG_LEN + AUTH_TRANSACTION_LEN + \ + AUTH_STATUS_LEN) + +#define HOST_MLME_AUTH_PENDING BIT(0) +#define HOST_MLME_AUTH_DONE BIT(1) + +#define HOST_MLME_MGMT_MASK (BIT(IEEE80211_STYPE_AUTH >> 4) | \ + BIT(IEEE80211_STYPE_DEAUTH >> 4) | \ + BIT(IEEE80211_STYPE_DISASSOC >> 4)) + +#define AUTH_TX_DEFAULT_WAIT_TIME 2400 + +#define WLAN_AUTH_NONE 0xFFFF + +#define NXPWIFI_MAX_TX_BASTREAM_SUPPORTED 2 +#define NXPWIFI_MAX_RX_BASTREAM_SUPPORTED 16 + +#define NXPWIFI_STA_AMPDU_DEF_TXWINSIZE 64 +#define NXPWIFI_STA_AMPDU_DEF_RXWINSIZE 64 +#define NXPWIFI_STA_COEX_AMPDU_DEF_RXWINSIZE 16 + +#define NXPWIFI_UAP_AMPDU_DEF_TXWINSIZE 32 + +#define NXPWIFI_UAP_COEX_AMPDU_DEF_RXWINSIZE 16 + +#define NXPWIFI_UAP_AMPDU_DEF_RXWINSIZE 16 +#define NXPWIFI_11AC_STA_AMPDU_DEF_TXWINSIZE 64 +#define NXPWIFI_11AC_STA_AMPDU_DEF_RXWINSIZE 64 +#define NXPWIFI_11AC_UAP_AMPDU_DEF_TXWINSIZE 64 +#define NXPWIFI_11AC_UAP_AMPDU_DEF_RXWINSIZE 64 + +#define NXPWIFI_DEFAULT_BLOCK_ACK_TIMEOUT 0xffff + +#define NXPWIFI_RATE_BITMAP_MCS0 32 + +#define NXPWIFI_RX_DATA_BUF_SIZE (4 * 1024) +#define NXPWIFI_RX_CMD_BUF_SIZE (2 * 1024) + +#define NXPWIFI_BEACON_PERIOD_MAX (4000) +#define NXPWIFI_BEACON_PERIOD_MIN (50) +#define NXPWIFI_INVALID_BEACON_PERIOD (NXPWIFI_BEACON_PERIOD_MAX + 1) +#define NXPWIFI_MAX_DTIM_PERIOD (100) +#define NXPWIFI_MIN_DTIM_PERIOD (1) +#define NXPWIFI_INVALID_DTIM_PERIOD (NXPWIFI_MAX_DTIM_PERIOD + 1) +#define NXPWIFI_RTS_THRESHOLD_MIN (0) +#define NXPWIFI_RTS_THRESHOLD_MAX (2347) +#define NXPWIFI_INVALID_RTS (NXPWIFI_RTS_THRESHOLD_MAX + 1) +#define NXPWIFI_FRAG_THRESHOLD_MIN (256) +#define NXPWIFI_FRAG_THRESHOLD_MAX (2346) +#define NXPWIFI_INVALID_FRAG (NXPWIFI_FRAG_THRESHOLD_MAX + 1) +#define NXPWIFI_RETRY_LIMIT_MAX 14 +#define NXPWIFI_INVALID_RETRY_LIMI (NXPWIFI_RETRY_LIMIT_MAX + 1) + +enum nxpwifi_bcast_ssid_ctl { + /* Hide SSID in beacons (SSID length = 0) */ + NXPWIFI_BCAST_SSID_HIDE_LEN_ZERO = 0, + /* Do not hide SSID (normal broadcast) */ + NXPWIFI_BCAST_SSID_VISIBLE, + /* Hide SSID, clear SSID content (ASCII 0), + * but keep the original SSID length + */ + NXPWIFI_BCAST_SSID_HIDE_LEN_RETAIN, +}; + +enum nxpwifi_radio_ctl { + NXPWIFI_RADIO_CTL_DISABLE = 0, + NXPWIFI_RADIO_CTL_ENABLE, + __NXPWIFI_RADIO_CTL_MAX, +}; + +static inline bool nxpwifi_radio_ctl_valid(enum nxpwifi_radio_ctl v) +{ + return v < __NXPWIFI_RADIO_CTL_MAX; +} + +#define NXPWIFI_WMM_VERSION 0x01 +#define NXPWIFI_WMM_SUBTYPE 0x01 + +#define NXPWIFI_SDIO_BLOCK_SIZE 256 + +#define NXPWIFI_BUF_FLAG_REQUEUED_PKT BIT(0) +#define NXPWIFI_BUF_FLAG_BRIDGED_PKT BIT(1) +#define NXPWIFI_BUF_FLAG_EAPOL_TX_STATUS BIT(3) +#define NXPWIFI_BUF_FLAG_ACTION_TX_STATUS BIT(4) +#define NXPWIFI_BUF_FLAG_AGGR_PKT BIT(5) + +#define NXPWIFI_BRIDGED_PKTS_THR_HIGH 1024 +#define NXPWIFI_BRIDGED_PKTS_THR_LOW 128 + +/* 54M rates, index from 0 to 11 */ +#define NXPWIFI_RATE_INDEX_MCS0 12 +/* 12-27=MCS0-15(BW20) */ +#define NXPWIFI_BW20_MCS_NUM 15 + +/* Rate index for OFDM 0 */ +#define NXPWIFI_RATE_INDEX_OFDM0 4 + +#define NXPWIFI_MAX_STA_NUM 3 +#define NXPWIFI_MAX_UAP_NUM 3 + +#define NXPWIFI_A_BAND_START_FREQ 5000 + +/* SDIO Aggr data packet special info */ +#define SDIO_MAX_AGGR_BUF_SIZE (256 * 255) +#define BLOCK_NUMBER_OFFSET 15 +#define SDIO_HEADER_OFFSET 28 + +#define NXPWIFI_SIZE_4K 0x4000 +#define NXPWIFI_EXT_CAPAB_IE_LEN 10 + +enum nxpwifi_bss_type { + NXPWIFI_BSS_TYPE_STA = 0, + NXPWIFI_BSS_TYPE_UAP = 1, + NXPWIFI_BSS_TYPE_ANY = 0xff, +}; + +enum nxpwifi_bss_role { + NXPWIFI_BSS_ROLE_STA = 0, + NXPWIFI_BSS_ROLE_UAP = 1, + NXPWIFI_BSS_ROLE_ANY = 0xff, +}; + +#define BSS_ROLE_BIT_MASK BIT(0) + +#define GET_BSS_ROLE(priv) ((priv)->bss_role & BSS_ROLE_BIT_MASK) + +enum nxpwifi_data_frame_type { + NXPWIFI_DATA_FRAME_TYPE_ETH_II = 0, + NXPWIFI_DATA_FRAME_TYPE_802_11, +}; + +struct nxpwifi_fw_image { + u8 *helper_buf; + u32 helper_len; + u8 *fw_buf; + u32 fw_len; +}; + +struct nxpwifi_802_11_ssid { + u32 ssid_len; + u8 ssid[IEEE80211_MAX_SSID_LEN]; +}; + +struct nxpwifi_wait_queue { + wait_queue_head_t wait; + int status; +}; + +struct nxpwifi_rxinfo { + struct sk_buff *parent; + u8 bss_num; + u8 bss_type; + u8 use_count; + u8 buf_type; + u16 pkt_len; +}; + +struct nxpwifi_txinfo { + u8 flags; + u8 bss_num; + u8 bss_type; + u8 aggr_num; + u32 pkt_len; + u8 ack_frame_id; + u64 cookie; +}; + +enum nxpwifi_wmm_ac_e { + WMM_AC_BK, + WMM_AC_BE, + WMM_AC_VI, + WMM_AC_VO +} __packed; + +struct nxpwifi_types_wmm_info { + u8 oui[4]; + u8 subtype; + u8 version; + u8 qos_info; + u8 reserved; + struct ieee80211_wmm_ac_param ac[IEEE80211_NUM_ACS]; +} __packed; + +struct nxpwifi_arp_eth_header { + struct arphdr hdr; + u8 ar_sha[ETH_ALEN]; + u8 ar_sip[4]; + u8 ar_tha[ETH_ALEN]; + u8 ar_tip[4]; +} __packed; + +struct nxpwifi_chan_stats { + u8 chan_num; + u8 bandcfg; + u8 flags; + s8 noise; + u16 total_bss; + u16 cca_scan_dur; + u16 cca_busy_dur; +} __packed; + +#define NXPWIFI_HIST_MAX_SAMPLES 1048576 +#define NXPWIFI_MAX_RX_RATES 44 +#define NXPWIFI_MAX_AC_RX_RATES 74 +#define NXPWIFI_MAX_SNR 256 +#define NXPWIFI_MAX_NOISE_FLR 256 +#define NXPWIFI_MAX_SIG_STRENGTH 256 + +struct nxpwifi_histogram_data { + atomic_t rx_rate[NXPWIFI_MAX_AC_RX_RATES]; + atomic_t snr[NXPWIFI_MAX_SNR]; + atomic_t noise_flr[NXPWIFI_MAX_NOISE_FLR]; + atomic_t sig_str[NXPWIFI_MAX_SIG_STRENGTH]; + atomic_t num_samples; +}; + +struct nxpwifi_iface_comb { + u8 sta_intf; + u8 uap_intf; +}; + +struct nxpwifi_radar_params { + struct cfg80211_chan_def *chandef; + u32 cac_time_ms; +} __packed; + +struct nxpwifi_11h_intf_state { + bool is_11h_enabled; + bool is_11h_active; +} __packed; + +#define NXPWIFI_FW_DUMP_IDX 0xff +#define NXPWIFI_FW_DUMP_MAX_MEMSIZE 0x160000 +#define NXPWIFI_DRV_INFO_IDX 20 +#define FW_DUMP_MAX_NAME_LEN 8 +#define FW_DUMP_HOST_READY 0xEE +#define FW_DUMP_DONE 0xFF +#define FW_DUMP_READ_DONE 0xFE + +/* Channel bandwidth */ +#define CHANNEL_BW_20MHZ 0 +#define CHANNEL_BW_40MHZ_ABOVE 1 +#define CHANNEL_BW_40MHZ_BELOW 3 +/* secondary channel is 80MHz bandwidth for 11ac */ +#define CHANNEL_BW_80MHZ 4 +#define CHANNEL_BW_160MHZ 5 + +struct memory_type_mapping { + u8 mem_name[FW_DUMP_MAX_NAME_LEN]; + u8 *mem_ptr; + u32 mem_size; + u8 done_flag; +}; + +enum rdwr_status { + RDWR_STATUS_SUCCESS = 0, + RDWR_STATUS_FAILURE = 1, + RDWR_STATUS_DONE = 2 +}; + +enum nxpwifi_chan_band { + BAND_2GHZ = 0, + BAND_5GHZ, + BAND_6GHZ, + BAND_4GHZ, +}; + +enum nxpwifi_chan_width { + CHAN_BW_20MHZ = 0, + CHAN_BW_10MHZ, + CHAN_BW_40MHZ, + CHAN_BW_80MHZ, + CHAN_BW_8080MHZ, + CHAN_BW_160MHZ, + CHAN_BW_5MHZ, +}; + +enum { + NXPWIFI_SCAN_TYPE_UNCHANGED = 0, + NXPWIFI_SCAN_TYPE_ACTIVE, + NXPWIFI_SCAN_TYPE_PASSIVE +}; + +#define NXPWIFI_PROMISC_MODE 1 +#define NXPWIFI_MULTICAST_MODE 2 +#define NXPWIFI_ALL_MULTI_MODE 4 +#define NXPWIFI_MAX_MULTICAST_LIST_SIZE 32 + +struct nxpwifi_multicast_list { + u32 mode; + u32 num_multicast_addr; + u8 mac_list[NXPWIFI_MAX_MULTICAST_LIST_SIZE][ETH_ALEN]; +}; + +struct nxpwifi_chan_freq { + u32 channel; + u32 freq; +}; + +struct nxpwifi_ssid_bssid { + struct cfg80211_ssid ssid; + u8 bssid[ETH_ALEN]; +}; + +enum { + BAND_B = 1, + BAND_G = 2, + BAND_A = 4, + BAND_GN = 8, + BAND_AN = 16, + BAND_GAC = 32, + BAND_AAC = 64, + BAND_GAX = 256, + BAND_AAX = 512, +}; + +#define NXPWIFI_WPA_PASSHPHRASE_LEN 64 +struct wpa_param { + u8 pairwise_cipher_wpa; + u8 pairwise_cipher_wpa2; + u8 group_cipher; + u32 length; + u8 passphrase[NXPWIFI_WPA_PASSHPHRASE_LEN]; +}; + +struct wep_key { + u8 key_index; + u8 is_default; + u16 length; + u8 key[WLAN_KEY_LEN_WEP104]; +}; + +#define KEY_MGMT_ON_HOST 0x03 +#define NXPWIFI_AUTH_MODE_AUTO 0xFF +#define BAND_CONFIG_BG 0x00 +#define BAND_CONFIG_A 0x01 +#define NXPWIFI_SEC_CHAN_BELOW 0x03 +#define NXPWIFI_SEC_CHAN_ABOVE 0x01 +#define NXPWIFI_SUPPORTED_RATES 14 +#define NXPWIFI_SUPPORTED_RATES_EXT 32 +#define NXPWIFI_PRIO_BK 2 +#define NXPWIFI_PRIO_VI 5 +#define NXPWIFI_SUPPORTED_CHANNELS 2 +#define NXPWIFI_OPERATING_CLASSES 16 + +struct nxpwifi_uap_bss_param { + u8 mac_addr[ETH_ALEN]; + u8 channel; + u8 band_cfg; + u16 rts_threshold; + u16 frag_threshold; + u8 retry_limit; + struct nxpwifi_802_11_ssid ssid; + u8 bcast_ssid_ctl; + u8 radio_ctl; + u8 dtim_period; + u16 beacon_period; + u16 auth_mode; + u16 protocol; + u16 key_mgmt; + u16 key_mgmt_operation; + struct wpa_param wpa_cfg; + struct wep_key wep_cfg[NUM_WEP_KEYS]; + struct ieee80211_ht_cap ht_cap; + struct ieee80211_vht_cap vht_cap; + u8 rates[NXPWIFI_SUPPORTED_RATES]; + u32 sta_ao_timer; + u32 ps_sta_ao_timer; + u8 power_constraint; + struct ieee80211_wmm_param_ie wmm_element; +}; + +struct nxpwifi_ds_get_stats { + u32 mcast_tx_frame; + u32 failed; + u32 retry; + u32 multi_retry; + u32 frame_dup; + u32 rts_success; + u32 rts_failure; + u32 ack_failure; + u32 rx_frag; + u32 mcast_rx_frame; + u32 fcs_error; + u32 tx_frame; + u32 wep_icv_error[4]; + u32 bcn_rcv_cnt; + u32 bcn_miss_cnt; +}; + +#define NXPWIFI_MAX_VER_STR_LEN 128 + +struct nxpwifi_ver_ext { + u32 version_str_sel; + char version_str[NXPWIFI_MAX_VER_STR_LEN]; +}; + +struct nxpwifi_bss_info { + u32 bss_mode; + struct cfg80211_ssid ssid; + u32 bss_chan; + u8 country_code[3]; + u32 media_connected; + u32 max_power_level; + u32 min_power_level; + signed int bcn_nf_last; + u32 wep_status; + u32 is_hs_configured; + u32 is_deep_sleep; + u8 bssid[ETH_ALEN]; +}; + +struct nxpwifi_sta_info { + u8 peer_mac[ETH_ALEN]; + struct station_parameters *params; +}; + +#define MAX_NUM_TID 8 + +#define MAX_RX_WINSIZE 64 + +struct nxpwifi_ds_rx_reorder_tbl { + u16 tid; + u8 ta[ETH_ALEN]; + u32 start_win; + u32 win_size; + u32 buffer[MAX_RX_WINSIZE]; +}; + +struct nxpwifi_ds_tx_ba_stream_tbl { + u16 tid; + u8 ra[ETH_ALEN]; + u8 amsdu; +}; + +#define DBG_CMD_NUM 5 +#define NXPWIFI_DBG_SDIO_MP_NUM 10 + +struct nxpwifi_debug_info { + unsigned int debug_mask; + u32 int_counter; + u32 packets_out[MAX_NUM_TID]; + u32 tx_buf_size; + u32 curr_tx_buf_size; + u32 tx_tbl_num; + struct nxpwifi_ds_tx_ba_stream_tbl + tx_tbl[NXPWIFI_MAX_TX_BASTREAM_SUPPORTED]; + u32 rx_tbl_num; + struct nxpwifi_ds_rx_reorder_tbl rx_tbl + [NXPWIFI_MAX_RX_BASTREAM_SUPPORTED]; + u16 ps_mode; + u32 ps_state; + u8 is_deep_sleep; + u8 pm_wakeup_card_req; + u32 pm_wakeup_fw_try; + u8 is_hs_configured; + u8 hs_activated; + u32 num_cmd_host_to_card_failure; + u32 num_cmd_sleep_cfm_host_to_card_failure; + u32 num_tx_host_to_card_failure; + u32 num_event_deauth; + u32 num_event_disassoc; + u32 num_event_link_lost; + u32 num_cmd_deauth; + u32 num_cmd_assoc_success; + u32 num_cmd_assoc_failure; + u32 num_tx_timeout; + u8 is_cmd_timedout; + u16 timeout_cmd_id; + u16 timeout_cmd_act; + u16 last_cmd_id[DBG_CMD_NUM]; + u16 last_cmd_act[DBG_CMD_NUM]; + u16 last_cmd_index; + u16 last_cmd_resp_id[DBG_CMD_NUM]; + u16 last_cmd_resp_index; + u16 last_event[DBG_CMD_NUM]; + u16 last_event_index; + u8 data_sent; + u8 cmd_sent; + u8 cmd_resp_received; + u8 event_received; + u32 last_mp_wr_bitmap[NXPWIFI_DBG_SDIO_MP_NUM]; + u32 last_mp_wr_ports[NXPWIFI_DBG_SDIO_MP_NUM]; + u32 last_mp_wr_len[NXPWIFI_DBG_SDIO_MP_NUM]; + u32 last_mp_curr_wr_port[NXPWIFI_DBG_SDIO_MP_NUM]; + u8 last_sdio_mp_index; +}; + +#define NXPWIFI_KEY_INDEX_UNICAST 0x40000000 +#define PN_LEN 16 + +struct nxpwifi_ds_encrypt_key { + u32 key_disable; + u32 key_index; + u32 key_len; + u32 key_cipher; + u8 key_material[WLAN_MAX_KEY_LEN]; + u8 mac_addr[ETH_ALEN]; + u8 pn[PN_LEN]; /* packet number */ + u8 pn_len; + u8 is_igtk_key; + u8 is_current_wep_key; + u8 is_rx_seq_valid; + u8 is_igtk_def_key; +}; + +struct nxpwifi_power_cfg { + u32 is_power_auto; + u32 is_power_fixed; + u32 power_level; +}; + +struct nxpwifi_ds_hs_cfg { + u32 is_invoke_hostcmd; + /* + * Bit0: non-unicast data + * Bit1: unicast data + * Bit2: mac events + * Bit3: magic packet + */ + u32 conditions; + u32 gpio; + u32 gap; +}; + +struct nxpwifi_ds_wakeup_reason { + u16 hs_wakeup_reason; +}; + +#define DEEP_SLEEP_ON 1 +#define DEEP_SLEEP_OFF 0 +#define DEEP_SLEEP_IDLE_TIME 100 +#define PS_MODE_AUTO 1 + +struct nxpwifi_ds_auto_ds { + u16 auto_ds; + u16 idle_time; +}; + +struct nxpwifi_ds_pm_cfg { + union { + u32 ps_mode; + struct nxpwifi_ds_hs_cfg hs_cfg; + struct nxpwifi_ds_auto_ds auto_deep_sleep; + u32 sleep_period; + } param; +}; + +struct nxpwifi_11ac_vht_cfg { + u8 band_config; + u8 misc_config; + u32 cap_info; + u32 mcs_tx_set; + u32 mcs_rx_set; +}; + +struct nxpwifi_ds_11n_tx_cfg { + u16 tx_htcap; + u16 tx_htinfo; + u16 misc_config; /* Needed for 802.11AC cards only */ +}; + +struct nxpwifi_ds_11n_amsdu_aggr_ctrl { + u16 enable; + u16 curr_buf_size; +}; + +struct nxpwifi_ds_ant_cfg { + u32 tx_ant; + u32 rx_ant; +}; + +#define NXPWIFI_NUM_OF_CMD_BUFFER 50 +#define NXPWIFI_SIZE_OF_CMD_BUFFER 2048 + +enum { + NXPWIFI_IE_TYPE_GEN_IE = 0, + NXPWIFI_IE_TYPE_ARP_FILTER, +}; + +enum { + NXPWIFI_REG_MAC = 1, + NXPWIFI_REG_BBP, + NXPWIFI_REG_RF, + NXPWIFI_REG_PMIC, + NXPWIFI_REG_CAU, +}; + +struct nxpwifi_ds_reg_rw { + u32 type; + u32 offset; + u32 value; +}; + +#define MAX_EEPROM_DATA 256 + +struct nxpwifi_ds_read_eeprom { + u16 offset; + u16 byte_count; + u8 value[MAX_EEPROM_DATA]; +}; + +struct nxpwifi_ds_mem_rw { + u32 addr; + u32 value; +}; + +#define IEEE_MAX_IE_SIZE 256 + +#define NXPWIFI_IE_HDR_SIZE (sizeof(struct nxpwifi_ie) - IEEE_MAX_IE_SIZE) + +struct nxpwifi_ds_misc_gen_ie { + u32 type; + u32 len; + u8 ie_data[IEEE_MAX_IE_SIZE]; +}; + +struct nxpwifi_ds_misc_cmd { + u32 len; + u8 cmd[NXPWIFI_SIZE_OF_CMD_BUFFER]; +}; + +#define BITMASK_BCN_RSSI_LOW BIT(0) +#define BITMASK_BCN_RSSI_HIGH BIT(4) + +enum subsc_evt_rssi_state { + EVENT_HANDLED, + RSSI_LOW_RECVD, + RSSI_HIGH_RECVD +}; + +struct subsc_evt_cfg { + u8 abs_value; + u8 evt_freq; +}; + +struct nxpwifi_ds_misc_subsc_evt { + u16 action; + u16 events; + struct subsc_evt_cfg bcn_l_rssi_cfg; + struct subsc_evt_cfg bcn_h_rssi_cfg; +}; + +#define NXPWIFI_MEF_MAX_BYTESEQ 6 /* non-adjustable */ +#define NXPWIFI_MEF_MAX_FILTERS 10 + +struct nxpwifi_mef_filter { + u16 repeat; + u16 offset; + s8 byte_seq[NXPWIFI_MEF_MAX_BYTESEQ + 1]; + u8 filt_type; + u8 filt_action; +}; + +struct nxpwifi_mef_entry { + u8 mode; + u8 action; + struct nxpwifi_mef_filter filter[NXPWIFI_MEF_MAX_FILTERS]; +}; + +struct nxpwifi_ds_mef_cfg { + u32 criteria; + u16 num_entries; + struct nxpwifi_mef_entry *mef_entry; +}; + +#define NXPWIFI_MAX_VSIE_LEN (256) +#define NXPWIFI_MAX_VSIE_NUM (8) +#define NXPWIFI_VSIE_MASK_CLEAR 0x00 +#define NXPWIFI_VSIE_MASK_SCAN 0x01 +#define NXPWIFI_VSIE_MASK_ASSOC 0x02 +#define NXPWIFI_VSIE_MASK_BGSCAN 0x08 + +enum { + NXPWIFI_FUNC_INIT = 1, + NXPWIFI_FUNC_SHUTDOWN, +}; + +enum COALESCE_OPERATION { + RECV_FILTER_MATCH_TYPE_EQ = 0x80, + RECV_FILTER_MATCH_TYPE_NE, +}; + +enum COALESCE_PACKET_TYPE { + PACKET_TYPE_UNICAST = 1, + PACKET_TYPE_MULTICAST = 2, + PACKET_TYPE_BROADCAST = 3 +}; + +#define NXPWIFI_COALESCE_MAX_RULES 8 +#define NXPWIFI_COALESCE_MAX_BYTESEQ 4 /* non-adjustable */ +#define NXPWIFI_COALESCE_MAX_FILTERS 4 +#define NXPWIFI_MAX_COALESCING_DELAY 100 /* in msecs */ + +struct filt_field_param { + u8 operation; + u8 operand_len; + u16 offset; + u8 operand_byte_stream[NXPWIFI_COALESCE_MAX_BYTESEQ]; +}; + +struct nxpwifi_coalesce_rule { + u16 max_coalescing_delay; + u8 num_of_fields; + u8 pkt_type; + struct filt_field_param params[NXPWIFI_COALESCE_MAX_FILTERS]; +}; + +struct nxpwifi_ds_coalesce_cfg { + u16 num_of_rules; + struct nxpwifi_coalesce_rule rule[NXPWIFI_COALESCE_MAX_RULES]; +}; + +struct nxpwifi_11ax_he_cap_cfg { + u16 id; + u16 len; + u8 ext_id; + struct ieee80211_he_cap_elem cap_elem; + u8 he_txrx_mcs_support[4]; + u8 val[28]; +}; + +#define HE_CAP_MAX_SIZE 54 + +struct nxpwifi_11ax_he_cfg { + u8 band; + union { + struct nxpwifi_11ax_he_cap_cfg he_cap_cfg; + u8 data[HE_CAP_MAX_SIZE]; + }; +}; + +#define NXPWIFI_11AXCMD_CFG_ID_SR_OBSS_PD_OFFSET 1 +#define NXPWIFI_11AXCMD_CFG_ID_SR_ENABLE 2 +#define NXPWIFI_11AXCMD_CFG_ID_BEAM_CHANGE 3 +#define NXPWIFI_11AXCMD_CFG_ID_HTC_ENABLE 4 +#define NXPWIFI_11AXCMD_CFG_ID_TXOP_RTS 5 +#define NXPWIFI_11AXCMD_CFG_ID_TX_OMI 6 +#define NXPWIFI_11AXCMD_CFG_ID_OBSSNBRU_TOLTIME 7 +#define NXPWIFI_11AXCMD_CFG_ID_SET_BSRP 8 +#define NXPWIFI_11AXCMD_CFG_ID_LLDE 9 + +#define NXPWIFI_11AXCMD_SR_SUBID 0x102 +#define NXPWIFI_11AXCMD_BEAM_SUBID 0x103 +#define NXPWIFI_11AXCMD_HTC_SUBID 0x104 +#define NXPWIFI_11AXCMD_TXOMI_SUBID 0x105 +#define NXPWIFI_11AXCMD_OBSS_TOLTIME_SUBID 0x106 +#define NXPWIFI_11AXCMD_TXOPRTS_SUBID 0x108 +#define NXPWIFI_11AXCMD_SET_BSRP_SUBID 0x109 +#define NXPWIFI_11AXCMD_LLDE_SUBID 0x110 + +#define NXPWIFI_11AX_TWT_SETUP_SUBID 0x114 +#define NXPWIFI_11AX_TWT_TEARDOWN_SUBID 0x115 +#define NXPWIFI_11AX_TWT_REPORT_SUBID 0x116 +#define NXPWIFI_11AX_TWT_INFORMATION_SUBID 0x119 +#define NXPWIFI_11AX_BTWT_AP_CONFIG_SUBID 0x120 +#define BTWT_AGREEMENT_MAX 5 + +struct nxpwifi_11axcmdcfg_obss_pd_offset { + /* */ + u8 offset[2]; +}; + +struct nxpwifi_11axcmdcfg_sr_control { + /* 1 enable, 0 disable */ + u8 control; +}; + +struct nxpwifi_11ax_sr_cmd { + /* type */ + u16 type; + /* length of TLV */ + u16 len; + /* value */ + union { + struct nxpwifi_11axcmdcfg_obss_pd_offset obss_pd_offset; + struct nxpwifi_11axcmdcfg_sr_control sr_control; + } param; +}; + +struct nxpwifi_11ax_beam_cmd { + /* command value: 1 is disable, 0 is enable */ + u8 value; +}; + +struct nxpwifi_11ax_htc_cmd { + /* command value: 1 is enable, 0 is disable */ + u8 value; +}; + +struct nxpwifi_11ax_txomi_cmd { + /* 11ax spec 9.2.4.6a.2 OM Control 12 bits. Bit 0 to bit 11 */ + u16 omi; + /* + * tx option + * 0: send OMI in QoS NULL; 1: send OMI in QoS data; 0xFF: set OMI in + * both + */ + u8 tx_option; + /* + * if OMI is sent in QoS data, specify the number of consecutive data + * packets containing the OMI + */ + u8 num_data_pkts; +}; + +struct nxpwifi_11ax_toltime_cmd { + /* OBSS Narrow Bandwidth RU Tolerance Time */ + u32 tol_time; +}; + +struct nxpwifi_11ax_txop_cmd { + /* + * Two byte rts threshold value of which only 10 bits, bit 0 to bit 9 + * are valid + */ + u16 rts_thres; +}; + +struct nxpwifi_11ax_set_bsrp_cmd { + /* command value: 1 is enable, 0 is disable */ + u8 value; +}; + +struct nxpwifi_11ax_llde_cmd { + /* Uplink LLDE: enable=1,disable=0 */ + u8 llde; + /* operation mode: default=0,carplay=1,gameplay=2 */ + u8 mode; + /* trigger frame rate: auto=0xff */ + u8 fixrate; + /* cap airtime limit index: auto=0xff */ + u8 trigger_limit; + /* cap peak UL rate */ + u8 peak_ul_rate; + /* Downlink LLDE: enable=1,disable=0 */ + u8 dl_llde; + /* Set trigger frame interval(us): auto=0 */ + u16 poll_interval; + /* Set TxOp duration */ + u16 tx_op_duration; + /* for other configurations */ + u16 llde_ctrl; + u16 mu_rts_successcnt; + u16 mu_rts_failcnt; + u16 basic_trigger_successcnt; + u16 basic_trigger_failcnt; + u16 tbppdu_nullcnt; + u16 tbppdu_datacnt; +}; + +struct nxpwifi_11ax_cmd_cfg { + u32 sub_command; + u32 sub_id; + union { + struct nxpwifi_11ax_sr_cmd sr_cfg; + struct nxpwifi_11ax_beam_cmd beam_cfg; + struct nxpwifi_11ax_htc_cmd htc_cfg; + struct nxpwifi_11ax_txomi_cmd txomi_cfg; + struct nxpwifi_11ax_toltime_cmd toltime_cfg; + struct nxpwifi_11ax_txop_cmd txop_cfg; + struct nxpwifi_11ax_set_bsrp_cmd setbsrp_cfg; + struct nxpwifi_11ax_llde_cmd llde_cfg; + } param; +}; + +struct nxpwifi_twt_setup { + /** Implicit, 0: TWT session is explicit, 1: Session is implicit */ + u8 implicit; + /** Announced, 0: Unannounced, 1: Announced TWT */ + u8 announced; + /** Trigger Enabled, 0: Non-Trigger enabled, 1: Trigger enabled TWT */ + u8 trigger_enabled; + /** TWT Information Disabled, 0: TWT info enabled, 1: TWT info disabled */ + u8 twt_info_disabled; + /* + * Negotiation Type, 0: Future Individual TWT SP start time, 1: + * Next Wake TBTT time + */ + u8 negotiation_type; + /* + * TWT Wakeup Duration, time after which the TWT requesting STA can + * transition to doze state + */ + u8 twt_wakeup_duration; + /** Flow Identifier. Range: [0-7]*/ + u8 flow_identifier; + /* + * Hard Constraint, 0: FW can tweak the TWT setup parameters if it is + * rejected by AP. + * 1: Firmware should not tweak any parameters. + */ + u8 hard_constraint; + /** TWT Exponent, Range: [0-63] */ + u8 twt_exponent; + /** TWT Mantissa Range: [0-sizeof(UINT16)] */ + __le16 twt_mantissa; + /** TWT Request Type, 0: REQUEST_TWT, 1: SUGGEST_TWT*/ + u8 twt_request; + /** TWT Setup State. Set to 0 by driver, filled by FW in response*/ + u8 twt_setup_state; + /** TWT link lost timeout threshold */ + __le16 bcn_miss_threshold; +} __packed; + +struct nxpwifi_twt_teardown { + /** TWT Flow Identifier. Range: [0-7] */ + u8 flow_identifier; + /* + * Negotiation Type. 0: Future Individual TWT SP start time, 1: Next + * Wake TBTT time + */ + u8 negotiation_type; + /** Tear down all TWT. 1: To teardown all TWT, 0 otherwise */ + u8 teardown_all_twt; + /** TWT Teardown State. Set to 0 by driver, filled by FW in response */ + u8 twt_teardown_state; + /** Reserved, set to 0. */ + u8 reserved[3]; +} __packed; + +#define NXPWIFI_BTWT_REPORT_LEN 9 +#define NXPWIFI_BTWT_REPORT_MAX_NUM 4 +struct nxpwifi_twt_report { + /** TWT report type, 0: BTWT id */ + u8 type; + /** TWT report length of value in data */ + u8 length; + u8 reserve[2]; + /** TWT report payload for FW response to fill */ + u8 data[NXPWIFI_BTWT_REPORT_LEN * NXPWIFI_BTWT_REPORT_MAX_NUM]; +} __packed; + +struct nxpwifi_twt_information { + /** TWT Flow Identifier. Range: [0-7] */ + u8 flow_identifier; + /* + * Suspend Duration. Range: [0-UINT32_MAX] + * 0:Suspend forever; + * Else:Suspend agreement for specific duration in milli seconds, + * after than resume the agreement and enter SP immediately + */ + __le32 suspend_duration; + /** TWT Information State. Set to 0 by driver, filled by FW in response */ + u8 twt_information_state; +} __packed; + +struct btwt_set { + u8 btwt_id; + __le16 ap_bcast_mantissa; + u8 ap_bcast_exponent; + u8 nominalwake; +} __packed; + +#define BTWT_AGREEMENT_MAX 5 +struct nxpwifi_btwt_ap_config { + u8 ap_bcast_bet_sta_wait; + __le16 ap_bcast_offset; + u8 bcast_twtli; + u8 count; + struct btwt_set btwt_sets[BTWT_AGREEMENT_MAX]; +} __packed; + +struct nxpwifi_twt_cfg { + u16 action; + u16 sub_id; + union { + struct nxpwifi_twt_setup twt_setup; + struct nxpwifi_twt_teardown twt_teardown; + struct nxpwifi_twt_report twt_report; + struct nxpwifi_twt_information twt_information; + struct nxpwifi_btwt_ap_config btwt_ap_config; + } param; +}; +#endif /* !_NXPWIFI_CFG_H_ */ diff --git a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c new file mode 100644 index 000000000000..6f48e2505340 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c @@ -0,0 +1,3931 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: cfg80211 support + * + * Copyright 2011-2024 NXP + */ + +#include "cfg80211.h" +#include "main.h" +#include "cmdevt.h" +#include "11n.h" +#include "wmm.h" + +static const struct ieee80211_iface_limit nxpwifi_ap_sta_limits[] = { + { + .max = NXPWIFI_MAX_BSS_NUM, + .types = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_MONITOR), + }, +}; + +static const struct ieee80211_iface_combination +nxpwifi_iface_comb_ap_sta = { + .limits = nxpwifi_ap_sta_limits, + .num_different_channels = 1, + .n_limits = ARRAY_SIZE(nxpwifi_ap_sta_limits), + .max_interfaces = NXPWIFI_MAX_BSS_NUM, + .beacon_int_infra_match = true, + .radar_detect_widths = BIT(NL80211_CHAN_WIDTH_20_NOHT) | + BIT(NL80211_CHAN_WIDTH_20) | + BIT(NL80211_CHAN_WIDTH_40), +}; + +static const struct ieee80211_iface_combination +nxpwifi_iface_comb_ap_sta_vht = { + .limits = nxpwifi_ap_sta_limits, + .num_different_channels = 1, + .n_limits = ARRAY_SIZE(nxpwifi_ap_sta_limits), + .max_interfaces = NXPWIFI_MAX_BSS_NUM, + .beacon_int_infra_match = true, + .radar_detect_widths = BIT(NL80211_CHAN_WIDTH_20_NOHT) | + BIT(NL80211_CHAN_WIDTH_20) | + BIT(NL80211_CHAN_WIDTH_40) | + BIT(NL80211_CHAN_WIDTH_80), +}; + +/* Map nl80211 channel types to secondary channel offsets */ +u8 nxpwifi_chan_type_to_sec_chan_offset(enum nl80211_channel_type chan_type) +{ + switch (chan_type) { + case NL80211_CHAN_NO_HT: + case NL80211_CHAN_HT20: + return IEEE80211_HT_PARAM_CHA_SEC_NONE; + case NL80211_CHAN_HT40PLUS: + return IEEE80211_HT_PARAM_CHA_SEC_ABOVE; + case NL80211_CHAN_HT40MINUS: + return IEEE80211_HT_PARAM_CHA_SEC_BELOW; + default: + return IEEE80211_HT_PARAM_CHA_SEC_NONE; + } +} + +/* Map IEEE HT secondary‑channel type to nl80211 channel type */ +u8 nxpwifi_get_chan_type(struct nxpwifi_private *priv) +{ + struct nxpwifi_channel_band channel_band; + int ret; + + ret = nxpwifi_get_chan_info(priv, &channel_band); + + if (!ret) { + switch (channel_band.band_config.chan_width) { + case CHAN_BW_20MHZ: + if (IS_11N_ENABLED(priv)) + return NL80211_CHAN_HT20; + else + return NL80211_CHAN_NO_HT; + case CHAN_BW_40MHZ: + if (channel_band.band_config.chan2_offset == + IEEE80211_HT_PARAM_CHA_SEC_ABOVE) + return NL80211_CHAN_HT40PLUS; + else + return NL80211_CHAN_HT40MINUS; + default: + return NL80211_CHAN_HT20; + } + } + + return NL80211_CHAN_HT20; +} + +/* Retrieve the driver private data from the wiphy */ +static void *nxpwifi_cfg80211_get_adapter(struct wiphy *wiphy) +{ + return (void *)(*(unsigned long *)wiphy_priv(wiphy)); +} + +/* cfg80211 operation handler to delete a network key. */ +static int +nxpwifi_cfg80211_del_key(struct wiphy *wiphy, struct wireless_dev *wdev, + int link_id, u8 key_index, bool pairwise, + const u8 *mac_addr) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + static const u8 bc_mac[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + const u8 *peer_mac = pairwise ? mac_addr : bc_mac; + int ret; + + ret = nxpwifi_set_encode(priv, NULL, NULL, 0, key_index, peer_mac, 1); + if (ret) + nxpwifi_dbg(priv->adapter, ERROR, + "crypto keys deleted failed %d\n", ret); + else + nxpwifi_dbg(priv->adapter, INFO, "info: crypto keys deleted\n"); + + return ret; +} + +/* Build an skb containing a management frame */ +static void +nxpwifi_form_mgmt_frame(struct sk_buff *skb, const u8 *buf, size_t len) +{ + u8 addr[ETH_ALEN]; + u16 pkt_len; + u32 tx_control = 0, pkt_type = PKT_TYPE_MGMT; + + eth_broadcast_addr(addr); + pkt_len = len + ETH_ALEN; + + skb_reserve(skb, NXPWIFI_MIN_DATA_HEADER_LEN + + NXPWIFI_MGMT_FRAME_HEADER_SIZE + sizeof(pkt_len)); + memcpy(skb_push(skb, sizeof(pkt_len)), &pkt_len, sizeof(pkt_len)); + + memcpy(skb_push(skb, sizeof(tx_control)), + &tx_control, sizeof(tx_control)); + + memcpy(skb_push(skb, sizeof(pkt_type)), &pkt_type, sizeof(pkt_type)); + + /* Add packet data and address4 */ + skb_put_data(skb, buf, sizeof(struct ieee80211_hdr_3addr)); + skb_put_data(skb, addr, ETH_ALEN); + skb_put_data(skb, buf + sizeof(struct ieee80211_hdr_3addr), + len - sizeof(struct ieee80211_hdr_3addr)); + + skb->priority = TC_PRIO_BESTEFFORT; + __net_timestamp(skb); +} + +/* cfg80211 operation handler to transmit a management frame. */ +static int +nxpwifi_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, + struct cfg80211_mgmt_tx_params *params, u64 *cookie) +{ + const u8 *buf = params->buf; + size_t len = params->len; + struct sk_buff *skb; + u16 pkt_len; + const struct ieee80211_mgmt *mgmt; + struct nxpwifi_txinfo *tx_info; + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + + if (!buf || !len) { + nxpwifi_dbg(priv->adapter, ERROR, "invalid buffer and length\n"); + return -EINVAL; + } + + mgmt = (const struct ieee80211_mgmt *)buf; + if (GET_BSS_ROLE(priv) != NXPWIFI_BSS_ROLE_STA && + ieee80211_is_probe_resp(mgmt->frame_control)) { + /* Offloaded probe responses; skip TX in AP/GO mode */ + nxpwifi_dbg(priv->adapter, INFO, + "info: skip to send probe resp in AP or GO mode\n"); + return 0; + } + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + if (ieee80211_is_auth(mgmt->frame_control)) + nxpwifi_dbg(priv->adapter, MSG, + "auth: send auth to %pM\n", mgmt->da); + if (ieee80211_is_deauth(mgmt->frame_control)) + nxpwifi_dbg(priv->adapter, MSG, + "auth: send deauth to %pM\n", mgmt->da); + if (ieee80211_is_disassoc(mgmt->frame_control)) + nxpwifi_dbg(priv->adapter, MSG, + "assoc: send disassoc to %pM\n", mgmt->da); + if (ieee80211_is_assoc_resp(mgmt->frame_control)) + nxpwifi_dbg(priv->adapter, MSG, + "assoc: send assoc resp to %pM\n", + mgmt->da); + if (ieee80211_is_reassoc_resp(mgmt->frame_control)) + nxpwifi_dbg(priv->adapter, MSG, + "assoc: send reassoc resp to %pM\n", + mgmt->da); + } + + pkt_len = len + ETH_ALEN; + skb = dev_alloc_skb(NXPWIFI_MIN_DATA_HEADER_LEN + + NXPWIFI_MGMT_FRAME_HEADER_SIZE + + pkt_len + sizeof(pkt_len)); + + if (!skb) { + nxpwifi_dbg(priv->adapter, ERROR, + "allocate skb failed for management frame\n"); + return -ENOMEM; + } + + tx_info = NXPWIFI_SKB_TXCB(skb); + memset(tx_info, 0, sizeof(*tx_info)); + tx_info->bss_num = priv->bss_num; + tx_info->bss_type = priv->bss_type; + tx_info->pkt_len = pkt_len; + + nxpwifi_form_mgmt_frame(skb, buf, len); + *cookie = nxpwifi_roc_cookie(priv->adapter); + + if (ieee80211_is_action(mgmt->frame_control)) + skb = nxpwifi_clone_skb_for_tx_status(priv, + skb, + NXPWIFI_BUF_FLAG_ACTION_TX_STATUS, cookie); + else + cfg80211_mgmt_tx_status(wdev, *cookie, buf, len, true, + GFP_ATOMIC); + + nxpwifi_queue_tx_pkt(priv, skb); + + nxpwifi_dbg(priv->adapter, INFO, "info: management frame transmitted\n"); + return 0; +} + +/* cfg80211 operation handler to register a mgmt frame. */ +static void +nxpwifi_cfg80211_update_mgmt_frame_registrations(struct wiphy *wiphy, + struct wireless_dev *wdev, + struct mgmt_frame_regs *upd) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + u32 mask = upd->interface_stypes; + + if (mask != priv->mgmt_frame_mask) { + priv->mgmt_frame_mask = mask; + if (priv->host_mlme_reg && + GET_BSS_ROLE(priv) != NXPWIFI_BSS_ROLE_UAP) + priv->mgmt_frame_mask |= HOST_MLME_MGMT_MASK; + + nxpwifi_mgmt_frame_reg(priv, priv->mgmt_frame_mask); + + nxpwifi_dbg(priv->adapter, INFO, "info: mgmt frame registered\n"); + } +} + +/* cfg80211 operation handler to remain on channel. */ +static int +nxpwifi_cfg80211_remain_on_channel(struct wiphy *wiphy, + struct wireless_dev *wdev, + struct ieee80211_channel *chan, + unsigned int duration, u64 *cookie, + const u8 *rx_addr) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + struct nxpwifi_adapter *adapter = priv->adapter; + int ret; + + if (!chan || !cookie) { + nxpwifi_dbg(adapter, ERROR, "Invalid parameter for ROC\n"); + return -EINVAL; + } + + if (priv->roc_cfg.cookie) { + nxpwifi_dbg(adapter, INFO, + "info: ongoing ROC, cookie = 0x%llx\n", + priv->roc_cfg.cookie); + return -EBUSY; + } + + ret = nxpwifi_remain_on_chan_cfg(priv, HOST_ACT_GEN_SET, chan, + duration); + + if (!ret) { + *cookie = nxpwifi_roc_cookie(adapter); + priv->roc_cfg.cookie = *cookie; + priv->roc_cfg.chan = *chan; + + cfg80211_ready_on_channel(wdev, *cookie, chan, + duration, GFP_ATOMIC); + + nxpwifi_dbg(adapter, INFO, + "info: ROC, cookie = 0x%llx\n", *cookie); + } + + return ret; +} + +/* cfg80211 operation handler to cancel remain on channel. */ +static int +nxpwifi_cfg80211_cancel_remain_on_channel(struct wiphy *wiphy, + struct wireless_dev *wdev, u64 cookie) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + int ret; + + if (cookie != priv->roc_cfg.cookie) + return -ENOENT; + + ret = nxpwifi_remain_on_chan_cfg(priv, HOST_ACT_GEN_REMOVE, + &priv->roc_cfg.chan, 0); + + if (!ret) { + cfg80211_remain_on_channel_expired(wdev, cookie, + &priv->roc_cfg.chan, + GFP_ATOMIC); + + memset(&priv->roc_cfg, 0, sizeof(struct nxpwifi_roc_cfg)); + + nxpwifi_dbg(priv->adapter, INFO, + "info: cancel ROC, cookie = 0x%llx\n", cookie); + } + + return ret; +} + +/* cfg80211 operation handler to set Tx power. */ +static int +nxpwifi_cfg80211_set_tx_power(struct wiphy *wiphy, + struct wireless_dev *wdev, + int radio_idx, + enum nl80211_tx_power_setting type, + int mbm) +{ + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + struct nxpwifi_private *priv; + struct nxpwifi_power_cfg power_cfg; + int dbm = MBM_TO_DBM(mbm); + + switch (type) { + case NL80211_TX_POWER_FIXED: + power_cfg.is_power_auto = 0; + power_cfg.is_power_fixed = 1; + power_cfg.power_level = dbm; + break; + case NL80211_TX_POWER_LIMITED: + power_cfg.is_power_auto = 0; + power_cfg.is_power_fixed = 0; + power_cfg.power_level = dbm; + break; + case NL80211_TX_POWER_AUTOMATIC: + power_cfg.is_power_auto = 1; + break; + } + + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + + return nxpwifi_set_tx_power(priv, &power_cfg); +} + +/* cfg80211 operation handler to get Tx power. */ +static int +nxpwifi_cfg80211_get_tx_power(struct wiphy *wiphy, + struct wireless_dev *wdev, + int radio_idx, + unsigned int link_id, + int *dbm) +{ + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + struct nxpwifi_private *priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + int ret = nxpwifi_get_tx_pwr(priv); + + if (ret < 0) + return ret; + + *dbm = priv->tx_power_level; + + return 0; +} + +/* + * cfg80211 handler for setting IEEE 802.11 Power Save mode. + * + * The 'timeout' parameter is not supported and is ignored. + */ +static int +nxpwifi_cfg80211_set_power_mgmt(struct wiphy *wiphy, + struct net_device *dev, + bool enabled, int timeout) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + u32 ps_mode; + + if (timeout) + nxpwifi_dbg(priv->adapter, INFO, + "info: ignore timeout value for IEEE Power Save\n"); + + ps_mode = enabled; + + return nxpwifi_drv_set_power(priv, &ps_mode); +} + +/* + * cfg80211 handler for setting the default WEP key. + * + * Ignored if WEP is not enabled. + */ +static int +nxpwifi_cfg80211_set_default_key(struct wiphy *wiphy, struct net_device *netdev, + int link_id, u8 key_index, bool unicast, + bool multicast) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(netdev); + int ret = 0; + + /* Return if WEP key not configured */ + if (!priv->sec_info.wep_enabled) + return 0; + + if (priv->bss_type == NXPWIFI_BSS_TYPE_UAP) { + priv->wep_key_curr_index = key_index; + } else { + ret = nxpwifi_set_encode(priv, NULL, NULL, 0, key_index, + NULL, 0); + if (ret) + nxpwifi_dbg(priv->adapter, ERROR, + "failed to set default Tx key index\n"); + } + + return ret; +} + +/* cfg80211 handler for adding an 802.11 encryption key. */ +static int +nxpwifi_cfg80211_add_key(struct wiphy *wiphy, struct wireless_dev *wdev, + int link_id, u8 key_index, bool pairwise, + const u8 *mac_addr, struct key_params *params) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + struct nxpwifi_wep_key *wep_key; + u8 bc_mac[ETH_ALEN]; + const u8 *peer_mac; + int ret; + + eth_broadcast_addr(bc_mac); + peer_mac = pairwise ? mac_addr : bc_mac; + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP && + (params->cipher == WLAN_CIPHER_SUITE_WEP40 || + params->cipher == WLAN_CIPHER_SUITE_WEP104)) { + if (params->key && params->key_len) { + wep_key = &priv->wep_key[key_index]; + memset(wep_key, 0, sizeof(struct nxpwifi_wep_key)); + memcpy(wep_key->key_material, params->key, + params->key_len); + wep_key->key_index = key_index; + wep_key->key_length = params->key_len; + priv->sec_info.wep_enabled = 1; + } + return 0; + } + + ret = nxpwifi_set_encode(priv, params, params->key, params->key_len, + key_index, peer_mac, 0); + if (ret) + nxpwifi_dbg(priv->adapter, ERROR, + "failed to add crypto keys\n"); + + return ret; +} + +/* cfg80211 handler for setting the default management key. */ +static int +nxpwifi_cfg80211_set_default_mgmt_key(struct wiphy *wiphy, + struct wireless_dev *wdev, + int link_id, + u8 key_index) +{ + return 0; +} + +/* + * Sends regulatory domain information to the firmware. + * + * Includes: + * - Country code + * - Sub-band definitions (first channel, channel count, max TX power) + */ +int nxpwifi_send_domain_info_cmd_fw(struct wiphy *wiphy, enum nl80211_band band) +{ + u8 no_of_triplet = 0; + struct ieee80211_country_ie_triplet *t; + u8 no_of_parsed_chan = 0; + u8 first_chan = 0, next_chan = 0, max_pwr = 0; + u8 i, flag = 0; + struct ieee80211_supported_band *sband; + struct ieee80211_channel *ch; + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + struct nxpwifi_private *priv; + struct nxpwifi_802_11d_domain_reg *domain_info = &adapter->domain_reg; + int ret; + + domain_info->dfs_region = adapter->dfs_region; + + /* Set country code */ + domain_info->country_code[0] = adapter->country_code[0]; + domain_info->country_code[1] = adapter->country_code[1]; + domain_info->country_code[2] = ' '; + + if (!wiphy->bands[band]) { + nxpwifi_dbg(adapter, ERROR, + "11D: setting domain info in FW\n"); + return -EINVAL; + } + + sband = wiphy->bands[band]; + + for (i = 0; i < sband->n_channels ; i++) { + ch = &sband->channels[i]; + if (ch->flags & IEEE80211_CHAN_DISABLED) + continue; + + if (!flag) { + flag = 1; + first_chan = (u32)ch->hw_value; + next_chan = first_chan; + max_pwr = ch->max_power; + no_of_parsed_chan = 1; + continue; + } + + if (ch->hw_value == next_chan + 1 && + ch->max_power == max_pwr) { + next_chan++; + no_of_parsed_chan++; + } else { + t = &domain_info->triplet[no_of_triplet]; + t->chans.first_channel = first_chan; + t->chans.num_channels = no_of_parsed_chan; + t->chans.max_power = max_pwr; + no_of_triplet++; + first_chan = (u32)ch->hw_value; + next_chan = first_chan; + max_pwr = ch->max_power; + no_of_parsed_chan = 1; + } + } + + if (flag) { + t = &domain_info->triplet[no_of_triplet]; + t->chans.first_channel = first_chan; + t->chans.num_channels = no_of_parsed_chan; + t->chans.max_power = max_pwr; + no_of_triplet++; + } + + domain_info->no_of_triplet = no_of_triplet; + + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + + ret = nxpwifi_apply_regdomain(priv); + + if (ret) + nxpwifi_dbg(adapter, INFO, + "11D: failed to set domain info in FW\n"); + + return ret; +} + +static void nxpwifi_reg_apply_radar_flags(struct wiphy *wiphy) +{ + struct ieee80211_supported_band *sband; + struct ieee80211_channel *chan; + unsigned int i; + + if (!wiphy->bands[NL80211_BAND_5GHZ]) + return; + sband = wiphy->bands[NL80211_BAND_5GHZ]; + + for (i = 0; i < sband->n_channels; i++) { + chan = &sband->channels[i]; + if ((!(chan->flags & IEEE80211_CHAN_DISABLED)) && + (chan->flags & IEEE80211_CHAN_RADAR)) + chan->flags |= IEEE80211_CHAN_NO_IR; + } +} + +/* + * cfg80211 regulatory domain change callback. + * + * Invoked when the regulatory domain is updated by: + * - the driver + * - the system core + * - the user + * - a received Country IE + */ +static void nxpwifi_reg_notifier(struct wiphy *wiphy, + struct regulatory_request *request) +{ + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + + nxpwifi_dbg(adapter, INFO, + "info: cfg80211 regulatory domain callback for %c%c\n", + request->alpha2[0], request->alpha2[1]); + nxpwifi_reg_apply_radar_flags(wiphy); + + switch (request->initiator) { + case NL80211_REGDOM_SET_BY_DRIVER: + case NL80211_REGDOM_SET_BY_CORE: + case NL80211_REGDOM_SET_BY_USER: + case NL80211_REGDOM_SET_BY_COUNTRY_IE: + break; + default: + nxpwifi_dbg(adapter, ERROR, + "unknown regdom initiator: %d\n", + request->initiator); + return; + } + + /* Skip world/unchanged regulatory domains. */ + if (strncmp(request->alpha2, "00", 2) && + strncmp(request->alpha2, adapter->country_code, + sizeof(request->alpha2))) { + memcpy(adapter->country_code, request->alpha2, + sizeof(request->alpha2)); + adapter->dfs_region = request->dfs_region; + nxpwifi_send_domain_info_cmd_fw(wiphy, NL80211_BAND_2GHZ); + if (adapter->fw_bands & BAND_A) + nxpwifi_send_domain_info_cmd_fw(wiphy, + NL80211_BAND_5GHZ); + } +} + +/* + * cfg80211 op: set wiphy parameters. + * Updates RTS/fragmentation thresholds and retry limits based on 'changed' + * flags. + */ +static int +nxpwifi_cfg80211_set_wiphy_params(struct wiphy *wiphy, int radio_idx, u32 changed) +{ + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + struct nxpwifi_private *priv; + struct nxpwifi_uap_bss_param *bss_cfg; + int ret = 0; + + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + + switch (priv->bss_role) { + case NXPWIFI_BSS_ROLE_UAP: + bss_cfg = kzalloc_obj(*bss_cfg, GFP_KERNEL); + if (!bss_cfg) { + ret = -ENOMEM; + break; + } + + nxpwifi_set_sys_config_invalid_data(bss_cfg); + + if (changed & WIPHY_PARAM_RTS_THRESHOLD) + bss_cfg->rts_threshold = wiphy->rts_threshold; + if (changed & WIPHY_PARAM_FRAG_THRESHOLD) + bss_cfg->frag_threshold = wiphy->frag_threshold; + if (changed & WIPHY_PARAM_RETRY_LONG) + bss_cfg->retry_limit = wiphy->retry_long; + + ret = nxpwifi_set_uap_sys_cfg(priv, bss_cfg); + + kfree(bss_cfg); + if (ret) + nxpwifi_dbg(adapter, ERROR, + "Failed to set wiphy phy params\n"); + break; + + case NXPWIFI_BSS_ROLE_STA: + if (changed & WIPHY_PARAM_RTS_THRESHOLD) { + ret = nxpwifi_set_rts(priv, + wiphy->rts_threshold); + if (ret) + break; + } + if (changed & WIPHY_PARAM_FRAG_THRESHOLD) + ret = nxpwifi_set_frag(priv, + wiphy->frag_threshold); + break; + } + + return ret; +} + +static int nxpwifi_deinit_priv_params(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int ret = 0; + + if (priv->mgmt_frame_mask) { + priv->mgmt_frame_mask = 0; + ret = nxpwifi_mgmt_frame_reg(priv, priv->mgmt_frame_mask); + + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "could not unregister mgmt frame rx\n"); + return ret; + } + priv->host_mlme_reg = false; + + } + + nxpwifi_deauthenticate(priv, NULL); + + atomic_set(&adapter->iface_changing, 1); + flush_workqueue(adapter->workqueue); + flush_workqueue(adapter->rx_workqueue); + nxpwifi_free_priv(priv); + priv->wdev.iftype = NL80211_IFTYPE_UNSPECIFIED; + priv->bss_mode = NL80211_IFTYPE_UNSPECIFIED; + priv->sec_info.authentication_mode = NL80211_AUTHTYPE_OPEN_SYSTEM; + + return ret; +} + +static int +nxpwifi_init_new_priv_params(struct nxpwifi_private *priv, + struct net_device *dev, + enum nl80211_iftype type) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + nxpwifi_init_priv(priv); + + priv->bss_mode = type; + priv->wdev.iftype = type; + + nxpwifi_init_priv_params(priv, priv->netdev); + priv->bss_started = 0; + + switch (type) { + case NL80211_IFTYPE_STATION: + priv->bss_role = NXPWIFI_BSS_ROLE_STA; + break; + case NL80211_IFTYPE_AP: + priv->bss_role = NXPWIFI_BSS_ROLE_UAP; + break; + default: + nxpwifi_dbg(adapter, ERROR, + "%s: changing to %d not supported\n", + dev->name, type); + return -EOPNOTSUPP; + } + + priv->bss_num = nxpwifi_get_unused_bss_num(adapter, priv->bss_type); + + flush_workqueue(adapter->workqueue); + atomic_set(&adapter->iface_changing, 0); + + nxpwifi_set_mac_address(priv, dev, false, NULL); + + return 0; +} + +static bool +is_vif_type_change_allowed(struct nxpwifi_adapter *adapter, + enum nl80211_iftype old_iftype, + enum nl80211_iftype new_iftype) +{ + switch (old_iftype) { + case NL80211_IFTYPE_STATION: + switch (new_iftype) { + case NL80211_IFTYPE_AP: + return adapter->curr_iface_comb.uap_intf != + adapter->iface_limit.uap_intf; + default: + return false; + } + + case NL80211_IFTYPE_AP: + switch (new_iftype) { + case NL80211_IFTYPE_STATION: + return adapter->curr_iface_comb.sta_intf != + adapter->iface_limit.sta_intf; + default: + return false; + } + + default: + break; + } + + return false; +} + +static void +update_vif_type_counter(struct nxpwifi_adapter *adapter, + enum nl80211_iftype iftype, + int change) +{ + switch (iftype) { + case NL80211_IFTYPE_UNSPECIFIED: + case NL80211_IFTYPE_STATION: + adapter->curr_iface_comb.sta_intf += change; + break; + case NL80211_IFTYPE_AP: + adapter->curr_iface_comb.uap_intf += change; + break; + case NL80211_IFTYPE_MONITOR: + break; + default: + nxpwifi_dbg(adapter, ERROR, + "%s: Unsupported iftype passed: %d\n", + __func__, iftype); + break; + } +} + +static int +nxpwifi_change_vif_to_sta(struct net_device *dev, + enum nl80211_iftype curr_iftype, + enum nl80211_iftype type, + struct vif_params *params) +{ + struct nxpwifi_private *priv; + struct nxpwifi_adapter *adapter; + int ret; + + priv = nxpwifi_netdev_get_priv(dev); + + if (!priv) + return -EINVAL; + + adapter = priv->adapter; + + nxpwifi_dbg(adapter, INFO, + "%s: changing role to station\n", dev->name); + + ret = nxpwifi_deinit_priv_params(priv); + if (ret) + goto done; + ret = nxpwifi_init_new_priv_params(priv, dev, type); + if (ret) + goto done; + + update_vif_type_counter(adapter, curr_iftype, -1); + update_vif_type_counter(adapter, type, 1); + dev->ieee80211_ptr->iftype = type; + + if (nxpwifi_set_bss_mode(priv)) + return -1; + + if (ret) + goto done; + + ret = nxpwifi_sta_init_cmd(priv, false, false); + +done: + return ret; +} + +static int +nxpwifi_change_vif_to_ap(struct net_device *dev, + enum nl80211_iftype curr_iftype, + enum nl80211_iftype type, + struct vif_params *params) +{ + struct nxpwifi_private *priv; + struct nxpwifi_adapter *adapter; + int ret; + + priv = nxpwifi_netdev_get_priv(dev); + + if (!priv) + return -EINVAL; + + adapter = priv->adapter; + + nxpwifi_dbg(adapter, INFO, + "%s: changing role to AP\n", dev->name); + + ret = nxpwifi_deinit_priv_params(priv); + if (ret) + goto done; + + ret = nxpwifi_init_new_priv_params(priv, dev, type); + if (ret) + goto done; + + update_vif_type_counter(adapter, curr_iftype, -1); + update_vif_type_counter(adapter, type, 1); + dev->ieee80211_ptr->iftype = type; + + if (nxpwifi_set_bss_mode(priv)) + return -1; + + if (ret) + goto done; + + ret = nxpwifi_sta_init_cmd(priv, false, false); + +done: + return ret; +} + +/* cfg80211 operation handler to change interface type. */ +static int +nxpwifi_cfg80211_change_virtual_intf(struct wiphy *wiphy, + struct net_device *dev, + enum nl80211_iftype type, + struct vif_params *params) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + enum nl80211_iftype curr_iftype = dev->ieee80211_ptr->iftype; + + if (priv->scan_request) { + nxpwifi_dbg(priv->adapter, ERROR, + "change virtual interface: scan in process\n"); + return -EBUSY; + } + + if (type == NL80211_IFTYPE_UNSPECIFIED) { + nxpwifi_dbg(priv->adapter, INFO, + "%s: no new type specified, keeping old type %d\n", + dev->name, curr_iftype); + return 0; + } + + if (curr_iftype == type) { + nxpwifi_dbg(priv->adapter, INFO, + "%s: interface already is of type %d\n", + dev->name, curr_iftype); + return 0; + } + + if (!is_vif_type_change_allowed(priv->adapter, curr_iftype, type)) { + nxpwifi_dbg(priv->adapter, ERROR, + "%s: change from type %d to %d is not allowed\n", + dev->name, curr_iftype, type); + return -EOPNOTSUPP; + } + + switch (curr_iftype) { + case NL80211_IFTYPE_STATION: + switch (type) { + case NL80211_IFTYPE_AP: + return nxpwifi_change_vif_to_ap(dev, curr_iftype, type, + params); + default: + goto errnotsupp; + } + + case NL80211_IFTYPE_AP: + switch (type) { + case NL80211_IFTYPE_STATION: + return nxpwifi_change_vif_to_sta(dev, curr_iftype, + type, params); + break; + default: + goto errnotsupp; + } + + default: + goto errnotsupp; + } + + return 0; + +errnotsupp: + nxpwifi_dbg(priv->adapter, ERROR, + "unsupported interface type transition: %d to %d\n", + curr_iftype, type); + return -EOPNOTSUPP; +} + +#define RATE_FORMAT_LG 0 +#define RATE_FORMAT_HT 1 +#define RATE_FORMAT_VHT 2 +#define RATE_FORMAT_HE 3 + +static void +nxpwifi_parse_htinfo(struct nxpwifi_private *priv, u8 rateinfo, u8 htinfo, + struct rate_info *rate) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u8 rate_format; + u8 he_dcm; + u8 stbc; + u8 gi; + u8 bw; + /* Bitrates in multiples of 100kb/s. */ + static const int legacy_rates[] = { + [0] = 10, + [1] = 20, + [2] = 55, + [3] = 110, + [4] = 60, /* NXPWIFI_RATE_INDEX_OFDM0 */ + [5] = 60, + [6] = 90, + [7] = 120, + [8] = 180, + [9] = 240, + [10] = 360, + [11] = 480, + [12] = 540, + }; + + rate_format = htinfo & 0x3; + + switch (rate_format) { + case RATE_FORMAT_LG: + if (rateinfo < ARRAY_SIZE(legacy_rates)) + rate->legacy = legacy_rates[rateinfo]; + break; + case RATE_FORMAT_HT: + rate->mcs = rateinfo; + rate->flags |= RATE_INFO_FLAGS_MCS; + break; + case RATE_FORMAT_VHT: + rate->mcs = rateinfo & 0xF; + rate->flags |= RATE_INFO_FLAGS_VHT_MCS; + break; + case RATE_FORMAT_HE: + rate->mcs = rateinfo & 0xF; + rate->flags |= RATE_INFO_FLAGS_HE_MCS; + he_dcm = 0; /* ToDo: ext_rate_info */ + gi = (htinfo & BIT(4)) >> 4 | + (htinfo & BIT(7)) >> 6; + stbc = (htinfo & BIT(5)) >> 5; + if (gi > 3) { + nxpwifi_dbg(adapter, ERROR, "Invalid gi value\n"); + break; + } + if (gi == 3 && stbc && he_dcm) { + gi = 0; + stbc = 0; + he_dcm = 0; + } + if (gi > 0) + gi -= 1; + rate->he_gi = gi; + rate->he_dcm = he_dcm; + break; + } + + bw = (htinfo & 0xC) >> 2; + + switch (bw) { + case 0: + rate->bw = RATE_INFO_BW_20; + break; + case 1: + rate->bw = RATE_INFO_BW_40; + break; + case 2: + rate->bw = RATE_INFO_BW_80; + break; + case 3: + rate->bw = RATE_INFO_BW_160; + break; + } + + if (rate_format != RATE_FORMAT_HE && (htinfo & BIT(4))) + rate->flags |= RATE_INFO_FLAGS_SHORT_GI; + + if ((rateinfo >> 4) == 1) + rate->nss = 2; + else + rate->nss = 1; +} + +/* + * Dump station statistics into station_info. + * Includes bytes/packets counters, signal level, and TX/RX rates. + */ +static int +nxpwifi_dump_station_info(struct nxpwifi_private *priv, + struct nxpwifi_sta_node *node, + struct station_info *sinfo) +{ + u32 rate; + int ret; + + sinfo->filled = BIT_ULL(NL80211_STA_INFO_RX_BYTES) | + BIT_ULL(NL80211_STA_INFO_TX_BYTES) | + BIT_ULL(NL80211_STA_INFO_RX_PACKETS) | + BIT_ULL(NL80211_STA_INFO_TX_PACKETS) | + BIT_ULL(NL80211_STA_INFO_TX_BITRATE) | + BIT_ULL(NL80211_STA_INFO_SIGNAL) | + BIT_ULL(NL80211_STA_INFO_SIGNAL_AVG); + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + if (!node) + return -ENOENT; + + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_INACTIVE_TIME) | + BIT_ULL(NL80211_STA_INFO_TX_FAILED); + sinfo->inactive_time = + jiffies_to_msecs(jiffies - node->stats.last_rx); + + sinfo->signal = node->stats.rssi; + sinfo->signal_avg = node->stats.rssi; + sinfo->rx_bytes = node->stats.rx_bytes; + sinfo->tx_bytes = node->stats.tx_bytes; + sinfo->rx_packets = node->stats.rx_packets; + sinfo->tx_packets = node->stats.tx_packets; + sinfo->tx_failed = node->stats.tx_failed; + + nxpwifi_parse_htinfo(priv, priv->tx_rate, + node->stats.last_tx_htinfo, + &sinfo->txrate); + sinfo->txrate.legacy = node->stats.last_tx_rate * 5; + + return 0; + } + + /* Get signal information from the firmware */ + ret = nxpwifi_get_rssi_info(priv); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "failed to get signal information\n"); + goto done; + } + + ret = nxpwifi_drv_get_data_rate(priv, &rate); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "getting data rate error\n"); + goto done; + } + + /* Retrieve DTIM period value from firmware. */ + nxpwifi_get_802_11_snmp_mib(priv, DTIM_PERIOD_I, &priv->dtim_period); + + nxpwifi_parse_htinfo(priv, priv->tx_rate, priv->tx_htinfo, + &sinfo->txrate); + + sinfo->signal_avg = priv->bcn_rssi_avg; + sinfo->rx_bytes = priv->stats.rx_bytes; + sinfo->tx_bytes = priv->stats.tx_bytes; + sinfo->rx_packets = priv->stats.rx_packets; + sinfo->tx_packets = priv->stats.tx_packets; + sinfo->signal = priv->bcn_rssi_avg; + /* Convert bitrate from 500 kb/s units to 100 kb/s units. */ + sinfo->txrate.legacy = rate * 5; + + sinfo->filled |= BIT(NL80211_STA_INFO_RX_BITRATE); + nxpwifi_parse_htinfo(priv, priv->rxpd_rate, priv->rxpd_htinfo, + &sinfo->rxrate); + + if (priv->bss_mode == NL80211_IFTYPE_STATION) { + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_BSS_PARAM); + sinfo->bss_param.flags = 0; + if (priv->curr_bss_params.bss_descriptor.cap_info_bitmap & + WLAN_CAPABILITY_SHORT_PREAMBLE) + sinfo->bss_param.flags |= + BSS_PARAM_FLAGS_SHORT_PREAMBLE; + if (priv->curr_bss_params.bss_descriptor.cap_info_bitmap & + WLAN_CAPABILITY_SHORT_SLOT_TIME) + sinfo->bss_param.flags |= + BSS_PARAM_FLAGS_SHORT_SLOT_TIME; + sinfo->bss_param.dtim_period = priv->dtim_period; + sinfo->bss_param.beacon_interval = + priv->curr_bss_params.bss_descriptor.beacon_period; + } + +done: + return ret; +} + +/* + * cfg80211 op: get station information. + * Works only when connected and fills station_info with current stats. + */ +static int +nxpwifi_cfg80211_get_station(struct wiphy *wiphy, struct wireless_dev *wdev, + const u8 *mac, struct station_info *sinfo) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + struct nxpwifi_sta_node *node; + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) { + if (!priv->media_connected || + memcmp(mac, priv->cfg_bssid, ETH_ALEN)) + return -ENOENT; + node = NULL; + } else { + rcu_read_lock(); + node = nxpwifi_get_sta_entry(priv, mac); + rcu_read_unlock(); + } + + return nxpwifi_dump_station_info(priv, node, sinfo); +} + +/* cfg80211 operation handler to dump station information. */ +static int +nxpwifi_cfg80211_dump_station(struct wiphy *wiphy, struct wireless_dev *wdev, + int idx, u8 *mac, struct station_info *sinfo) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + struct nxpwifi_sta_node *node; + struct nxpwifi_sta_node *found = NULL; + int i; + + if ((GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) && + priv->media_connected && idx == 0) { + ether_addr_copy(mac, priv->cfg_bssid); + return nxpwifi_dump_station_info(priv, NULL, sinfo); + } else if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + nxpwifi_ap_get_sta_list(priv); + + i = 0; + rcu_read_lock(); + list_for_each_entry_rcu(node, &priv->sta_list, list) { + if (i++ != idx) + continue; + found = node; + break; + } + rcu_read_unlock(); + + if (found) { + ether_addr_copy(mac, node->mac_addr); + return nxpwifi_dump_station_info(priv, node, sinfo); + } + } + + return -ENOENT; +} + +static int +nxpwifi_cfg80211_dump_survey(struct wiphy *wiphy, struct net_device *dev, + int idx, struct survey_info *survey) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + struct nxpwifi_chan_stats *pchan_stats = priv->adapter->chan_stats; + enum nl80211_band band; + u8 chan_num; + + nxpwifi_dbg(priv->adapter, DUMP, "dump_survey idx=%d\n", idx); + + memset(survey, 0, sizeof(struct survey_info)); + + if ((GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) && + priv->media_connected && idx == 0) { + u8 curr_bss_band = priv->curr_bss_params.band; + u32 chan = priv->curr_bss_params.bss_descriptor.channel; + + band = nxpwifi_band_to_radio_type(curr_bss_band); + survey->channel = ieee80211_get_channel + (wiphy, + ieee80211_channel_to_frequency(chan, band)); + + if (priv->bcn_nf_last) { + survey->filled = SURVEY_INFO_NOISE_DBM; + survey->noise = priv->bcn_nf_last; + } + return 0; + } + + if (idx >= priv->adapter->num_in_chan_stats) + return -ENOENT; + + if (!pchan_stats[idx].cca_scan_dur) + return 0; + + band = pchan_stats[idx].bandcfg; + chan_num = pchan_stats[idx].chan_num; + survey->channel = ieee80211_get_channel + (wiphy, + ieee80211_channel_to_frequency(chan_num, band)); + survey->filled = SURVEY_INFO_NOISE_DBM | + SURVEY_INFO_TIME | + SURVEY_INFO_TIME_BUSY; + survey->noise = pchan_stats[idx].noise; + survey->time = pchan_stats[idx].cca_scan_dur; + survey->time_busy = pchan_stats[idx].cca_busy_dur; + + return 0; +} + +/* Supported rates to be advertised to the cfg80211 */ +static struct ieee80211_rate nxpwifi_rates[] = { + {.bitrate = 10, .hw_value = 2, }, + {.bitrate = 20, .hw_value = 4, }, + {.bitrate = 55, .hw_value = 11, }, + {.bitrate = 110, .hw_value = 22, }, + {.bitrate = 60, .hw_value = 12, }, + {.bitrate = 90, .hw_value = 18, }, + {.bitrate = 120, .hw_value = 24, }, + {.bitrate = 180, .hw_value = 36, }, + {.bitrate = 240, .hw_value = 48, }, + {.bitrate = 360, .hw_value = 72, }, + {.bitrate = 480, .hw_value = 96, }, + {.bitrate = 540, .hw_value = 108, }, +}; + +/* Channel definitions to be advertised to cfg80211 */ +static struct ieee80211_channel nxpwifi_channels_2ghz[] = { + {.center_freq = 2412, .hw_value = 1, }, + {.center_freq = 2417, .hw_value = 2, }, + {.center_freq = 2422, .hw_value = 3, }, + {.center_freq = 2427, .hw_value = 4, }, + {.center_freq = 2432, .hw_value = 5, }, + {.center_freq = 2437, .hw_value = 6, }, + {.center_freq = 2442, .hw_value = 7, }, + {.center_freq = 2447, .hw_value = 8, }, + {.center_freq = 2452, .hw_value = 9, }, + {.center_freq = 2457, .hw_value = 10, }, + {.center_freq = 2462, .hw_value = 11, }, + {.center_freq = 2467, .hw_value = 12, }, + {.center_freq = 2472, .hw_value = 13, }, + {.center_freq = 2484, .hw_value = 14, }, +}; + +static struct ieee80211_supported_band nxpwifi_band_2ghz = { + .band = NL80211_BAND_2GHZ, + .channels = nxpwifi_channels_2ghz, + .n_channels = ARRAY_SIZE(nxpwifi_channels_2ghz), + .bitrates = nxpwifi_rates, + .n_bitrates = ARRAY_SIZE(nxpwifi_rates), +}; + +static struct ieee80211_channel nxpwifi_channels_5ghz[] = { + {.center_freq = 5040, .hw_value = 8, }, + {.center_freq = 5060, .hw_value = 12, }, + {.center_freq = 5080, .hw_value = 16, }, + {.center_freq = 5170, .hw_value = 34, }, + {.center_freq = 5190, .hw_value = 38, }, + {.center_freq = 5210, .hw_value = 42, }, + {.center_freq = 5230, .hw_value = 46, }, + {.center_freq = 5180, .hw_value = 36, }, + {.center_freq = 5200, .hw_value = 40, }, + {.center_freq = 5220, .hw_value = 44, }, + {.center_freq = 5240, .hw_value = 48, }, + {.center_freq = 5260, .hw_value = 52, }, + {.center_freq = 5280, .hw_value = 56, }, + {.center_freq = 5300, .hw_value = 60, }, + {.center_freq = 5320, .hw_value = 64, }, + {.center_freq = 5500, .hw_value = 100, }, + {.center_freq = 5520, .hw_value = 104, }, + {.center_freq = 5540, .hw_value = 108, }, + {.center_freq = 5560, .hw_value = 112, }, + {.center_freq = 5580, .hw_value = 116, }, + {.center_freq = 5600, .hw_value = 120, }, + {.center_freq = 5620, .hw_value = 124, }, + {.center_freq = 5640, .hw_value = 128, }, + {.center_freq = 5660, .hw_value = 132, }, + {.center_freq = 5680, .hw_value = 136, }, + {.center_freq = 5700, .hw_value = 140, }, + {.center_freq = 5745, .hw_value = 149, }, + {.center_freq = 5765, .hw_value = 153, }, + {.center_freq = 5785, .hw_value = 157, }, + {.center_freq = 5805, .hw_value = 161, }, + {.center_freq = 5825, .hw_value = 165, }, +}; + +static struct ieee80211_supported_band nxpwifi_band_5ghz = { + .band = NL80211_BAND_5GHZ, + .channels = nxpwifi_channels_5ghz, + .n_channels = ARRAY_SIZE(nxpwifi_channels_5ghz), + .bitrates = nxpwifi_rates + 4, + .n_bitrates = ARRAY_SIZE(nxpwifi_rates) - 4, +}; + +/* Supported crypto cipher suits to be advertised to cfg80211 */ +static const u32 nxpwifi_cipher_suites[] = { + WLAN_CIPHER_SUITE_WEP40, + WLAN_CIPHER_SUITE_WEP104, + WLAN_CIPHER_SUITE_TKIP, + WLAN_CIPHER_SUITE_CCMP, + WLAN_CIPHER_SUITE_SMS4, + WLAN_CIPHER_SUITE_AES_CMAC, + WLAN_CIPHER_SUITE_GCMP_256, + WLAN_CIPHER_SUITE_CCMP_256, + WLAN_CIPHER_SUITE_BIP_GMAC_256, + WLAN_CIPHER_SUITE_BIP_CMAC_256, +}; + +/* Supported mgmt frame types to be advertised to cfg80211 */ +static const struct ieee80211_txrx_stypes +nxpwifi_mgmt_stypes[NUM_NL80211_IFTYPES] = { + [NL80211_IFTYPE_STATION] = { + .tx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_RESP >> 4), + .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4), + }, + [NL80211_IFTYPE_AP] = { + .tx = 0xffff, + .rx = BIT(IEEE80211_STYPE_ASSOC_REQ >> 4) | + BIT(IEEE80211_STYPE_REASSOC_REQ >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4) | + BIT(IEEE80211_STYPE_DISASSOC >> 4) | + BIT(IEEE80211_STYPE_AUTH >> 4) | + BIT(IEEE80211_STYPE_DEAUTH >> 4) | + BIT(IEEE80211_STYPE_ACTION >> 4), + }, +}; + +/* + * cfg80211 op: set bitrate mask. + * Converts cfg80211 bitrate selections into firmware bitmap format. + */ +static int +nxpwifi_cfg80211_set_bitrate_mask(struct wiphy *wiphy, + struct net_device *dev, + unsigned int link_id, + const u8 *peer, + const struct cfg80211_bitrate_mask *mask) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + u16 bitmap_rates[MAX_BITMAP_RATES_SIZE]; + enum nl80211_band band; + struct nxpwifi_adapter *adapter = priv->adapter; + + if (!priv->media_connected) { + nxpwifi_dbg(adapter, ERROR, + "Can not set Tx data rate in disconnected state\n"); + return -EINVAL; + } + + band = nxpwifi_band_to_radio_type(priv->curr_bss_params.band); + + memset(bitmap_rates, 0, sizeof(bitmap_rates)); + + /* Fill HR/DSSS legacy rates (2.4 GHz only). */ + if (band == NL80211_BAND_2GHZ) + bitmap_rates[0] = mask->control[band].legacy & 0x000f; + + /* Fill OFDM legacy rates. */ + if (band == NL80211_BAND_2GHZ) + bitmap_rates[1] = (mask->control[band].legacy & 0x0ff0) >> 4; + else + bitmap_rates[1] = mask->control[band].legacy; + + /* Fill HT MCS bitmap (1x1 or 2x2 depending on hardware). */ + bitmap_rates[2] = mask->control[band].ht_mcs[0]; + if (adapter->hw_dev_mcs_support == HT_STREAM_2X2) + bitmap_rates[2] |= mask->control[band].ht_mcs[1] << 8; + + /* Fill VHT MCS bitmap if supported by firmware. */ + if (adapter->fw_api_ver == NXPWIFI_FW_V15) { + bitmap_rates[10] = mask->control[band].vht_mcs[0]; + if (adapter->hw_dev_mcs_support == HT_STREAM_2X2) + bitmap_rates[11] = mask->control[band].vht_mcs[1]; + } + + return nxpwifi_set_tx_rate(priv, bitmap_rates); +} + +/* + * cfg80211 op: configure connection-quality monitoring. + * Subscribes or unsubscribes HIGH_RSSI and LOW_RSSI events to firmware. + */ +static int nxpwifi_cfg80211_set_cqm_rssi_config(struct wiphy *wiphy, + struct net_device *dev, + s32 rssi_thold, u32 rssi_hyst) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + struct nxpwifi_ds_misc_subsc_evt subsc_evt; + int ret = 0; + + priv->cqm_rssi_thold = rssi_thold; + priv->cqm_rssi_hyst = rssi_hyst; + + memset(&subsc_evt, 0x00, sizeof(struct nxpwifi_ds_misc_subsc_evt)); + subsc_evt.events = BITMASK_BCN_RSSI_LOW | BITMASK_BCN_RSSI_HIGH; + + /* Subscribe/unsubscribe low and high rssi events */ + if (rssi_thold && rssi_hyst) { + subsc_evt.action = HOST_ACT_BITWISE_SET; + subsc_evt.bcn_l_rssi_cfg.abs_value = abs(rssi_thold); + subsc_evt.bcn_h_rssi_cfg.abs_value = abs(rssi_thold); + subsc_evt.bcn_l_rssi_cfg.evt_freq = 1; + subsc_evt.bcn_h_rssi_cfg.evt_freq = 1; + ret = nxpwifi_802_11_subscribe_event(priv, &subsc_evt); + } else { + subsc_evt.action = HOST_ACT_BITWISE_CLR; + ret = nxpwifi_802_11_subscribe_event(priv, &subsc_evt); + } + + return ret; +} + +/* + * cfg80211 operation handler for change_beacon. + * Function retrieves and sets modified management IEs to FW. + */ +int nxpwifi_cfg80211_change_beacon(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_ap_update *params) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + struct nxpwifi_adapter *adapter = priv->adapter; + struct cfg80211_beacon_data *data = ¶ms->beacon; + int ret; + + nxpwifi_cancel_scan(adapter); + + if (GET_BSS_ROLE(priv) != NXPWIFI_BSS_ROLE_UAP) { + nxpwifi_dbg(priv->adapter, ERROR, + "%s: bss_type mismatched\n", __func__); + return -EINVAL; + } + + ret = nxpwifi_set_mgmt_ies(priv, data); + if (ret) + nxpwifi_dbg(priv->adapter, ERROR, + "%s: setting mgmt ies failed\n", __func__); + + return ret; +} + +/* + * cfg80211 operation handler for del_station. + * Function deauthenticates station which value is provided in mac parameter. + * If mac is NULL/broadcast, all stations in associated station list are + * deauthenticated. If bss is not started or there are no stations in + * associated stations list, no action is taken. + */ +static int +nxpwifi_cfg80211_del_station(struct wiphy *wiphy, struct wireless_dev *wdev, + struct station_del_parameters *params) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + struct nxpwifi_sta_node *sta_node; + u8 deauth_mac[ETH_ALEN]; + int ret = 0; + + if (!priv->bss_started && priv->wdev.links[0].cac_started) { + nxpwifi_dbg(priv->adapter, INFO, "%s: abort CAC!\n", __func__); + nxpwifi_abort_cac(priv); + } + + if (list_empty(&priv->sta_list) || !priv->bss_started) + return 0; + + if (!params->mac || is_broadcast_ether_addr(params->mac)) + return 0; + + nxpwifi_dbg(priv->adapter, INFO, "%s: mac address %pM\n", + __func__, params->mac); + + eth_zero_addr(deauth_mac); + + sta_node = nxpwifi_get_sta_entry(priv, params->mac); + if (sta_node) + ether_addr_copy(deauth_mac, params->mac); + + if (is_valid_ether_addr(deauth_mac)) { + ret = nxpwifi_uap_sta_deauth(priv, deauth_mac); + nxpwifi_del_sta_entry(priv, deauth_mac); + } + return ret; +} + +static int +nxpwifi_cfg80211_set_antenna(struct wiphy *wiphy, int radio_idx, u32 tx_ant, u32 rx_ant) +{ + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + struct nxpwifi_private *priv = nxpwifi_get_priv(adapter, + NXPWIFI_BSS_ROLE_ANY); + struct nxpwifi_ds_ant_cfg ant_cfg; + + if (!tx_ant || !rx_ant) + return -EOPNOTSUPP; + + if (adapter->hw_dev_mcs_support != HT_STREAM_2X2) { + /* + * Not a MIMO chip. User should provide specific antenna number + * for Tx/Rx path or enable all antennas for diversity + */ + if (tx_ant != rx_ant) + return -EOPNOTSUPP; + + if ((tx_ant & (tx_ant - 1)) && + (tx_ant != BIT(adapter->number_of_antenna) - 1)) + return -EOPNOTSUPP; + + if ((tx_ant == BIT(adapter->number_of_antenna) - 1) && + priv->adapter->number_of_antenna > 1) { + tx_ant = RF_ANTENNA_AUTO; + rx_ant = RF_ANTENNA_AUTO; + } + } else { + struct ieee80211_sta_ht_cap *ht_info; + int rx_mcs_supp; + enum nl80211_band band; + + if ((tx_ant == 0x1 && rx_ant == 0x1)) { + adapter->user_dev_mcs_support = HT_STREAM_1X1; + if (adapter->is_hw_11ac_capable) + adapter->usr_dot_11ac_mcs_support = + NXPWIFI_11AC_MCS_MAP_1X1; + } else { + adapter->user_dev_mcs_support = HT_STREAM_2X2; + if (adapter->is_hw_11ac_capable) + adapter->usr_dot_11ac_mcs_support = + NXPWIFI_11AC_MCS_MAP_2X2; + } + + for (band = 0; band < NUM_NL80211_BANDS; band++) { + if (!adapter->wiphy->bands[band]) + continue; + + ht_info = &adapter->wiphy->bands[band]->ht_cap; + rx_mcs_supp = + GET_RXMCSSUPP(adapter->user_dev_mcs_support); + memset(&ht_info->mcs, 0, adapter->number_of_antenna); + memset(&ht_info->mcs, 0xff, rx_mcs_supp); + } + } + + ant_cfg.tx_ant = tx_ant; + ant_cfg.rx_ant = rx_ant; + + return nxpwifi_set_rf_antenna(priv, &ant_cfg); +} + +static int +nxpwifi_cfg80211_get_antenna(struct wiphy *wiphy, int radio_idx, u32 *tx_ant, u32 *rx_ant) +{ + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + struct nxpwifi_private *priv = nxpwifi_get_priv(adapter, + NXPWIFI_BSS_ROLE_ANY); + int ret; + + ret = nxpwifi_get_rf_antenna(priv, tx_ant, rx_ant); + + return ret; +} + +/* + * cfg80211 op: stop AP. + * Stops the BSS running on the uAP interface. + */ +static int nxpwifi_cfg80211_stop_ap(struct wiphy *wiphy, struct net_device *dev, + unsigned int link_id) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + int ret; + + nxpwifi_abort_cac(priv); + + if (nxpwifi_del_mgmt_ies(priv)) + nxpwifi_dbg(priv->adapter, ERROR, + "Failed to delete mgmt IEs!\n"); + + priv->ap_11n_enabled = 0; + memset(&priv->bss_cfg, 0, sizeof(priv->bss_cfg)); + + ret = nxpwifi_ap_stop_bss(priv); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "Failed to stop the BSS\n"); + goto done; + } + + ret = nxpwifi_ap_sys_reset(priv); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "Failed to reset BSS\n"); + goto done; + } + + netif_carrier_off(priv->netdev); + nxpwifi_stop_net_dev_queue(priv->netdev, priv->adapter); + + if (atomic_dec_and_test(&priv->adapter->uap_count)) { + priv->adapter->chandef_valid = false; + memset(&priv->adapter->chandef, 0, sizeof(priv->adapter->chandef)); + } + +done: + return ret; +} + +/* + * cfg80211 op: start AP. + * Applies beacon/DTIM/SSID/security settings to the uAP configuration and + * starts the BSS. + */ +static int nxpwifi_cfg80211_start_ap(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_ap_settings *params) +{ + struct nxpwifi_uap_bss_param *bss_cfg; + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + struct nxpwifi_adapter *adapter = priv->adapter; + struct cfg80211_chan_def use_chandef; + bool is_first_uap = false; + int ret; + + /* + * Adapter is the HW channel owner (single PHY). + * All UAP interfaces on the same adapter must share + * the same RF channel. + */ + use_chandef = params->chandef; + + if (adapter->chandef_valid) { + if (!cfg80211_chandef_identical(&adapter->chandef, + ¶ms->chandef)) { + nxpwifi_dbg(adapter, INFO, + "UAP already running on channel %d, ignore requested channel %d\n", + adapter->chandef.chan->hw_value, + params->chandef.chan->hw_value); + } + use_chandef = adapter->chandef; + } else { + is_first_uap = true; + } + + if (GET_BSS_ROLE(priv) != NXPWIFI_BSS_ROLE_UAP) + return -EINVAL; + + if (!nxpwifi_is_channel_setting_allowable(priv, params->chandef.chan)) + return -EOPNOTSUPP; + + bss_cfg = kzalloc_obj(*bss_cfg, GFP_KERNEL); + if (!bss_cfg) + return -ENOMEM; + + nxpwifi_set_sys_config_invalid_data(bss_cfg); + + memcpy(bss_cfg->mac_addr, priv->curr_addr, ETH_ALEN); + + if (params->beacon_interval) + bss_cfg->beacon_period = params->beacon_interval; + if (params->dtim_period) + bss_cfg->dtim_period = params->dtim_period; + + if (params->ssid && params->ssid_len) { + memcpy(bss_cfg->ssid.ssid, params->ssid, params->ssid_len); + bss_cfg->ssid.ssid_len = params->ssid_len; + } + if (params->inactivity_timeout > 0) { + /* sta_ao_timer/ps_sta_ao_timer is in unit of 100ms */ + bss_cfg->sta_ao_timer = 10 * params->inactivity_timeout; + bss_cfg->ps_sta_ao_timer = 10 * params->inactivity_timeout; + } + + /* Default: SSID is visible */ + bss_cfg->bcast_ssid_ctl = NXPWIFI_BCAST_SSID_VISIBLE; + + switch (params->hidden_ssid) { + case NL80211_HIDDEN_SSID_NOT_IN_USE: + bss_cfg->bcast_ssid_ctl = NXPWIFI_BCAST_SSID_VISIBLE; + break; + case NL80211_HIDDEN_SSID_ZERO_LEN: + bss_cfg->bcast_ssid_ctl = NXPWIFI_BCAST_SSID_HIDE_LEN_ZERO; + break; + case NL80211_HIDDEN_SSID_ZERO_CONTENTS: + bss_cfg->bcast_ssid_ctl = NXPWIFI_BCAST_SSID_HIDE_LEN_RETAIN; + break; + } + + nxpwifi_uap_set_channel(priv, bss_cfg, use_chandef); + nxpwifi_set_uap_rates(bss_cfg, params); + + ret = nxpwifi_set_secure_params(priv, bss_cfg, params); + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "Failed to parse security parameters!\n"); + goto done; + } + + nxpwifi_set_ht_params(priv, bss_cfg, params); + + if (adapter->is_hw_11ac_capable) { + nxpwifi_set_vht_params(priv, bss_cfg, params); + nxpwifi_set_vht_width(priv, use_chandef.width, + priv->ap_11ac_enabled); + } + + if (priv->ap_11ac_enabled) + nxpwifi_set_11ac_ba_params(priv); + else + nxpwifi_set_ba_params(priv); + + if (adapter->is_hw_11ax_capable) { + priv->ap_11ax_enabled = + nxpwifi_check_11ax_capability(priv, bss_cfg, params); + if (priv->ap_11ax_enabled) + nxpwifi_set_11ax_status(priv, bss_cfg, params); + } + + nxpwifi_set_wmm_params(priv, bss_cfg, params); + + if (nxpwifi_is_11h_active(priv)) + nxpwifi_set_tpc_params(priv, bss_cfg, params); + + if (nxpwifi_is_11h_active(priv) && + !cfg80211_chandef_dfs_required(wiphy, ¶ms->chandef, + priv->bss_mode)) { + nxpwifi_dbg(priv->adapter, INFO, + "Disable 11h extensions in FW\n"); + ret = nxpwifi_11h_activate(priv, false); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "Failed to disable 11h extensions!!"); + goto done; + } + priv->state_11h.is_11h_active = false; + } + + nxpwifi_config_uap_11d(priv, ¶ms->beacon); + + ret = nxpwifi_set_mgmt_ies(priv, ¶ms->beacon); + if (ret) + goto done; + + ret = nxpwifi_config_start_uap(priv, bss_cfg); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "Failed to start AP\n"); + goto done; + } + + /* First UAP records adapter-level HW channel */ + if (is_first_uap) { + adapter->chandef = use_chandef; + adapter->chandef_valid = true; + } + atomic_inc(&adapter->uap_count); + netif_carrier_on(priv->netdev); + nxpwifi_wake_up_net_dev_queue(priv->netdev, priv->adapter); + + memcpy(&priv->bss_cfg, bss_cfg, sizeof(priv->bss_cfg)); + +done: + kfree(bss_cfg); + return ret; +} + +/* + * cfg80211 op: handle scan request. + * Issues a firmware scan using the requested parameters and reports the + * results. + */ +static int +nxpwifi_cfg80211_scan(struct wiphy *wiphy, + struct cfg80211_scan_request *request) +{ + struct net_device *dev = request->wdev->netdev; + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + int i, offset, ret; + struct ieee80211_channel *chan; + struct element *ie; + struct nxpwifi_user_scan_cfg *user_scan_cfg; + u8 mac_addr[ETH_ALEN]; + + nxpwifi_dbg(priv->adapter, CMD, + "info: received scan request on %s\n", dev->name); + + /* + * Block scan requests during active scanning or scan cleanup. + * Prevents new scans when the interface is disabled or teardown is in + * progress. + */ + if (priv->scan_request || priv->scan_aborting) { + nxpwifi_dbg(priv->adapter, WARN, + "cmd: Scan already in process..\n"); + return -EBUSY; + } + + if (!priv->wdev.connected && priv->scan_block) + priv->scan_block = false; + + if (!nxpwifi_stop_bg_scan(priv)) + cfg80211_sched_scan_stopped_locked(priv->wdev.wiphy, 0); + + user_scan_cfg = kzalloc_obj(*user_scan_cfg, GFP_KERNEL); + if (!user_scan_cfg) + return -ENOMEM; + + priv->scan_request = request; + + if (request->flags & NL80211_SCAN_FLAG_RANDOM_ADDR) { + get_random_mask_addr(mac_addr, request->mac_addr, + request->mac_addr_mask); + ether_addr_copy(request->mac_addr, mac_addr); + ether_addr_copy(user_scan_cfg->random_mac, mac_addr); + } + + user_scan_cfg->num_ssids = request->n_ssids; + user_scan_cfg->ssid_list = request->ssids; + + if (request->ie && request->ie_len) { + offset = 0; + for (i = 0; i < NXPWIFI_MAX_VSIE_NUM; i++) { + if (priv->vs_ie[i].mask != NXPWIFI_VSIE_MASK_CLEAR) + continue; + priv->vs_ie[i].mask = NXPWIFI_VSIE_MASK_SCAN; + ie = (struct element *)(request->ie + offset); + memcpy(&priv->vs_ie[i].ie, ie, + sizeof(*ie) + ie->datalen); + offset += sizeof(*ie) + ie->datalen; + + if (offset >= request->ie_len) + break; + } + } + + for (i = 0; i < min_t(u32, request->n_channels, + NXPWIFI_USER_SCAN_CHAN_MAX); i++) { + chan = request->channels[i]; + user_scan_cfg->chan_list[i].chan_number = chan->hw_value; + user_scan_cfg->chan_list[i].radio_type = chan->band; + + if ((chan->flags & IEEE80211_CHAN_NO_IR) || !request->n_ssids) + user_scan_cfg->chan_list[i].scan_type = + NXPWIFI_SCAN_TYPE_PASSIVE; + else + user_scan_cfg->chan_list[i].scan_type = + NXPWIFI_SCAN_TYPE_ACTIVE; + + user_scan_cfg->chan_list[i].scan_time = 0; + } + + if (priv->adapter->scan_chan_gap_enabled && + nxpwifi_is_any_intf_active(priv)) + user_scan_cfg->scan_chan_gap = + priv->adapter->scan_chan_gap_time; + + ret = nxpwifi_scan_networks(priv, user_scan_cfg); + kfree(user_scan_cfg); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "scan failed: %d\n", ret); + priv->scan_aborting = false; + priv->scan_request = NULL; + return ret; + } + + if (request->ie && request->ie_len) { + for (i = 0; i < NXPWIFI_MAX_VSIE_NUM; i++) { + if (priv->vs_ie[i].mask == NXPWIFI_VSIE_MASK_SCAN) { + priv->vs_ie[i].mask = NXPWIFI_VSIE_MASK_CLEAR; + memset(&priv->vs_ie[i].ie, 0, + NXPWIFI_MAX_VSIE_LEN); + } + } + } + return 0; +} + +/* + * cfg80211 sched_scan_start handler. + * + * Send a bgscan configuration request to the firmware based on the + * scheduled scan parameters. On success, the firmware later issues a + * BGSCAN_REPORT event, after which the driver should query the firmware + * for scan results. + */ +static int +nxpwifi_cfg80211_sched_scan_start(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_sched_scan_request *request) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + int i, offset; + struct ieee80211_channel *chan; + struct nxpwifi_bg_scan_cfg *bgscan_cfg; + struct element *ie; + int ret; + + if (!request || (!request->n_ssids && !request->n_match_sets)) { + wiphy_err(wiphy, "%s : Invalid Sched_scan parameters", + __func__); + return -EINVAL; + } + + wiphy_info(wiphy, "sched_scan start : n_ssids=%d n_match_sets=%d ", + request->n_ssids, request->n_match_sets); + wiphy_info(wiphy, "n_channels=%d interval=%d ie_len=%d\n", + request->n_channels, request->scan_plans->interval, + (int)request->ie_len); + + bgscan_cfg = kzalloc_obj(*bgscan_cfg, GFP_KERNEL); + if (!bgscan_cfg) + return -ENOMEM; + + if (priv->scan_request || priv->scan_aborting) + bgscan_cfg->start_later = true; + + bgscan_cfg->num_ssids = request->n_match_sets; + bgscan_cfg->ssid_list = request->match_sets; + + if (request->ie && request->ie_len) { + offset = 0; + for (i = 0; i < NXPWIFI_MAX_VSIE_NUM; i++) { + if (priv->vs_ie[i].mask != NXPWIFI_VSIE_MASK_CLEAR) + continue; + priv->vs_ie[i].mask = NXPWIFI_VSIE_MASK_BGSCAN; + ie = (struct element *)(request->ie + offset); + memcpy(&priv->vs_ie[i].ie, ie, + sizeof(*ie) + ie->datalen); + offset += sizeof(*ie) + ie->datalen; + + if (offset >= request->ie_len) + break; + } + } + + for (i = 0; i < min_t(u32, request->n_channels, + NXPWIFI_BG_SCAN_CHAN_MAX); i++) { + chan = request->channels[i]; + bgscan_cfg->chan_list[i].chan_number = chan->hw_value; + bgscan_cfg->chan_list[i].radio_type = chan->band; + + if ((chan->flags & IEEE80211_CHAN_NO_IR) || !request->n_ssids) + bgscan_cfg->chan_list[i].scan_type = + NXPWIFI_SCAN_TYPE_PASSIVE; + else + bgscan_cfg->chan_list[i].scan_type = + NXPWIFI_SCAN_TYPE_ACTIVE; + + bgscan_cfg->chan_list[i].scan_time = 0; + } + + bgscan_cfg->chan_per_scan = min_t(u32, request->n_channels, + NXPWIFI_BG_SCAN_CHAN_MAX); + + /* Minimum scan cycle duration: 15 seconds */ + bgscan_cfg->scan_interval = (request->scan_plans->interval > + NXPWIFI_BGSCAN_INTERVAL) ? + request->scan_plans->interval : + NXPWIFI_BGSCAN_INTERVAL; + + bgscan_cfg->repeat_count = NXPWIFI_BGSCAN_REPEAT_COUNT; + bgscan_cfg->report_condition = NXPWIFI_BGSCAN_SSID_MATCH | + NXPWIFI_BGSCAN_WAIT_ALL_CHAN_DONE; + bgscan_cfg->bss_type = NXPWIFI_BSS_MODE_INFRA; + bgscan_cfg->action = NXPWIFI_BGSCAN_ACT_SET; + bgscan_cfg->enable = true; + if (request->min_rssi_thold != NL80211_SCAN_RSSI_THOLD_OFF) { + bgscan_cfg->report_condition |= NXPWIFI_BGSCAN_SSID_RSSI_MATCH; + bgscan_cfg->rssi_threshold = request->min_rssi_thold; + } + + ret = nxpwifi_bg_scan_config(priv, bgscan_cfg); + + if (!ret) + priv->sched_scanning = true; + + kfree(bgscan_cfg); + return ret; +} + +/* + * cfg80211 sched_scan_stop handler. + * + * Send a bgscan configuration command to disable the previous + * background scan settings in the firmware. + */ +static int nxpwifi_cfg80211_sched_scan_stop(struct wiphy *wiphy, + struct net_device *dev, u64 reqid) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + + wiphy_info(wiphy, "sched scan stop!"); + return nxpwifi_stop_bg_scan(priv); +} + +/* + * Set default cfg80211 HT capabilities. + */ +static void +nxpwifi_setup_ht_caps(struct nxpwifi_private *priv, + struct ieee80211_sta_ht_cap *ht_info) +{ + int rx_mcs_supp; + struct ieee80211_mcs_info mcs_set; + u8 *mcs = (u8 *)&mcs_set; + struct nxpwifi_adapter *adapter = priv->adapter; + + ht_info->ht_supported = true; + ht_info->ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K; + ht_info->ampdu_density = IEEE80211_HT_MPDU_DENSITY_NONE; + + memset(&ht_info->mcs, 0, sizeof(ht_info->mcs)); + + /* Fill HT capability information */ + if (ISSUPP_CHANWIDTH40(adapter->hw_dot_11n_dev_cap)) + ht_info->cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40; + else + ht_info->cap &= ~IEEE80211_HT_CAP_SUP_WIDTH_20_40; + + if (ISSUPP_SHORTGI20(adapter->hw_dot_11n_dev_cap)) + ht_info->cap |= IEEE80211_HT_CAP_SGI_20; + else + ht_info->cap &= ~IEEE80211_HT_CAP_SGI_20; + + if (ISSUPP_SHORTGI40(adapter->hw_dot_11n_dev_cap)) + ht_info->cap |= IEEE80211_HT_CAP_SGI_40; + else + ht_info->cap &= ~IEEE80211_HT_CAP_SGI_40; + + if (adapter->user_dev_mcs_support == HT_STREAM_2X2) + ht_info->cap |= 2 << IEEE80211_HT_CAP_RX_STBC_SHIFT; + else + ht_info->cap |= 1 << IEEE80211_HT_CAP_RX_STBC_SHIFT; + + if (ISSUPP_TXSTBC(adapter->hw_dot_11n_dev_cap)) + ht_info->cap |= IEEE80211_HT_CAP_TX_STBC; + else + ht_info->cap &= ~IEEE80211_HT_CAP_TX_STBC; + + if (ISSUPP_GREENFIELD(adapter->hw_dot_11n_dev_cap)) + ht_info->cap |= IEEE80211_HT_CAP_GRN_FLD; + else + ht_info->cap &= ~IEEE80211_HT_CAP_GRN_FLD; + + if (ISENABLED_40MHZ_INTOLERANT(adapter->hw_dot_11n_dev_cap)) + ht_info->cap |= IEEE80211_HT_CAP_40MHZ_INTOLERANT; + else + ht_info->cap &= ~IEEE80211_HT_CAP_40MHZ_INTOLERANT; + + if (ISSUPP_RXLDPC(adapter->hw_dot_11n_dev_cap)) + ht_info->cap |= IEEE80211_HT_CAP_LDPC_CODING; + else + ht_info->cap &= ~IEEE80211_HT_CAP_LDPC_CODING; + + ht_info->cap &= ~IEEE80211_HT_CAP_MAX_AMSDU; + ht_info->cap |= IEEE80211_HT_CAP_SM_PS; + + rx_mcs_supp = GET_RXMCSSUPP(adapter->user_dev_mcs_support); + /* Set MCS for 1x1/2x2 */ + memset(mcs, 0xff, rx_mcs_supp); + /* Clear all the other values */ + memset(&mcs[rx_mcs_supp], 0, + sizeof(struct ieee80211_mcs_info) - rx_mcs_supp); + if (priv->bss_mode == NL80211_IFTYPE_STATION || + ISSUPP_CHANWIDTH40(adapter->hw_dot_11n_dev_cap)) + /* Set MCS32 for infra mode or ad-hoc mode with 40MHz support */ + SETHT_MCS32(mcs_set.rx_mask); + + memcpy((u8 *)&ht_info->mcs, mcs, sizeof(struct ieee80211_mcs_info)); + + ht_info->mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED; +} + +static void +nxpwifi_setup_vht_caps(struct nxpwifi_private *priv, + struct ieee80211_sta_vht_cap *vht_info) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + vht_info->vht_supported = true; + + vht_info->cap = adapter->hw_dot_11ac_dev_cap; + /* Update MCS support for VHT */ + vht_info->vht_mcs.rx_mcs_map = + cpu_to_le16(adapter->hw_dot_11ac_mcs_support & 0xFFFF); + vht_info->vht_mcs.rx_highest = 0; + vht_info->vht_mcs.tx_mcs_map = + cpu_to_le16(adapter->hw_dot_11ac_mcs_support >> 16); + vht_info->vht_mcs.tx_highest = 0; +} + +/* + * 5 GHz HE capability masks for UAP mode. + * + * MAC: TWT requester/respondor, broadcast TWT, OMI control. + * + * PHY: 40/80 MHz width, puncturing, LDPC, NDP 4xLTF, STBC, + * Doppler, DCM, SU BF/BFe, STS, sounding dims, extended + * range, PPE present, 4xLTF 0.8us GI, Rx 1024-QAM. + */ +#define UAP_HE_MAC_CAP0_MASK (IEEE80211_HE_MAC_CAP0_TWT_REQ | \ + IEEE80211_HE_MAC_CAP0_TWT_RES) + +#define UAP_HE_MAC_CAP1_MASK 0 +#define UAP_HE_MAC_CAP2_MASK IEEE80211_HE_MAC_CAP2_BCAST_TWT +#define UAP_HE_MAC_CAP3_MASK IEEE80211_HE_MAC_CAP3_OMI_CONTROL +#define UAP_HE_MAC_CAP4_MASK 0 +#define UAP_HE_MAC_CAP5_MASK 0 + +#define UAP_HE_PHY_CAP0_MASK IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G +#define UAP_HE_PHY_CAP1_MASK (IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD | \ + IEEE80211_HE_PHY_CAP1_PREAMBLE_PUNC_RX_80MHZ_ONLY_SECOND_20MHZ | \ + IEEE80211_HE_PHY_CAP1_PREAMBLE_PUNC_RX_80MHZ_ONLY_SECOND_40MHZ) +#define UAP_HE_PHY_CAP2_MASK (IEEE80211_HE_PHY_CAP2_NDP_4x_LTF_AND_3_2US | \ + IEEE80211_HE_PHY_CAP2_STBC_TX_UNDER_80MHZ | \ + IEEE80211_HE_PHY_CAP2_STBC_RX_UNDER_80MHZ | \ + IEEE80211_HE_PHY_CAP2_DOPPLER_TX | \ + IEEE80211_HE_PHY_CAP2_DOPPLER_RX) +#define UAP_HE_PHY_CAP3_MASK (IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_TX_BPSK | \ + IEEE80211_HE_PHY_CAP3_DCM_MAX_TX_NSS_1 | \ + IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_RX_BPSK | \ + IEEE80211_HE_PHY_CAP3_DCM_MAX_RX_NSS_1 | \ + IEEE80211_HE_PHY_CAP3_SU_BEAMFORMER) +#define UAP_HE_PHY_CAP4_MASK (IEEE80211_HE_PHY_CAP4_SU_BEAMFORMEE | \ + IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_UNDER_80MHZ_8) +#define UAP_HE_PHY_CAP5_MASK IEEE80211_HE_PHY_CAP5_BEAMFORMEE_NUM_SND_DIM_UNDER_80MHZ_2 +#define UAP_HE_PHY_CAP6_MASK (IEEE80211_HE_PHY_CAP6_PARTIAL_BW_EXT_RANGE | \ + IEEE80211_HE_PHY_CAP6_PPE_THRESHOLD_PRESENT) +#define UAP_HE_PHY_CAP7_MASK (IEEE80211_HE_PHY_CAP7_HE_SU_MU_PPDU_4XLTF_AND_08_US_GI | \ + IEEE80211_HE_PHY_CAP7_MAX_NC_1) +#define UAP_HE_PHY_CAP8_MASK 0 +#define UAP_HE_PHY_CAP9_MASK IEEE80211_HE_PHY_CAP9_RX_1024_QAM_LESS_THAN_242_TONE_RU +#define UAP_HE_PHY_CAP10_MASK 0 + +/* + * 2.4 GHz HE capability masks for UAP mode. + * + * MAC: HTC HE, OMI control (no UL OFDMA). + * PHY: 40 MHz, LDPC, NDP 4xLTF, STBC, Doppler, DCM, + * SU BF/BFe, STS/sounding dims, extended range, + * PPE present, 4xLTF 0.8us GI, Rx 1024-QAM. + */ +#define UAP_HE_2G_MAC_CAP0_MASK 0x00 +#define UAP_HE_2G_MAC_CAP1_MASK 0x00 +#define UAP_HE_2G_MAC_CAP2_MASK 0x00 +#define UAP_HE_2G_MAC_CAP3_MASK IEEE80211_HE_MAC_CAP3_OMI_CONTROL +#define UAP_HE_2G_MAC_CAP4_MASK 0x00 +#define UAP_HE_2G_MAC_CAP5_MASK 0x00 + +#define UAP_HE_2G_PHY_CAP0_MASK IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G +#define UAP_HE_2G_PHY_CAP1_MASK IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD +#define UAP_HE_2G_PHY_CAP2_MASK (IEEE80211_HE_PHY_CAP2_NDP_4x_LTF_AND_3_2US | \ + IEEE80211_HE_PHY_CAP2_STBC_TX_UNDER_80MHZ | \ + IEEE80211_HE_PHY_CAP2_STBC_RX_UNDER_80MHZ | \ + IEEE80211_HE_PHY_CAP2_DOPPLER_TX | \ + IEEE80211_HE_PHY_CAP2_DOPPLER_RX) +#define UAP_HE_2G_PHY_CAP3_MASK (IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_TX_BPSK | \ + IEEE80211_HE_PHY_CAP3_DCM_MAX_TX_NSS_1 | \ + IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_RX_BPSK | \ + IEEE80211_HE_PHY_CAP3_DCM_MAX_RX_NSS_1 | \ + IEEE80211_HE_PHY_CAP3_SU_BEAMFORMER) +#define UAP_HE_2G_PHY_CAP4_MASK (IEEE80211_HE_PHY_CAP4_SU_BEAMFORMEE | \ + IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_UNDER_80MHZ_8) +#define UAP_HE_2G_PHY_CAP5_MASK IEEE80211_HE_PHY_CAP5_BEAMFORMEE_NUM_SND_DIM_UNDER_80MHZ_2 +#define UAP_HE_2G_PHY_CAP6_MASK (IEEE80211_HE_PHY_CAP6_PARTIAL_BW_EXT_RANGE | \ + IEEE80211_HE_PHY_CAP6_PPE_THRESHOLD_PRESENT) +#define UAP_HE_2G_PHY_CAP7_MASK (IEEE80211_HE_PHY_CAP7_HE_SU_MU_PPDU_4XLTF_AND_08_US_GI | \ + IEEE80211_HE_PHY_CAP7_MAX_NC_1) +#define UAP_HE_2G_PHY_CAP8_MASK 0x00 +#define UAP_HE_2G_PHY_CAP9_MASK IEEE80211_HE_PHY_CAP9_RX_1024_QAM_LESS_THAN_242_TONE_RU +#define UAP_HE_2G_PHY_CAP10_MASK 0x00 +#define HE_CAP_FIX_SIZE 22 + +static void +nxpwifi_update_11ax_ie(u8 band, + struct nxpwifi_11ax_he_cap_cfg *he_cap_cfg) +{ + if (band == BAND_A) { + he_cap_cfg->cap_elem.mac_cap_info[0] &= UAP_HE_MAC_CAP0_MASK; + he_cap_cfg->cap_elem.mac_cap_info[1] &= UAP_HE_MAC_CAP1_MASK; + he_cap_cfg->cap_elem.mac_cap_info[2] &= UAP_HE_MAC_CAP2_MASK; + he_cap_cfg->cap_elem.mac_cap_info[3] &= UAP_HE_MAC_CAP3_MASK; + he_cap_cfg->cap_elem.mac_cap_info[4] &= UAP_HE_MAC_CAP4_MASK; + he_cap_cfg->cap_elem.mac_cap_info[5] &= UAP_HE_MAC_CAP5_MASK; + he_cap_cfg->cap_elem.phy_cap_info[0] &= UAP_HE_PHY_CAP0_MASK; + he_cap_cfg->cap_elem.phy_cap_info[1] &= UAP_HE_PHY_CAP1_MASK; + he_cap_cfg->cap_elem.phy_cap_info[2] &= UAP_HE_PHY_CAP2_MASK; + he_cap_cfg->cap_elem.phy_cap_info[3] &= UAP_HE_PHY_CAP3_MASK; + he_cap_cfg->cap_elem.phy_cap_info[4] &= UAP_HE_PHY_CAP4_MASK; + he_cap_cfg->cap_elem.phy_cap_info[5] &= UAP_HE_PHY_CAP5_MASK; + he_cap_cfg->cap_elem.phy_cap_info[6] &= UAP_HE_PHY_CAP6_MASK; + he_cap_cfg->cap_elem.phy_cap_info[7] &= UAP_HE_PHY_CAP7_MASK; + he_cap_cfg->cap_elem.phy_cap_info[8] &= UAP_HE_PHY_CAP8_MASK; + he_cap_cfg->cap_elem.phy_cap_info[9] &= UAP_HE_PHY_CAP9_MASK; + he_cap_cfg->cap_elem.phy_cap_info[10] &= UAP_HE_PHY_CAP10_MASK; + } else { + he_cap_cfg->cap_elem.mac_cap_info[0] &= UAP_HE_2G_MAC_CAP0_MASK; + he_cap_cfg->cap_elem.mac_cap_info[1] &= UAP_HE_2G_MAC_CAP1_MASK; + he_cap_cfg->cap_elem.mac_cap_info[2] &= UAP_HE_2G_MAC_CAP2_MASK; + he_cap_cfg->cap_elem.mac_cap_info[3] &= UAP_HE_2G_MAC_CAP3_MASK; + he_cap_cfg->cap_elem.mac_cap_info[4] &= UAP_HE_2G_MAC_CAP4_MASK; + he_cap_cfg->cap_elem.mac_cap_info[5] &= UAP_HE_2G_MAC_CAP5_MASK; + he_cap_cfg->cap_elem.phy_cap_info[0] &= UAP_HE_2G_PHY_CAP0_MASK; + he_cap_cfg->cap_elem.phy_cap_info[1] &= UAP_HE_2G_PHY_CAP1_MASK; + he_cap_cfg->cap_elem.phy_cap_info[2] &= UAP_HE_2G_PHY_CAP2_MASK; + he_cap_cfg->cap_elem.phy_cap_info[3] &= UAP_HE_2G_PHY_CAP3_MASK; + he_cap_cfg->cap_elem.phy_cap_info[4] &= UAP_HE_2G_PHY_CAP4_MASK; + he_cap_cfg->cap_elem.phy_cap_info[5] &= UAP_HE_2G_PHY_CAP5_MASK; + he_cap_cfg->cap_elem.phy_cap_info[6] &= UAP_HE_2G_PHY_CAP6_MASK; + he_cap_cfg->cap_elem.phy_cap_info[7] &= UAP_HE_2G_PHY_CAP7_MASK; + he_cap_cfg->cap_elem.phy_cap_info[8] &= UAP_HE_2G_PHY_CAP8_MASK; + he_cap_cfg->cap_elem.phy_cap_info[9] &= UAP_HE_2G_PHY_CAP9_MASK; + he_cap_cfg->cap_elem.phy_cap_info[10] &= UAP_HE_2G_PHY_CAP10_MASK; + } +} + +static void +nxpwifi_setup_he_caps(struct nxpwifi_private *priv, + struct ieee80211_supported_band *band) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct ieee80211_sband_iftype_data *iftype_data; + struct nxpwifi_11ax_he_cap_cfg he_cap_cfg; + u8 hw_he_cap_len; + u8 extra_mcs_size; + int ppe_threshold_len; + + if (band->band == NL80211_BAND_5GHZ) { + hw_he_cap_len = adapter->hw_he_cap_len; + memcpy(&he_cap_cfg, adapter->hw_he_cap, hw_he_cap_len); + nxpwifi_update_11ax_ie(BAND_A, &he_cap_cfg); + } else { + hw_he_cap_len = adapter->hw_2g_he_cap_len; + memcpy(&he_cap_cfg, adapter->hw_2g_he_cap, hw_he_cap_len); + nxpwifi_update_11ax_ie(BAND_G, &he_cap_cfg); + } + + if (!hw_he_cap_len) + return; + + iftype_data = kmalloc_obj(*iftype_data, GFP_KERNEL); + if (!iftype_data) + return; + memset(iftype_data, 0, sizeof(*iftype_data)); + + iftype_data->types_mask = + BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_AP); + iftype_data->he_cap.has_he = true; + + memcpy(iftype_data->he_cap.he_cap_elem.mac_cap_info, + he_cap_cfg.cap_elem.mac_cap_info, + sizeof(he_cap_cfg.cap_elem.mac_cap_info)); + memcpy(iftype_data->he_cap.he_cap_elem.phy_cap_info, + he_cap_cfg.cap_elem.phy_cap_info, + sizeof(he_cap_cfg.cap_elem.phy_cap_info)); + memset(&iftype_data->he_cap.he_mcs_nss_supp, + 0xff, + sizeof(iftype_data->he_cap.he_mcs_nss_supp)); + memcpy(&iftype_data->he_cap.he_mcs_nss_supp, + he_cap_cfg.he_txrx_mcs_support, + sizeof(he_cap_cfg.he_txrx_mcs_support)); + + extra_mcs_size = 0; + /* Add 160 MHz MCS/NSS if supported */ + if (he_cap_cfg.cap_elem.phy_cap_info[0] & BIT(3)) + extra_mcs_size += 4; + /* Add 80+80 MHz MCS/NSS if supported */ + if (he_cap_cfg.cap_elem.phy_cap_info[0] & BIT(4)) + extra_mcs_size += 4; + if (extra_mcs_size) + memcpy((u8 *)&iftype_data->he_cap.he_mcs_nss_supp.rx_mcs_160, + he_cap_cfg.val, extra_mcs_size); + + /* Parse PPE thresholds when present */ + ppe_threshold_len = he_cap_cfg.len - HE_CAP_FIX_SIZE - extra_mcs_size; + if (he_cap_cfg.cap_elem.phy_cap_info[6] & BIT(7) && ppe_threshold_len) { + memcpy(iftype_data->he_cap.ppe_thres, + &he_cap_cfg.val[extra_mcs_size], + ppe_threshold_len); + } else { + iftype_data->he_cap.he_cap_elem.phy_cap_info[6] &= BIT(7); + } + + _ieee80211_set_sband_iftype_data(band, iftype_data, 1); +} + +/* create a new virtual interface with the given name and name assign type */ +struct wireless_dev *nxpwifi_add_virtual_intf(struct wiphy *wiphy, + const char *name, + unsigned char name_assign_type, + enum nl80211_iftype type, + struct vif_params *params) +{ + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + struct nxpwifi_private *priv; + struct net_device *dev; + void *mdev_priv; + int ret; + + if (!adapter) + return ERR_PTR(-EFAULT); + + switch (type) { + case NL80211_IFTYPE_UNSPECIFIED: + case NL80211_IFTYPE_STATION: + if (adapter->curr_iface_comb.sta_intf == + adapter->iface_limit.sta_intf) { + nxpwifi_dbg(adapter, ERROR, + "cannot create multiple sta ifaces\n"); + return ERR_PTR(-EINVAL); + } + + priv = nxpwifi_get_unused_priv_by_bss_type + (adapter, NXPWIFI_BSS_TYPE_STA); + if (!priv) { + nxpwifi_dbg(adapter, ERROR, + "could not get free private struct\n"); + return ERR_PTR(-EFAULT); + } + + priv->wdev.wiphy = wiphy; + priv->wdev.iftype = NL80211_IFTYPE_STATION; + + if (type == NL80211_IFTYPE_UNSPECIFIED) + priv->bss_mode = NL80211_IFTYPE_STATION; + else + priv->bss_mode = type; + + priv->bss_type = NXPWIFI_BSS_TYPE_STA; + priv->frame_type = NXPWIFI_DATA_FRAME_TYPE_ETH_II; + priv->bss_priority = 0; + priv->bss_role = NXPWIFI_BSS_ROLE_STA; + + break; + case NL80211_IFTYPE_AP: + if (adapter->curr_iface_comb.uap_intf == + adapter->iface_limit.uap_intf) { + nxpwifi_dbg(adapter, ERROR, + "cannot create multiple AP ifaces\n"); + return ERR_PTR(-EINVAL); + } + + priv = nxpwifi_get_unused_priv_by_bss_type + (adapter, NXPWIFI_BSS_TYPE_UAP); + if (!priv) { + nxpwifi_dbg(adapter, ERROR, + "could not get free private struct\n"); + return ERR_PTR(-EFAULT); + } + + priv->wdev.wiphy = wiphy; + priv->wdev.iftype = NL80211_IFTYPE_AP; + + priv->bss_type = NXPWIFI_BSS_TYPE_UAP; + priv->frame_type = NXPWIFI_DATA_FRAME_TYPE_ETH_II; + priv->bss_priority = 0; + priv->bss_role = NXPWIFI_BSS_ROLE_UAP; + priv->bss_started = 0; + priv->bss_mode = type; + + break; + case NL80211_IFTYPE_MONITOR: + priv = nxpwifi_get_unused_priv_by_bss_type + (adapter, NXPWIFI_BSS_TYPE_UAP); + if (!priv) { + nxpwifi_dbg(adapter, ERROR, + "could not get free private struct\n"); + return ERR_PTR(-EFAULT); + } + priv->wdev.wiphy = wiphy; + priv->wdev.iftype = NL80211_IFTYPE_MONITOR; + + priv->bss_type = NXPWIFI_BSS_TYPE_UAP; + priv->frame_type = NXPWIFI_DATA_FRAME_TYPE_ETH_II; + priv->bss_priority = 0; + priv->bss_started = 0; + priv->bss_mode = type; + + break; + default: + nxpwifi_dbg(adapter, ERROR, "type not supported\n"); + return ERR_PTR(-EINVAL); + } + + dev = alloc_netdev_mqs(sizeof(struct nxpwifi_private *), name, + name_assign_type, ether_setup, + IEEE80211_NUM_ACS, 1); + if (!dev) { + nxpwifi_dbg(adapter, ERROR, + "no memory available for netdevice\n"); + ret = -ENOMEM; + goto err_alloc_netdev; + } + + nxpwifi_init_priv_params(priv, dev); + + priv->netdev = dev; + + nxpwifi_set_mac_address(priv, dev, false, NULL); + + if (type != NL80211_IFTYPE_MONITOR) { + ret = nxpwifi_set_bss_mode(priv); + + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "%s: err_set_bss_mode\n", __func__); + goto err_set_bss_mode; + } + } + + ret = nxpwifi_sta_init_cmd(priv, false, false); + if (ret) + goto err_sta_init; + + dev_net_set(dev, wiphy_net(wiphy)); + dev->ieee80211_ptr = &priv->wdev; + dev->ieee80211_ptr->iftype = priv->bss_mode; + SET_NETDEV_DEV(dev, wiphy_dev(wiphy)); + + dev->flags |= IFF_BROADCAST | IFF_MULTICAST; + dev->watchdog_timeo = NXPWIFI_DEFAULT_WATCHDOG_TIMEOUT; + dev->needed_headroom = NXPWIFI_MIN_DATA_HEADER_LEN; + dev->ethtool_ops = &nxpwifi_ethtool_ops; + + mdev_priv = netdev_priv(dev); + *((unsigned long *)mdev_priv) = (unsigned long)priv; + + if (type == NL80211_IFTYPE_MONITOR) + dev->type = ARPHRD_IEEE80211_RADIOTAP; + + SET_NETDEV_DEV(dev, adapter->dev); + + wiphy_work_init(&priv->reset_conn_state_work, nxpwifi_reset_conn_state_work); + + wiphy_delayed_work_init(&priv->dfs_cac_work, nxpwifi_dfs_cac_work); + + wiphy_delayed_work_init(&priv->dfs_chan_sw_work, nxpwifi_dfs_chan_sw_work); + + /* Register network device */ + if (cfg80211_register_netdevice(dev)) { + nxpwifi_dbg(adapter, ERROR, "cannot register network device\n"); + ret = -EFAULT; + goto err_reg_netdev; + } + + nxpwifi_dbg(adapter, INFO, + "info: %s: NXP 802.11 Adapter\n", dev->name); + +#ifdef CONFIG_DEBUG_FS + nxpwifi_dev_debugfs_init(priv); +#endif + + update_vif_type_counter(adapter, type, 1); + + return &priv->wdev; + +err_reg_netdev: + free_netdev(dev); + priv->netdev = NULL; +err_sta_init: +err_set_bss_mode: +err_alloc_netdev: + memset(&priv->wdev, 0, sizeof(priv->wdev)); + priv->wdev.iftype = NL80211_IFTYPE_UNSPECIFIED; + priv->bss_mode = NL80211_IFTYPE_UNSPECIFIED; + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(nxpwifi_add_virtual_intf); + +/* del_virtual_intf: remove the virtual interface determined by dev */ +int nxpwifi_del_virtual_intf(struct wiphy *wiphy, struct wireless_dev *wdev) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + struct nxpwifi_adapter *adapter = priv->adapter; + struct sk_buff *skb, *tmp; + +#ifdef CONFIG_DEBUG_FS + nxpwifi_dev_debugfs_remove(priv); +#endif + if (priv->bss_mode == NL80211_IFTYPE_MONITOR) { + struct nxpwifi_802_11_net_monitor netmon_cfg; + + memset(&netmon_cfg, 0, sizeof(struct nxpwifi_802_11_net_monitor)); + nxpwifi_config_monitor_mode(priv, &netmon_cfg); + } + + if (priv->sched_scanning) + priv->sched_scanning = false; + + nxpwifi_stop_net_dev_queue(priv->netdev, adapter); + + skb_queue_walk_safe(&priv->bypass_txq, skb, tmp) { + skb_unlink(skb, &priv->bypass_txq); + nxpwifi_write_data_complete(priv->adapter, skb, 0, -1); + } + + netif_carrier_off(priv->netdev); + + if (wdev->netdev->reg_state == NETREG_REGISTERED) + cfg80211_unregister_netdevice(wdev->netdev); + + /* Clear the priv in adapter */ + priv->netdev = NULL; + + update_vif_type_counter(adapter, priv->bss_mode, -1); + + priv->bss_mode = NL80211_IFTYPE_UNSPECIFIED; + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA || + GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) + kfree(priv->hist_data); + + return 0; +} +EXPORT_SYMBOL_GPL(nxpwifi_del_virtual_intf); + +static bool +nxpwifi_is_pattern_supported(struct cfg80211_pkt_pattern *pat, s8 *byte_seq, + u8 max_byte_seq) +{ + int j, k, valid_byte_cnt = 0; + bool dont_care_byte = false; + + for (j = 0; j < DIV_ROUND_UP(pat->pattern_len, 8); j++) { + for (k = 0; k < 8; k++) { + if (pat->mask[j] & 1 << k) { + memcpy(byte_seq + valid_byte_cnt, + &pat->pattern[j * 8 + k], 1); + valid_byte_cnt++; + if (dont_care_byte) + return false; + } else { + if (valid_byte_cnt) + dont_care_byte = true; + } + + /* wildcard bytes record as the offset before the valid byte */ + if (!valid_byte_cnt && !dont_care_byte) + pat->pkt_offset++; + + if (valid_byte_cnt > max_byte_seq) + return false; + } + } + + byte_seq[max_byte_seq] = valid_byte_cnt; + + return true; +} + +#ifdef CONFIG_PM +static void nxpwifi_set_auto_arp_mef_entry(struct nxpwifi_private *priv, + struct nxpwifi_mef_entry *mef_entry) +{ + int i, filt_num = 0, num_ipv4 = 0; + struct in_device *in_dev; + struct in_ifaddr *ifa; + __be32 ips[NXPWIFI_MAX_SUPPORTED_IPADDR]; + struct nxpwifi_adapter *adapter = priv->adapter; + + mef_entry->mode = MEF_MODE_HOST_SLEEP; + mef_entry->action = MEF_ACTION_AUTO_ARP; + + /* Enable ARP offload feature */ + memset(ips, 0, sizeof(ips)); + for (i = 0; i < adapter->priv_num; i++) { + if (adapter->priv[i]->netdev) { + in_dev = __in_dev_get_rtnl(adapter->priv[i]->netdev); + if (!in_dev) + continue; + ifa = rtnl_dereference(in_dev->ifa_list); + if (!ifa || !ifa->ifa_local) + continue; + ips[i] = ifa->ifa_local; + num_ipv4++; + } + } + + for (i = 0; i < num_ipv4; i++) { + if (!ips[i]) + continue; + mef_entry->filter[filt_num].repeat = 1; + memcpy(mef_entry->filter[filt_num].byte_seq, + (u8 *)&ips[i], sizeof(ips[i])); + mef_entry->filter[filt_num].byte_seq[NXPWIFI_MEF_MAX_BYTESEQ] = + sizeof(ips[i]); + mef_entry->filter[filt_num].offset = 46; + mef_entry->filter[filt_num].filt_type = TYPE_EQ; + if (filt_num) { + mef_entry->filter[filt_num].filt_action = + TYPE_OR; + } + filt_num++; + } + + mef_entry->filter[filt_num].repeat = 1; + mef_entry->filter[filt_num].byte_seq[0] = 0x08; + mef_entry->filter[filt_num].byte_seq[1] = 0x06; + mef_entry->filter[filt_num].byte_seq[NXPWIFI_MEF_MAX_BYTESEQ] = 2; + mef_entry->filter[filt_num].offset = 20; + mef_entry->filter[filt_num].filt_type = TYPE_EQ; + mef_entry->filter[filt_num].filt_action = TYPE_AND; +} + +static int nxpwifi_set_wowlan_mef_entry(struct nxpwifi_private *priv, + struct nxpwifi_ds_mef_cfg *mef_cfg, + struct nxpwifi_mef_entry *mef_entry, + struct cfg80211_wowlan *wowlan) +{ + int i, filt_num = 0, ret = 0; + bool first_pat = true; + u8 byte_seq[NXPWIFI_MEF_MAX_BYTESEQ + 1]; + + mef_entry->mode = MEF_MODE_HOST_SLEEP; + mef_entry->action = MEF_ACTION_ALLOW_AND_WAKEUP_HOST; + + for (i = 0; i < wowlan->n_patterns; i++) { + memset(byte_seq, 0, sizeof(byte_seq)); + if (!nxpwifi_is_pattern_supported + (&wowlan->patterns[i], byte_seq, + NXPWIFI_MEF_MAX_BYTESEQ)) { + nxpwifi_dbg(priv->adapter, ERROR, + "Pattern not supported\n"); + return -EOPNOTSUPP; + } + + if (!wowlan->patterns[i].pkt_offset) { + if (is_unicast_ether_addr(byte_seq) && + byte_seq[NXPWIFI_MEF_MAX_BYTESEQ] == 1) { + mef_cfg->criteria |= NXPWIFI_CRITERIA_UNICAST; + continue; + } else if (is_broadcast_ether_addr(byte_seq)) { + mef_cfg->criteria |= NXPWIFI_CRITERIA_BROADCAST; + continue; + } else if ((!memcmp(byte_seq, "\x33\x33", 2) && + (byte_seq[NXPWIFI_MEF_MAX_BYTESEQ] == 2)) || + (!memcmp(byte_seq, "\x01\x00\x5e", 3) && + (byte_seq[NXPWIFI_MEF_MAX_BYTESEQ] == 3))) { + mef_cfg->criteria |= NXPWIFI_CRITERIA_MULTICAST; + continue; + } + } + mef_entry->filter[filt_num].repeat = 1; + mef_entry->filter[filt_num].offset = + wowlan->patterns[i].pkt_offset; + memcpy(mef_entry->filter[filt_num].byte_seq, byte_seq, + sizeof(byte_seq)); + mef_entry->filter[filt_num].filt_type = TYPE_EQ; + + if (first_pat) { + first_pat = false; + nxpwifi_dbg(priv->adapter, INFO, "Wake on patterns\n"); + } else { + mef_entry->filter[filt_num].filt_action = TYPE_AND; + } + + filt_num++; + } + + if (wowlan->magic_pkt) { + mef_cfg->criteria |= NXPWIFI_CRITERIA_UNICAST; + mef_entry->filter[filt_num].repeat = 16; + memcpy(mef_entry->filter[filt_num].byte_seq, priv->curr_addr, + ETH_ALEN); + mef_entry->filter[filt_num].byte_seq[NXPWIFI_MEF_MAX_BYTESEQ] = + ETH_ALEN; + mef_entry->filter[filt_num].offset = 28; + mef_entry->filter[filt_num].filt_type = TYPE_EQ; + if (filt_num) + mef_entry->filter[filt_num].filt_action = TYPE_OR; + + filt_num++; + mef_entry->filter[filt_num].repeat = 16; + memcpy(mef_entry->filter[filt_num].byte_seq, priv->curr_addr, + ETH_ALEN); + mef_entry->filter[filt_num].byte_seq[NXPWIFI_MEF_MAX_BYTESEQ] = + ETH_ALEN; + mef_entry->filter[filt_num].offset = 56; + mef_entry->filter[filt_num].filt_type = TYPE_EQ; + mef_entry->filter[filt_num].filt_action = TYPE_OR; + nxpwifi_dbg(priv->adapter, INFO, "Wake on magic packet\n"); + } + return ret; +} + +static int nxpwifi_set_mef_filter(struct nxpwifi_private *priv, + struct cfg80211_wowlan *wowlan) +{ + int ret = 0, num_entries = 1; + struct nxpwifi_ds_mef_cfg mef_cfg; + struct nxpwifi_mef_entry *mef_entry; + + if (wowlan->n_patterns || wowlan->magic_pkt) + num_entries++; + + mef_entry = kzalloc_objs(*mef_entry, num_entries, GFP_KERNEL); + if (!mef_entry) + return -ENOMEM; + + memset(&mef_cfg, 0, sizeof(mef_cfg)); + mef_cfg.criteria |= NXPWIFI_CRITERIA_BROADCAST | + NXPWIFI_CRITERIA_UNICAST; + mef_cfg.num_entries = num_entries; + mef_cfg.mef_entry = mef_entry; + + nxpwifi_set_auto_arp_mef_entry(priv, &mef_entry[0]); + + if (wowlan->n_patterns || wowlan->magic_pkt) { + ret = nxpwifi_set_wowlan_mef_entry(priv, &mef_cfg, + &mef_entry[1], wowlan); + if (ret) + goto done; + } + + if (!mef_cfg.criteria) + mef_cfg.criteria = NXPWIFI_CRITERIA_BROADCAST | + NXPWIFI_CRITERIA_UNICAST | + NXPWIFI_CRITERIA_MULTICAST; + + ret = nxpwifi_mef_cfg(priv, &mef_cfg); + +done: + kfree(mef_entry); + return ret; +} + +static int nxpwifi_cfg80211_suspend(struct wiphy *wiphy, + struct cfg80211_wowlan *wowlan) +{ + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + struct nxpwifi_ds_hs_cfg hs_cfg; + int i, ret = 0, retry_num = 10; + struct nxpwifi_private *priv; + struct nxpwifi_private *sta_priv = + nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_STA); + + adapter->wowlan_enabled = false; + + sta_priv->scan_aborting = true; + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + nxpwifi_abort_cac(priv); + } + + nxpwifi_cancel_all_pending_cmd(adapter); + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + if (priv->netdev) + netif_device_detach(priv->netdev); + } + + for (i = 0; i < retry_num; i++) { + if (!nxpwifi_wmm_lists_empty(adapter) || + !nxpwifi_bypass_txlist_empty(adapter) || + !skb_queue_empty(&adapter->tx_data_q)) + usleep_range(10000, 15000); + else + break; + } + + if (!wowlan) { + nxpwifi_dbg(adapter, INFO, + "None of the WOWLAN triggers enabled\n"); + ret = 0; + goto done; + } + + if (!sta_priv->media_connected && !wowlan->nd_config) { + nxpwifi_dbg(adapter, ERROR, + "Can not configure WOWLAN in disconnected state\n"); + ret = 0; + goto done; + } + + ret = nxpwifi_set_mef_filter(sta_priv, wowlan); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "Failed to set MEF filter\n"); + goto done; + } + + memset(&hs_cfg, 0, sizeof(hs_cfg)); + hs_cfg.conditions = le32_to_cpu(adapter->hs_cfg.conditions); + + if (wowlan->nd_config) { + nxpwifi_dbg(adapter, INFO, "Wake on net detect\n"); + hs_cfg.conditions |= HS_CFG_COND_MAC_EVENT; + nxpwifi_cfg80211_sched_scan_start(wiphy, sta_priv->netdev, + wowlan->nd_config); + } + + if (wowlan->disconnect) { + hs_cfg.conditions |= HS_CFG_COND_MAC_EVENT; + nxpwifi_dbg(sta_priv->adapter, INFO, "Wake on device disconnect\n"); + } + + hs_cfg.is_invoke_hostcmd = false; + hs_cfg.gpio = adapter->hs_cfg.gpio; + hs_cfg.gap = adapter->hs_cfg.gap; + ret = nxpwifi_set_hs_params(sta_priv, HOST_ACT_GEN_SET, + NXPWIFI_SYNC_CMD, &hs_cfg); + if (ret) + nxpwifi_dbg(adapter, ERROR, "Failed to set HS params\n"); + + adapter->wowlan_enabled = true; + +done: + sta_priv->scan_aborting = false; + return ret; +} + +static int nxpwifi_cfg80211_resume(struct wiphy *wiphy) +{ + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + struct nxpwifi_private *priv; + struct nxpwifi_ds_wakeup_reason wakeup_reason; + struct cfg80211_wowlan_wakeup wakeup_report; + int i; + bool report_wakeup_reason = true; + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + if (priv->netdev) + netif_device_attach(priv->netdev); + } + + if (!wiphy->wowlan_config) + goto done; + + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_STA); + nxpwifi_get_wakeup_reason(priv, HOST_ACT_GEN_GET, NXPWIFI_SYNC_CMD, + &wakeup_reason); + memset(&wakeup_report, 0, sizeof(struct cfg80211_wowlan_wakeup)); + + wakeup_report.pattern_idx = -1; + + switch (wakeup_reason.hs_wakeup_reason) { + case NO_HSWAKEUP_REASON: + break; + case BCAST_DATA_MATCHED: + break; + case MCAST_DATA_MATCHED: + break; + case UCAST_DATA_MATCHED: + break; + case MASKTABLE_EVENT_MATCHED: + break; + case NON_MASKABLE_EVENT_MATCHED: + if (wiphy->wowlan_config->disconnect) + wakeup_report.disconnect = true; + if (wiphy->wowlan_config->nd_config) + wakeup_report.net_detect = adapter->nd_info; + break; + case NON_MASKABLE_CONDITION_MATCHED: + break; + case MAGIC_PATTERN_MATCHED: + if (wiphy->wowlan_config->magic_pkt) + wakeup_report.magic_pkt = true; + if (wiphy->wowlan_config->n_patterns) + wakeup_report.pattern_idx = 1; + break; + case GTK_REKEY_FAILURE: + if (wiphy->wowlan_config->gtk_rekey_failure) + wakeup_report.gtk_rekey_failure = true; + break; + default: + report_wakeup_reason = false; + break; + } + + if (report_wakeup_reason) + cfg80211_report_wowlan_wakeup(&priv->wdev, &wakeup_report, + GFP_KERNEL); + +done: + if (adapter->nd_info) { + for (i = 0 ; i < adapter->nd_info->n_matches ; i++) + kfree(adapter->nd_info->matches[i]); + kfree(adapter->nd_info); + adapter->nd_info = NULL; + } + + return 0; +} + +static void nxpwifi_cfg80211_set_wakeup(struct wiphy *wiphy, + bool enabled) +{ + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + + device_set_wakeup_enable(adapter->dev, enabled); +} +#endif + +static int nxpwifi_get_coalesce_pkt_type(u8 *byte_seq) +{ + if ((byte_seq[0] & 0x01) && + byte_seq[NXPWIFI_COALESCE_MAX_BYTESEQ] == 1) + return PACKET_TYPE_UNICAST; + else if (is_broadcast_ether_addr(byte_seq)) + return PACKET_TYPE_BROADCAST; + else if ((!memcmp(byte_seq, "\x33\x33", 2) && + byte_seq[NXPWIFI_COALESCE_MAX_BYTESEQ] == 2) || + (!memcmp(byte_seq, "\x01\x00\x5e", 3) && + byte_seq[NXPWIFI_COALESCE_MAX_BYTESEQ] == 3)) + return PACKET_TYPE_MULTICAST; + + return 0; +} + +static int +nxpwifi_fill_coalesce_rule_info(struct nxpwifi_private *priv, + struct cfg80211_coalesce_rules *crule, + struct nxpwifi_coalesce_rule *mrule) +{ + u8 byte_seq[NXPWIFI_COALESCE_MAX_BYTESEQ + 1]; + struct filt_field_param *param; + int i; + + mrule->max_coalescing_delay = crule->delay; + + param = mrule->params; + + for (i = 0; i < crule->n_patterns; i++) { + memset(byte_seq, 0, sizeof(byte_seq)); + if (!nxpwifi_is_pattern_supported(&crule->patterns[i], + byte_seq, + NXPWIFI_COALESCE_MAX_BYTESEQ)) { + nxpwifi_dbg(priv->adapter, ERROR, + "Pattern not supported\n"); + return -EOPNOTSUPP; + } + + if (!crule->patterns[i].pkt_offset) { + u8 pkt_type; + + pkt_type = nxpwifi_get_coalesce_pkt_type(byte_seq); + if (pkt_type && mrule->pkt_type) { + nxpwifi_dbg(priv->adapter, ERROR, + "Multiple packet types not allowed\n"); + return -EOPNOTSUPP; + } else if (pkt_type) { + mrule->pkt_type = pkt_type; + continue; + } + } + + if (crule->condition == NL80211_COALESCE_CONDITION_MATCH) + param->operation = RECV_FILTER_MATCH_TYPE_EQ; + else + param->operation = RECV_FILTER_MATCH_TYPE_NE; + + param->operand_len = byte_seq[NXPWIFI_COALESCE_MAX_BYTESEQ]; + memcpy(param->operand_byte_stream, byte_seq, + param->operand_len); + param->offset = crule->patterns[i].pkt_offset; + param++; + + mrule->num_of_fields++; + } + + if (!mrule->pkt_type) { + nxpwifi_dbg(priv->adapter, ERROR, + "Packet type can not be determined\n"); + return -EOPNOTSUPP; + } + + return 0; +} + +static int nxpwifi_cfg80211_set_coalesce(struct wiphy *wiphy, + struct cfg80211_coalesce *coalesce) +{ + struct nxpwifi_adapter *adapter = nxpwifi_cfg80211_get_adapter(wiphy); + int i, ret; + struct nxpwifi_ds_coalesce_cfg coalesce_cfg; + struct nxpwifi_private *priv = + nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_STA); + + memset(&coalesce_cfg, 0, sizeof(coalesce_cfg)); + + if (!coalesce) + return nxpwifi_coalesce_cfg(priv, &coalesce_cfg); + + coalesce_cfg.num_of_rules = coalesce->n_rules; + for (i = 0; i < coalesce->n_rules; i++) { + ret = nxpwifi_fill_coalesce_rule_info(priv, &coalesce->rules[i], + &coalesce_cfg.rule[i]); + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "Recheck the patterns provided for rule %d\n", + i + 1); + return ret; + } + } + + return nxpwifi_coalesce_cfg(priv, &coalesce_cfg); +} + +static int +nxpwifi_cfg80211_uap_add_station(struct nxpwifi_private *priv, const u8 *mac, + struct station_parameters *params) +{ + struct nxpwifi_sta_info add_sta; + int ret; + + memcpy(add_sta.peer_mac, mac, ETH_ALEN); + add_sta.params = params; + + ret = nxpwifi_add_new_station(priv, &add_sta); + + return ret; +} + +static int +nxpwifi_cfg80211_add_station(struct wiphy *wiphy, struct wireless_dev *wdev, + const u8 *mac, struct station_parameters *params) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + int ret = -EOPNOTSUPP; + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) + ret = nxpwifi_cfg80211_uap_add_station(priv, mac, params); + + return ret; +} + +static int +nxpwifi_cfg80211_channel_switch(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_csa_settings *params) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + int chsw_msec; + int ret; + + if (priv->adapter->scan_processing) { + nxpwifi_dbg(priv->adapter, ERROR, + "radar detection: scan in process...\n"); + return -EBUSY; + } + + if (priv->wdev.links[0].cac_started) + return -EBUSY; + + if (cfg80211_chandef_identical(¶ms->chandef, + &priv->dfs_chandef)) + return -EINVAL; + + if (params->block_tx) { + netif_carrier_off(priv->netdev); + nxpwifi_stop_net_dev_queue(priv->netdev, priv->adapter); + priv->uap_stop_tx = true; + } + + ret = nxpwifi_del_mgmt_ies(priv); + if (ret) + nxpwifi_dbg(priv->adapter, ERROR, + "Failed to delete mgmt IEs!\n"); + + ret = nxpwifi_set_mgmt_ies(priv, ¶ms->beacon_csa); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "%s: setting mgmt ies failed\n", __func__); + goto done; + } + + memcpy(&priv->dfs_chandef, ¶ms->chandef, sizeof(priv->dfs_chandef)); + memcpy(&priv->ap_update_info.beacon, ¶ms->beacon_after, + sizeof(priv->ap_update_info.beacon)); + + chsw_msec = max(params->count * priv->bss_cfg.beacon_period, 100); + + nxpwifi_queue_delayed_wiphy_work(priv->adapter, + &priv->dfs_chan_sw_work, + msecs_to_jiffies(chsw_msec)); + +done: + return ret; +} + +static int nxpwifi_cfg80211_get_channel(struct wiphy *wiphy, + struct wireless_dev *wdev, + unsigned int link_id, + struct cfg80211_chan_def *chandef) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + struct nxpwifi_bssdescriptor *curr_bss; + struct ieee80211_channel *chan; + enum nl80211_channel_type chan_type; + enum nl80211_band band; + int freq; + int ret = -ENODATA; + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP && + cfg80211_chandef_valid(&priv->bss_chandef)) { + *chandef = priv->bss_chandef; + ret = 0; + } else if (priv->media_connected) { + curr_bss = &priv->curr_bss_params.bss_descriptor; + band = nxpwifi_band_to_radio_type(priv->curr_bss_params.band); + freq = ieee80211_channel_to_frequency(curr_bss->channel, band); + chan = ieee80211_get_channel(wiphy, freq); + + if (priv->ht_param_present) { + chan_type = nxpwifi_get_chan_type(priv); + cfg80211_chandef_create(chandef, chan, chan_type); + } else { + cfg80211_chandef_create(chandef, chan, + NL80211_CHAN_NO_HT); + } + ret = 0; + } + + return ret; +} + +#ifdef CONFIG_NL80211_TESTMODE + +enum nxpwifi_tm_attr { + __NXPWIFI_TM_ATTR_INVALID = 0, + NXPWIFI_TM_ATTR_CMD = 1, + NXPWIFI_TM_ATTR_DATA = 2, + + /* keep last */ + __NXPWIFI_TM_ATTR_AFTER_LAST, + NXPWIFI_TM_ATTR_MAX = __NXPWIFI_TM_ATTR_AFTER_LAST - 1, +}; + +static const struct nla_policy nxpwifi_tm_policy[NXPWIFI_TM_ATTR_MAX + 1] = { + [NXPWIFI_TM_ATTR_CMD] = { .type = NLA_U32 }, + [NXPWIFI_TM_ATTR_DATA] = { .type = NLA_BINARY, + .len = NXPWIFI_SIZE_OF_CMD_BUFFER }, +}; + +enum nxpwifi_tm_command { + NXPWIFI_TM_CMD_HOSTCMD = 0, +}; + +static int nxpwifi_tm_cmd(struct wiphy *wiphy, struct wireless_dev *wdev, + void *data, int len) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(wdev->netdev); + struct nxpwifi_ds_misc_cmd *hostcmd; + struct nlattr *tb[NXPWIFI_TM_ATTR_MAX + 1]; + struct sk_buff *skb; + int err; + + if (!priv) + return -EINVAL; + + err = nla_parse_deprecated(tb, NXPWIFI_TM_ATTR_MAX, data, len, + nxpwifi_tm_policy, NULL); + if (err) + return err; + + if (!tb[NXPWIFI_TM_ATTR_CMD]) + return -EINVAL; + + switch (nla_get_u32(tb[NXPWIFI_TM_ATTR_CMD])) { + case NXPWIFI_TM_CMD_HOSTCMD: + if (!tb[NXPWIFI_TM_ATTR_DATA]) + return -EINVAL; + + hostcmd = kzalloc_obj(*hostcmd, GFP_KERNEL); + if (!hostcmd) + return -ENOMEM; + + hostcmd->len = nla_len(tb[NXPWIFI_TM_ATTR_DATA]); + memcpy(hostcmd->cmd, nla_data(tb[NXPWIFI_TM_ATTR_DATA]), + hostcmd->len); + + if (nxpwifi_hostcmd(priv, hostcmd)) { + nxpwifi_dbg(priv->adapter, ERROR, "Failed to process hostcmd\n"); + kfree(hostcmd); + return -EFAULT; + } + + /* process hostcmd response*/ + skb = cfg80211_testmode_alloc_reply_skb(wiphy, hostcmd->len); + if (!skb) { + kfree(hostcmd); + return -ENOMEM; + } + err = nla_put(skb, NXPWIFI_TM_ATTR_DATA, + hostcmd->len, hostcmd->cmd); + if (err) { + kfree(hostcmd); + kfree_skb(skb); + return -EMSGSIZE; + } + + err = cfg80211_testmode_reply(skb); + kfree(hostcmd); + return err; + default: + return -EOPNOTSUPP; + } +} +#endif + +static int +nxpwifi_cfg80211_start_radar_detection(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_chan_def *chandef, + u32 cac_time_ms, int link_id) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + struct nxpwifi_radar_params radar_params; + int ret; + + if (priv->adapter->scan_processing) { + nxpwifi_dbg(priv->adapter, ERROR, + "radar detection: scan already in process...\n"); + return -EBUSY; + } + + if (!nxpwifi_is_11h_active(priv)) { + nxpwifi_dbg(priv->adapter, INFO, + "Enable 11h extensions in FW\n"); + if (nxpwifi_11h_activate(priv, true)) { + nxpwifi_dbg(priv->adapter, ERROR, + "Failed to activate 11h extensions!!"); + return -EPERM; + } + priv->state_11h.is_11h_active = true; + } + + memset(&radar_params, 0, sizeof(struct nxpwifi_radar_params)); + radar_params.chandef = chandef; + radar_params.cac_time_ms = cac_time_ms; + + memcpy(&priv->dfs_chandef, chandef, sizeof(priv->dfs_chandef)); + + ret = nxpwifi_chan_report_request(priv, &radar_params); + if (!ret) + nxpwifi_queue_delayed_wiphy_work(priv->adapter, + &priv->dfs_cac_work, + msecs_to_jiffies(cac_time_ms)); + + return ret; +} + +static int +nxpwifi_cfg80211_change_station(struct wiphy *wiphy, struct wireless_dev *wdev, + const u8 *mac, + struct station_parameters *params) +{ + return 0; +} + +static int +nxpwifi_cfg80211_authenticate(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_auth_request *req) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + struct nxpwifi_adapter *adapter = priv->adapter; + struct sk_buff *skb; + u16 pkt_len, auth_alg; + int ret; + struct ieee80211_mgmt *mgmt; + struct nxpwifi_txinfo *tx_info; + u8 trans = 1, status_code = 0; + u8 *varptr = NULL; + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + nxpwifi_dbg(adapter, ERROR, "Interface role is AP\n"); + return -EINVAL; + } + + if (priv->wdev.iftype != NL80211_IFTYPE_STATION) { + nxpwifi_dbg(adapter, ERROR, + "Interface type is not correct (type %d)\n", + priv->wdev.iftype); + return -EINVAL; + } + + if (!nxpwifi_is_channel_setting_allowable(priv, req->bss->channel)) + return -EOPNOTSUPP; + + if (priv->auth_alg != WLAN_AUTH_SAE && + (priv->auth_flag & HOST_MLME_AUTH_PENDING)) { + nxpwifi_dbg(adapter, ERROR, "Pending auth on going\n"); + return -EBUSY; + } + + if (!priv->host_mlme_reg) { + priv->host_mlme_reg = true; + priv->mgmt_frame_mask |= HOST_MLME_MGMT_MASK; + nxpwifi_mgmt_frame_reg(priv, priv->mgmt_frame_mask); + } + + switch (req->auth_type) { + case NL80211_AUTHTYPE_OPEN_SYSTEM: + auth_alg = WLAN_AUTH_OPEN; + break; + case NL80211_AUTHTYPE_SHARED_KEY: + auth_alg = WLAN_AUTH_SHARED_KEY; + break; + case NL80211_AUTHTYPE_FT: + auth_alg = WLAN_AUTH_FT; + break; + case NL80211_AUTHTYPE_NETWORK_EAP: + auth_alg = WLAN_AUTH_LEAP; + break; + case NL80211_AUTHTYPE_SAE: + auth_alg = WLAN_AUTH_SAE; + break; + default: + nxpwifi_dbg(adapter, ERROR, + "unsupported auth type=%d\n", req->auth_type); + return -EOPNOTSUPP; + } + + if (!(priv->auth_flag & HOST_MLME_AUTH_PENDING)) { + ret = nxpwifi_remain_on_chan_cfg(priv, HOST_ACT_GEN_SET, + req->bss->channel, + AUTH_TX_DEFAULT_WAIT_TIME); + + if (!ret) { + priv->roc_cfg.cookie = + nxpwifi_roc_cookie(adapter); + priv->roc_cfg.chan = *req->bss->channel; + } else { + return -EPERM; + } + } + + priv->sec_info.authentication_mode = auth_alg; + + nxpwifi_cancel_scan(adapter); + + pkt_len = (u16)req->ie_len + req->auth_data_len + + NXPWIFI_MGMT_HEADER_LEN + NXPWIFI_AUTH_BODY_LEN; + + if (req->auth_data_len >= 4) + pkt_len -= 4; + + mgmt = kzalloc(pkt_len, GFP_KERNEL); + + skb = dev_alloc_skb(NXPWIFI_MIN_DATA_HEADER_LEN + + NXPWIFI_MGMT_FRAME_HEADER_SIZE + + pkt_len + sizeof(pkt_len)); + if (!skb) { + nxpwifi_dbg(adapter, ERROR, + "allocate skb failed for management frame\n"); + return -ENOMEM; + } + + tx_info = NXPWIFI_SKB_TXCB(skb); + memset(tx_info, 0, sizeof(*tx_info)); + tx_info->bss_num = priv->bss_num; + tx_info->bss_type = priv->bss_type; + tx_info->pkt_len = pkt_len; + + memcpy(mgmt->da, req->bss->bssid, ETH_ALEN); + memcpy(mgmt->sa, priv->curr_addr, ETH_ALEN); + memcpy(mgmt->bssid, req->bss->bssid, ETH_ALEN); + mgmt->frame_control = + cpu_to_le16(IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_AUTH); + + if (req->auth_data_len >= 4) { + if (req->auth_type == NL80211_AUTHTYPE_SAE) { + __le16 *pos = (__le16 *)req->auth_data; + + trans = le16_to_cpu(pos[0]); + status_code = le16_to_cpu(pos[1]); + } + memcpy((u8 *)(&mgmt->u.auth.variable), req->auth_data + 4, + req->auth_data_len - 4); + varptr = (u8 *)&mgmt->u.auth.variable + + (req->auth_data_len - 4); + } + + mgmt->u.auth.auth_alg = cpu_to_le16(auth_alg); + mgmt->u.auth.auth_transaction = cpu_to_le16(trans); + mgmt->u.auth.status_code = cpu_to_le16(status_code); + + if (req->ie && req->ie_len) { + if (!varptr) + varptr = (u8 *)&mgmt->u.auth.variable; + memcpy((u8 *)varptr, req->ie, req->ie_len); + } + + nxpwifi_form_mgmt_frame(skb, (const u8 *)mgmt, pkt_len); + kfree(mgmt); + priv->auth_flag = HOST_MLME_AUTH_PENDING; + priv->auth_alg = auth_alg; + skb->priority = WMM_HIGHEST_PRIORITY; + __net_timestamp(skb); + + nxpwifi_dbg(adapter, MSG, + "auth: send authentication to %pM\n", req->bss->bssid); + + nxpwifi_queue_tx_pkt(priv, skb); + + return 0; +} + +static int +nxpwifi_cfg80211_associate(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_assoc_request *req) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + struct nxpwifi_adapter *adapter = priv->adapter; + int ret; + struct cfg80211_ssid req_ssid; + const u8 *ssid_ie; + + if (GET_BSS_ROLE(priv) != NXPWIFI_BSS_ROLE_STA) { + nxpwifi_dbg(adapter, ERROR, + "%s: reject infra assoc request in non-STA role\n", + dev->name); + return -EINVAL; + } + + if (test_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags) || + test_bit(NXPWIFI_IS_CMD_TIMEDOUT, &adapter->work_flags)) { + nxpwifi_dbg(adapter, ERROR, + "%s: Ignore association.\t" + "Card removed or FW in bad state\n", + dev->name); + return -EPERM; + } + + if (priv->auth_alg == WLAN_AUTH_SAE) + priv->auth_flag = HOST_MLME_AUTH_DONE; + + if (priv->auth_flag && !(priv->auth_flag & HOST_MLME_AUTH_DONE)) + return -EBUSY; + + if (priv->roc_cfg.cookie) { + ret = nxpwifi_remain_on_chan_cfg(priv, HOST_ACT_GEN_REMOVE, + &priv->roc_cfg.chan, 0); + if (!ret) + memset(&priv->roc_cfg, 0, + sizeof(struct nxpwifi_roc_cfg)); + else + return ret; + } + + if (!nxpwifi_stop_bg_scan(priv)) + cfg80211_sched_scan_stopped_locked(priv->wdev.wiphy, 0); + + memset(&req_ssid, 0, sizeof(struct cfg80211_ssid)); + rcu_read_lock(); + ssid_ie = ieee80211_bss_get_ie(req->bss, WLAN_EID_SSID); + + if (!ssid_ie) + goto ssid_err; + + req_ssid.ssid_len = ssid_ie[1]; + if (req_ssid.ssid_len > IEEE80211_MAX_SSID_LEN) { + nxpwifi_dbg(adapter, ERROR, "invalid SSID - aborting\n"); + goto ssid_err; + } + + memcpy(req_ssid.ssid, ssid_ie + 2, req_ssid.ssid_len); + if (!req_ssid.ssid_len || req_ssid.ssid[0] < 0x20) { + nxpwifi_dbg(adapter, ERROR, "invalid SSID - aborting\n"); + goto ssid_err; + } + rcu_read_unlock(); + + /* + * As this is new association, clear locally stored + * keys and security related flags + */ + priv->sec_info.wpa_enabled = false; + priv->sec_info.wpa2_enabled = false; + priv->wep_key_curr_index = 0; + priv->sec_info.encryption_mode = 0; + priv->sec_info.is_authtype_auto = 0; + ret = nxpwifi_set_encode(priv, NULL, NULL, 0, 0, NULL, 1); + + if (req->crypto.n_ciphers_pairwise) + priv->sec_info.encryption_mode = + req->crypto.ciphers_pairwise[0]; + + if (req->crypto.cipher_group) + priv->sec_info.encryption_mode = req->crypto.cipher_group; + + if (req->ie) + ret = nxpwifi_set_gen_ie(priv, req->ie, req->ie_len); + + memcpy(priv->cfg_bssid, req->bss->bssid, ETH_ALEN); + + nxpwifi_dbg(adapter, MSG, + "assoc: send association to %pM\n", req->bss->bssid); + + cfg80211_ref_bss(adapter->wiphy, req->bss); + + ret = nxpwifi_bss_start(priv, req->bss, &req_ssid); + + if (ret) { + priv->auth_flag = 0; + priv->auth_alg = WLAN_AUTH_NONE; + eth_zero_addr(priv->cfg_bssid); + } + + if (ret >= 0) { + if (priv->assoc_rsp_size) { + priv->req_bss = req->bss; + adapter->assoc_resp_received = true; + nxpwifi_queue_wiphy_work(adapter, + &adapter->host_mlme_work); + } + ret = 0; + } + + cfg80211_put_bss(priv->adapter->wiphy, req->bss); + + return ret; + +ssid_err: + + rcu_read_unlock(); + return -EINVAL; +} + +static int +nxpwifi_cfg80211_disconnect(struct wiphy *wiphy, struct net_device *dev, + u16 reason_code) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + int ret; + + if (!nxpwifi_stop_bg_scan(priv)) + cfg80211_sched_scan_stopped_locked(priv->wdev.wiphy, 0); + + ret = nxpwifi_deauthenticate(priv, NULL); + if (!ret) { + eth_zero_addr(priv->cfg_bssid); + priv->hs2_enabled = false; + } + + return ret; +} + +static int +nxpwifi_cfg80211_deauthenticate(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_deauth_request *req) +{ + return nxpwifi_cfg80211_disconnect(wiphy, dev, req->reason_code); +} + +static int +nxpwifi_cfg80211_disassociate(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_disassoc_request *req) +{ + return nxpwifi_cfg80211_disconnect(wiphy, dev, req->reason_code); +} + +static int +nxpwifi_cfg80211_probe_client(struct wiphy *wiphy, + struct net_device *dev, const u8 *peer, + u64 *cookie) +{ + /* + * hostapd looks for NL80211_CMD_PROBE_CLIENT support; otherwise, + * it requires monitor-mode support (which mwifiex doesn't support). + * Provide fake probe_client support to work around this. + */ + return -EOPNOTSUPP; +} + +/* station cfg80211 operations */ +static const struct cfg80211_ops nxpwifi_cfg80211_ops = { + .add_virtual_intf = nxpwifi_add_virtual_intf, + .del_virtual_intf = nxpwifi_del_virtual_intf, + .change_virtual_intf = nxpwifi_cfg80211_change_virtual_intf, + .scan = nxpwifi_cfg80211_scan, + .auth = nxpwifi_cfg80211_authenticate, + .assoc = nxpwifi_cfg80211_associate, + .deauth = nxpwifi_cfg80211_deauthenticate, + .disassoc = nxpwifi_cfg80211_disassociate, + .probe_client = nxpwifi_cfg80211_probe_client, + .get_station = nxpwifi_cfg80211_get_station, + .dump_station = nxpwifi_cfg80211_dump_station, + .dump_survey = nxpwifi_cfg80211_dump_survey, + .set_wiphy_params = nxpwifi_cfg80211_set_wiphy_params, + .add_key = nxpwifi_cfg80211_add_key, + .del_key = nxpwifi_cfg80211_del_key, + .set_default_mgmt_key = nxpwifi_cfg80211_set_default_mgmt_key, + .mgmt_tx = nxpwifi_cfg80211_mgmt_tx, + .update_mgmt_frame_registrations = + nxpwifi_cfg80211_update_mgmt_frame_registrations, + .remain_on_channel = nxpwifi_cfg80211_remain_on_channel, + .cancel_remain_on_channel = nxpwifi_cfg80211_cancel_remain_on_channel, + .set_default_key = nxpwifi_cfg80211_set_default_key, + .set_power_mgmt = nxpwifi_cfg80211_set_power_mgmt, + .set_tx_power = nxpwifi_cfg80211_set_tx_power, + .get_tx_power = nxpwifi_cfg80211_get_tx_power, + .set_bitrate_mask = nxpwifi_cfg80211_set_bitrate_mask, + .start_ap = nxpwifi_cfg80211_start_ap, + .stop_ap = nxpwifi_cfg80211_stop_ap, + .change_beacon = nxpwifi_cfg80211_change_beacon, + .set_cqm_rssi_config = nxpwifi_cfg80211_set_cqm_rssi_config, + .set_antenna = nxpwifi_cfg80211_set_antenna, + .get_antenna = nxpwifi_cfg80211_get_antenna, + .del_station = nxpwifi_cfg80211_del_station, + .sched_scan_start = nxpwifi_cfg80211_sched_scan_start, + .sched_scan_stop = nxpwifi_cfg80211_sched_scan_stop, + .change_station = nxpwifi_cfg80211_change_station, +#ifdef CONFIG_PM + .suspend = nxpwifi_cfg80211_suspend, + .resume = nxpwifi_cfg80211_resume, + .set_wakeup = nxpwifi_cfg80211_set_wakeup, +#endif + .set_coalesce = nxpwifi_cfg80211_set_coalesce, + .add_station = nxpwifi_cfg80211_add_station, + CFG80211_TESTMODE_CMD(nxpwifi_tm_cmd) + .get_channel = nxpwifi_cfg80211_get_channel, + .start_radar_detection = nxpwifi_cfg80211_start_radar_detection, + .channel_switch = nxpwifi_cfg80211_channel_switch, +}; + +#ifdef CONFIG_PM +static const struct wiphy_wowlan_support nxpwifi_wowlan_support = { + .flags = WIPHY_WOWLAN_MAGIC_PKT | WIPHY_WOWLAN_DISCONNECT | + WIPHY_WOWLAN_NET_DETECT | WIPHY_WOWLAN_SUPPORTS_GTK_REKEY | + WIPHY_WOWLAN_GTK_REKEY_FAILURE, + .n_patterns = NXPWIFI_MEF_MAX_FILTERS, + .pattern_min_len = 1, + .pattern_max_len = NXPWIFI_MAX_PATTERN_LEN, + .max_pkt_offset = NXPWIFI_MAX_OFFSET_LEN, + .max_nd_match_sets = NXPWIFI_MAX_ND_MATCH_SETS, +}; + +static const struct wiphy_wowlan_support nxpwifi_wowlan_support_no_gtk = { + .flags = WIPHY_WOWLAN_MAGIC_PKT | WIPHY_WOWLAN_DISCONNECT | + WIPHY_WOWLAN_NET_DETECT, + .n_patterns = NXPWIFI_MEF_MAX_FILTERS, + .pattern_min_len = 1, + .pattern_max_len = NXPWIFI_MAX_PATTERN_LEN, + .max_pkt_offset = NXPWIFI_MAX_OFFSET_LEN, + .max_nd_match_sets = NXPWIFI_MAX_ND_MATCH_SETS, +}; +#endif + +static const struct wiphy_coalesce_support nxpwifi_coalesce_support = { + .n_rules = NXPWIFI_COALESCE_MAX_RULES, + .max_delay = NXPWIFI_MAX_COALESCING_DELAY, + .n_patterns = NXPWIFI_COALESCE_MAX_FILTERS, + .pattern_min_len = 1, + .pattern_max_len = NXPWIFI_MAX_PATTERN_LEN, + .max_pkt_offset = NXPWIFI_MAX_OFFSET_LEN, +}; + +int nxpwifi_init_channel_scan_gap(struct nxpwifi_adapter *adapter) +{ + u32 n_channels_bg, n_channels_a = 0; + + n_channels_bg = nxpwifi_band_2ghz.n_channels; + + if (adapter->fw_bands & BAND_A) + n_channels_a = nxpwifi_band_5ghz.n_channels; + + /* + * allocate twice the number total channels, since the driver issues an + * additional active scan request for hidden SSIDs on passive channels. + */ + adapter->num_in_chan_stats = 2 * (n_channels_bg + n_channels_a); + adapter->chan_stats = vmalloc(array_size(sizeof(*adapter->chan_stats), + adapter->num_in_chan_stats)); + + if (!adapter->chan_stats) + return -ENOMEM; + + return 0; +} + +/* + * Register the device with cfg80211. + * + * Create and initialize the wiphy, fill in defaults and handlers, + * then register it with the cfg80211 subsystem. + */ +int nxpwifi_register_cfg80211(struct nxpwifi_adapter *adapter) +{ + int ret; + void *wdev_priv; + struct wiphy *wiphy; + struct nxpwifi_private *priv = adapter->priv[NXPWIFI_BSS_TYPE_STA]; + struct ieee80211_sta_ht_cap *ht_cap; + struct ieee80211_sta_vht_cap *vht_cap; + u8 *country_code; + u32 thr, retry; + + /* create a new wiphy for use with cfg80211 */ + wiphy = wiphy_new(&nxpwifi_cfg80211_ops, + sizeof(struct nxpwifi_adapter *)); + if (!wiphy) { + nxpwifi_dbg(adapter, ERROR, + "%s: creating new wiphy\n", __func__); + return -ENOMEM; + } + + wiphy->max_scan_ssids = NXPWIFI_MAX_SSID_LIST_LENGTH; + wiphy->max_scan_ie_len = NXPWIFI_MAX_VSIE_LEN; + + wiphy->mgmt_stypes = nxpwifi_mgmt_stypes; + wiphy->max_remain_on_channel_duration = 5000; + wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_MONITOR); + + wiphy->max_num_akm_suites = CFG80211_MAX_NUM_AKM_SUITES; + + wiphy->bands[NL80211_BAND_2GHZ] = + devm_kmemdup(adapter->dev, &nxpwifi_band_2ghz, + sizeof(nxpwifi_band_2ghz), GFP_KERNEL); + if (!wiphy->bands[NL80211_BAND_2GHZ]) { + ret = -ENOMEM; + goto err; + } + + if (adapter->fw_bands & BAND_A) { + wiphy->bands[NL80211_BAND_5GHZ] = + devm_kmemdup(adapter->dev, &nxpwifi_band_5ghz, + sizeof(nxpwifi_band_5ghz), GFP_KERNEL); + if (!wiphy->bands[NL80211_BAND_5GHZ]) { + ret = -ENOMEM; + goto err; + } + } else { + wiphy->bands[NL80211_BAND_5GHZ] = NULL; + } + + ht_cap = &wiphy->bands[NL80211_BAND_2GHZ]->ht_cap; + nxpwifi_setup_ht_caps(priv, ht_cap); + + if (adapter->is_hw_11ac_capable) { + vht_cap = &wiphy->bands[NL80211_BAND_2GHZ]->vht_cap; + nxpwifi_setup_vht_caps(priv, vht_cap); + } + + if (adapter->is_hw_11ax_capable) + nxpwifi_setup_he_caps(priv, wiphy->bands[NL80211_BAND_2GHZ]); + + if (adapter->fw_bands & BAND_A) { + ht_cap = &wiphy->bands[NL80211_BAND_5GHZ]->ht_cap; + nxpwifi_setup_ht_caps(priv, ht_cap); + + if (adapter->is_hw_11ac_capable) { + vht_cap = &wiphy->bands[NL80211_BAND_5GHZ]->vht_cap; + nxpwifi_setup_vht_caps(priv, vht_cap); + } + + if (adapter->is_hw_11ax_capable) + nxpwifi_setup_he_caps(priv, wiphy->bands[NL80211_BAND_5GHZ]); + } + + if (adapter->is_hw_11ac_capable) + wiphy->iface_combinations = &nxpwifi_iface_comb_ap_sta_vht; + else + wiphy->iface_combinations = &nxpwifi_iface_comb_ap_sta; + wiphy->n_iface_combinations = 1; + + wiphy->max_ap_assoc_sta = adapter->max_sta_conn; + + /* Initialize cipher suits */ + wiphy->cipher_suites = nxpwifi_cipher_suites; + wiphy->n_cipher_suites = ARRAY_SIZE(nxpwifi_cipher_suites); + + if (adapter->regd) { + wiphy->regulatory_flags |= REGULATORY_CUSTOM_REG | + REGULATORY_DISABLE_BEACON_HINTS | + REGULATORY_COUNTRY_IE_IGNORE; + wiphy_apply_custom_regulatory(wiphy, adapter->regd); + } + + ether_addr_copy(wiphy->perm_addr, adapter->perm_addr); + wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM; + wiphy->flags |= WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD | + WIPHY_FLAG_AP_UAPSD | + WIPHY_FLAG_REPORTS_OBSS | + WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL | + WIPHY_FLAG_HAS_CHANNEL_SWITCH | + WIPHY_FLAG_NETNS_OK | + WIPHY_FLAG_PS_ON_BY_DEFAULT; + wiphy->max_num_csa_counters = NXPWIFI_MAX_CSA_COUNTERS; + +#ifdef CONFIG_PM + if (ISSUPP_FIRMWARE_SUPPLICANT(priv->adapter->fw_cap_info)) + wiphy->wowlan = &nxpwifi_wowlan_support; + else + wiphy->wowlan = &nxpwifi_wowlan_support_no_gtk; +#endif + + wiphy->coalesce = &nxpwifi_coalesce_support; + + wiphy->probe_resp_offload = NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS | + NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS2; + + wiphy->max_sched_scan_reqs = 1; + wiphy->max_sched_scan_ssids = NXPWIFI_MAX_SSID_LIST_LENGTH; + wiphy->max_sched_scan_ie_len = NXPWIFI_MAX_VSIE_LEN; + wiphy->max_match_sets = NXPWIFI_MAX_SSID_LIST_LENGTH; + + wiphy->available_antennas_tx = BIT(adapter->number_of_antenna) - 1; + wiphy->available_antennas_rx = BIT(adapter->number_of_antenna) - 1; + + wiphy->features |= NL80211_FEATURE_SAE | + NL80211_FEATURE_INACTIVITY_TIMER | + NL80211_FEATURE_LOW_PRIORITY_SCAN | + NL80211_FEATURE_NEED_OBSS_SCAN; + + if (ISSUPP_RANDOM_MAC(adapter->fw_cap_info)) + wiphy->features |= NL80211_FEATURE_SCAN_RANDOM_MAC_ADDR | + NL80211_FEATURE_SCHED_SCAN_RANDOM_MAC_ADDR | + NL80211_FEATURE_ND_RANDOM_MAC_ADDR; + + if (adapter->fw_api_ver == NXPWIFI_FW_V15) + wiphy->features |= NL80211_FEATURE_SK_TX_STATUS; + + /* Reserve space for nxpwifi specific private data for BSS */ + wiphy->bss_priv_size = sizeof(struct nxpwifi_bss_priv); + + wiphy->reg_notifier = nxpwifi_reg_notifier; + + /* Set struct nxpwifi_adapter pointer in wiphy_priv */ + wdev_priv = wiphy_priv(wiphy); + *(unsigned long *)wdev_priv = (unsigned long)adapter; + + set_wiphy_dev(wiphy, priv->adapter->dev); + + ret = wiphy_register(wiphy); + if (ret < 0) { + nxpwifi_dbg(adapter, ERROR, + "%s: wiphy_register failed: %d\n", __func__, ret); + goto err; + } + + if (!adapter->regd) { + if (adapter->region_code == 0x00) { + nxpwifi_dbg(adapter, WARN, + "Ignore world regulatory domain\n"); + } else { + wiphy->regulatory_flags |= + REGULATORY_DISABLE_BEACON_HINTS | + REGULATORY_COUNTRY_IE_IGNORE; + country_code = + nxpwifi_11d_code_2_region(adapter->region_code); + if (country_code && + regulatory_hint(wiphy, country_code)) + nxpwifi_dbg(priv->adapter, ERROR, + "regulatory_hint() failed\n"); + } + } + + nxpwifi_get_802_11_snmp_mib(priv, FRAG_THRESH_I, &thr); + wiphy->frag_threshold = thr; + nxpwifi_get_802_11_snmp_mib(priv, RTS_THRESH_I, &thr); + wiphy->rts_threshold = thr; + nxpwifi_get_802_11_snmp_mib(priv, SHORT_RETRY_LIM_I, &retry); + wiphy->retry_short = (u8)retry; + nxpwifi_get_802_11_snmp_mib(priv, LONG_RETRY_LIM_I, &retry); + wiphy->retry_long = (u8)retry; + + adapter->wiphy = wiphy; + return ret; + +err: + wiphy_free(wiphy); + return ret; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/cfg80211.h b/drivers/net/wireless/nxp/nxpwifi/cfg80211.h new file mode 100644 index 000000000000..3a9a204df195 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/cfg80211.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * nxpwifi: cfg80211 support + * + * Copyright 2011-2024 NXP + */ + +#ifndef __NXPWIFI_CFG80211__ +#define __NXPWIFI_CFG80211__ + +#include "main.h" + +int nxpwifi_register_cfg80211(struct nxpwifi_adapter *adapter); + +int nxpwifi_cfg80211_change_beacon(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_ap_update *params); +#endif diff --git a/drivers/net/wireless/nxp/nxpwifi/cfp.c b/drivers/net/wireless/nxp/nxpwifi/cfp.c new file mode 100644 index 000000000000..e4adbeb6a09c --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/cfp.c @@ -0,0 +1,458 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: Channel, Frequency and Power + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cfg80211.h" + +/* 100mW */ +#define NXPWIFI_TX_PWR_DEFAULT 20 +/* 100mW */ +#define NXPWIFI_TX_PWR_US_DEFAULT 20 +/* 50mW */ +#define NXPWIFI_TX_PWR_JP_DEFAULT 16 +/* 100mW */ +#define NXPWIFI_TX_PWR_FR_100MW 20 +/* 10mW */ +#define NXPWIFI_TX_PWR_FR_10MW 10 +/* 100mW */ +#define NXPWIFI_TX_PWR_EMEA_DEFAULT 20 + +static u8 supported_rates_a[A_SUPPORTED_RATES] = { 0x0c, 0x12, 0x18, 0x24, + 0xb0, 0x48, 0x60, 0x6c, 0 }; +static u16 nxpwifi_data_rates[NXPWIFI_SUPPORTED_RATES_EXT] = { 0x02, 0x04, + 0x0B, 0x16, 0x00, 0x0C, 0x12, 0x18, + 0x24, 0x30, 0x48, 0x60, 0x6C, 0x90, + 0x0D, 0x1A, 0x27, 0x34, 0x4E, 0x68, + 0x75, 0x82, 0x0C, 0x1B, 0x36, 0x51, + 0x6C, 0xA2, 0xD8, 0xF3, 0x10E, 0x00 }; + +static u8 supported_rates_b[B_SUPPORTED_RATES] = { 0x02, 0x04, 0x0b, 0x16, 0 }; + +static u8 supported_rates_g[G_SUPPORTED_RATES] = { 0x0c, 0x12, 0x18, 0x24, + 0x30, 0x48, 0x60, 0x6c, 0 }; + +static u8 supported_rates_bg[BG_SUPPORTED_RATES] = { 0x02, 0x04, 0x0b, 0x0c, + 0x12, 0x16, 0x18, 0x24, 0x30, 0x48, + 0x60, 0x6c, 0 }; + +u16 region_code_index[NXPWIFI_MAX_REGION_CODE] = { 0x00, 0x10, 0x20, 0x30, + 0x31, 0x32, 0x40, 0x41, 0x50 }; + +/* mcs_rate: first 8 entries for 1x1; all 16 for 2x2. */ +static const u16 mcs_rate[4][16] = { + /* LGI 40M */ + { 0x1b, 0x36, 0x51, 0x6c, 0xa2, 0xd8, 0xf3, 0x10e, + 0x36, 0x6c, 0xa2, 0xd8, 0x144, 0x1b0, 0x1e6, 0x21c }, + + /* SGI 40M */ + { 0x1e, 0x3c, 0x5a, 0x78, 0xb4, 0xf0, 0x10e, 0x12c, + 0x3c, 0x78, 0xb4, 0xf0, 0x168, 0x1e0, 0x21c, 0x258 }, + + /* LGI 20M */ + { 0x0d, 0x1a, 0x27, 0x34, 0x4e, 0x68, 0x75, 0x82, + 0x1a, 0x34, 0x4e, 0x68, 0x9c, 0xd0, 0xea, 0x104 }, + + /* SGI 20M */ + { 0x0e, 0x1c, 0x2b, 0x39, 0x56, 0x73, 0x82, 0x90, + 0x1c, 0x39, 0x56, 0x73, 0xad, 0xe7, 0x104, 0x120 } +}; + +/* AC rates */ +static const u16 ac_mcs_rate_nss1[8][10] = { + /* LG 160M */ + { 0x75, 0xEA, 0x15F, 0x1D4, 0x2BE, 0x3A8, 0x41D, + 0x492, 0x57C, 0x618 }, + + /* SG 160M */ + { 0x82, 0x104, 0x186, 0x208, 0x30C, 0x410, 0x492, + 0x514, 0x618, 0x6C6 }, + + /* LG 80M */ + { 0x3B, 0x75, 0xB0, 0xEA, 0x15F, 0x1D4, 0x20F, + 0x249, 0x2BE, 0x30C }, + + /* SG 80M */ + { 0x41, 0x82, 0xC3, 0x104, 0x186, 0x208, 0x249, + 0x28A, 0x30C, 0x363 }, + + /* LG 40M */ + { 0x1B, 0x36, 0x51, 0x6C, 0xA2, 0xD8, 0xF3, + 0x10E, 0x144, 0x168 }, + + /* SG 40M */ + { 0x1E, 0x3C, 0x5A, 0x78, 0xB4, 0xF0, 0x10E, + 0x12C, 0x168, 0x190 }, + + /* LG 20M */ + { 0xD, 0x1A, 0x27, 0x34, 0x4E, 0x68, 0x75, 0x82, 0x9C, 0x00 }, + + /* SG 20M */ + { 0xF, 0x1D, 0x2C, 0x3A, 0x57, 0x74, 0x82, 0x91, 0xAE, 0x00 }, +}; + +/* NSS2 note: the value in the table is 2 multiplier of the actual rate */ +static const u16 ac_mcs_rate_nss2[8][10] = { + /* LG 160M */ + { 0xEA, 0x1D4, 0x2BE, 0x3A8, 0x57C, 0x750, 0x83A, + 0x924, 0xAF8, 0xC30 }, + + /* SG 160M */ + { 0x104, 0x208, 0x30C, 0x410, 0x618, 0x820, 0x924, + 0xA28, 0xC30, 0xD8B }, + + /* LG 80M */ + { 0x75, 0xEA, 0x15F, 0x1D4, 0x2BE, 0x3A8, 0x41D, + 0x492, 0x57C, 0x618 }, + + /* SG 80M */ + { 0x82, 0x104, 0x186, 0x208, 0x30C, 0x410, 0x492, + 0x514, 0x618, 0x6C6 }, + + /* LG 40M */ + { 0x36, 0x6C, 0xA2, 0xD8, 0x144, 0x1B0, 0x1E6, + 0x21C, 0x288, 0x2D0 }, + + /* SG 40M */ + { 0x3C, 0x78, 0xB4, 0xF0, 0x168, 0x1E0, 0x21C, + 0x258, 0x2D0, 0x320 }, + + /* LG 20M */ + { 0x1A, 0x34, 0x4A, 0x68, 0x9C, 0xD0, 0xEA, 0x104, + 0x138, 0x00 }, + + /* SG 20M */ + { 0x1D, 0x3A, 0x57, 0x74, 0xAE, 0xE6, 0x104, 0x121, + 0x15B, 0x00 }, +}; + +struct region_code_mapping { + u8 code; + u8 region[IEEE80211_COUNTRY_STRING_LEN]; +}; + +static struct region_code_mapping region_code_mapping_t[] = { + { 0x10, "US " }, /* US FCC */ + { 0x20, "CA " }, /* IC Canada */ + { 0x30, "FR " }, /* France */ + { 0x31, "ES " }, /* Spain */ + { 0x32, "FR " }, /* France */ + { 0x40, "JP " }, /* Japan */ + { 0x41, "JP " }, /* Japan */ + { 0x50, "CN " }, /* China */ +}; + +/* Convert 11d country code to region string. */ +u8 *nxpwifi_11d_code_2_region(u8 code) +{ + u8 i; + + /* Look for code in mapping table */ + for (i = 0; i < ARRAY_SIZE(region_code_mapping_t); i++) + if (region_code_mapping_t[i].code == code) + return region_code_mapping_t[i].region; + + return NULL; +} + +/* Map supported rate index to AC/VHT data rate. */ +u32 nxpwifi_index_to_acs_data_rate(struct nxpwifi_private *priv, + u8 index, u8 ht_info) +{ + u32 rate = 0; + u8 mcs_index = 0; + u8 bw = 0; + u8 gi = 0; + + if ((ht_info & 0x3) == NXPWIFI_RATE_FORMAT_VHT) { + mcs_index = min(index & 0xF, 9); + + /* 20M: bw=0, 40M: bw=1, 80M: bw=2, 160M: bw=3 */ + bw = (ht_info & 0xC) >> 2; + + /* LGI: gi =0, SGI: gi = 1 */ + gi = (ht_info & 0x10) >> 4; + + if ((index >> 4) == 1) /* NSS = 2 */ + rate = ac_mcs_rate_nss2[2 * (3 - bw) + gi][mcs_index]; + else /* NSS = 1 */ + rate = ac_mcs_rate_nss1[2 * (3 - bw) + gi][mcs_index]; + } else if ((ht_info & 0x3) == NXPWIFI_RATE_FORMAT_HT) { + /* 20M: bw=0, 40M: bw=1 */ + bw = (ht_info & 0xC) >> 2; + + /* LGI: gi =0, SGI: gi = 1 */ + gi = (ht_info & 0x10) >> 4; + + if (index == NXPWIFI_RATE_BITMAP_MCS0) { + if (gi == 1) + rate = 0x0D; /* MCS 32 SGI rate */ + else + rate = 0x0C; /* MCS 32 LGI rate */ + } else if (index < 16) { + if (bw == 1 || bw == 0) + rate = mcs_rate[2 * (1 - bw) + gi][index]; + else + rate = nxpwifi_data_rates[0]; + } else { + rate = nxpwifi_data_rates[0]; + } + } else { + /* 11n non-HT rates */ + if (index >= NXPWIFI_SUPPORTED_RATES_EXT) + index = 0; + rate = nxpwifi_data_rates[index]; + } + + return rate; +} + +/* Map supported rate index to data rate. */ +u32 nxpwifi_index_to_data_rate(struct nxpwifi_private *priv, + u8 index, u8 ht_info) +{ + u32 mcs_num_supp = + (priv->adapter->user_dev_mcs_support == HT_STREAM_2X2) ? 16 : 8; + u32 rate; + + if (priv->adapter->is_hw_11ac_capable) + return nxpwifi_index_to_acs_data_rate(priv, index, ht_info); + + if (ht_info & BIT(0)) { + if (index == NXPWIFI_RATE_BITMAP_MCS0) { + if (ht_info & BIT(2)) + rate = 0x0D; /* MCS 32 SGI rate */ + else + rate = 0x0C; /* MCS 32 LGI rate */ + } else if (index < mcs_num_supp) { + if (ht_info & BIT(1)) { + if (ht_info & BIT(2)) + /* SGI, 40M */ + rate = mcs_rate[1][index]; + else + /* LGI, 40M */ + rate = mcs_rate[0][index]; + } else { + if (ht_info & BIT(2)) + /* SGI, 20M */ + rate = mcs_rate[3][index]; + else + /* LGI, 20M */ + rate = mcs_rate[2][index]; + } + } else { + rate = nxpwifi_data_rates[0]; + } + } else { + if (index >= NXPWIFI_SUPPORTED_RATES_EXT) + index = 0; + rate = nxpwifi_data_rates[index]; + } + return rate; +} + +/* Return current active data rates (depends on connection). */ +u32 nxpwifi_get_active_data_rates(struct nxpwifi_private *priv, u8 *rates) +{ + if (!priv->media_connected) + return nxpwifi_get_supported_rates(priv, rates); + else + return nxpwifi_copy_rates(rates, 0, + priv->curr_bss_params.data_rates, + priv->curr_bss_params.num_of_rates); +} + +/* Find Channel/Frequency/Power by band and channel or frequency. */ +struct nxpwifi_chan_freq_power * +nxpwifi_get_cfp(struct nxpwifi_private *priv, u8 band, u16 channel, u32 freq) +{ + struct nxpwifi_chan_freq_power *cfp = NULL; + struct ieee80211_supported_band *sband; + struct ieee80211_channel *ch = NULL; + int i; + + if (!channel && !freq) + return cfp; + + if (nxpwifi_band_to_radio_type(band) == HOST_SCAN_RADIO_TYPE_BG) + sband = priv->wdev.wiphy->bands[NL80211_BAND_2GHZ]; + else + sband = priv->wdev.wiphy->bands[NL80211_BAND_5GHZ]; + + if (!sband) { + nxpwifi_dbg(priv->adapter, ERROR, + "%s: cannot find cfp by band %d\n", + __func__, band); + return cfp; + } + + for (i = 0; i < sband->n_channels; i++) { + ch = &sband->channels[i]; + + if (ch->flags & IEEE80211_CHAN_DISABLED) + continue; + + if (freq) { + if (ch->center_freq == freq) + break; + } else { + /* Find by valid channel. */ + if (ch->hw_value == channel || + channel == FIRST_VALID_CHANNEL) + break; + } + } + if (i == sband->n_channels) { + nxpwifi_dbg(priv->adapter, WARN, + "%s: cannot find cfp by band %d\t" + "& channel=%d freq=%d\n", + __func__, band, channel, freq); + } else { + if (!ch) + return cfp; + + priv->cfp.channel = ch->hw_value; + priv->cfp.freq = ch->center_freq; + priv->cfp.max_tx_power = ch->max_power; + cfp = &priv->cfp; + } + + return cfp; +} + +/* Return true if data rate is set to auto. */ +u8 +nxpwifi_is_rate_auto(struct nxpwifi_private *priv) +{ + u32 i; + int rate_num = 0; + + for (i = 0; i < ARRAY_SIZE(priv->bitmap_rates); i++) + if (priv->bitmap_rates[i]) + rate_num++; + + if (rate_num > 1) + return true; + else + return false; +} + +/* Extract supported rates from cfg80211_scan_request bitmask. */ +u32 nxpwifi_get_rates_from_cfg80211(struct nxpwifi_private *priv, + u8 *rates, u8 radio_type) +{ + struct wiphy *wiphy = priv->adapter->wiphy; + struct cfg80211_scan_request *request = priv->scan_request; + u32 num_rates, rate_mask; + struct ieee80211_supported_band *sband; + int i; + + if (radio_type) { + sband = wiphy->bands[NL80211_BAND_5GHZ]; + if (WARN_ON_ONCE(!sband)) + return 0; + rate_mask = request->rates[NL80211_BAND_5GHZ]; + } else { + sband = wiphy->bands[NL80211_BAND_2GHZ]; + if (WARN_ON_ONCE(!sband)) + return 0; + rate_mask = request->rates[NL80211_BAND_2GHZ]; + } + + num_rates = 0; + for (i = 0; i < sband->n_bitrates; i++) { + if ((BIT(i) & rate_mask) == 0) + continue; /* skip rate */ + rates[num_rates++] = (u8)(sband->bitrates[i].bitrate / 5); + } + + return num_rates; +} + +/* Convert config_bands to B/G/A band */ +static u16 nxpwifi_convert_config_bands(u16 config_bands) +{ + u16 bands = 0; + + if (config_bands & BAND_B) + bands |= BAND_B; + if (config_bands & BAND_G || config_bands & BAND_GN || + config_bands & BAND_GAC || config_bands & BAND_GAX) + bands |= BAND_G; + if (config_bands & BAND_A || config_bands & BAND_AN || + config_bands & BAND_AAC || config_bands & BAND_AAX) + bands |= BAND_A; + + return bands; +} + +/* Get supported rates in infrastructure (STA/P2P client) mode. */ +u32 nxpwifi_get_supported_rates(struct nxpwifi_private *priv, u8 *rates) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u32 k = 0; + u16 bands = 0; + + bands = nxpwifi_convert_config_bands(adapter->fw_bands); + + if (priv->bss_mode == NL80211_IFTYPE_STATION) { + if (bands == BAND_B) { + /* B only */ + nxpwifi_dbg(adapter, INFO, "info: infra band=%d\t" + "supported_rates_b\n", + priv->config_bands); + k = nxpwifi_copy_rates(rates, k, supported_rates_b, + sizeof(supported_rates_b)); + } else if (bands == BAND_G) { + /* G only */ + nxpwifi_dbg(adapter, INFO, "info: infra band=%d\t" + "supported_rates_g\n", + priv->config_bands); + k = nxpwifi_copy_rates(rates, k, supported_rates_g, + sizeof(supported_rates_g)); + } else if (bands & (BAND_B | BAND_G)) { + /* BG only */ + nxpwifi_dbg(adapter, INFO, "info: infra band=%d\t" + "supported_rates_bg\n", + priv->config_bands); + k = nxpwifi_copy_rates(rates, k, supported_rates_bg, + sizeof(supported_rates_bg)); + } else if (bands & BAND_A) { + /* support A */ + nxpwifi_dbg(adapter, INFO, "info: infra band=%d\t" + "supported_rates_a\n", + priv->config_bands); + k = nxpwifi_copy_rates(rates, k, supported_rates_a, + sizeof(supported_rates_a)); + } + } + + return k; +} + +u8 nxpwifi_adjust_data_rate(struct nxpwifi_private *priv, + u8 rx_rate, u8 rate_info) +{ + u8 rate_index = 0; + + /* HT40 */ + if ((rate_info & BIT(0)) && (rate_info & BIT(1))) + rate_index = NXPWIFI_RATE_INDEX_MCS0 + + NXPWIFI_BW20_MCS_NUM + rx_rate; + else if (rate_info & BIT(0)) /* HT20 */ + rate_index = NXPWIFI_RATE_INDEX_MCS0 + rx_rate; + else + rate_index = (rx_rate > NXPWIFI_RATE_INDEX_OFDM0) ? + rx_rate - 1 : rx_rate; + + if (rate_index >= NXPWIFI_MAX_AC_RX_RATES) + rate_index = NXPWIFI_MAX_AC_RX_RATES - 1; + + return rate_index; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/cmdevt.c b/drivers/net/wireless/nxp/nxpwifi/cmdevt.c new file mode 100644 index 000000000000..4eb17ada5db8 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/cmdevt.c @@ -0,0 +1,1310 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: commands and events + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cmdevt.h" +#include "wmm.h" +#include "11n.h" + +static void nxpwifi_cancel_pending_ioctl(struct nxpwifi_adapter *adapter); + +/* Initialize command node; set defaults; buffers are supplied by caller. */ +static void +nxpwifi_init_cmd_node(struct nxpwifi_private *priv, + struct cmd_ctrl_node *cmd_node, + u32 cmd_no, void *data_buf, bool sync) +{ + cmd_node->priv = priv; + cmd_node->cmd_no = cmd_no; + + if (sync) { + cmd_node->wait_q_enabled = true; + cmd_node->cmd_wait_q_woken = false; + cmd_node->condition = &cmd_node->cmd_wait_q_woken; + } + cmd_node->data_buf = data_buf; + cmd_node->cmd_skb = cmd_node->skb; + cmd_node->cmd_resp = NULL; +} + +/* Get a free command node from cmd_free_q; return NULL if none. */ +static struct cmd_ctrl_node * +nxpwifi_get_cmd_node(struct nxpwifi_adapter *adapter) +{ + struct cmd_ctrl_node *cmd_node; + + spin_lock_bh(&adapter->cmd_free_q_lock); + if (list_empty(&adapter->cmd_free_q)) { + nxpwifi_dbg(adapter, ERROR, + "GET_CMD_NODE: cmd node not available\n"); + spin_unlock_bh(&adapter->cmd_free_q_lock); + return NULL; + } + cmd_node = list_first_entry(&adapter->cmd_free_q, + struct cmd_ctrl_node, list); + list_del(&cmd_node->list); + spin_unlock_bh(&adapter->cmd_free_q_lock); + + return cmd_node; +} + +/* Reset cmd node state; trim cmd skb; complete and clear resp_skb if present. */ +static void +nxpwifi_clean_cmd_node(struct nxpwifi_adapter *adapter, + struct cmd_ctrl_node *cmd_node) +{ + cmd_node->cmd_no = 0; + cmd_node->cmd_flag = 0; + cmd_node->data_buf = NULL; + cmd_node->wait_q_enabled = false; + + if (cmd_node->cmd_skb) + skb_trim(cmd_node->cmd_skb, 0); + + if (cmd_node->resp_skb) { + adapter->if_ops.cmdrsp_complete(adapter, cmd_node->resp_skb); + cmd_node->resp_skb = NULL; + } +} + +/* Optionally complete waiters, clean the node, and add it back to cmd_free_q. */ +static void +nxpwifi_insert_cmd_to_free_q(struct nxpwifi_adapter *adapter, + struct cmd_ctrl_node *cmd_node) +{ + if (!cmd_node) + return; + + if (cmd_node->wait_q_enabled) + nxpwifi_complete_cmd(adapter, cmd_node); + /* Clean the node */ + nxpwifi_clean_cmd_node(adapter, cmd_node); + + /* Insert node into cmd_free_q */ + spin_lock_bh(&adapter->cmd_free_q_lock); + list_add_tail(&cmd_node->list, &adapter->cmd_free_q); + spin_unlock_bh(&adapter->cmd_free_q_lock); +} + +/* Reuse a command node. */ +void nxpwifi_recycle_cmd_node(struct nxpwifi_adapter *adapter, + struct cmd_ctrl_node *cmd_node) +{ + struct host_cmd_ds_command *host_cmd = (void *)cmd_node->cmd_skb->data; + + nxpwifi_insert_cmd_to_free_q(adapter, cmd_node); + + atomic_dec(&adapter->cmd_pending); + nxpwifi_dbg(adapter, CMD, + "cmd: FREE_CMD: cmd=%#x, cmd_pending=%d\n", + le16_to_cpu(host_cmd->command), + atomic_read(&adapter->cmd_pending)); +} + +/* Copy host command (userspace-provided) into the driver cmd buffer. */ +static int nxpwifi_cmd_host_cmd(struct nxpwifi_private *priv, + struct cmd_ctrl_node *cmd_node) +{ + struct host_cmd_ds_command *cmd; + struct nxpwifi_ds_misc_cmd *pcmd_ptr; + + cmd = (struct host_cmd_ds_command *)cmd_node->skb->data; + pcmd_ptr = (struct nxpwifi_ds_misc_cmd *)cmd_node->data_buf; + + /* Copy the HOST command to command buffer */ + memcpy(cmd, pcmd_ptr->cmd, pcmd_ptr->len); + nxpwifi_dbg(priv->adapter, CMD, + "cmd: host cmd size = %d\n", pcmd_ptr->len); + return 0; +} + +/* Send prepared command to FW: set seq no, adjust skb length, log, start timer. */ +static int nxpwifi_dnld_cmd_to_fw(struct nxpwifi_private *priv, + struct cmd_ctrl_node *cmd_node) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int ret; + struct host_cmd_ds_command *host_cmd; + u16 cmd_code; + u16 cmd_size; + + if (!adapter || !cmd_node) + return -EINVAL; + + host_cmd = (struct host_cmd_ds_command *)(cmd_node->cmd_skb->data); + + /* Sanity test */ + if (host_cmd->size == 0) { + nxpwifi_dbg(adapter, ERROR, + "DNLD_CMD: host_cmd is null\t" + "or cmd size is 0, not sending\n"); + if (cmd_node->wait_q_enabled) + adapter->cmd_wait_q.status = -1; + nxpwifi_recycle_cmd_node(adapter, cmd_node); + return -EINVAL; + } + + cmd_code = le16_to_cpu(host_cmd->command); + cmd_node->cmd_no = cmd_code; + cmd_size = le16_to_cpu(host_cmd->size); + + if (adapter->hw_status == NXPWIFI_HW_STATUS_RESET && + cmd_code != HOST_CMD_FUNC_SHUTDOWN && + cmd_code != HOST_CMD_FUNC_INIT) { + nxpwifi_dbg(adapter, ERROR, + "DNLD_CMD: FW in reset state, ignore cmd %#x\n", + cmd_code); + nxpwifi_recycle_cmd_node(adapter, cmd_node); + nxpwifi_queue_work(adapter, &adapter->main_work); + return -EPERM; + } + + /* Set command sequence number */ + adapter->seq_num++; + host_cmd->seq_num = cpu_to_le16(HOST_SET_SEQ_NO_BSS_INFO + (adapter->seq_num, + cmd_node->priv->bss_num, + cmd_node->priv->bss_type)); + + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + adapter->curr_cmd = cmd_node; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + + /* Adjust skb length */ + if (cmd_node->cmd_skb->len > cmd_size) + /* + * cmd_size is less than sizeof(struct host_cmd_ds_command). + * Trim off the unused portion. + */ + skb_trim(cmd_node->cmd_skb, cmd_size); + else if (cmd_node->cmd_skb->len < cmd_size) + /* + * cmd_size is larger than sizeof(struct host_cmd_ds_command) + * because we have appended custom element TLV. Increase skb length + * accordingly. + */ + skb_put(cmd_node->cmd_skb, cmd_size - cmd_node->cmd_skb->len); + + nxpwifi_dbg(adapter, CMD, + "cmd: DNLD_CMD: %#x, act %#x, len %d, seqno %#x\n", + cmd_code, + get_unaligned_le16((u8 *)host_cmd + S_DS_GEN), + cmd_size, le16_to_cpu(host_cmd->seq_num)); + nxpwifi_dbg_dump(adapter, CMD_D, "cmd buffer:", host_cmd, cmd_size); + + skb_push(cmd_node->cmd_skb, adapter->intf_hdr_len); + ret = adapter->if_ops.host_to_card(adapter, NXPWIFI_TYPE_CMD, + cmd_node->cmd_skb, NULL); + skb_pull(cmd_node->cmd_skb, adapter->intf_hdr_len); + + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "DNLD_CMD: host to card failed\n"); + if (cmd_node->wait_q_enabled) + adapter->cmd_wait_q.status = -1; + nxpwifi_recycle_cmd_node(adapter, adapter->curr_cmd); + + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + adapter->curr_cmd = NULL; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + + adapter->dbg.num_cmd_host_to_card_failure++; + return ret; + } + + /* Save the last command id and action to debug log */ + adapter->dbg.last_cmd_index = + (adapter->dbg.last_cmd_index + 1) % DBG_CMD_NUM; + adapter->dbg.last_cmd_id[adapter->dbg.last_cmd_index] = cmd_code; + adapter->dbg.last_cmd_act[adapter->dbg.last_cmd_index] = + get_unaligned_le16((u8 *)host_cmd + S_DS_GEN); + + /* + * Setup the timer after transmit command, except that specific + * command might not have command response. + */ + if (cmd_code != HOST_CMD_FW_DUMP_EVENT) + mod_timer(&adapter->cmd_timer, + jiffies + msecs_to_jiffies(NXPWIFI_TIMER_10S)); + + /* Clear BSS_NO_BITS from HOST */ + cmd_code &= HOST_CMD_ID_MASK; + + return 0; +} + +/* Send sleep-confirm command to FW; set seq no; resp may be skipped when resp_ctrl=0. */ +static int nxpwifi_dnld_sleep_confirm_cmd(struct nxpwifi_adapter *adapter) +{ + int ret; + struct nxpwifi_private *priv; + struct nxpwifi_opt_sleep_confirm *sleep_cfm_buf = + (struct nxpwifi_opt_sleep_confirm *) + adapter->sleep_cfm->data; + + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + + adapter->seq_num++; + sleep_cfm_buf->seq_num = + cpu_to_le16(HOST_SET_SEQ_NO_BSS_INFO + (adapter->seq_num, priv->bss_num, + priv->bss_type)); + + nxpwifi_dbg(adapter, CMD, + "cmd: DNLD_CMD: %#x, act %#x, len %d, seqno %#x\n", + le16_to_cpu(sleep_cfm_buf->command), + le16_to_cpu(sleep_cfm_buf->action), + le16_to_cpu(sleep_cfm_buf->size), + le16_to_cpu(sleep_cfm_buf->seq_num)); + nxpwifi_dbg_dump(adapter, CMD_D, "SLEEP_CFM buffer: ", sleep_cfm_buf, + le16_to_cpu(sleep_cfm_buf->size)); + + skb_push(adapter->sleep_cfm, adapter->intf_hdr_len); + ret = adapter->if_ops.host_to_card(adapter, NXPWIFI_TYPE_CMD, + adapter->sleep_cfm, NULL); + skb_pull(adapter->sleep_cfm, adapter->intf_hdr_len); + + if (ret) { + nxpwifi_dbg(adapter, ERROR, "SLEEP_CFM: failed\n"); + adapter->dbg.num_cmd_sleep_cfm_host_to_card_failure++; + return ret; + } + + if (!le16_to_cpu(sleep_cfm_buf->resp_ctrl)) + /* Response is not needed for sleep confirm command */ + adapter->ps_state = PS_STATE_SLEEP; + else + adapter->ps_state = PS_STATE_SLEEP_CFM; + + if (!le16_to_cpu(sleep_cfm_buf->resp_ctrl) && + (test_bit(NXPWIFI_IS_HS_CONFIGURED, &adapter->work_flags) && + !adapter->sleep_period.period)) { + adapter->pm_wakeup_card_req = true; + nxpwifi_hs_activated_event(nxpwifi_get_priv + (adapter, NXPWIFI_BSS_ROLE_ANY), true); + } + + return ret; +} + +/* Allocate cmd pool and link all nodes to cmd_free_q (used/returned by cmds). */ +int nxpwifi_alloc_cmd_buffer(struct nxpwifi_adapter *adapter) +{ + struct cmd_ctrl_node *cmd_array; + u32 i; + + /* Allocate and initialize struct cmd_ctrl_node */ + cmd_array = kzalloc_objs(struct cmd_ctrl_node, + NXPWIFI_NUM_OF_CMD_BUFFER, GFP_KERNEL); + if (!cmd_array) + return -ENOMEM; + + adapter->cmd_pool = cmd_array; + + /* Allocate and initialize command buffers */ + for (i = 0; i < NXPWIFI_NUM_OF_CMD_BUFFER; i++) { + cmd_array[i].skb = dev_alloc_skb(NXPWIFI_SIZE_OF_CMD_BUFFER); + if (!cmd_array[i].skb) + return -ENOMEM; + } + + for (i = 0; i < NXPWIFI_NUM_OF_CMD_BUFFER; i++) + nxpwifi_insert_cmd_to_free_q(adapter, &cmd_array[i]); + + return 0; +} + +/* Free cmd pool; release any remaining resp skbs. */ +void nxpwifi_free_cmd_buffer(struct nxpwifi_adapter *adapter) +{ + struct cmd_ctrl_node *cmd_array; + u32 i; + + /* Need to check if cmd pool is allocated or not */ + if (!adapter->cmd_pool) { + nxpwifi_dbg(adapter, FATAL, + "info: FREE_CMD_BUF: cmd_pool is null\n"); + return; + } + + cmd_array = adapter->cmd_pool; + + /* Release shared memory buffers */ + for (i = 0; i < NXPWIFI_NUM_OF_CMD_BUFFER; i++) { + if (cmd_array[i].skb) { + nxpwifi_dbg(adapter, CMD, + "cmd: free cmd buffer %d\n", i); + dev_kfree_skb_any(cmd_array[i].skb); + } + if (!cmd_array[i].resp_skb) + continue; + + dev_kfree_skb_any(cmd_array[i].resp_skb); + } + /* Release struct cmd_ctrl_node */ + if (adapter->cmd_pool) { + nxpwifi_dbg(adapter, CMD, + "cmd: free cmd pool\n"); + kfree(adapter->cmd_pool); + adapter->cmd_pool = NULL; + } +} + +/* + * Handle FW event: select per-BSS priv, fill rxinfo, dispatch to STA/UAP handler, complete. + */ +int nxpwifi_process_event(struct nxpwifi_adapter *adapter) +{ + int ret, i; + struct nxpwifi_private *priv = + nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + struct sk_buff *skb = adapter->event_skb; + u32 eventcause; + struct nxpwifi_rxinfo *rx_info; + + if ((adapter->event_cause & EVENT_ID_MASK) == EVENT_RADAR_DETECTED) { + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + if (nxpwifi_is_11h_active(priv)) { + adapter->event_cause |= + ((priv->bss_num & 0xff) << 16) | + ((priv->bss_type & 0xff) << 24); + break; + } + } + } + + eventcause = adapter->event_cause; + + /* Save the last event to debug log */ + adapter->dbg.last_event_index = + (adapter->dbg.last_event_index + 1) % DBG_CMD_NUM; + adapter->dbg.last_event[adapter->dbg.last_event_index] = + (u16)eventcause; + + /* Get BSS number and corresponding priv */ + priv = nxpwifi_get_priv_by_id(adapter, EVENT_GET_BSS_NUM(eventcause), + EVENT_GET_BSS_TYPE(eventcause)); + if (!priv) + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + + /* Clear BSS_NO_BITS from event */ + eventcause &= EVENT_ID_MASK; + adapter->event_cause = eventcause; + + if (skb) { + rx_info = NXPWIFI_SKB_RXCB(skb); + memset(rx_info, 0, sizeof(*rx_info)); + rx_info->bss_num = priv->bss_num; + rx_info->bss_type = priv->bss_type; + nxpwifi_dbg_dump(adapter, EVT_D, "Event Buf:", + skb->data, skb->len); + } + + nxpwifi_dbg(adapter, EVENT, "EVENT: cause: %#x\n", eventcause); + + if (priv->bss_role == NXPWIFI_BSS_ROLE_UAP) + ret = nxpwifi_process_uap_event(priv); + else + ret = nxpwifi_process_sta_event(priv); + + adapter->event_cause = 0; + adapter->event_skb = NULL; + adapter->if_ops.event_complete(adapter, skb); + + return ret; +} + +/* + * Prepare and queue a command: sanity checks, get node, init, fill, and enqueue/dispatch. + */ +int nxpwifi_send_cmd(struct nxpwifi_private *priv, u16 cmd_no, + u16 cmd_action, u32 cmd_oid, void *data_buf, bool sync) +{ + int ret; + struct nxpwifi_adapter *adapter = priv->adapter; + struct cmd_ctrl_node *cmd_node; + + if (!adapter) { + pr_err("PREP_CMD: adapter is NULL\n"); + return -EINVAL; + } + + if (test_bit(NXPWIFI_IS_SUSPENDED, &adapter->work_flags)) { + nxpwifi_dbg(adapter, ERROR, + "PREP_CMD: device in suspended state\n"); + return -EPERM; + } + + if (test_bit(NXPWIFI_IS_HS_ENABLING, &adapter->work_flags) && + cmd_no != HOST_CMD_802_11_HS_CFG_ENH) { + nxpwifi_dbg(adapter, ERROR, + "PREP_CMD: host entering sleep state\n"); + return -EPERM; + } + + if (test_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags)) { + nxpwifi_dbg(adapter, ERROR, + "PREP_CMD: card is removed\n"); + return -EPERM; + } + + if (test_bit(NXPWIFI_IS_CMD_TIMEDOUT, &adapter->work_flags)) { + nxpwifi_dbg(adapter, ERROR, + "PREP_CMD: FW is in bad state\n"); + return -EPERM; + } + + if (adapter->hw_status == NXPWIFI_HW_STATUS_RESET) { + if (cmd_no != HOST_CMD_FUNC_INIT) { + nxpwifi_dbg(adapter, ERROR, + "PREP_CMD: FW in reset state\n"); + return -EPERM; + } + } + + if (priv->adapter->hs_activated_manually && + cmd_no != HOST_CMD_802_11_HS_CFG_ENH) { + nxpwifi_cancel_hs(priv, NXPWIFI_ASYNC_CMD); + priv->adapter->hs_activated_manually = false; + } + + /* Get a new command node */ + cmd_node = nxpwifi_get_cmd_node(adapter); + + if (!cmd_node) { + nxpwifi_dbg(adapter, ERROR, + "PREP_CMD: no free cmd node\n"); + return -ENOMEM; + } + + /* Initialize the command node */ + nxpwifi_init_cmd_node(priv, cmd_node, cmd_no, data_buf, sync); + + if (!cmd_node->cmd_skb) { + nxpwifi_dbg(adapter, ERROR, + "PREP_CMD: no free cmd buf\n"); + return -ENOMEM; + } + + skb_put_zero(cmd_node->cmd_skb, sizeof(struct host_cmd_ds_command)); + + /* Prepare command */ + if (cmd_no) { + switch (cmd_no) { + case HOST_CMD_UAP_SYS_CONFIG: + case HOST_CMD_UAP_BSS_START: + case HOST_CMD_UAP_BSS_STOP: + case HOST_CMD_UAP_STA_DEAUTH: + case HOST_CMD_APCMD_SYS_RESET: + case HOST_CMD_APCMD_STA_LIST: + case HOST_CMD_CHAN_REPORT_REQUEST: + case HOST_CMD_ADD_NEW_STATION: + ret = nxpwifi_uap_prepare_cmd(priv, cmd_node, + cmd_action, cmd_oid); + break; + default: + ret = nxpwifi_sta_prepare_cmd(priv, cmd_node, + cmd_action, cmd_oid); + break; + } + } else { + ret = nxpwifi_cmd_host_cmd(priv, cmd_node); + cmd_node->cmd_flag |= CMD_F_HOSTCMD; + } + + /* Return error, since the command preparation failed */ + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "PREP_CMD: cmd %#x preparation failed\n", + cmd_no); + nxpwifi_insert_cmd_to_free_q(adapter, cmd_node); + return ret; + } + + /* Send command */ + if (cmd_no == HOST_CMD_802_11_SCAN || + cmd_no == HOST_CMD_802_11_SCAN_EXT) { + nxpwifi_queue_scan_cmd(priv, cmd_node); + } else { + nxpwifi_insert_cmd_to_pending_q(adapter, cmd_node); + nxpwifi_queue_work(adapter, &adapter->main_work); + if (cmd_node->wait_q_enabled) + ret = nxpwifi_wait_queue_complete(adapter, cmd_node); + } + + return ret; +} + +/* Queue command to cmd_pending_q; EXIT_PS and HS_ACTIVATE go to the head. */ +void +nxpwifi_insert_cmd_to_pending_q(struct nxpwifi_adapter *adapter, + struct cmd_ctrl_node *cmd_node) +{ + struct host_cmd_ds_command *host_cmd = NULL; + u16 command; + bool add_tail = true; + + host_cmd = (struct host_cmd_ds_command *)(cmd_node->cmd_skb->data); + if (!host_cmd) { + nxpwifi_dbg(adapter, ERROR, "QUEUE_CMD: host_cmd is NULL\n"); + return; + } + + command = le16_to_cpu(host_cmd->command); + + /* Exit_PS command needs to be queued in the header always. */ + if (command == HOST_CMD_802_11_PS_MODE_ENH) { + struct host_cmd_ds_802_11_ps_mode_enh *pm = + &host_cmd->params.psmode_enh; + if ((le16_to_cpu(pm->action) == DIS_PS) || + (le16_to_cpu(pm->action) == DIS_AUTO_PS)) { + if (adapter->ps_state != PS_STATE_AWAKE) + add_tail = false; + } + } + + /* Same with exit host sleep cmd, luckily that can't happen at the same time as EXIT_PS */ + if (command == HOST_CMD_802_11_HS_CFG_ENH) { + struct host_cmd_ds_802_11_hs_cfg_enh *hs_cfg = + &host_cmd->params.opt_hs_cfg; + + if (le16_to_cpu(hs_cfg->action) == HS_ACTIVATE) + add_tail = false; + } + + spin_lock_bh(&adapter->cmd_pending_q_lock); + if (add_tail) + list_add_tail(&cmd_node->list, &adapter->cmd_pending_q); + else + list_add(&cmd_node->list, &adapter->cmd_pending_q); + spin_unlock_bh(&adapter->cmd_pending_q_lock); + + atomic_inc(&adapter->cmd_pending); + nxpwifi_dbg(adapter, CMD, + "cmd: QUEUE_CMD: cmd=%#x, cmd_pending=%d\n", + command, atomic_read(&adapter->cmd_pending)); +} + +/* Dequeue next cmd and download to FW; if HS active (except HS_CFG), deactivate it. */ +int nxpwifi_exec_next_cmd(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_private *priv; + struct cmd_ctrl_node *cmd_node; + int ret = 0; + struct host_cmd_ds_command *host_cmd; + + /* Check if already in processing */ + if (adapter->curr_cmd) { + nxpwifi_dbg(adapter, FATAL, + "EXEC_NEXT_CMD: cmd in processing\n"); + return -EBUSY; + } + + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + /* Check if any command is pending */ + spin_lock_bh(&adapter->cmd_pending_q_lock); + if (list_empty(&adapter->cmd_pending_q)) { + spin_unlock_bh(&adapter->cmd_pending_q_lock); + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + return -ENODATA; + } + cmd_node = list_first_entry(&adapter->cmd_pending_q, + struct cmd_ctrl_node, list); + + host_cmd = (struct host_cmd_ds_command *)(cmd_node->cmd_skb->data); + priv = cmd_node->priv; + + if (adapter->ps_state != PS_STATE_AWAKE) { + nxpwifi_dbg(adapter, ERROR, + "%s: cannot send cmd in sleep state,\t" + "this should not happen\n", __func__); + spin_unlock_bh(&adapter->cmd_pending_q_lock); + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + return ret; + } + + list_del(&cmd_node->list); + spin_unlock_bh(&adapter->cmd_pending_q_lock); + + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + ret = nxpwifi_dnld_cmd_to_fw(priv, cmd_node); + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + /* + * Any command sent to the firmware when host is in sleep + * mode should de-configure host sleep. We should skip the + * host sleep configuration command itself though + */ + if (priv && host_cmd->command != + cpu_to_le16(HOST_CMD_802_11_HS_CFG_ENH)) { + if (adapter->hs_activated) { + clear_bit(NXPWIFI_IS_HS_CONFIGURED, + &adapter->work_flags); + nxpwifi_hs_activated_event(priv, false); + } + } + + return ret; +} + +static void +nxpwifi_process_cmdresp_error(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_ds_802_11_ps_mode_enh *pm; + + nxpwifi_dbg(adapter, ERROR, + "CMD_RESP: cmd %#x error, result=%#x\n", + resp->command, resp->result); + + if (adapter->curr_cmd->wait_q_enabled) + adapter->cmd_wait_q.status = -1; + + switch (le16_to_cpu(resp->command)) { + case HOST_CMD_802_11_PS_MODE_ENH: + pm = &resp->params.psmode_enh; + nxpwifi_dbg(adapter, ERROR, + "PS_MODE_ENH cmd failed: result=0x%x action=0x%X\n", + resp->result, le16_to_cpu(pm->action)); + break; + case HOST_CMD_802_11_SCAN: + case HOST_CMD_802_11_SCAN_EXT: + nxpwifi_cancel_scan(adapter); + break; + + case HOST_CMD_MAC_CONTROL: + break; + + case HOST_CMD_SDIO_SP_RX_AGGR_CFG: + nxpwifi_dbg(adapter, MSG, + "SDIO RX single-port aggregation Not support\n"); + break; + + default: + break; + } + /* Handling errors here */ + nxpwifi_recycle_cmd_node(adapter, adapter->curr_cmd); + + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + adapter->curr_cmd = NULL; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); +} + +/* Handle command response: validate, cancel timer, dispatch, set status, recycle node. */ +int nxpwifi_process_cmdresp(struct nxpwifi_adapter *adapter) +{ + struct host_cmd_ds_command *resp; + struct nxpwifi_private *priv = + nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + int ret = 0; + u16 orig_cmdresp_no; + u16 cmdresp_no; + u16 cmdresp_result; + + if (!adapter->curr_cmd || !adapter->curr_cmd->resp_skb) { + resp = (struct host_cmd_ds_command *)adapter->upld_buf; + nxpwifi_dbg(adapter, ERROR, + "CMD_RESP: NULL curr_cmd, %#x\n", + le16_to_cpu(resp->command)); + return -EINVAL; + } + + resp = (struct host_cmd_ds_command *)adapter->curr_cmd->resp_skb->data; + orig_cmdresp_no = le16_to_cpu(resp->command); + cmdresp_no = (orig_cmdresp_no & HOST_CMD_ID_MASK); + + if (adapter->curr_cmd->cmd_no != cmdresp_no) { + nxpwifi_dbg(adapter, ERROR, + "cmdresp error: cmd=0x%x cmd_resp=0x%x\n", + adapter->curr_cmd->cmd_no, cmdresp_no); + return -EINVAL; + } + /* Now we got response from FW, cancel the command timer */ + timer_delete_sync(&adapter->cmd_timer); + clear_bit(NXPWIFI_IS_CMD_TIMEDOUT, &adapter->work_flags); + + if (adapter->curr_cmd->cmd_flag & CMD_F_HOSTCMD) { + /* Copy original response back to response buffer */ + struct nxpwifi_ds_misc_cmd *hostcmd; + u16 size = le16_to_cpu(resp->size); + + nxpwifi_dbg(adapter, INFO, + "info: host cmd resp size = %d\n", size); + size = min_t(u16, size, NXPWIFI_SIZE_OF_CMD_BUFFER); + if (adapter->curr_cmd->data_buf) { + hostcmd = adapter->curr_cmd->data_buf; + hostcmd->len = size; + memcpy(hostcmd->cmd, resp, size); + } + } + + /* Get BSS number and corresponding priv */ + priv = nxpwifi_get_priv_by_id + (adapter, HOST_GET_BSS_NO(le16_to_cpu(resp->seq_num)), + HOST_GET_BSS_TYPE(le16_to_cpu(resp->seq_num))); + if (!priv) + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + /* Clear RET_BIT from HOST */ + resp->command = cpu_to_le16(orig_cmdresp_no & HOST_CMD_ID_MASK); + + cmdresp_no = le16_to_cpu(resp->command); + cmdresp_result = le16_to_cpu(resp->result); + + /* Save the last command response to debug log */ + adapter->dbg.last_cmd_resp_index = + (adapter->dbg.last_cmd_resp_index + 1) % DBG_CMD_NUM; + adapter->dbg.last_cmd_resp_id[adapter->dbg.last_cmd_resp_index] = + orig_cmdresp_no; + + nxpwifi_dbg(adapter, CMD, + "cmd: CMD_RESP: 0x%x, result %d, len %d, seqno 0x%x\n", + orig_cmdresp_no, cmdresp_result, + le16_to_cpu(resp->size), le16_to_cpu(resp->seq_num)); + nxpwifi_dbg_dump(adapter, CMD_D, "CMD_RESP buffer:", resp, + le16_to_cpu(resp->size)); + + if (!(orig_cmdresp_no & HOST_RET_BIT)) { + nxpwifi_dbg(adapter, ERROR, "CMD_RESP: invalid cmd resp\n"); + if (adapter->curr_cmd->wait_q_enabled) + adapter->cmd_wait_q.status = -1; + + nxpwifi_recycle_cmd_node(adapter, adapter->curr_cmd); + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + adapter->curr_cmd = NULL; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + return -EINVAL; + } + + if (adapter->curr_cmd->cmd_flag & CMD_F_HOSTCMD) { + adapter->curr_cmd->cmd_flag &= ~CMD_F_HOSTCMD; + if (cmdresp_result == HOST_RESULT_OK && + cmdresp_no == HOST_CMD_802_11_HS_CFG_ENH) + ret = nxpwifi_ret_802_11_hs_cfg(priv, resp); + } else { + if (resp->result != HOST_RESULT_OK) { + nxpwifi_process_cmdresp_error(priv, resp); + return -EFAULT; + } + if (adapter->curr_cmd->cmd_resp) { + void *data_buf = adapter->curr_cmd->data_buf; + + ret = adapter->curr_cmd->cmd_resp(priv, resp, + cmdresp_no, + data_buf); + } + } + + if (adapter->curr_cmd) { + if (adapter->curr_cmd->wait_q_enabled) + adapter->cmd_wait_q.status = ret; + + nxpwifi_recycle_cmd_node(adapter, adapter->curr_cmd); + + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + adapter->curr_cmd = NULL; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + } + + return ret; +} + +void nxpwifi_process_assoc_resp(struct nxpwifi_adapter *adapter) +{ + struct cfg80211_rx_assoc_resp_data assoc_resp = { + .uapsd_queues = -1, + }; + struct nxpwifi_private *priv = + nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_STA); + + if (priv->assoc_rsp_size) { + assoc_resp.links[0].bss = priv->req_bss; + assoc_resp.buf = priv->assoc_rsp_buf; + assoc_resp.len = priv->assoc_rsp_size; + cfg80211_rx_assoc_resp(priv->netdev, + &assoc_resp); + priv->assoc_rsp_size = 0; + } +} + +/* + * Command timeout handler: mark timed out, cancel pending IOCTL, dump/reset device if provided. + */ +void +nxpwifi_cmd_timeout_func(struct timer_list *t) +{ + struct nxpwifi_adapter *adapter = timer_container_of(adapter, t, cmd_timer); + struct cmd_ctrl_node *cmd_node; + + set_bit(NXPWIFI_IS_CMD_TIMEDOUT, &adapter->work_flags); + if (!adapter->curr_cmd) { + nxpwifi_dbg(adapter, ERROR, + "cmd: empty curr_cmd\n"); + return; + } + cmd_node = adapter->curr_cmd; + if (cmd_node) { + adapter->dbg.timeout_cmd_id = + adapter->dbg.last_cmd_id[adapter->dbg.last_cmd_index]; + adapter->dbg.timeout_cmd_act = + adapter->dbg.last_cmd_act[adapter->dbg.last_cmd_index]; + nxpwifi_dbg(adapter, MSG, + "%s: Timeout cmd id = %#x, act = %#x\n", __func__, + adapter->dbg.timeout_cmd_id, + adapter->dbg.timeout_cmd_act); + + nxpwifi_dbg(adapter, MSG, + "num_data_h2c_failure = %d\n", + adapter->dbg.num_tx_host_to_card_failure); + nxpwifi_dbg(adapter, MSG, + "num_cmd_h2c_failure = %d\n", + adapter->dbg.num_cmd_host_to_card_failure); + + nxpwifi_dbg(adapter, MSG, + "is_cmd_timedout = %d\n", + test_bit(NXPWIFI_IS_CMD_TIMEDOUT, + &adapter->work_flags)); + nxpwifi_dbg(adapter, MSG, + "num_tx_timeout = %d\n", + adapter->dbg.num_tx_timeout); + + nxpwifi_dbg(adapter, MSG, + "last_cmd_index = %d\n", + adapter->dbg.last_cmd_index); + nxpwifi_dbg(adapter, MSG, + "last_cmd_id: %*ph\n", + (int)sizeof(adapter->dbg.last_cmd_id), + adapter->dbg.last_cmd_id); + nxpwifi_dbg(adapter, MSG, + "last_cmd_act: %*ph\n", + (int)sizeof(adapter->dbg.last_cmd_act), + adapter->dbg.last_cmd_act); + + nxpwifi_dbg(adapter, MSG, + "last_cmd_resp_index = %d\n", + adapter->dbg.last_cmd_resp_index); + nxpwifi_dbg(adapter, MSG, + "last_cmd_resp_id: %*ph\n", + (int)sizeof(adapter->dbg.last_cmd_resp_id), + adapter->dbg.last_cmd_resp_id); + + nxpwifi_dbg(adapter, MSG, + "last_event_index = %d\n", + adapter->dbg.last_event_index); + nxpwifi_dbg(adapter, MSG, + "last_event: %*ph\n", + (int)sizeof(adapter->dbg.last_event), + adapter->dbg.last_event); + + nxpwifi_dbg(adapter, MSG, + "data_sent=%d cmd_sent=%d\n", + adapter->data_sent, adapter->cmd_sent); + + nxpwifi_dbg(adapter, MSG, + "ps_mode=%d ps_state=%d\n", + adapter->ps_mode, adapter->ps_state); + + if (cmd_node->wait_q_enabled) { + adapter->cmd_wait_q.status = -ETIMEDOUT; + nxpwifi_cancel_pending_ioctl(adapter); + } + } + + if (adapter->if_ops.device_dump) + adapter->if_ops.device_dump(adapter); + + if (adapter->if_ops.card_reset) + adapter->if_ops.card_reset(adapter); +} + +void +nxpwifi_cancel_pending_scan_cmd(struct nxpwifi_adapter *adapter) +{ + struct cmd_ctrl_node *cmd_node = NULL, *tmp_node; + + /* Cancel all pending scan command */ + spin_lock_bh(&adapter->scan_pending_q_lock); + list_for_each_entry_safe(cmd_node, tmp_node, + &adapter->scan_pending_q, list) { + list_del(&cmd_node->list); + cmd_node->wait_q_enabled = false; + nxpwifi_insert_cmd_to_free_q(adapter, cmd_node); + } + spin_unlock_bh(&adapter->scan_pending_q_lock); +} + +/* + * Cancel current cmd (if waiting), all pending cmds, and pending scan cmds; complete with error. + */ +void +nxpwifi_cancel_all_pending_cmd(struct nxpwifi_adapter *adapter) +{ + struct cmd_ctrl_node *cmd_node = NULL, *tmp_node; + + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + /* Cancel current cmd */ + if (adapter->curr_cmd && adapter->curr_cmd->wait_q_enabled) { + adapter->cmd_wait_q.status = -1; + nxpwifi_complete_cmd(adapter, adapter->curr_cmd); + adapter->curr_cmd->wait_q_enabled = false; + /* no recycle probably wait for response */ + } + /* Cancel all pending command */ + spin_lock_bh(&adapter->cmd_pending_q_lock); + list_for_each_entry_safe(cmd_node, tmp_node, + &adapter->cmd_pending_q, list) { + list_del(&cmd_node->list); + + if (cmd_node->wait_q_enabled) + adapter->cmd_wait_q.status = -1; + nxpwifi_recycle_cmd_node(adapter, cmd_node); + } + spin_unlock_bh(&adapter->cmd_pending_q_lock); + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + + nxpwifi_cancel_scan(adapter); +} + +/* Cancel current/pending commands for the pending IOCTL; also cancel scan cmds. */ +static void +nxpwifi_cancel_pending_ioctl(struct nxpwifi_adapter *adapter) +{ + struct cmd_ctrl_node *cmd_node = NULL; + + if (adapter->curr_cmd && + adapter->curr_cmd->wait_q_enabled) { + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + cmd_node = adapter->curr_cmd; + /* + * Be careful when setting curr_cmd = NULL: + * nxpwifi_process_cmdresp expects a non-NULL pointer. + * This is safe here because only cmd_timeout calls this path + * and no response is expected at that point. + */ + adapter->curr_cmd = NULL; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + + nxpwifi_recycle_cmd_node(adapter, cmd_node); + } + + nxpwifi_cancel_scan(adapter); +} + +/* If no cmd/event/tx is pending, send sleep-confirm to FW; otherwise defer. */ +void +nxpwifi_check_ps_cond(struct nxpwifi_adapter *adapter) +{ + if (!adapter->cmd_sent && !atomic_read(&adapter->tx_hw_pending) && + !adapter->curr_cmd && !IS_CARD_RX_RCVD(adapter)) + nxpwifi_dnld_sleep_confirm_cmd(adapter); + else + nxpwifi_dbg(adapter, CMD, + "cmd: Delay Sleep Confirm (%s%s%s%s)\n", + (adapter->cmd_sent) ? "D" : "", + atomic_read(&adapter->tx_hw_pending) ? "T" : "", + (adapter->curr_cmd) ? "C" : "", + (IS_CARD_RX_RCVD(adapter)) ? "R" : ""); +} + +/* Generate HS activated/deactivated event for userspace; update flags and wake waiters. */ +void +nxpwifi_hs_activated_event(struct nxpwifi_private *priv, u8 activated) +{ + if (activated) { + if (test_bit(NXPWIFI_IS_HS_CONFIGURED, + &priv->adapter->work_flags)) { + priv->adapter->hs_activated = true; + nxpwifi_update_rxreor_flags(priv->adapter, + RXREOR_FORCE_NO_DROP); + nxpwifi_dbg(priv->adapter, EVENT, + "event: hs_activated\n"); + priv->adapter->hs_activate_wait_q_woken = true; + wake_up_interruptible(&priv->adapter->hs_activate_wait_q); + } else { + nxpwifi_dbg(priv->adapter, EVENT, + "event: HS not configured\n"); + } + } else { + nxpwifi_dbg(priv->adapter, EVENT, + "event: hs_deactivated\n"); + priv->adapter->hs_activated = false; + } +} + +/* Handle HS_CFG response: update HS configured/activated flags and emit HS events. */ +int nxpwifi_ret_802_11_hs_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_ds_802_11_hs_cfg_enh *phs_cfg = + &resp->params.opt_hs_cfg; + u32 conditions = le32_to_cpu(phs_cfg->params.hs_config.conditions); + + if (phs_cfg->action == cpu_to_le16(HS_ACTIVATE)) { + nxpwifi_hs_activated_event(priv, true); + goto done; + } else { + nxpwifi_dbg(adapter, CMD, + "cmd: CMD_RESP: HS_CFG cmd reply\t" + " result=%#x, conditions=0x%x gpio=0x%x gap=0x%x\n", + resp->result, conditions, + phs_cfg->params.hs_config.gpio, + phs_cfg->params.hs_config.gap); + } + if (conditions != HS_CFG_CANCEL) { + set_bit(NXPWIFI_IS_HS_CONFIGURED, &adapter->work_flags); + } else { + clear_bit(NXPWIFI_IS_HS_CONFIGURED, &adapter->work_flags); + if (adapter->hs_activated) + nxpwifi_hs_activated_event(priv, false); + } + +done: + return 0; +} + +/* On power-up interrupt, wake device and cancel HS if armed; clear flags and notify. */ +void +nxpwifi_process_hs_config(struct nxpwifi_adapter *adapter) +{ + nxpwifi_dbg(adapter, INFO, + "info: %s: auto cancelling host sleep\t" + "since there is interrupt from the firmware\n", + __func__); + + adapter->if_ops.wakeup(adapter); + + if (adapter->hs_activated_manually) { + nxpwifi_cancel_hs(nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY), + NXPWIFI_ASYNC_CMD); + adapter->hs_activated_manually = false; + } + + adapter->hs_activated = false; + clear_bit(NXPWIFI_IS_HS_CONFIGURED, &adapter->work_flags); + clear_bit(NXPWIFI_IS_SUSPENDED, &adapter->work_flags); + nxpwifi_hs_activated_event(nxpwifi_get_priv(adapter, + NXPWIFI_BSS_ROLE_ANY), + false); +} +EXPORT_SYMBOL_GPL(nxpwifi_process_hs_config); + +/* Handle sleep-confirm response; set ps_state and hs activation accordingly. */ +void +nxpwifi_process_sleep_confirm_resp(struct nxpwifi_adapter *adapter, + u8 *pbuf, u32 upld_len) +{ + struct host_cmd_ds_command *cmd = (struct host_cmd_ds_command *)pbuf; + u16 result = le16_to_cpu(cmd->result); + u16 command = le16_to_cpu(cmd->command); + u16 seq_num = le16_to_cpu(cmd->seq_num); + + if (!upld_len) { + nxpwifi_dbg(adapter, ERROR, + "%s: cmd size is 0\n", __func__); + return; + } + + nxpwifi_dbg(adapter, CMD, + "cmd: CMD_RESP: 0x%x, result %d, len %d, seqno 0x%x\n", + command, result, le16_to_cpu(cmd->size), seq_num); + + /* Update sequence number */ + seq_num = HOST_GET_SEQ_NO(seq_num); + /* Clear RET_BIT from HOST */ + command &= HOST_CMD_ID_MASK; + + if (command != HOST_CMD_802_11_PS_MODE_ENH) { + nxpwifi_dbg(adapter, ERROR, + "%s: rcvd unexpected resp for cmd %#x, result = %x\n", + __func__, command, result); + return; + } + + if (result) { + nxpwifi_dbg(adapter, ERROR, + "%s: sleep confirm cmd failed\n", + __func__); + adapter->pm_wakeup_card_req = false; + adapter->ps_state = PS_STATE_AWAKE; + return; + } + adapter->pm_wakeup_card_req = true; + if (test_bit(NXPWIFI_IS_HS_CONFIGURED, &adapter->work_flags)) + nxpwifi_hs_activated_event(nxpwifi_get_priv + (adapter, NXPWIFI_BSS_ROLE_ANY), + true); + adapter->ps_state = PS_STATE_SLEEP; + cmd->command = cpu_to_le16(command); + cmd->seq_num = cpu_to_le16(seq_num); +} +EXPORT_SYMBOL_GPL(nxpwifi_process_sleep_confirm_resp); + +int nxpwifi_mgmt_frame_reg(struct nxpwifi_private *priv, u32 mask) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_MGMT_FRAME_REG, HOST_ACT_GEN_SET, + 0, &mask, false); +} + +int nxpwifi_set_uap_sys_cfg(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *cfg) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_UAP_SYS_CONFIG, HOST_ACT_GEN_SET, + UAP_BSS_PARAMS_I, cfg, false); +} + +int nxpwifi_set_rts(struct nxpwifi_private *priv, u32 rts_thr) +{ + if (rts_thr < NXPWIFI_RTS_THRESHOLD_MIN || rts_thr > NXPWIFI_RTS_THRESHOLD_MAX) + rts_thr = NXPWIFI_RTS_THRESHOLD_MAX; + + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_SNMP_MIB, + HOST_ACT_GEN_SET, RTS_THRESH_I, &rts_thr, true); +} + +int nxpwifi_set_frag(struct nxpwifi_private *priv, u32 frag_thr) +{ + if (frag_thr < NXPWIFI_FRAG_THRESHOLD_MIN || + frag_thr > NXPWIFI_FRAG_THRESHOLD_MAX) + frag_thr = NXPWIFI_FRAG_THRESHOLD_MAX; + + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_SNMP_MIB, + HOST_ACT_GEN_SET, FRAG_THRESH_I, &frag_thr, + true); +} + +int nxpwifi_set_bss_mode(struct nxpwifi_private *priv) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_SET_BSS_MODE, HOST_ACT_GEN_SET, + 0, NULL, true); +} + +int nxpwifi_config_monitor_mode(struct nxpwifi_private *priv, + struct nxpwifi_802_11_net_monitor *cfg) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_NET_MONITOR, + HOST_ACT_GEN_SET, 0, cfg, true); +} + +int nxpwifi_get_tx_pwr(struct nxpwifi_private *priv) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_RF_TX_PWR, HOST_ACT_GEN_GET, 0, + NULL, true); +} + +int nxpwifi_apply_regdomain(struct nxpwifi_private *priv) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_802_11D_DOMAIN_INFO, HOST_ACT_GEN_SET, + 0, NULL, false); +} + +int nxpwifi_get_rssi_info(struct nxpwifi_private *priv) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_RSSI_INFO, HOST_ACT_GEN_GET, 0, + NULL, true); +} + +int nxpwifi_get_802_11_snmp_mib(struct nxpwifi_private *priv, u16 oid, void *value) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_SNMP_MIB, HOST_ACT_GEN_GET, + oid, value, true); +} + +int nxpwifi_set_rf_antenna(struct nxpwifi_private *priv, void *antcfg) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_RF_ANTENNA, HOST_ACT_GEN_SET, 0, antcfg, + true); +} + +int nxpwifi_get_rf_antenna(struct nxpwifi_private *priv, u32 *tx_ant, u32 *rx_ant) +{ + int ret; + + ret = nxpwifi_send_cmd(priv, HOST_CMD_RF_ANTENNA, HOST_ACT_GEN_GET, 0, NULL, true); + + if (!ret) { + *tx_ant = priv->tx_ant; + *rx_ant = priv->rx_ant; + } + + return ret; +} + +int nxpwifi_ap_stop_bss(struct nxpwifi_private *priv) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_UAP_BSS_STOP, HOST_ACT_GEN_SET, + 0, NULL, true); +} + +int nxpwifi_ap_sys_reset(struct nxpwifi_private *priv) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_APCMD_SYS_RESET, + HOST_ACT_GEN_SET, 0, NULL, true); +} + +int nxpwifi_ap_get_sta_list(struct nxpwifi_private *priv) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_APCMD_STA_LIST, HOST_ACT_GEN_GET, + 0, NULL, true); +} + +int nxpwifi_set_tx_rate(struct nxpwifi_private *priv, void *bitmap_rates) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_TX_RATE_CFG, HOST_ACT_GEN_SET, 0, + bitmap_rates, true); +} + +int nxpwifi_802_11_subscribe_event(struct nxpwifi_private *priv, + struct nxpwifi_ds_misc_subsc_evt *subsc_evt) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_SUBSCRIBE_EVENT, 0, 0, subsc_evt, + true); +} + +int nxpwifi_uap_sta_deauth(struct nxpwifi_private *priv, u8 *mac) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_UAP_STA_DEAUTH, HOST_ACT_GEN_SET, 0, mac, true); +} + +int nxpwifi_bg_scan_config(struct nxpwifi_private *priv, void *bg_scan_cfg) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_BG_SCAN_CONFIG, HOST_ACT_GEN_SET, + 0, bg_scan_cfg, true); +} + +int nxpwifi_mef_cfg(struct nxpwifi_private *priv, void *mef_cfg) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_MEF_CFG, HOST_ACT_GEN_SET, 0, mef_cfg, + true); +} + +int nxpwifi_coalesce_cfg(struct nxpwifi_private *priv, void *coalesce_cfg) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_COALESCE_CFG, HOST_ACT_GEN_SET, 0, + coalesce_cfg, true); +} + +int nxpwifi_add_new_station(struct nxpwifi_private *priv, void *add_sta) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_ADD_NEW_STATION, HOST_ACT_ADD_STA, 0, + add_sta, true); +} + +int nxpwifi_hostcmd(struct nxpwifi_private *priv, struct nxpwifi_ds_misc_cmd *hostcmd) +{ + return nxpwifi_send_cmd(priv, 0, 0, 0, hostcmd, true); +} + +int nxpwifi_chan_report_request(struct nxpwifi_private *priv, void *radar_params) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_CHAN_REPORT_REQUEST, HOST_ACT_GEN_SET, 0, + radar_params, true); +} diff --git a/drivers/net/wireless/nxp/nxpwifi/cmdevt.h b/drivers/net/wireless/nxp/nxpwifi/cmdevt.h new file mode 100644 index 000000000000..6112760b697e --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/cmdevt.h @@ -0,0 +1,122 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * nxpwifi: commands and events + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_CMD_EVT_H_ +#define _NXPWIFI_CMD_EVT_H_ + +struct nxpwifi_cmd_entry { + u16 cmd_no; + int (*prepare_cmd)(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type); + int (*cmd_resp)(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf); +}; + +struct nxpwifi_evt_entry { + u32 event_cause; + int (*event_handler)(struct nxpwifi_private *priv); +}; + +static inline int +nxpwifi_cmd_fill_head_only(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(cmd_no); + cmd->size = cpu_to_le16(S_DS_GEN); + + return 0; +} + +int nxpwifi_send_cmd(struct nxpwifi_private *priv, u16 cmd_no, + u16 cmd_action, u32 cmd_oid, void *data_buf, bool sync); +int nxpwifi_sta_prepare_cmd(struct nxpwifi_private *priv, + struct cmd_ctrl_node *cmd_node, + u16 cmd_action, u32 cmd_oid); +int nxpwifi_sta_init_cmd(struct nxpwifi_private *priv, u8 first_sta, bool init); +int nxpwifi_uap_prepare_cmd(struct nxpwifi_private *priv, + struct cmd_ctrl_node *cmd_node, + u16 cmd_action, u32 type); +int nxpwifi_set_secure_params(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_config, + struct cfg80211_ap_settings *params); +void nxpwifi_set_ht_params(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params); +void nxpwifi_set_vht_params(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params); +void nxpwifi_set_tpc_params(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params); +void nxpwifi_set_uap_rates(struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params); +void nxpwifi_set_vht_width(struct nxpwifi_private *priv, + enum nl80211_chan_width width, + bool ap_11ac_disable); +bool nxpwifi_check_11ax_capability(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params); +int nxpwifi_set_11ax_status(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params); +void nxpwifi_set_sys_config_invalid_data(struct nxpwifi_uap_bss_param *config); +void nxpwifi_set_wmm_params(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params); +void nxpwifi_config_uap_11d(struct nxpwifi_private *priv, + struct cfg80211_beacon_data *beacon_data); +void nxpwifi_uap_set_channel(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_chan_def chandef); +int nxpwifi_config_start_uap(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg); +int nxpwifi_process_event(struct nxpwifi_adapter *adapter); +int nxpwifi_process_sta_event(struct nxpwifi_private *priv); +int nxpwifi_process_uap_event(struct nxpwifi_private *priv); +void nxpwifi_reset_connect_state(struct nxpwifi_private *priv, u16 reason, + bool from_ap); +void nxpwifi_process_multi_chan_event(struct nxpwifi_private *priv, + struct sk_buff *event_skb); +void nxpwifi_process_tx_pause_event(struct nxpwifi_private *priv, + struct sk_buff *event); +void nxpwifi_bt_coex_wlan_param_update_event(struct nxpwifi_private *priv, + struct sk_buff *event_skb); +int nxpwifi_mgmt_frame_reg(struct nxpwifi_private *priv, u32 mask); +int nxpwifi_set_uap_sys_cfg(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *cfg); +int nxpwifi_set_rts(struct nxpwifi_private *priv, u32 rts_thr); +int nxpwifi_set_frag(struct nxpwifi_private *priv, u32 frag_thr); +int nxpwifi_set_bss_mode(struct nxpwifi_private *priv); +int nxpwifi_config_monitor_mode(struct nxpwifi_private *priv, + struct nxpwifi_802_11_net_monitor *cfg); +int nxpwifi_apply_regdomain(struct nxpwifi_private *priv); +int nxpwifi_get_tx_pwr(struct nxpwifi_private *priv); +int nxpwifi_get_rssi_info(struct nxpwifi_private *priv); +int nxpwifi_get_802_11_snmp_mib(struct nxpwifi_private *priv, u16 oid, void *value); +int nxpwifi_set_rf_antenna(struct nxpwifi_private *priv, void *antcfg); +int nxpwifi_get_rf_antenna(struct nxpwifi_private *priv, u32 *tx_ant, u32 *rx_ant); +int nxpwifi_ap_stop_bss(struct nxpwifi_private *priv); +int nxpwifi_ap_sys_reset(struct nxpwifi_private *priv); +int nxpwifi_cfg80211_deinit_p2p(struct nxpwifi_private *priv); +int nxpwifi_ap_get_sta_list(struct nxpwifi_private *priv); +int nxpwifi_set_tx_rate(struct nxpwifi_private *priv, void *bitmap_rates); +int nxpwifi_802_11_subscribe_event(struct nxpwifi_private *priv, + struct nxpwifi_ds_misc_subsc_evt *subsc_evt); +int nxpwifi_uap_sta_deauth(struct nxpwifi_private *priv, u8 *mac); +int nxpwifi_bg_scan_config(struct nxpwifi_private *priv, void *bg_scan_cfg); +int nxpwifi_mef_cfg(struct nxpwifi_private *priv, void *mef_cfg); +int nxpwifi_coalesce_cfg(struct nxpwifi_private *priv, void *coalesce_cfg); +int nxpwifi_add_new_station(struct nxpwifi_private *priv, void *add_sta); +int nxpwifi_hostcmd(struct nxpwifi_private *priv, struct nxpwifi_ds_misc_cmd *hostcmd); +int nxpwifi_chan_report_request(struct nxpwifi_private *priv, void *radar_params); +#endif /* !_NXPWIFI_CMD_EVT_H_ */ diff --git a/drivers/net/wireless/nxp/nxpwifi/debugfs.c b/drivers/net/wireless/nxp/nxpwifi/debugfs.c new file mode 100644 index 000000000000..ccaf0eae37e3 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/debugfs.c @@ -0,0 +1,1094 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: debugfs + * + * Copyright 2011-2024 NXP + */ + +#include + +#include "main.h" +#include "cmdevt.h" +#include "11n.h" + +static struct dentry *nxpwifi_dfs_dir; + +static char *bss_modes[] = { + "UNSPECIFIED", + "ADHOC", + "STATION", + "AP", + "AP_VLAN", + "WDS", + "MONITOR", + "MESH_POINT", + "P2P_CLIENT", + "P2P_GO", + "P2P_DEVICE", +}; + +/* + * debugfs "info" read handler: dump driver name/version, interface, BSS mode, + * link state, MAC, counters; STA adds SSID/BSSID/channel/country/region and + * multicast list. + */ +static ssize_t +nxpwifi_info_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = + (struct nxpwifi_private *)file->private_data; + struct net_device *netdev = priv->netdev; + struct netdev_hw_addr *ha; + struct netdev_queue *txq; + unsigned long page = get_zeroed_page(GFP_KERNEL); + char *p = (char *)page, fmt[64]; + struct nxpwifi_bss_info info; + ssize_t ret; + int i = 0; + + if (!p) + return -ENOMEM; + + memset(&info, 0, sizeof(info)); + ret = nxpwifi_get_bss_info(priv, &info); + if (ret) + goto free_and_exit; + + nxpwifi_drv_get_driver_version(priv->adapter, fmt, sizeof(fmt) - 1); + + nxpwifi_get_ver_ext(priv, 0); + + p += sprintf(p, "driver_name = "); + p += sprintf(p, "\"nxpwifi\"\n"); + p += sprintf(p, "driver_version = %s", fmt); + p += sprintf(p, "\nverext = %s", priv->version_str); + p += sprintf(p, "\ninterface_name=\"%s\"\n", netdev->name); + + if (info.bss_mode >= ARRAY_SIZE(bss_modes)) + p += sprintf(p, "bss_mode=\"%d\"\n", info.bss_mode); + else + p += sprintf(p, "bss_mode=\"%s\"\n", bss_modes[info.bss_mode]); + + p += sprintf(p, "media_state=\"%s\"\n", + (!priv->media_connected ? "Disconnected" : "Connected")); + p += sprintf(p, "mac_address=\"%pM\"\n", netdev->dev_addr); + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) { + p += sprintf(p, "multicast_count=\"%d\"\n", + netdev_mc_count(netdev)); + p += sprintf(p, "essid=\"%.*s\"\n", info.ssid.ssid_len, + info.ssid.ssid); + p += sprintf(p, "bssid=\"%pM\"\n", info.bssid); + p += sprintf(p, "channel=\"%d\"\n", (int)info.bss_chan); + p += sprintf(p, "country_code = \"%s\"\n", info.country_code); + p += sprintf(p, "region_code=\"0x%x\"\n", + priv->adapter->region_code); + + netdev_for_each_mc_addr(ha, netdev) + p += sprintf(p, "multicast_address[%d]=\"%pM\"\n", + i++, ha->addr); + } + + p += sprintf(p, "num_tx_bytes = %lu\n", priv->stats.tx_bytes); + p += sprintf(p, "num_rx_bytes = %lu\n", priv->stats.rx_bytes); + p += sprintf(p, "num_tx_pkts = %lu\n", priv->stats.tx_packets); + p += sprintf(p, "num_rx_pkts = %lu\n", priv->stats.rx_packets); + p += sprintf(p, "num_tx_pkts_dropped = %lu\n", priv->stats.tx_dropped); + p += sprintf(p, "num_rx_pkts_dropped = %lu\n", priv->stats.rx_dropped); + p += sprintf(p, "num_tx_pkts_err = %lu\n", priv->stats.tx_errors); + p += sprintf(p, "num_rx_pkts_err = %lu\n", priv->stats.rx_errors); + p += sprintf(p, "carrier %s\n", ((netif_carrier_ok(priv->netdev)) + ? "on" : "off")); + p += sprintf(p, "tx queue"); + for (i = 0; i < netdev->num_tx_queues; i++) { + txq = netdev_get_tx_queue(netdev, i); + p += sprintf(p, " %d:%s", i, netif_tx_queue_stopped(txq) ? + "stopped" : "started"); + } + p += sprintf(p, "\n"); + + ret = simple_read_from_buffer(ubuf, count, ppos, (char *)page, + (unsigned long)p - page); + +free_and_exit: + free_page(page); + return ret; +} + +/* + * debugfs "getlog" read handler: dump firmware/802.11 counters (retry, RTS/ACK, dup, + * frag, mcast, FCS, beacon stats). + */ +static ssize_t +nxpwifi_getlog_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = + (struct nxpwifi_private *)file->private_data; + unsigned long page = get_zeroed_page(GFP_KERNEL); + char *p = (char *)page; + ssize_t ret; + struct nxpwifi_ds_get_stats stats; + + if (!p) + return -ENOMEM; + + memset(&stats, 0, sizeof(stats)); + ret = nxpwifi_get_stats_info(priv, &stats); + if (ret) + goto free_and_exit; + + p += sprintf(p, "\n" + "mcasttxframe %u\n" + "failed %u\n" + "retry %u\n" + "multiretry %u\n" + "framedup %u\n" + "rtssuccess %u\n" + "rtsfailure %u\n" + "ackfailure %u\n" + "rxfrag %u\n" + "mcastrxframe %u\n" + "fcserror %u\n" + "txframe %u\n" + "wepicverrcnt-1 %u\n" + "wepicverrcnt-2 %u\n" + "wepicverrcnt-3 %u\n" + "wepicverrcnt-4 %u\n" + "bcn_rcv_cnt %u\n" + "bcn_miss_cnt %u\n", + stats.mcast_tx_frame, + stats.failed, + stats.retry, + stats.multi_retry, + stats.frame_dup, + stats.rts_success, + stats.rts_failure, + stats.ack_failure, + stats.rx_frag, + stats.mcast_rx_frame, + stats.fcs_error, + stats.tx_frame, + stats.wep_icv_error[0], + stats.wep_icv_error[1], + stats.wep_icv_error[2], + stats.wep_icv_error[3], + stats.bcn_rcv_cnt, + stats.bcn_miss_cnt); + + ret = simple_read_from_buffer(ubuf, count, ppos, (char *)page, + (unsigned long)p - page); + +free_and_exit: + free_page(page); + return ret; +} + +/* + * debugfs "histogram" read handler: report sample count and per-rate/SNR/noise + * floor/signal strength histograms. + */ +static ssize_t +nxpwifi_histogram_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = + (struct nxpwifi_private *)file->private_data; + ssize_t ret; + struct nxpwifi_histogram_data *phist_data; + int i, value; + unsigned long page = get_zeroed_page(GFP_KERNEL); + char *p = (char *)page; + + if (!p) + return -ENOMEM; + + if (!priv || !priv->hist_data) { + ret = -EFAULT; + goto free_and_exit; + } + + phist_data = priv->hist_data; + + p += sprintf(p, "\n" + "total samples = %d\n", + atomic_read(&phist_data->num_samples)); + + p += sprintf(p, + "rx rates (in Mbps): 0=1M 1=2M 2=5.5M 3=11M 4=6M 5=9M 6=12M\n" + "7=18M 8=24M 9=36M 10=48M 11=54M 12-27=MCS0-15(BW20) 28-43=MCS0-15(BW40)\n"); + + if (ISSUPP_11ACENABLED(priv->adapter->fw_cap_info)) { + p += sprintf(p, + "44-53=MCS0-9(VHT:BW20) 54-63=MCS0-9(VHT:BW40) 64-73=MCS0-9(VHT:BW80)\n\n"); + } else { + p += sprintf(p, "\n"); + } + + for (i = 0; i < NXPWIFI_MAX_RX_RATES; i++) { + value = atomic_read(&phist_data->rx_rate[i]); + if (value) + p += sprintf(p, "rx_rate[%02d] = %d\n", i, value); + } + + if (ISSUPP_11ACENABLED(priv->adapter->fw_cap_info)) { + for (i = NXPWIFI_MAX_RX_RATES; i < NXPWIFI_MAX_AC_RX_RATES; + i++) { + value = atomic_read(&phist_data->rx_rate[i]); + if (value) + p += sprintf(p, "rx_rate[%02d] = %d\n", + i, value); + } + } + + for (i = 0; i < NXPWIFI_MAX_SNR; i++) { + value = atomic_read(&phist_data->snr[i]); + if (value) + p += sprintf(p, "snr[%02ddB] = %d\n", i, value); + } + for (i = 0; i < NXPWIFI_MAX_NOISE_FLR; i++) { + value = atomic_read(&phist_data->noise_flr[i]); + if (value) + p += sprintf(p, "noise_flr[%02ddBm] = %d\n", + (int)(i - 128), value); + } + for (i = 0; i < NXPWIFI_MAX_SIG_STRENGTH; i++) { + value = atomic_read(&phist_data->sig_str[i]); + if (value) + p += sprintf(p, "sig_strength[-%02ddBm] = %d\n", + i, value); + } + + ret = simple_read_from_buffer(ubuf, count, ppos, (char *)page, + (unsigned long)p - page); + +free_and_exit: + free_page(page); + return ret; +} + +static ssize_t +nxpwifi_histogram_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = (void *)file->private_data; + + if (priv && priv->hist_data) + nxpwifi_hist_data_reset(priv); + return 0; +} + +static struct nxpwifi_debug_info info; + +/* debugfs "debug" read handler: dump adapter debug info and BA/reorder tables. */ +static ssize_t +nxpwifi_debug_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = + (struct nxpwifi_private *)file->private_data; + unsigned long page = get_zeroed_page(GFP_KERNEL); + char *p = (char *)page; + ssize_t ret; + + if (!p) + return -ENOMEM; + + ret = nxpwifi_get_debug_info(priv, &info); + if (ret) + goto free_and_exit; + + p += nxpwifi_debug_info_to_buffer(priv, p, &info); + + ret = simple_read_from_buffer(ubuf, count, ppos, (char *)page, + (unsigned long)p - page); + +free_and_exit: + free_page(page); + return ret; +} + +static u32 saved_reg_type, saved_reg_offset, saved_reg_value; + +/* + * debugfs "regrdwr" write handler: parse and store for + * readback/IO. + */ +static ssize_t +nxpwifi_regrdwr_write(struct file *file, + const char __user *ubuf, size_t count, loff_t *ppos) +{ + char *buf; + int ret; + u32 reg_type = 0, reg_offset = 0, reg_value = UINT_MAX; + int rv; + + buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1))); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + rv = sscanf(buf, "%u %x %x", ®_type, ®_offset, ®_value); + + if (rv != 3) { + ret = -EINVAL; + goto done; + } + + if (reg_type == 0 || reg_offset == 0) { + ret = -EINVAL; + goto done; + } else { + saved_reg_type = reg_type; + saved_reg_offset = reg_offset; + saved_reg_value = reg_value; + ret = count; + } +done: + kfree(buf); + return ret; +} + +/* + * debugfs "regrdwr" read handler: perform pending register read/write and return + * . + */ +static ssize_t +nxpwifi_regrdwr_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = + (struct nxpwifi_private *)file->private_data; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + int pos = 0, ret = 0; + u32 reg_value; + + if (!buf) + return -ENOMEM; + + if (!saved_reg_type) { + /* No command has been given */ + pos += snprintf(buf, PAGE_SIZE, "0"); + goto done; + } + /* Set command has been given */ + if (saved_reg_value != UINT_MAX) { + ret = nxpwifi_reg_write(priv, saved_reg_type, saved_reg_offset, + saved_reg_value); + + pos += snprintf(buf, PAGE_SIZE, "%u 0x%x 0x%x\n", + saved_reg_type, saved_reg_offset, + saved_reg_value); + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos); + + goto done; + } + /* Get command has been given */ + ret = nxpwifi_reg_read(priv, saved_reg_type, + saved_reg_offset, ®_value); + if (ret) { + ret = -EINVAL; + goto done; + } + + pos += snprintf(buf, PAGE_SIZE, "%u 0x%x 0x%x\n", saved_reg_type, + saved_reg_offset, reg_value); + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos); + +done: + free_page(addr); + return ret; +} + +/* debugfs "debug_mask" read handler: show driver debug mask. */ + +static ssize_t +nxpwifi_debug_mask_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = + (struct nxpwifi_private *)file->private_data; + unsigned long page = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)page; + size_t ret = 0; + int pos = 0; + + if (!buf) + return -ENOMEM; + + pos += snprintf(buf, PAGE_SIZE, "debug mask=0x%08x\n", + priv->adapter->debug_mask); + ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos); + + free_page(page); + return ret; +} + +/* debugfs "debug_mask" write handler: set driver debug mask. */ + +static ssize_t +nxpwifi_debug_mask_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + int ret; + unsigned long debug_mask; + struct nxpwifi_private *priv = (void *)file->private_data; + char *buf; + + buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1))); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + if (kstrtoul(buf, 0, &debug_mask)) { + ret = -EINVAL; + goto done; + } + + priv->adapter->debug_mask = debug_mask; + ret = count; +done: + kfree(buf); + return ret; +} + +/* debugfs "verext" write handler: select extended version string. */ +static ssize_t +nxpwifi_verext_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + int ret; + u32 versionstrsel; + struct nxpwifi_private *priv = (void *)file->private_data; + + ret = kstrtou32_from_user(ubuf, count, 10, &versionstrsel); + if (ret) + return ret; + + priv->versionstrsel = versionstrsel; + + return count; +} + +/* debugfs "verext" read handler: show extended version string. */ +static ssize_t +nxpwifi_verext_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = + (struct nxpwifi_private *)file->private_data; + char buf[256]; + int ret; + + nxpwifi_get_ver_ext(priv, priv->versionstrsel); + ret = snprintf(buf, sizeof(buf), "version string: %s\n", + priv->version_str); + + return simple_read_from_buffer(ubuf, count, ppos, buf, ret); +} + +/* debugfs "memrw" write handler: read/write firmware memory (addr, value). */ +static ssize_t +nxpwifi_memrw_write(struct file *file, const char __user *ubuf, size_t count, + loff_t *ppos) +{ + int ret; + char cmd; + struct nxpwifi_ds_mem_rw mem_rw; + u16 cmd_action; + struct nxpwifi_private *priv = (void *)file->private_data; + char *buf; + + buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1))); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + ret = sscanf(buf, "%c %x %x", &cmd, &mem_rw.addr, &mem_rw.value); + if (ret != 3) { + ret = -EINVAL; + goto done; + } + + if ((cmd == 'r') || (cmd == 'R')) { + cmd_action = HOST_ACT_GEN_GET; + mem_rw.value = 0; + } else if ((cmd == 'w') || (cmd == 'W')) { + cmd_action = HOST_ACT_GEN_SET; + } else { + ret = -EINVAL; + goto done; + } + + memcpy(&priv->mem_rw, &mem_rw, sizeof(mem_rw)); + ret = nxpwifi_send_cmd(priv, HOST_CMD_MEM_ACCESS, cmd_action, 0, + &mem_rw, true); + if (!ret) + ret = count; + +done: + kfree(buf); + return ret; +} + +/* debugfs "memrw" read handler: show last memory access result. */ +static ssize_t +nxpwifi_memrw_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = (void *)file->private_data; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + int ret, pos = 0; + + if (!buf) + return -ENOMEM; + + pos += snprintf(buf, PAGE_SIZE, "0x%x 0x%x\n", priv->mem_rw.addr, + priv->mem_rw.value); + ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos); + + free_page(addr); + return ret; +} + +static u32 saved_offset = -1, saved_bytes = -1; + +/* debugfs "rdeeprom" write handler: set EEPROM offset/length to read. */ +static ssize_t +nxpwifi_rdeeprom_write(struct file *file, + const char __user *ubuf, size_t count, loff_t *ppos) +{ + char *buf; + int ret = 0; + int offset = -1, bytes = -1; + int rv; + + buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1))); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + rv = sscanf(buf, "%d %d", &offset, &bytes); + + if (rv != 2) { + ret = -EINVAL; + goto done; + } + + if (offset == -1 || bytes == -1) { + ret = -EINVAL; + goto done; + } else { + saved_offset = offset; + saved_bytes = bytes; + ret = count; + } +done: + kfree(buf); + return ret; +} + +/* debugfs "rdeeprom" read handler: dump EEPROM bytes from saved offset/length. */ +static ssize_t +nxpwifi_rdeeprom_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = + (struct nxpwifi_private *)file->private_data; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + int pos, ret, i; + u8 value[MAX_EEPROM_DATA]; + + if (!buf) + return -ENOMEM; + + if (saved_offset == -1) { + /* No command has been given */ + pos = snprintf(buf, PAGE_SIZE, "0"); + goto done; + } + + /* Get command has been given */ + ret = nxpwifi_eeprom_read(priv, (u16)saved_offset, + (u16)saved_bytes, value); + if (ret) { + ret = -EINVAL; + goto out_free; + } + + pos = snprintf(buf, PAGE_SIZE, "%d %d ", saved_offset, saved_bytes); + + for (i = 0; i < saved_bytes; i++) + pos += scnprintf(buf + pos, PAGE_SIZE - pos, "%d ", value[i]); + +done: + ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos); +out_free: + free_page(addr); + return ret; +} + +/* + * debugfs "hscfg" write handler: configure host-sleep (conditions/gpio/gap) or + * cancel. + */ +static ssize_t +nxpwifi_hscfg_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = (void *)file->private_data; + char *buf; + int ret, arg_num; + struct nxpwifi_ds_hs_cfg hscfg; + int conditions = HS_CFG_COND_DEF; + u32 gpio = HS_CFG_GPIO_DEF, gap = HS_CFG_GAP_DEF; + + buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1))); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + arg_num = sscanf(buf, "%d %x %x", &conditions, &gpio, &gap); + + memset(&hscfg, 0, sizeof(struct nxpwifi_ds_hs_cfg)); + + if (arg_num > 3) { + nxpwifi_dbg(priv->adapter, ERROR, + "Too many arguments\n"); + ret = -EINVAL; + goto done; + } + + if (arg_num >= 1 && arg_num < 3) + nxpwifi_set_hs_params(priv, HOST_ACT_GEN_GET, + NXPWIFI_SYNC_CMD, &hscfg); + + if (arg_num) { + if (conditions == HS_CFG_CANCEL) { + nxpwifi_cancel_hs(priv, NXPWIFI_ASYNC_CMD); + ret = count; + goto done; + } + hscfg.conditions = conditions; + } + if (arg_num >= 2) + hscfg.gpio = gpio; + if (arg_num == 3) + hscfg.gap = gap; + + hscfg.is_invoke_hostcmd = false; + nxpwifi_set_hs_params(priv, HOST_ACT_GEN_SET, + NXPWIFI_SYNC_CMD, &hscfg); + + nxpwifi_enable_hs(priv->adapter); + clear_bit(NXPWIFI_IS_HS_ENABLING, &priv->adapter->work_flags); + ret = count; +done: + kfree(buf); + return ret; +} + +/* debugfs "hscfg" read handler: show current host-sleep configuration. */ +static ssize_t +nxpwifi_hscfg_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = (void *)file->private_data; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + int pos, ret; + struct nxpwifi_ds_hs_cfg hscfg; + + if (!buf) + return -ENOMEM; + + nxpwifi_set_hs_params(priv, HOST_ACT_GEN_GET, + NXPWIFI_SYNC_CMD, &hscfg); + + pos = snprintf(buf, PAGE_SIZE, "%u 0x%x 0x%x\n", hscfg.conditions, + hscfg.gpio, hscfg.gap); + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos); + + free_page(addr); + return ret; +} + +static ssize_t +nxpwifi_timeshare_coex_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = file->private_data; + char buf[3]; + bool timeshare_coex; + int ret; + unsigned int len; + + if (priv->adapter->fw_api_ver != NXPWIFI_FW_V15) + return -EOPNOTSUPP; + + ret = nxpwifi_send_cmd(priv, HOST_CMD_ROBUST_COEX, + HOST_ACT_GEN_GET, 0, ×hare_coex, true); + if (ret) + return ret; + + len = sprintf(buf, "%d\n", timeshare_coex); + return simple_read_from_buffer(ubuf, count, ppos, buf, len); +} + +static ssize_t +nxpwifi_timeshare_coex_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + bool timeshare_coex; + struct nxpwifi_private *priv = file->private_data; + int ret; + + if (priv->adapter->fw_api_ver != NXPWIFI_FW_V15) + return -EOPNOTSUPP; + + ret = kstrtobool_from_user(ubuf, count, ×hare_coex); + if (ret) + return ret; + + ret = nxpwifi_send_cmd(priv, HOST_CMD_ROBUST_COEX, + HOST_ACT_GEN_SET, 0, ×hare_coex, true); + if (ret) + return ret; + else + return count; +} + +static ssize_t +nxpwifi_reset_write(struct file *file, + const char __user *ubuf, size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = file->private_data; + struct nxpwifi_adapter *adapter = priv->adapter; + bool result; + int rc; + + rc = kstrtobool_from_user(ubuf, count, &result); + if (rc) + return rc; + + if (!result) + return -EINVAL; + + if (adapter->if_ops.card_reset) { + nxpwifi_dbg(adapter, INFO, "Resetting per request\n"); + adapter->if_ops.card_reset(adapter); + } + + return count; +} + +static ssize_t +nxpwifi_fake_radar_detect_write(struct file *file, + const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = file->private_data; + struct nxpwifi_adapter *adapter = priv->adapter; + bool result; + int rc; + + rc = kstrtobool_from_user(ubuf, count, &result); + if (rc) + return rc; + + if (!result) + return -EINVAL; + + if (priv->wdev.links[0].cac_started) { + nxpwifi_dbg(adapter, MSG, + "Generate fake radar detected during CAC\n"); + if (nxpwifi_stop_radar_detection(priv, &priv->dfs_chandef)) + nxpwifi_dbg(adapter, ERROR, + "Failed to stop CAC in FW\n"); + wiphy_delayed_work_cancel(priv->adapter->wiphy, &priv->dfs_cac_work); + cfg80211_cac_event(priv->netdev, &priv->dfs_chandef, + NL80211_RADAR_CAC_ABORTED, GFP_KERNEL, 0); + cfg80211_radar_event(adapter->wiphy, &priv->dfs_chandef, + GFP_KERNEL); + } else { + if (priv->bss_chandef.chan->dfs_cac_ms) { + nxpwifi_dbg(adapter, MSG, + "Generate fake radar detected\n"); + cfg80211_radar_event(adapter->wiphy, + &priv->dfs_chandef, + GFP_KERNEL); + } + } + + return count; +} + +static ssize_t +nxpwifi_netmon_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + int ret; + struct nxpwifi_802_11_net_monitor netmon_cfg; + struct nxpwifi_private *priv = (void *)file->private_data; + char *buf; + + buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1))); + if (IS_ERR(buf)) + return PTR_ERR(buf); + memset(&netmon_cfg, 0, sizeof(struct nxpwifi_802_11_net_monitor)); + ret = sscanf(buf, "%u %u %u %u %u", + &netmon_cfg.enable_net_mon, + &netmon_cfg.filter_flag, + &netmon_cfg.band, + &netmon_cfg.channel, + &netmon_cfg.chan_bandwidth); + + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_NET_MONITOR, + HOST_ACT_GEN_SET, 0, &netmon_cfg, true); + + if (!ret) + ret = count; + + kfree(buf); + return ret; +} + +static ssize_t +nxpwifi_twt_setup_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + int ret; + struct nxpwifi_twt_cfg twt_cfg; + struct nxpwifi_private *priv = (void *)file->private_data; + char *buf; + u16 twt_mantissa, bcn_miss_threshold; + + buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1))); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + ret = sscanf(buf, "%hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hu %hhu %hu", + &twt_cfg.param.twt_setup.implicit, + &twt_cfg.param.twt_setup.announced, + &twt_cfg.param.twt_setup.trigger_enabled, + &twt_cfg.param.twt_setup.twt_info_disabled, + &twt_cfg.param.twt_setup.negotiation_type, + &twt_cfg.param.twt_setup.twt_wakeup_duration, + &twt_cfg.param.twt_setup.flow_identifier, + &twt_cfg.param.twt_setup.hard_constraint, + &twt_cfg.param.twt_setup.twt_exponent, + &twt_mantissa, + &twt_cfg.param.twt_setup.twt_request, + &bcn_miss_threshold); + + twt_cfg.param.twt_setup.twt_mantissa = cpu_to_le16(twt_mantissa); + twt_cfg.param.twt_setup.bcn_miss_threshold = cpu_to_le16(bcn_miss_threshold); + twt_cfg.sub_id = NXPWIFI_11AX_TWT_SETUP_SUBID; + ret = nxpwifi_send_cmd(priv, HOST_CMD_TWT_CFG, HOST_ACT_GEN_SET, 0, + &twt_cfg, true); + if (!ret) + ret = count; + + kfree(buf); + return ret; +} + +static ssize_t +nxpwifi_twt_teardown_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + int ret; + struct nxpwifi_twt_cfg twt_cfg; + struct nxpwifi_private *priv = (void *)file->private_data; + char *buf; + + buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1))); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + ret = sscanf(buf, "%hhu %hhu %hhu", + &twt_cfg.param.twt_teardown.flow_identifier, + &twt_cfg.param.twt_teardown.negotiation_type, + &twt_cfg.param.twt_teardown.teardown_all_twt); + + twt_cfg.sub_id = NXPWIFI_11AX_TWT_TEARDOWN_SUBID; + ret = nxpwifi_send_cmd(priv, HOST_CMD_TWT_CFG, HOST_ACT_GEN_SET, 0, + &twt_cfg, true); + + if (!ret) + ret = count; + + kfree(buf); + return ret; +} + +static ssize_t +nxpwifi_twt_report_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct nxpwifi_private *priv = + (struct nxpwifi_private *)file->private_data; + unsigned long page = get_zeroed_page(GFP_KERNEL); + char *p = (char *)page; + ssize_t ret; + struct nxpwifi_twt_cfg twt_cfg; + u8 num, i, j; + + if (!p) + return -ENOMEM; + + twt_cfg.sub_id = NXPWIFI_11AX_TWT_REPORT_SUBID; + ret = nxpwifi_send_cmd(priv, HOST_CMD_TWT_CFG, HOST_ACT_GEN_GET, 0, + &twt_cfg, true); + if (ret) + goto done; + num = twt_cfg.param.twt_report.length / NXPWIFI_BTWT_REPORT_LEN; + num = num <= NXPWIFI_BTWT_REPORT_MAX_NUM ? num : NXPWIFI_BTWT_REPORT_MAX_NUM; + p += sprintf(p, "\ntwt_report len %hhu, num %hhu, twt_report_info:\n", + twt_cfg.param.twt_report.length, num); + for (i = 0; i < num; i++) { + p += sprintf(p, "id[%hu]:\r\n", i); + for (j = 0; j < NXPWIFI_BTWT_REPORT_LEN; j++) { + p += sprintf(p, + " 0x%02x", + twt_cfg.param.twt_report.data[i * NXPWIFI_BTWT_REPORT_LEN + j]); + } + p += sprintf(p, "\r\n"); + } + + ret = simple_read_from_buffer(ubuf, count, ppos, (char *)page, + (unsigned long)p - page); + +done: + free_page(page); + return ret; +} + +static ssize_t +nxpwifi_twt_information_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + int ret; + struct nxpwifi_twt_cfg twt_cfg; + struct nxpwifi_private *priv = (void *)file->private_data; + char *buf; + u32 suspend_duration; + + buf = memdup_user_nul(ubuf, min(count, (size_t)(PAGE_SIZE - 1))); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + ret = sscanf(buf, "%hhu %u", + &twt_cfg.param.twt_information.flow_identifier, &suspend_duration); + twt_cfg.param.twt_information.suspend_duration = cpu_to_le32(suspend_duration); + + twt_cfg.sub_id = NXPWIFI_11AX_TWT_INFORMATION_SUBID; + ret = nxpwifi_send_cmd(priv, HOST_CMD_TWT_CFG, HOST_ACT_GEN_SET, 0, + &twt_cfg, true); + + if (!ret) + ret = count; + + kfree(buf); + return ret; +} + +#define NXPWIFI_DFS_ADD_FILE(name) debugfs_create_file(#name, 0644, \ + priv->dfs_dev_dir, priv, \ + &nxpwifi_dfs_##name##_fops) + +#define NXPWIFI_DFS_FILE_OPS(name) \ +static const struct file_operations nxpwifi_dfs_##name##_fops = { \ + .read = nxpwifi_##name##_read, \ + .write = nxpwifi_##name##_write, \ + .open = simple_open, \ +} + +#define NXPWIFI_DFS_FILE_READ_OPS(name) \ +static const struct file_operations nxpwifi_dfs_##name##_fops = { \ + .read = nxpwifi_##name##_read, \ + .open = simple_open, \ +} + +#define NXPWIFI_DFS_FILE_WRITE_OPS(name) \ +static const struct file_operations nxpwifi_dfs_##name##_fops = { \ + .write = nxpwifi_##name##_write, \ + .open = simple_open, \ +} + +NXPWIFI_DFS_FILE_READ_OPS(info); +NXPWIFI_DFS_FILE_READ_OPS(debug); +NXPWIFI_DFS_FILE_READ_OPS(getlog); +NXPWIFI_DFS_FILE_OPS(regrdwr); +NXPWIFI_DFS_FILE_OPS(rdeeprom); +NXPWIFI_DFS_FILE_OPS(memrw); +NXPWIFI_DFS_FILE_OPS(hscfg); +NXPWIFI_DFS_FILE_OPS(histogram); +NXPWIFI_DFS_FILE_OPS(debug_mask); +NXPWIFI_DFS_FILE_OPS(timeshare_coex); +NXPWIFI_DFS_FILE_WRITE_OPS(reset); +NXPWIFI_DFS_FILE_WRITE_OPS(fake_radar_detect); +NXPWIFI_DFS_FILE_OPS(verext); +NXPWIFI_DFS_FILE_WRITE_OPS(netmon); +NXPWIFI_DFS_FILE_WRITE_OPS(twt_setup); +NXPWIFI_DFS_FILE_WRITE_OPS(twt_teardown); +NXPWIFI_DFS_FILE_READ_OPS(twt_report); +NXPWIFI_DFS_FILE_WRITE_OPS(twt_information); + +/* Create per-netdev debugfs directory and files. */ +void +nxpwifi_dev_debugfs_init(struct nxpwifi_private *priv) +{ + if (!nxpwifi_dfs_dir || !priv) + return; + + priv->dfs_dev_dir = debugfs_create_dir(priv->netdev->name, + nxpwifi_dfs_dir); + + NXPWIFI_DFS_ADD_FILE(info); + NXPWIFI_DFS_ADD_FILE(debug); + NXPWIFI_DFS_ADD_FILE(getlog); + NXPWIFI_DFS_ADD_FILE(regrdwr); + NXPWIFI_DFS_ADD_FILE(rdeeprom); + + NXPWIFI_DFS_ADD_FILE(memrw); + NXPWIFI_DFS_ADD_FILE(hscfg); + NXPWIFI_DFS_ADD_FILE(histogram); + NXPWIFI_DFS_ADD_FILE(debug_mask); + NXPWIFI_DFS_ADD_FILE(timeshare_coex); + NXPWIFI_DFS_ADD_FILE(reset); + NXPWIFI_DFS_ADD_FILE(fake_radar_detect); + NXPWIFI_DFS_ADD_FILE(verext); + NXPWIFI_DFS_ADD_FILE(netmon); + NXPWIFI_DFS_ADD_FILE(twt_setup); + NXPWIFI_DFS_ADD_FILE(twt_teardown); + NXPWIFI_DFS_ADD_FILE(twt_report); + NXPWIFI_DFS_ADD_FILE(twt_information); +} + +/* Remove per-netdev debugfs directory and files. */ +void +nxpwifi_dev_debugfs_remove(struct nxpwifi_private *priv) +{ + if (!priv) + return; + + debugfs_remove_recursive(priv->dfs_dev_dir); +} + +/* Create top-level debugfs directory. */ +void +nxpwifi_debugfs_init(void) +{ + if (!nxpwifi_dfs_dir) + nxpwifi_dfs_dir = debugfs_create_dir("nxpwifi", NULL); +} + +/* Remove top-level debugfs directory. */ +void +nxpwifi_debugfs_remove(void) +{ + debugfs_remove(nxpwifi_dfs_dir); +} diff --git a/drivers/net/wireless/nxp/nxpwifi/ethtool.c b/drivers/net/wireless/nxp/nxpwifi/ethtool.c new file mode 100644 index 000000000000..aabb635afcf5 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/ethtool.c @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: ethtool + * + * Copyright 2011-2024 NXP + */ + +#include "main.h" + +static void nxpwifi_ethtool_get_wol(struct net_device *dev, + struct ethtool_wolinfo *wol) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + u32 conditions = le32_to_cpu(priv->adapter->hs_cfg.conditions); + + wol->supported = WAKE_UCAST | WAKE_MCAST | WAKE_BCAST | WAKE_PHY; + + if (conditions == HS_CFG_COND_DEF) + return; + + if (conditions & HS_CFG_COND_UNICAST_DATA) + wol->wolopts |= WAKE_UCAST; + if (conditions & HS_CFG_COND_MULTICAST_DATA) + wol->wolopts |= WAKE_MCAST; + if (conditions & HS_CFG_COND_BROADCAST_DATA) + wol->wolopts |= WAKE_BCAST; + if (conditions & HS_CFG_COND_MAC_EVENT) + wol->wolopts |= WAKE_PHY; +} + +static int nxpwifi_ethtool_set_wol(struct net_device *dev, + struct ethtool_wolinfo *wol) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + u32 conditions = 0; + + if (wol->wolopts & ~(WAKE_UCAST | WAKE_MCAST | WAKE_BCAST | WAKE_PHY)) + return -EOPNOTSUPP; + + if (wol->wolopts & WAKE_UCAST) + conditions |= HS_CFG_COND_UNICAST_DATA; + if (wol->wolopts & WAKE_MCAST) + conditions |= HS_CFG_COND_MULTICAST_DATA; + if (wol->wolopts & WAKE_BCAST) + conditions |= HS_CFG_COND_BROADCAST_DATA; + if (wol->wolopts & WAKE_PHY) + conditions |= HS_CFG_COND_MAC_EVENT; + if (wol->wolopts == 0) + conditions |= HS_CFG_COND_DEF; + priv->adapter->hs_cfg.conditions = cpu_to_le32(conditions); + + return 0; +} + +const struct ethtool_ops nxpwifi_ethtool_ops = { + .get_wol = nxpwifi_ethtool_get_wol, + .set_wol = nxpwifi_ethtool_set_wol, +}; diff --git a/drivers/net/wireless/nxp/nxpwifi/fw.h b/drivers/net/wireless/nxp/nxpwifi/fw.h new file mode 100644 index 000000000000..db4b7b7425a5 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/fw.h @@ -0,0 +1,2459 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * nxpwifi: Firmware-specific macros and structures + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_FW_H_ +#define _NXPWIFI_FW_H_ + +#include + +#define INTF_HEADER_LEN 4 + +struct rfc_1042_hdr { + u8 llc_dsap; + u8 llc_ssap; + u8 llc_ctrl; + u8 snap_oui[3]; + __be16 snap_type; +} __packed; + +struct rx_packet_hdr { + struct ethhdr eth803_hdr; + struct rfc_1042_hdr rfc1042_hdr; +} __packed; + +struct tx_packet_hdr { + struct ethhdr eth803_hdr; + struct rfc_1042_hdr rfc1042_hdr; +} __packed; + +struct nxpwifi_fw_header { + __le32 dnld_cmd; + __le32 base_addr; + __le32 data_length; + __le32 crc; +} __packed; + +struct nxpwifi_fw_data { + struct nxpwifi_fw_header header; + __le32 seq_num; + u8 data[]; +} __packed; + +struct nxpwifi_fw_dump_header { + __le16 seq_num; + __le16 reserved; + __le16 type; + __le16 len; +} __packed; + +#define FW_DUMP_INFO_ENDED 0x0002 + +#define NXPWIFI_FW_DNLD_CMD_1 0x1 +#define NXPWIFI_FW_DNLD_CMD_5 0x5 +#define NXPWIFI_FW_DNLD_CMD_6 0x6 +#define NXPWIFI_FW_DNLD_CMD_7 0x7 + +#define B_SUPPORTED_RATES 5 +#define G_SUPPORTED_RATES 9 +#define BG_SUPPORTED_RATES 13 +#define A_SUPPORTED_RATES 9 +#define HOSTCMD_SUPPORTED_RATES 14 +#define N_SUPPORTED_RATES 3 +#define ALL_802_11_BANDS \ + (BAND_A | BAND_B | BAND_G | BAND_GN | BAND_AN | BAND_AAC | BAND_GAC) +#define FW_MULTI_BANDS_SUPPORT \ + (BIT(8) | BIT(9) | BIT(10) | BIT(11) | BIT(12) | BIT(13)) +#define IS_SUPPORT_MULTI_BANDS(adapter) \ + ((adapter)->fw_cap_info & FW_MULTI_BANDS_SUPPORT) + +/* + * Map fw_cap_info for default bands: shift 11ac flags so bits + * 11:GN, 12:AN, 13:GAC, 14:AAC match driver layout after >>8. + */ +#define GET_FW_DEFAULT_BANDS(adapter) ({\ + typeof(adapter) (_adapter) = adapter; \ + (((((_adapter->fw_cap_info & 0x3000) << 1) | \ + (_adapter->fw_cap_info & ~0xF000)) \ + >> 8) & \ + ALL_802_11_BANDS); \ + }) + +#define HOST_WEP_KEY_INDEX_MASK 0x3fff + +#define KEY_INFO_ENABLED 0x01 +enum KEY_TYPE_ID { + KEY_TYPE_ID_WEP = 0, + KEY_TYPE_ID_TKIP, + KEY_TYPE_ID_AES, + KEY_TYPE_ID_WAPI, + KEY_TYPE_ID_AES_CMAC, + KEY_TYPE_ID_GCMP, + KEY_TYPE_ID_GCMP_256, + KEY_TYPE_ID_CCMP_256, + KEY_TYPE_ID_BIP_GMAC_128, + KEY_TYPE_ID_BIP_GMAC_256, +}; + +#define WPA_PN_SIZE 8 +#define KEY_PARAMS_FIXED_LEN 10 +#define KEY_INDEX_MASK 0xf +#define KEY_API_VER_MAJOR_V2 2 + +#define KEY_MCAST BIT(0) +#define KEY_UNICAST BIT(1) +#define KEY_ENABLED BIT(2) +#define KEY_DEFAULT BIT(3) +#define KEY_TX_KEY BIT(4) +#define KEY_RX_KEY BIT(5) +#define KEY_IGTK BIT(10) + +#define MAX_POLL_TRIES 10000 +#define MAX_FIRMWARE_POLL_TRIES 300 + +#define FIRMWARE_READY_SDIO 0xfedc +#define FIRMWARE_READY_PCIE 0xfedcba00 + +#define NXPWIFI_COEX_MODE_TIMESHARE 0x01 +#define NXPWIFI_COEX_MODE_SPATIAL 0x82 + +enum nxpwifi_usb_ep { + NXPWIFI_USB_EP_CMD_EVENT = 1, + NXPWIFI_USB_EP_DATA = 2, + NXPWIFI_USB_EP_DATA_CH2 = 3, +}; + +enum NXPWIFI_802_11_PRIVACY_FILTER { + NXPWIFI_802_11_PRIV_FILTER_ACCEPT_ALL, + NXPWIFI_802_11_PRIV_FILTER_8021X_WEP +}; + +#define CAL_SNR(RSSI, NF) ((s16)((s16)(RSSI) - (s16)(NF))) +#define CAL_RSSI(SNR, NF) ((s16)((s16)(SNR) + (s16)(NF))) + +#define UAP_BSS_PARAMS_I 0 +#define UAP_CUSTOM_IE_I 1 +#define NXPWIFI_AUTO_IDX_MASK 0xffff +#define NXPWIFI_DELETE_MASK 0x0000 +#define MGMT_MASK_ASSOC_REQ 0x01 +#define MGMT_MASK_REASSOC_REQ 0x04 +#define MGMT_MASK_ASSOC_RESP 0x02 +#define MGMT_MASK_REASSOC_RESP 0x08 +#define MGMT_MASK_PROBE_REQ 0x10 +#define MGMT_MASK_PROBE_RESP 0x20 +#define MGMT_MASK_BEACON 0x100 + +#define TLV_TYPE_UAP_SSID 0x0000 +#define TLV_TYPE_UAP_RATES 0x0001 +#define TLV_TYPE_PWR_CONSTRAINT 0x0020 +#define TLV_TYPE_HT_CAPABILITY 0x002d +#define TLV_TYPE_EXTENSION_ID 0x00ff + +#define PROPRIETARY_TLV_BASE_ID 0x0100 +#define TLV_TYPE_KEY_MATERIAL (PROPRIETARY_TLV_BASE_ID + 0) +#define TLV_TYPE_CHANLIST (PROPRIETARY_TLV_BASE_ID + 1) +#define TLV_TYPE_NUMPROBES (PROPRIETARY_TLV_BASE_ID + 2) +#define TLV_TYPE_RSSI_LOW (PROPRIETARY_TLV_BASE_ID + 4) +#define TLV_TYPE_PASSTHROUGH (PROPRIETARY_TLV_BASE_ID + 10) +#define TLV_TYPE_WMMQSTATUS (PROPRIETARY_TLV_BASE_ID + 16) +#define TLV_TYPE_WILDCARDSSID (PROPRIETARY_TLV_BASE_ID + 18) +#define TLV_TYPE_TSFTIMESTAMP (PROPRIETARY_TLV_BASE_ID + 19) +#define TLV_TYPE_RSSI_HIGH (PROPRIETARY_TLV_BASE_ID + 22) +#define TLV_TYPE_BGSCAN_START_LATER (PROPRIETARY_TLV_BASE_ID + 30) +#define TLV_TYPE_AUTH_TYPE (PROPRIETARY_TLV_BASE_ID + 31) +#define TLV_TYPE_STA_MAC_ADDR (PROPRIETARY_TLV_BASE_ID + 32) +#define TLV_TYPE_BSSID (PROPRIETARY_TLV_BASE_ID + 35) +#define TLV_TYPE_CHANNELBANDLIST (PROPRIETARY_TLV_BASE_ID + 42) +#define TLV_TYPE_UAP_MAC_ADDRESS (PROPRIETARY_TLV_BASE_ID + 43) +#define TLV_TYPE_UAP_BEACON_PERIOD (PROPRIETARY_TLV_BASE_ID + 44) +#define TLV_TYPE_UAP_DTIM_PERIOD (PROPRIETARY_TLV_BASE_ID + 45) +#define TLV_TYPE_UAP_BCAST_SSID (PROPRIETARY_TLV_BASE_ID + 48) +#define TLV_TYPE_UAP_PREAMBLE_CTL (PROPRIETARY_TLV_BASE_ID + 49) +#define TLV_TYPE_UAP_RTS_THRESHOLD (PROPRIETARY_TLV_BASE_ID + 51) +#define TLV_TYPE_UAP_AO_TIMER (PROPRIETARY_TLV_BASE_ID + 57) +#define TLV_TYPE_UAP_WEP_KEY (PROPRIETARY_TLV_BASE_ID + 59) +#define TLV_TYPE_UAP_WPA_PASSPHRASE (PROPRIETARY_TLV_BASE_ID + 60) +#define TLV_TYPE_UAP_ENCRY_PROTOCOL (PROPRIETARY_TLV_BASE_ID + 64) +#define TLV_TYPE_UAP_AKMP (PROPRIETARY_TLV_BASE_ID + 65) +#define TLV_TYPE_UAP_FRAG_THRESHOLD (PROPRIETARY_TLV_BASE_ID + 70) +#define TLV_TYPE_RATE_DROP_CONTROL (PROPRIETARY_TLV_BASE_ID + 82) +#define TLV_TYPE_RATE_SCOPE (PROPRIETARY_TLV_BASE_ID + 83) +#define TLV_TYPE_POWER_GROUP (PROPRIETARY_TLV_BASE_ID + 84) +#define TLV_TYPE_BSS_SCAN_RSP (PROPRIETARY_TLV_BASE_ID + 86) +#define TLV_TYPE_BSS_SCAN_INFO (PROPRIETARY_TLV_BASE_ID + 87) +#define TLV_TYPE_CHANRPT_11H_BASIC (PROPRIETARY_TLV_BASE_ID + 91) +#define TLV_TYPE_UAP_RETRY_LIMIT (PROPRIETARY_TLV_BASE_ID + 93) +#define TLV_TYPE_ROBUST_COEX (PROPRIETARY_TLV_BASE_ID + 96) +#define TLV_TYPE_UAP_MGMT_FRAME (PROPRIETARY_TLV_BASE_ID + 104) +#define TLV_TYPE_MGMT_IE (PROPRIETARY_TLV_BASE_ID + 105) +#define TLV_TYPE_AUTO_DS_PARAM (PROPRIETARY_TLV_BASE_ID + 113) +#define TLV_TYPE_PS_PARAM (PROPRIETARY_TLV_BASE_ID + 114) +#define TLV_TYPE_UAP_PS_AO_TIMER (PROPRIETARY_TLV_BASE_ID + 123) +#define TLV_TYPE_PWK_CIPHER (PROPRIETARY_TLV_BASE_ID + 145) +#define TLV_TYPE_GWK_CIPHER (PROPRIETARY_TLV_BASE_ID + 146) +#define TLV_TYPE_TX_PAUSE (PROPRIETARY_TLV_BASE_ID + 148) +#define TLV_TYPE_RXBA_SYNC (PROPRIETARY_TLV_BASE_ID + 153) +#define TLV_TYPE_COALESCE_RULE (PROPRIETARY_TLV_BASE_ID + 154) +#define TLV_TYPE_KEY_PARAM_V2 (PROPRIETARY_TLV_BASE_ID + 156) +#define TLV_TYPE_REGION_DOMAIN_CODE (PROPRIETARY_TLV_BASE_ID + 171) +#define TLV_TYPE_REPEAT_COUNT (PROPRIETARY_TLV_BASE_ID + 176) +#define TLV_TYPE_PS_PARAMS_IN_HS (PROPRIETARY_TLV_BASE_ID + 181) +#define TLV_TYPE_MULTI_CHAN_INFO (PROPRIETARY_TLV_BASE_ID + 183) +#define TLV_TYPE_MC_GROUP_INFO (PROPRIETARY_TLV_BASE_ID + 184) +#define TLV_TYPE_SCAN_CHANNEL_GAP (PROPRIETARY_TLV_BASE_ID + 197) +#define TLV_TYPE_API_REV (PROPRIETARY_TLV_BASE_ID + 199) +#define TLV_TYPE_CHANNEL_STATS (PROPRIETARY_TLV_BASE_ID + 198) +#define TLV_BTCOEX_WL_AGGR_WINSIZE (PROPRIETARY_TLV_BASE_ID + 202) +#define TLV_BTCOEX_WL_SCANTIME (PROPRIETARY_TLV_BASE_ID + 203) +#define TLV_TYPE_BSS_MODE (PROPRIETARY_TLV_BASE_ID + 206) +#define TLV_TYPE_RANDOM_MAC (PROPRIETARY_TLV_BASE_ID + 236) +#define TLV_TYPE_CHAN_ATTR_CFG (PROPRIETARY_TLV_BASE_ID + 237) +#define TLV_TYPE_MAX_CONN (PROPRIETARY_TLV_BASE_ID + 279) +#define TLV_TYPE_HOST_MLME (PROPRIETARY_TLV_BASE_ID + 307) +#define TLV_TYPE_UAP_STA_FLAGS (PROPRIETARY_TLV_BASE_ID + 313) +#define TLV_TYPE_FW_CAP_INFO (PROPRIETARY_TLV_BASE_ID + 318) +#define TLV_TYPE_AX_ENABLE_SR (PROPRIETARY_TLV_BASE_ID + 322) +#define TLV_TYPE_AX_OBSS_PD_OFFSET (PROPRIETARY_TLV_BASE_ID + 323) +#define TLV_TYPE_SAE_PWE_MODE (PROPRIETARY_TLV_BASE_ID + 339) +#define TLV_TYPE_6E_INBAND_FRAMES (PROPRIETARY_TLV_BASE_ID + 345) +#define TLV_TYPE_SECURE_BOOT_UUID (PROPRIETARY_TLV_BASE_ID + 348) + +#define NXPWIFI_TX_DATA_BUF_SIZE_2K 2048 + +#define SSN_MASK 0xfff0 + +#define BA_RESULT_SUCCESS 0x0 +#define BA_RESULT_TIMEOUT 0x2 + +#define IS_BASTREAM_SETUP(ptr) ((ptr)->ba_status) + +#define BA_STREAM_NOT_ALLOWED 0xff + +#define IS_11N_ENABLED(priv) ({ \ + typeof(priv) (_priv) = priv; \ + (((_priv)->config_bands & BAND_GN || \ + (_priv)->config_bands & BAND_AN) && \ + (_priv)->curr_bss_params.bss_descriptor.bcn_ht_cap && \ + !(_priv)->curr_bss_params.bss_descriptor.disable_11n); \ + }) +#define INITIATOR_BIT(del_ba_param_set) (((del_ba_param_set) &\ + BIT(DELBA_INITIATOR_POS)) >> DELBA_INITIATOR_POS) + +#define NXPWIFI_TX_DATA_BUF_SIZE_4K 4096 +#define NXPWIFI_TX_DATA_BUF_SIZE_8K 8192 +#define NXPWIFI_TX_DATA_BUF_SIZE_12K 12288 + +#define ISSUPP_11NENABLED(fw_cap_info) ((fw_cap_info) & BIT(11)) +#define ISSUPP_DRCS_ENABLED(fw_cap_info) ((fw_cap_info) & BIT(15)) +#define ISSUPP_SDIO_SPA_ENABLED(fw_cap_info) ((fw_cap_info) & BIT(16)) +#define ISSUPP_RANDOM_MAC(fw_cap_info) ((fw_cap_info) & BIT(27)) +#define ISSUPP_FIRMWARE_SUPPLICANT(fw_cap_info) ((fw_cap_info) & BIT(21)) + +#define NXPWIFI_DEF_HT_CAP (IEEE80211_HT_CAP_DSSSCCK40 | \ + (1 << IEEE80211_HT_CAP_RX_STBC_SHIFT) | \ + IEEE80211_HT_CAP_SM_PS) + +#define NXPWIFI_DEF_11N_TX_BF_CAP 0x09E1E008 + +#define NXPWIFI_DEF_AMPDU IEEE80211_HT_AMPDU_PARM_FACTOR + +#define RXPD_FLAG_EXTRA_HEADER BIT(1) +/* channel number at bit 5-13 */ +#define RXPD_CHAN_MASK 0x3FE0 +/* DCM at bit 16 */ +#define RXPD_DCM_MASK 0x10000 + +/* + * dot11n dev_cap bits: 17:20/40MHz, 23:SGI20, 24:SGI40, 25:TXSTBC, + * 26:RXSTBC, 29:Greenfield. + */ +#define ISSUPP_CHANWIDTH40(dot_11n_dev_cap) ((dot_11n_dev_cap) & BIT(17)) +#define ISSUPP_SHORTGI20(dot_11n_dev_cap) ((dot_11n_dev_cap) & BIT(23)) +#define ISSUPP_SHORTGI40(dot_11n_dev_cap) ((dot_11n_dev_cap) & BIT(24)) +#define ISSUPP_TXSTBC(dot_11n_dev_cap) ((dot_11n_dev_cap) & BIT(25)) +#define ISSUPP_RXSTBC(dot_11n_dev_cap) ((dot_11n_dev_cap) & BIT(26)) +#define ISSUPP_GREENFIELD(dot_11n_dev_cap) ((dot_11n_dev_cap) & BIT(29)) +#define ISENABLED_40MHZ_INTOLERANT(dot_11n_dev_cap) ((dot_11n_dev_cap) & BIT(8)) +#define ISSUPP_RXLDPC(dot_11n_dev_cap) ((dot_11n_dev_cap) & BIT(22)) +#define ISSUPP_BEAMFORMING(dot_11n_dev_cap) ((dot_11n_dev_cap) & BIT(30)) +#define ISALLOWED_CHANWIDTH40(ht_param) ((ht_param) & BIT(2)) +#define GETSUPP_TXBASTREAMS(dot_11n_dev_cap) (((dot_11n_dev_cap) >> 18) & 0xF) + +/* AMPDU factor size */ +#define AMPDU_FACTOR_64K 0x03 +/* hw_dev_cap : MPDU DENSITY */ +#define GET_MPDU_DENSITY(hw_dev_cap) ((hw_dev_cap) & 0x7) + +/* httxcfg bits: 1:20/40, 4:GF, 5:SGI20, 6:SGI40. */ +#define NXPWIFI_FW_DEF_HTTXCFG (BIT(1) | BIT(4) | BIT(5) | BIT(6)) + +/* 11ac MCS map (1x1): stream0 supports 0-9, others not supported. */ +#define NXPWIFI_11AC_MCS_MAP_1X1 0xfffefffe + +/* 11ac MCS map (2x2): stream0/1 support 0-9, others not supported. */ +#define NXPWIFI_11AC_MCS_MAP_2X2 0xfffafffa + +#define GET_TXMCSSUPP(dev_mcs_supported) ((dev_mcs_supported) >> 4) +#define GET_RXMCSSUPP(dev_mcs_supported) ((dev_mcs_supported) & 0x0f) +#define SETHT_MCS32(x) (x[4] |= 1) +#define HT_STREAM_1X1 0x11 +#define HT_STREAM_2X2 0x22 + +#define SET_SECONDARYCHAN(radio_type, sec_chan) \ + ((radio_type) |= ((sec_chan) << 4)) + +#define LLC_SNAP_LEN 8 + +/* HW_SPEC fw_cap_info */ + +#define ISSUPP_11ACENABLED(fw_cap_info) ((fw_cap_info) & BIT(13)) +#define NO_NSS_SUPPORT 0x3 +#define GET_VHTNSSMCS(mcs_mapset, nss) \ + (((mcs_mapset) >> (2 * ((nss) - 1))) & 0x3) +#define SET_VHTNSSMCS(mcs_mapset, nss, value) \ + ((mcs_mapset) |= ((value) & 0x3) << (2 * ((nss) - 1))) +#define GET_DEVTXMCSMAP(dev_mcs_map) ((dev_mcs_map) >> 16) +#define GET_DEVRXMCSMAP(dev_mcs_map) ((dev_mcs_map) & 0xFFFF) + +/* Clear SU/MU beamformer/beamformee and sounding dimension bits. */ +#define NXPWIFI_DEF_11AC_CAP_BF_RESET_MASK \ + (IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE | \ + IEEE80211_VHT_CAP_MU_BEAMFORMER_CAPABLE | \ + IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE | \ + IEEE80211_VHT_CAP_SOUNDING_DIMENSIONS_MASK) + +#define MOD_CLASS_HR_DSSS 0x03 +#define MOD_CLASS_OFDM 0x07 +#define MOD_CLASS_HT 0x08 +#define HT_BW_20 0 +#define HT_BW_40 1 + +#define DFS_CHAN_MOVE_TIME 10000 + +#define ISSUPP_11AXENABLED(fw_cap_ext) ((fw_cap_ext) & BIT(7)) + +#define HOST_CMD_GET_HW_SPEC 0x0003 +#define HOST_CMD_802_11_SCAN 0x0006 +#define HOST_CMD_802_11_GET_LOG 0x000b +#define HOST_CMD_MAC_MULTICAST_ADR 0x0010 +#define HOST_CMD_802_11_ASSOCIATE 0x0012 +#define HOST_CMD_802_11_SNMP_MIB 0x0016 +#define HOST_CMD_MAC_REG_ACCESS 0x0019 +#define HOST_CMD_BBP_REG_ACCESS 0x001a +#define HOST_CMD_RF_REG_ACCESS 0x001b +#define HOST_CMD_RF_TX_PWR 0x001e +#define HOST_CMD_RF_ANTENNA 0x0020 +#define HOST_CMD_802_11_DEAUTHENTICATE 0x0024 +#define HOST_CMD_MAC_CONTROL 0x0028 +#define HOST_CMD_802_11_MAC_ADDRESS 0x004D +#define HOST_CMD_802_11_EEPROM_ACCESS 0x0059 +#define HOST_CMD_802_11D_DOMAIN_INFO 0x005b +#define HOST_CMD_802_11_KEY_MATERIAL 0x005e +#define HOST_CMD_802_11_BG_SCAN_CONFIG 0x006b +#define HOST_CMD_802_11_BG_SCAN_QUERY 0x006c +#define HOST_CMD_WMM_GET_STATUS 0x0071 +#define HOST_CMD_802_11_SUBSCRIBE_EVENT 0x0075 +#define HOST_CMD_802_11_TX_RATE_QUERY 0x007f +#define HOST_CMD_MEM_ACCESS 0x0086 +#define HOST_CMD_CFG_DATA 0x008f +#define HOST_CMD_VERSION_EXT 0x0097 +#define HOST_CMD_MEF_CFG 0x009a +#define HOST_CMD_RSSI_INFO 0x00a4 +#define HOST_CMD_FUNC_INIT 0x00a9 +#define HOST_CMD_FUNC_SHUTDOWN 0x00aa +#define HOST_CMD_PMIC_REG_ACCESS 0x00ad +#define HOST_CMD_APCMD_SYS_RESET 0x00af +#define HOST_CMD_UAP_SYS_CONFIG 0x00b0 +#define HOST_CMD_UAP_BSS_START 0x00b1 +#define HOST_CMD_UAP_BSS_STOP 0x00b2 +#define HOST_CMD_APCMD_STA_LIST 0x00b3 +#define HOST_CMD_UAP_STA_DEAUTH 0x00b5 +#define HOST_CMD_11N_CFG 0x00cd +#define HOST_CMD_11N_ADDBA_REQ 0x00ce +#define HOST_CMD_11N_ADDBA_RSP 0x00cf +#define HOST_CMD_11N_DELBA 0x00d0 +#define HOST_CMD_TXPWR_CFG 0x00d1 +#define HOST_CMD_TX_RATE_CFG 0x00d6 +#define HOST_CMD_RECONFIGURE_TX_BUFF 0x00d9 +#define HOST_CMD_CHAN_REPORT_REQUEST 0x00dd +#define HOST_CMD_AMSDU_AGGR_CTRL 0x00df +#define HOST_CMD_ROBUST_COEX 0x00e0 +#define HOST_CMD_802_11_PS_MODE_ENH 0x00e4 +#define HOST_CMD_802_11_HS_CFG_ENH 0x00e5 +#define HOST_CMD_CAU_REG_ACCESS 0x00ed +#define HOST_CMD_SET_BSS_MODE 0x00f7 +#define HOST_CMD_PCIE_DESC_DETAILS 0x00fa +#define HOST_CMD_802_11_NET_MONITOR 0x0102 +#define HOST_CMD_802_11_SCAN_EXT 0x0107 +#define HOST_CMD_COALESCE_CFG 0x010a +#define HOST_CMD_MGMT_FRAME_REG 0x010c +#define HOST_CMD_REMAIN_ON_CHAN 0x010d +#define HOST_CMD_GTK_REKEY_OFFLOAD_CFG 0x010f +#define HOST_CMD_11AC_CFG 0x0112 +#define HOST_CMD_HS_WAKEUP_REASON 0x0116 +#define HOST_CMD_MC_POLICY 0x0121 +#define HOST_CMD_FW_DUMP_EVENT 0x0125 +#define HOST_CMD_SDIO_SP_RX_AGGR_CFG 0x0223 +#define HOST_CMD_STA_CONFIGURE 0x023f +#define HOST_CMD_VDLL 0x0240 +#define HOST_CMD_CHAN_REGION_CFG 0x0242 +#define HOST_CMD_PACKET_AGGR_CTRL 0x0251 +#define HOST_CMD_ADD_NEW_STATION 0x025f +#define HOST_CMD_11AX_CFG 0x0266 +#define HOST_CMD_11AX_CMD 0x026d +#define HOST_CMD_TWT_CFG 0x0270 + +#define PROTOCOL_NO_SECURITY 0x01 +#define PROTOCOL_STATIC_WEP 0x02 +#define PROTOCOL_WPA 0x08 +#define PROTOCOL_WPA2 0x20 +#define PROTOCOL_WPA2_MIXED 0x28 +#define PROTOCOL_EAP 0x40 +#define KEY_MGMT_EAP 0x01 +#define KEY_MGMT_PSK 0x02 +#define KEY_MGMT_NONE 0x04 +#define KEY_MGMT_PSK_SHA256 0x100 +#define KEY_MGMT_OWE 0x200 +#define KEY_MGMT_SAE 0x400 +#define CIPHER_TKIP 0x04 +#define CIPHER_AES_CCMP 0x08 +#define VALID_CIPHER_BITMAP 0x0c + +enum ENH_PS_MODES { + EN_PS = 1, + DIS_PS = 2, + EN_AUTO_DS = 3, + DIS_AUTO_DS = 4, + SLEEP_CONFIRM = 5, + GET_PS = 0, + EN_AUTO_PS = 0xff, + DIS_AUTO_PS = 0xfe, +}; + +enum nxpwifi_channel_flags { + NXPWIFI_CHANNEL_PASSIVE = BIT(0), + NXPWIFI_CHANNEL_DFS = BIT(1), + NXPWIFI_CHANNEL_NOHT40 = BIT(2), + NXPWIFI_CHANNEL_NOHT80 = BIT(3), + NXPWIFI_CHANNEL_DISABLED = BIT(7), +}; + +#define HOST_RET_BIT 0x8000 +#define HOST_ACT_GEN_GET 0x0000 +#define HOST_ACT_GEN_SET 0x0001 +#define HOST_ACT_GEN_REMOVE 0x0004 +#define HOST_ACT_BITWISE_SET 0x0002 +#define HOST_ACT_BITWISE_CLR 0x0003 +#define HOST_RESULT_OK 0x0000 +#define HOST_ACT_MAC_RX_ON BIT(0) +#define HOST_ACT_MAC_TX_ON BIT(1) +#define HOST_ACT_MAC_WEP_ENABLE BIT(3) +#define HOST_ACT_MAC_ETHERNETII_ENABLE BIT(4) +#define HOST_ACT_MAC_PROMISCUOUS_ENABLE BIT(7) +#define HOST_ACT_MAC_ALL_MULTICAST_ENABLE BIT(8) +#define HOST_ACT_MAC_DYNAMIC_BW_ENABLE BIT(16) + +#define HOST_BSS_MODE_IBSS 0x0002 +#define HOST_BSS_MODE_ANY 0x0003 + +#define HOST_SCAN_RADIO_TYPE_BG 0 +#define HOST_SCAN_RADIO_TYPE_A 1 + +#define HS_CFG_CANCEL 0xffffffff +#define HS_CFG_COND_DEF 0x00000000 +#define HS_CFG_GPIO_DEF 0xff +#define HS_CFG_GAP_DEF 0xff +#define HS_CFG_COND_BROADCAST_DATA 0x00000001 +#define HS_CFG_COND_UNICAST_DATA 0x00000002 +#define HS_CFG_COND_MAC_EVENT 0x00000004 +#define HS_CFG_COND_MULTICAST_DATA 0x00000008 + +#define CONNECT_ERR_AUTH_ERR_STA_FAILURE 0xFFFB +#define CONNECT_ERR_ASSOC_ERR_TIMEOUT 0xFFFC +#define CONNECT_ERR_ASSOC_ERR_AUTH_REFUSED 0xFFFD +#define CONNECT_ERR_AUTH_MSG_UNHANDLED 0xFFFE +#define CONNECT_ERR_STA_FAILURE 0xFFFF + +#define CMD_F_HOSTCMD BIT(0) + +#define HOST_CMD_ID_MASK 0x0fff + +#define HOST_SEQ_NUM_MASK 0x00ff + +#define HOST_BSS_NUM_MASK 0x0f00 + +#define HOST_BSS_TYPE_MASK 0xf000 + +#define HOST_ACT_SET_RX 0x0001 +#define HOST_ACT_SET_TX 0x0002 +#define HOST_ACT_SET_BOTH 0x0003 +#define HOST_ACT_GET_RX 0x0004 +#define HOST_ACT_GET_TX 0x0008 +#define HOST_ACT_GET_BOTH 0x000c + +#define HOST_ACT_REMOVE_STA 0x0 +#define HOST_ACT_ADD_STA 0x1 + +#define RF_ANTENNA_AUTO 0xFFFF + +#define HOST_SET_SEQ_NO_BSS_INFO(seq, num, type) \ + ((((seq) & 0x00ff) | \ + (((num) & 0x000f) << 8)) | \ + (((type) & 0x000f) << 12)) + +#define HOST_GET_SEQ_NO(seq) \ + ((seq) & HOST_SEQ_NUM_MASK) + +#define HOST_GET_BSS_NO(seq) \ + (((seq) & HOST_BSS_NUM_MASK) >> 8) + +#define HOST_GET_BSS_TYPE(seq) \ + (((seq) & HOST_BSS_TYPE_MASK) >> 12) + +#define EVENT_DUMMY_HOST_WAKEUP_SIGNAL 0x00000001 +#define EVENT_LINK_LOST 0x00000003 +#define EVENT_LINK_SENSED 0x00000004 +#define EVENT_MIB_CHANGED 0x00000006 +#define EVENT_INIT_DONE 0x00000007 +#define EVENT_DEAUTHENTICATED 0x00000008 +#define EVENT_DISASSOCIATED 0x00000009 +#define EVENT_PS_AWAKE 0x0000000a +#define EVENT_PS_SLEEP 0x0000000b +#define EVENT_MIC_ERR_MULTICAST 0x0000000d +#define EVENT_MIC_ERR_UNICAST 0x0000000e +#define EVENT_DEEP_SLEEP_AWAKE 0x00000010 +#define EVENT_WMM_STATUS_CHANGE 0x00000017 +#define EVENT_BG_SCAN_REPORT 0x00000018 +#define EVENT_RSSI_LOW 0x00000019 +#define EVENT_SNR_LOW 0x0000001a +#define EVENT_MAX_FAIL 0x0000001b +#define EVENT_RSSI_HIGH 0x0000001c +#define EVENT_SNR_HIGH 0x0000001d +#define EVENT_DATA_RSSI_LOW 0x00000024 +#define EVENT_DATA_SNR_LOW 0x00000025 +#define EVENT_DATA_RSSI_HIGH 0x00000026 +#define EVENT_DATA_SNR_HIGH 0x00000027 +#define EVENT_LINK_QUALITY 0x00000028 +#define EVENT_PORT_RELEASE 0x0000002b +#define EVENT_UAP_STA_DEAUTH 0x0000002c +#define EVENT_UAP_STA_ASSOC 0x0000002d +#define EVENT_UAP_BSS_START 0x0000002e +#define EVENT_PRE_BEACON_LOST 0x00000031 +#define EVENT_ADDBA 0x00000033 +#define EVENT_DELBA 0x00000034 +#define EVENT_BA_STREAM_TIEMOUT 0x00000037 +#define EVENT_AMSDU_AGGR_CTRL 0x00000042 +#define EVENT_UAP_BSS_IDLE 0x00000043 +#define EVENT_UAP_BSS_ACTIVE 0x00000044 +#define EVENT_WEP_ICV_ERR 0x00000046 +#define EVENT_HS_ACT_REQ 0x00000047 +#define EVENT_BW_CHANGE 0x00000048 +#define EVENT_UAP_MIC_COUNTERMEASURES 0x0000004c +#define EVENT_HOSTWAKE_STAIE 0x0000004d +#define EVENT_CHANNEL_SWITCH_ANN 0x00000050 +#define EVENT_RADAR_DETECTED 0x00000053 +#define EVENT_CHANNEL_REPORT_RDY 0x00000054 +#define EVENT_TX_DATA_PAUSE 0x00000055 +#define EVENT_EXT_SCAN_REPORT 0x00000058 +#define EVENT_RXBA_SYNC 0x00000059 +#define EVENT_REMAIN_ON_CHAN_EXPIRED 0x0000005f +#define EVENT_UNKNOWN_DEBUG 0x00000063 +#define EVENT_BG_SCAN_STOPPED 0x00000065 +#define EVENT_MULTI_CHAN_INFO 0x0000006a +#define EVENT_FW_DUMP_INFO 0x00000073 +#define EVENT_TX_STATUS_REPORT 0x00000074 +#define EVENT_BT_COEX_WLAN_PARA_CHANGE 0X00000076 +#define EVENT_VDLL_IND 0x00000081 + +#define EVENT_ID_MASK 0xffff +#define BSS_NUM_MASK 0xf + +#define EVENT_GET_BSS_NUM(event_cause) \ + (((event_cause) >> 16) & BSS_NUM_MASK) + +#define EVENT_GET_BSS_TYPE(event_cause) \ + (((event_cause) >> 24) & 0x00ff) + +#define NXPWIFI_MAX_PATTERN_LEN 40 +#define NXPWIFI_MAX_OFFSET_LEN 100 +#define NXPWIFI_MAX_ND_MATCH_SETS 10 + +#define STACK_NBYTES 100 +#define TYPE_DNUM 1 +#define TYPE_BYTESEQ 2 +#define MAX_OPERAND 0x40 +#define TYPE_EQ (MAX_OPERAND + 1) +#define TYPE_EQ_DNUM (MAX_OPERAND + 2) +#define TYPE_EQ_BIT (MAX_OPERAND + 3) +#define TYPE_AND (MAX_OPERAND + 4) +#define TYPE_OR (MAX_OPERAND + 5) +#define MEF_MODE_HOST_SLEEP 1 +#define MEF_ACTION_ALLOW_AND_WAKEUP_HOST 3 +#define MEF_ACTION_AUTO_ARP 0x10 +#define NXPWIFI_CRITERIA_BROADCAST BIT(0) +#define NXPWIFI_CRITERIA_UNICAST BIT(1) +#define NXPWIFI_CRITERIA_MULTICAST BIT(3) +#define NXPWIFI_MAX_SUPPORTED_IPADDR 4 + +#define NXPWIFI_DEF_CS_UNIT_TIME 2 +#define NXPWIFI_DEF_CS_THR_OTHERLINK 10 +#define NXPWIFI_DEF_THR_DIRECTLINK 0 +#define NXPWIFI_DEF_CS_TIME 10 +#define NXPWIFI_DEF_CS_TIMEOUT 16 +#define NXPWIFI_DEF_CS_REG_CLASS 12 +#define NXPWIFI_DEF_CS_PERIODICITY 1 + +#define NXPWIFI_FW_V15 15 + +#define NXPWIFI_MASTER_RADAR_DET_MASK BIT(1) + +struct nxpwifi_ie_types_header { + __le16 type; + __le16 len; +} __packed; + +struct nxpwifi_ie_types_data { + struct nxpwifi_ie_types_header header; + u8 data[]; +} __packed; + +/* Generic TLV wrapper for firmware data */ +struct nxpwifi_tlv { + __le16 type; + __le16 len; + u8 data[]; +} __packed; + +#define NXPWIFI_TxPD_POWER_MGMT_NULL_PACKET 0x01 +#define NXPWIFI_TxPD_POWER_MGMT_LAST_PACKET 0x08 +#define NXPWIFI_TXPD_FLAGS_REQ_TX_STATUS 0x20 + +enum HS_WAKEUP_REASON { + NO_HSWAKEUP_REASON = 0, + BCAST_DATA_MATCHED, + MCAST_DATA_MATCHED, + UCAST_DATA_MATCHED, + MASKTABLE_EVENT_MATCHED, + NON_MASKABLE_EVENT_MATCHED, + NON_MASKABLE_CONDITION_MATCHED, + MAGIC_PATTERN_MATCHED, + CONTROL_FRAME_MATCHED, + MANAGEMENT_FRAME_MATCHED, + GTK_REKEY_FAILURE, + RESERVED +}; + +struct txpd { + u8 bss_type; + u8 bss_num; + __le16 tx_pkt_length; + __le16 tx_pkt_offset; + __le16 tx_pkt_type; + __le32 tx_control; + u8 priority; + u8 flags; + u8 pkt_delay_2ms; + u8 reserved1[2]; + u8 tx_token_id; + u8 reserved[2]; +} __packed; + +struct rxpd { + u8 bss_type; + u8 bss_num; + __le16 rx_pkt_length; + __le16 rx_pkt_offset; + __le16 rx_pkt_type; + __le16 seq_num; + u8 priority; + u8 rx_rate; + s8 snr; + s8 nf; + /* + * rate_info bit definition (FW encoded) + * + * [1:0] format + * 00 = legacy + * 01 = HT + * 10 = VHT + * 11 = HE + * + * [3:2] bandwidth + * 00 = 20 MHz + * 01 = 40 MHz + * 10 = 80 MHz + * 11 = 160 MHz + * + * [4] GI (HT/VHT) / HE GI LSB + * HT/VHT: + * 0 = LGI + * 1 = SGI + * + * HE: + * used as GI[0] + * + * [5] STBC + * 0 = no STBC + * 1 = STBC enabled + * + * [6] LDPC + * 0 = BCC + * 1 = LDPC + * + * [7] HE GI MSB + * + * HE GI encoding (combined from bit7:bit4): + * GI[1:0] = {bit7, bit4} + * + * 00 = 0.8 us + * 01 = 1.6 us + * 10 = 3.2 us + * 11 = reserved / undefined + */ + u8 rate_info; + u8 reserved[3]; + u8 flags; + u8 antenna; + /* toa_tod_tstamps: [31:0] ToA, [63:32] ToD (ns). */ + __le64 toa_tod_tstamps; + /* rx info */ + __le32 rx_info; + /* Reserved */ + u8 reserved3[8]; + u8 ta_mac[6]; + u8 reserved4[2]; +} __packed; + +struct radiotap_timestamp { + /* device timestamp */ + u64 device_timestamp; + /* accuracy */ + u16 accuracy; + /* + * unit: + * 0 milliseconds, + * 1 microseconds, + * 2 nanoseconds, + * 3-15 reserved + */ + u8 unit : 4; + /* + * position: + * 0 first bit (or symbol containing it) of MPDU - matches TSFT field + * 1 signal acquisition at start of PLCP + * 2 end of PPDU + * 3 end of MPDU (after FCS) + * 4-14 reserved + * 15 unknown or vendor/OOB defined + */ + u8 position : 4; + /* + * flags + * 0x01 32-bit counter (high 32 bits are unused) + * 0x02 accuracy known + * 0xFC reserved + */ + u8 flags; +} __packed; + +struct rxpd_extra_info { + /* flags */ + u8 flags; + /* channel.flags */ + u16 channel_flags; + /* mcs.known */ + u8 mcs_known; + /* mcs.flags */ + u8 mcs_flags; + /* vht/he sig1 */ + u32 vht_he_sig1; + /* vht/he sig2 */ + u32 vht_he_sig2; + /* HE user idx */ + u32 user_idx; + /** timestamp */ + struct radiotap_timestamp timestamp; + /** PLCP CRC Failed */ + u8 plcp_crc_failed; + u8 rssi_dbm_a; + u8 rssi_dbm_b; +} __packed; + +struct uap_txpd { + u8 bss_type; + u8 bss_num; + __le16 tx_pkt_length; + __le16 tx_pkt_offset; + __le16 tx_pkt_type; + __le32 tx_control; + u8 priority; + u8 flags; + u8 pkt_delay_2ms; + u8 reserved1[2]; + u8 tx_token_id; + u8 reserved[2]; +} __packed; + +struct uap_rxpd { + u8 bss_type; + u8 bss_num; + __le16 rx_pkt_length; + __le16 rx_pkt_offset; + __le16 rx_pkt_type; + __le16 seq_num; + u8 priority; + u8 rx_rate; + s8 snr; + s8 nf; + u8 ht_info; + u8 reserved[3]; + u8 flags; +} __packed; + +struct nxpwifi_auth { + __le16 auth_alg; + __le16 auth_transaction; + __le16 status_code; + /* possibly followed by Challenge text */ + u8 variable[]; +} __packed; + +struct nxpwifi_ieee80211_mgmt { + __le16 frame_control; + __le16 duration; + u8 da[ETH_ALEN]; + u8 sa[ETH_ALEN]; + u8 bssid[ETH_ALEN]; + __le16 seq_ctrl; + u8 addr4[ETH_ALEN]; + struct nxpwifi_auth auth; +} __packed; + +struct nxpwifi_fw_chan_stats { + u8 chan_num; + u8 bandcfg; + u8 flags; + s8 noise; + __le16 total_bss; + __le16 cca_scan_dur; + __le16 cca_busy_dur; +} __packed; + +enum nxpwifi_chan_scan_mode_bitmasks { + NXPWIFI_PASSIVE_SCAN = BIT(0), + NXPWIFI_DISABLE_CHAN_FILT = BIT(1), + NXPWIFI_HIDDEN_SSID_REPORT = BIT(4), +}; + +struct nxpwifi_chan_scan_param_set { + u8 band_cfg; + u8 chan_number; + u8 chan_scan_mode_bmap; + __le16 min_scan_time; + __le16 max_scan_time; +} __packed; + +struct nxpwifi_ie_types_chan_list_param_set { + struct nxpwifi_ie_types_header header; + struct nxpwifi_chan_scan_param_set chan_scan_param[]; +} __packed; + +struct nxpwifi_ie_types_rxba_sync { + struct nxpwifi_ie_types_header header; + u8 mac[ETH_ALEN]; + u8 tid; + u8 reserved; + __le16 seq_num; + __le16 bitmap_len; + u8 bitmap[]; +} __packed; + +struct chan_band_param_set { + u8 radio_type; + u8 chan_number; +}; + +struct nxpwifi_ie_types_chan_band_list_param_set { + struct nxpwifi_ie_types_header header; + struct chan_band_param_set chan_band_param[]; +} __packed; + +struct nxpwifi_ie_types_rates_param_set { + struct nxpwifi_ie_types_header header; + u8 rates[]; +} __packed; + +struct nxpwifi_ie_types_ssid_param_set { + struct nxpwifi_ie_types_header header; + u8 ssid[]; +} __packed; + +struct nxpwifi_ie_types_host_mlme { + struct nxpwifi_ie_types_header header; + u8 host_mlme; +} __packed; + +struct nxpwifi_ie_types_num_probes { + struct nxpwifi_ie_types_header header; + __le16 num_probes; +} __packed; + +struct nxpwifi_ie_types_repeat_count { + struct nxpwifi_ie_types_header header; + __le16 repeat_count; +} __packed; + +struct nxpwifi_ie_types_min_rssi_threshold { + struct nxpwifi_ie_types_header header; + __le16 rssi_threshold; +} __packed; + +struct nxpwifi_ie_types_bgscan_start_later { + struct nxpwifi_ie_types_header header; + __le16 start_later; +} __packed; + +struct nxpwifi_ie_types_scan_chan_gap { + struct nxpwifi_ie_types_header header; + /* time gap in TUs to be used between two consecutive channels scan */ + __le16 chan_gap; +} __packed; + +struct nxpwifi_ie_types_random_mac { + struct nxpwifi_ie_types_header header; + u8 mac[ETH_ALEN]; +} __packed; + +struct nxpwifi_ietypes_chanstats { + struct nxpwifi_ie_types_header header; + struct nxpwifi_fw_chan_stats chanstats[]; +} __packed; + +struct nxpwifi_ie_types_wildcard_ssid_params { + struct nxpwifi_ie_types_header header; + u8 max_ssid_length; + u8 ssid[]; +} __packed; + +#define TSF_DATA_SIZE 8 +struct nxpwifi_ie_types_tsf_timestamp { + struct nxpwifi_ie_types_header header; + u8 tsf_data[]; +} __packed; + +struct nxpwifi_cf_param_set { + u8 cfp_cnt; + u8 cfp_period; + __le16 cfp_max_duration; + __le16 cfp_duration_remaining; +} __packed; + +struct nxpwifi_ibss_param_set { + __le16 atim_window; +} __packed; + +struct nxpwifi_ie_types_ss_param_set { + struct nxpwifi_ie_types_header header; + union { + struct nxpwifi_cf_param_set cf_param_set[1]; + struct nxpwifi_ibss_param_set ibss_param_set[1]; + } cf_ibss; +} __packed; + +struct nxpwifi_fh_param_set { + __le16 dwell_time; + u8 hop_set; + u8 hop_pattern; + u8 hop_index; +} __packed; + +struct nxpwifi_ds_param_set { + u8 current_chan; +} __packed; + +struct nxpwifi_ie_types_phy_param_set { + struct nxpwifi_ie_types_header header; + union { + struct nxpwifi_fh_param_set fh_param_set[1]; + struct nxpwifi_ds_param_set ds_param_set[1]; + } fh_ds; +} __packed; + +struct nxpwifi_ie_types_auth_type { + struct nxpwifi_ie_types_header header; + __le16 auth_type; +} __packed; + +struct nxpwifi_ie_types_vendor_param_set { + struct nxpwifi_ie_types_header header; + u8 ie[NXPWIFI_MAX_VSIE_LEN]; +}; + +#define NXPWIFI_AUTHTYPE_SAE 6 + +struct nxpwifi_ie_types_sae_pwe_mode { + struct nxpwifi_ie_types_header header; + u8 pwe[]; +} __packed; + +struct nxpwifi_ie_types_rsn_param_set { + struct nxpwifi_ie_types_header header; + u8 rsn_ie[]; +} __packed; + +#define KEYPARAMSET_FIXED_LEN 6 + +#define IGTK_PN_LEN 8 + +struct nxpwifi_cmac_param { + u8 ipn[IGTK_PN_LEN]; + u8 key[WLAN_KEY_LEN_AES_CMAC]; +} __packed; + +struct nxpwifi_wep_param { + __le16 key_len; + u8 key[WLAN_KEY_LEN_WEP104]; +} __packed; + +struct nxpwifi_tkip_param { + u8 pn[WPA_PN_SIZE]; + __le16 key_len; + u8 key[WLAN_KEY_LEN_TKIP]; +} __packed; + +struct nxpwifi_aes_param { + u8 pn[WPA_PN_SIZE]; + __le16 key_len; + u8 key[WLAN_KEY_LEN_CCMP_256]; +} __packed; + +struct nxpwifi_cmac_aes_param { + u8 ipn[IGTK_PN_LEN]; + __le16 key_len; + u8 key[WLAN_KEY_LEN_AES_CMAC]; +} __packed; + +struct nxpwifi_gmac_aes_param { + u8 ipn[IGTK_PN_LEN]; + __le16 key_len; + u8 key[WLAN_KEY_LEN_BIP_GMAC_256]; +} __packed; + +struct nxpwifi_ie_type_key_param_set { + __le16 type; + __le16 len; + u8 mac_addr[ETH_ALEN]; + u8 key_idx; + u8 key_type; + __le16 key_info; + union { + struct nxpwifi_wep_param wep; + struct nxpwifi_tkip_param tkip; + struct nxpwifi_aes_param aes; + struct nxpwifi_cmac_aes_param cmac_aes; + struct nxpwifi_gmac_aes_param gmac_aes; + } key_params; +} __packed; + +struct host_cmd_ds_802_11_key_material { + __le16 action; + struct nxpwifi_ie_type_key_param_set key_param_set; +} __packed; + +struct host_cmd_ds_gen { + __le16 command; + __le16 size; + __le16 seq_num; + __le16 result; +}; + +#define S_DS_GEN sizeof(struct host_cmd_ds_gen) + +enum sleep_resp_ctrl { + RESP_NOT_NEEDED = 0, + RESP_NEEDED, +}; + +struct nxpwifi_ps_param { + __le16 null_pkt_interval; + __le16 multiple_dtims; + __le16 bcn_miss_timeout; + __le16 local_listen_interval; + __le16 reserved; + __le16 mode; + __le16 delay_to_ps; +} __packed; + +#define HS_DEF_WAKE_INTERVAL 100 +#define HS_DEF_INACTIVITY_TIMEOUT 50 + +struct nxpwifi_ps_param_in_hs { + struct nxpwifi_ie_types_header header; + __le32 hs_wake_int; + __le32 hs_inact_timeout; +} __packed; + +#define BITMAP_AUTO_DS 0x01 +#define BITMAP_STA_PS 0x10 + +struct nxpwifi_ie_types_auto_ds_param { + struct nxpwifi_ie_types_header header; + __le16 deep_sleep_timeout; +} __packed; + +struct nxpwifi_ie_types_ps_param { + struct nxpwifi_ie_types_header header; + struct nxpwifi_ps_param param; +} __packed; + +struct host_cmd_ds_802_11_ps_mode_enh { + __le16 action; + + union { + struct nxpwifi_ps_param opt_ps; + __le16 ps_bitmap; + } params; +} __packed; + +enum API_VER_ID { + KEY_API_VER_ID = 1, + FW_API_VER_ID = 2, + UAP_FW_API_VER_ID = 3, + CHANRPT_API_VER_ID = 4, + FW_HOTFIX_VER_ID = 5, +}; + +struct hw_spec_api_rev { + struct nxpwifi_ie_types_header header; + __le16 api_id; + u8 major_ver; + u8 minor_ver; +} __packed; + +struct hw_spec_max_conn { + struct nxpwifi_ie_types_header header; + u8 reserved; + u8 max_sta_conn; +} __packed; + +struct hw_spec_extension { + struct nxpwifi_ie_types_header header; + u8 ext_id; + u8 tlv[]; +} __packed; + +/* HE MAC Capabilities Information field BIT 1 for TWT Req */ +#define HE_MAC_CAP_TWT_REQ_SUPPORT BIT(1) +/* HE MAC Capabilities Information field BIT 2 for TWT Resp*/ +#define HE_MAC_CAP_TWT_RESP_SUPPORT BIT(2) + +struct nxpwifi_ie_types_he_cap { + struct nxpwifi_ie_types_header header; + u8 ext_id; + u8 he_mac_cap[6]; + u8 he_phy_cap[11]; + __le16 rx_mcs_80; + __le16 tx_mcs_80; + __le16 rx_mcs_160; + __le16 tx_mcs_160; + __le16 rx_mcs_80p80; + __le16 tx_mcs_80p80; + u8 val[20]; +} __packed; + +struct nxpwifi_ie_types_he_op { + struct nxpwifi_ie_types_header header; + u8 ext_id; + __le16 he_op_param1; + u8 he_op_param2; + u8 bss_color_info; + __le16 basic_he_mcs_nss; + u8 option[9]; +} __packed; + +struct hw_spec_secure_boot_uuid { + struct nxpwifi_ie_types_header header; + __le64 uuid_lo; + __le64 uuid_hi; +} __packed; + +struct hw_spec_fw_cap_info { + struct nxpwifi_ie_types_header header; + __le32 fw_cap_info; + __le32 fw_cap_ext; +} __packed; + +struct host_cmd_ds_get_hw_spec { + __le16 hw_if_version; + __le16 version; + __le16 reserved; + __le16 num_of_mcast_adr; + u8 permanent_addr[ETH_ALEN]; + __le16 region_code; + __le16 number_of_antenna; + __le32 fw_release_number; + __le32 hw_dev_cap; + __le32 reserved_1; + __le32 reserved_2; + __le32 fw_cap_info; + __le32 dot_11n_dev_cap; + u8 dev_mcs_support; + __le16 mp_end_port; /* SDIO only, reserved for other interfaces */ + __le16 mgmt_buf_count; /* mgmt element buffer count */ + __le32 reserved_3; + __le32 reserved_4; + __le32 dot_11ac_dev_cap; + __le32 dot_11ac_mcs_support; + u8 tlv[]; +} __packed; + +struct host_cmd_ds_802_11_rssi_info { + __le16 action; + __le16 ndata; + __le16 nbcn; + __le16 reserved[9]; + long long reserved_1; +} __packed; + +struct host_cmd_ds_802_11_rssi_info_rsp { + __le16 action; + __le16 ndata; + __le16 nbcn; + __le16 data_rssi_last; + __le16 data_nf_last; + __le16 data_rssi_avg; + __le16 data_nf_avg; + __le16 bcn_rssi_last; + __le16 bcn_nf_last; + __le16 bcn_rssi_avg; + __le16 bcn_nf_avg; + long long tsf_bcn; +} __packed; + +struct host_cmd_ds_802_11_mac_address { + __le16 action; + u8 mac_addr[ETH_ALEN]; +} __packed; + +struct host_cmd_ds_mac_control { + __le32 action; +}; + +struct host_cmd_ds_mac_multicast_adr { + __le16 action; + __le16 num_of_adrs; + u8 mac_list[NXPWIFI_MAX_MULTICAST_LIST_SIZE][ETH_ALEN]; +} __packed; + +struct host_cmd_ds_802_11_deauthenticate { + u8 mac_addr[ETH_ALEN]; + __le16 reason_code; +} __packed; + +struct host_cmd_ds_802_11_associate { + u8 peer_sta_addr[ETH_ALEN]; + __le16 cap_info_bitmap; + __le16 listen_interval; + __le16 beacon_period; + u8 dtim_period; +} __packed; + +struct ieee_types_assoc_rsp { + __le16 cap_info_bitmap; + __le16 status_code; + __le16 a_id; + u8 ie_buffer[]; +} __packed; + +struct host_cmd_ds_802_11_associate_rsp { + struct ieee_types_assoc_rsp assoc_rsp; +} __packed; + +struct ieee_types_cf_param_set { + u8 element_id; + u8 len; + u8 cfp_cnt; + u8 cfp_period; + __le16 cfp_max_duration; + __le16 cfp_duration_remaining; +} __packed; + +struct ieee_types_fh_param_set { + u8 element_id; + u8 len; + __le16 dwell_time; + u8 hop_set; + u8 hop_pattern; + u8 hop_index; +} __packed; + +struct ieee_types_ds_param_set { + u8 element_id; + u8 len; + u8 current_chan; +} __packed; + +union ieee_types_phy_param_set { + struct ieee_types_fh_param_set fh_param_set; + struct ieee_types_ds_param_set ds_param_set; +} __packed; + +struct ieee_types_oper_mode_ntf { + u8 element_id; + u8 len; + u8 oper_mode; +} __packed; + +struct host_cmd_ds_802_11_get_log { + __le32 mcast_tx_frame; + __le32 failed; + __le32 retry; + __le32 multi_retry; + __le32 frame_dup; + __le32 rts_success; + __le32 rts_failure; + __le32 ack_failure; + __le32 rx_frag; + __le32 mcast_rx_frame; + __le32 fcs_error; + __le32 tx_frame; + __le32 reserved; + __le32 wep_icv_err_cnt[4]; + __le32 bcn_rcv_cnt; + __le32 bcn_miss_cnt; +} __packed; + +/* Enumeration for rate format */ +enum nxpwifi_rate_format { + NXPWIFI_RATE_FORMAT_LG = 0, + NXPWIFI_RATE_FORMAT_HT, + NXPWIFI_RATE_FORMAT_VHT, + NXPWIFI_RATE_FORMAT_HE, + NXPWIFI_RATE_FORMAT_AUTO = 0xFF, +}; + +struct host_cmd_ds_tx_rate_query { + u8 tx_rate; + /* + * Tx Rate Info: For 802.11 AC cards + * + * [Bit 0-1] tx rate format: LG = 0, HT = 1, VHT = 2 + * [Bit 2-3] HT/VHT Bandwidth: BW20 = 0, BW40 = 1, BW80 = 2, BW160 = 3 + * [Bit 4] HT/VHT Guard Interval: LGI = 0, SGI = 1 + * + * For non-802.11 AC cards + * Ht Info [Bit 0] RxRate format: LG=0, HT=1 + * [Bit 1] HT Bandwidth: BW20 = 0, BW40 = 1 + * [Bit 2] HT Guard Interval: LGI = 0, SGI = 1 + */ + u8 ht_info; +} __packed; + +struct nxpwifi_tx_pause_tlv { + struct nxpwifi_ie_types_header header; + u8 peermac[ETH_ALEN]; + u8 tx_pause; + u8 pkt_cnt; +} __packed; + +enum host_sleep_action { + HS_CONFIGURE = 0x0001, + HS_ACTIVATE = 0x0002, +}; + +struct nxpwifi_hs_config_param { + __le32 conditions; + u8 gpio; + u8 gap; +} __packed; + +struct hs_activate_param { + __le16 resp_ctrl; +} __packed; + +struct host_cmd_ds_802_11_hs_cfg_enh { + __le16 action; + + union { + struct nxpwifi_hs_config_param hs_config; + struct hs_activate_param hs_activate; + } params; +} __packed; + +enum SNMP_MIB_INDEX { + OP_RATE_SET_I = 1, + DTIM_PERIOD_I = 3, + RTS_THRESH_I = 5, + SHORT_RETRY_LIM_I = 6, + LONG_RETRY_LIM_I = 7, + FRAG_THRESH_I = 8, + DOT11D_I = 9, + DOT11H_I = 10, +}; + +enum nxpwifi_assocmd_failurepoint { + NXPWIFI_ASSOC_CMD_SUCCESS = 0, + NXPWIFI_ASSOC_CMD_FAILURE_ASSOC, + NXPWIFI_ASSOC_CMD_FAILURE_AUTH, + NXPWIFI_ASSOC_CMD_FAILURE_JOIN +}; + +#define MAX_SNMP_BUF_SIZE 128 + +struct host_cmd_ds_802_11_snmp_mib { + __le16 query_type; + __le16 oid; + __le16 buf_size; + u8 value[]; +} __packed; + +struct nxpwifi_rate_scope { + __le16 type; + __le16 length; + __le16 hr_dsss_rate_bitmap; + __le16 ofdm_rate_bitmap; + __le16 ht_mcs_rate_bitmap[8]; + __le16 vht_mcs_rate_bitmap[8]; +} __packed; + +struct nxpwifi_rate_drop_pattern { + __le16 type; + __le16 length; + __le32 rate_drop_mode; +} __packed; + +struct host_cmd_ds_tx_rate_cfg { + __le16 action; + __le16 cfg_index; +} __packed; + +struct nxpwifi_power_group { + u8 modulation_class; + u8 first_rate_code; + u8 last_rate_code; + s8 power_step; + s8 power_min; + s8 power_max; + u8 ht_bandwidth; + u8 reserved; +} __packed; + +struct nxpwifi_types_power_group { + __le16 type; + __le16 length; +} __packed; + +struct host_cmd_ds_txpwr_cfg { + __le16 action; + __le16 cfg_index; + __le32 mode; +} __packed; + +struct host_cmd_ds_rf_tx_pwr { + __le16 action; + __le16 cur_level; + u8 max_power; + u8 min_power; +} __packed; + +struct host_cmd_ds_rf_ant_mimo { + __le16 action_tx; + __le16 tx_ant_mode; + __le16 action_rx; + __le16 rx_ant_mode; +} __packed; + +struct host_cmd_ds_rf_ant_siso { + __le16 action; + __le16 ant_mode; +} __packed; + +#define BAND_CFG_CHAN_BAND_MASK 0x03 +#define BAND_CFG_CHAN_BAND_SHIFT_BIT 0 +#define BAND_CFG_CHAN_WIDTH_MASK 0x0C +#define BAND_CFG_CHAN_WIDTH_SHIFT_BIT 2 +#define BAND_CFG_CHAN2_OFFSET_MASK 0x30 +#define BAND_CFG_CHAN2_SHIFT_BIT 4 + +struct nxpwifi_chan_desc { + __le16 start_freq; + u8 band_cfg; + u8 chan_num; +} __packed; + +struct host_cmd_ds_chan_rpt_req { + struct nxpwifi_chan_desc chan_desc; + __le32 msec_dwell_time; +} __packed; + +struct host_cmd_ds_chan_rpt_event { + __le32 result; + __le64 start_tsf; + __le32 duration; + u8 tlvbuf[]; +} __packed; + +struct host_cmd_sdio_sp_rx_aggr_cfg { + u8 action; + u8 enable; + __le16 block_size; +} __packed; + +struct nxpwifi_fixed_bcn_param { + __le64 timestamp; + __le16 beacon_period; + __le16 cap_info_bitmap; +} __packed; + +struct nxpwifi_event_scan_result { + __le16 event_id; + u8 bss_index; + u8 bss_type; + u8 more_event; + u8 reserved[3]; + __le16 buf_size; + u8 num_of_set; +} __packed; + +struct tx_status_event { + u8 packet_type; + u8 tx_token_id; + u8 status; +} __packed; + +#define NXPWIFI_USER_SCAN_CHAN_MAX 50 + +#define NXPWIFI_MAX_SSID_LIST_LENGTH 10 + +struct nxpwifi_scan_cmd_config { + /* BSS mode to be sent in the firmware command */ + u8 bss_mode; + + /* Specific BSSID used to filter scan results in the firmware */ + u8 specific_bssid[ETH_ALEN]; + + /* Length of TLVs sent in command starting at tlvBuffer */ + u32 tlv_buf_len; + + /* + * SSID TLV(s) and ChanList TLVs to be sent in the firmware command + * + * TLV_TYPE_CHANLIST, nxpwifi_ie_types_chan_list_param_set + * WLAN_EID_SSID, nxpwifi_ie_types_ssid_param_set + */ + u8 tlv_buf[]; /* SSID TLV(s) and ChanList TLVs are stored here */ +} __packed; + +struct nxpwifi_user_scan_chan { + u8 chan_number; + u8 radio_type; + u8 scan_type; + u8 reserved; + u32 scan_time; +} __packed; + +struct nxpwifi_user_scan_cfg { + /* BSS mode to be sent in the firmware command */ + u8 bss_mode; + /* Configure the number of probe requests for active chan scans */ + u8 num_probes; + u8 reserved; + /* BSSID filter sent in the firmware command to limit the results */ + u8 specific_bssid[ETH_ALEN]; + /* SSID filter list used in the firmware to limit the scan results */ + struct cfg80211_ssid *ssid_list; + u8 num_ssids; + /* Variable number (fixed maximum) of channels to scan up */ + struct nxpwifi_user_scan_chan chan_list[NXPWIFI_USER_SCAN_CHAN_MAX]; + u16 scan_chan_gap; + u8 random_mac[ETH_ALEN]; +} __packed; + +#define NXPWIFI_BG_SCAN_CHAN_MAX 38 +#define NXPWIFI_BSS_MODE_INFRA 1 +#define NXPWIFI_BGSCAN_ACT_GET 0x0000 +#define NXPWIFI_BGSCAN_ACT_SET 0x0001 +#define NXPWIFI_BGSCAN_ACT_SET_ALL 0xff01 +/** ssid match */ +#define NXPWIFI_BGSCAN_SSID_MATCH 0x0001 +/** ssid match and RSSI exceeded */ +#define NXPWIFI_BGSCAN_SSID_RSSI_MATCH 0x0004 +/**wait for all channel scan to complete to report scan result*/ +#define NXPWIFI_BGSCAN_WAIT_ALL_CHAN_DONE 0x80000000 + +struct nxpwifi_bg_scan_cfg { + u16 action; + u8 enable; + u8 bss_type; + u8 chan_per_scan; + u32 scan_interval; + u32 report_condition; + u8 num_probes; + u8 rssi_threshold; + u8 snr_threshold; + u16 repeat_count; + u16 start_later; + struct cfg80211_match_set *ssid_list; + u8 num_ssids; + struct nxpwifi_user_scan_chan chan_list[NXPWIFI_BG_SCAN_CHAN_MAX]; + u16 scan_chan_gap; +} __packed; + +struct ie_body { + u8 grp_key_oui[4]; + u8 ptk_cnt[2]; + u8 ptk_body[4]; +} __packed; + +struct host_cmd_ds_802_11_scan { + u8 bss_mode; + u8 bssid[ETH_ALEN]; + u8 tlv_buffer[]; +} __packed; + +struct host_cmd_ds_802_11_scan_rsp { + __le16 bss_descript_size; + u8 number_of_sets; + u8 bss_desc_and_tlv_buffer[]; +} __packed; + +struct host_cmd_ds_802_11_scan_ext { + u32 reserved; + u8 tlv_buffer[]; +} __packed; + +struct nxpwifi_ie_types_bss_mode { + struct nxpwifi_ie_types_header header; + u8 bss_mode; +} __packed; + +struct nxpwifi_ie_types_scan_rsp { + struct nxpwifi_ie_types_header header; + u8 bssid[ETH_ALEN]; + u8 frame_body[]; +} __packed; + +struct nxpwifi_ie_types_scan_inf { + struct nxpwifi_ie_types_header header; + __le16 rssi; + __le16 anpi; + u8 cca_busy_fraction; + u8 radio_type; + u8 channel; + u8 reserved; + __le64 tsf; +} __packed; + +struct host_cmd_ds_802_11_bg_scan_config { + __le16 action; + u8 enable; + u8 bss_type; + u8 chan_per_scan; + u8 reserved; + __le16 reserved1; + __le32 scan_interval; + __le32 reserved2; + __le32 report_condition; + __le16 reserved3; + u8 tlv[]; +} __packed; + +struct host_cmd_ds_802_11_bg_scan_query { + u8 flush; +} __packed; + +struct host_cmd_ds_802_11_bg_scan_query_rsp { + __le32 report_condition; + struct host_cmd_ds_802_11_scan_rsp scan_resp; +} __packed; + +struct nxpwifi_ietypes_domain_code { + struct nxpwifi_ie_types_header header; + u8 domain_code; + u8 reserved; +} __packed; + +struct nxpwifi_ietypes_domain_param_set { + struct nxpwifi_ie_types_header header; + u8 country_code[IEEE80211_COUNTRY_STRING_LEN]; + struct ieee80211_country_ie_triplet triplet[]; +} __packed; + +struct host_cmd_ds_802_11d_domain_info { + __le16 action; + struct nxpwifi_ietypes_domain_param_set domain; +} __packed; + +struct host_cmd_ds_802_11d_domain_info_rsp { + __le16 action; + struct nxpwifi_ietypes_domain_param_set domain; +} __packed; + +struct host_cmd_ds_11n_addba_req { + u8 add_req_result; + u8 peer_mac_addr[ETH_ALEN]; + u8 dialog_token; + __le16 block_ack_param_set; + __le16 block_ack_tmo; + __le16 ssn; +} __packed; + +struct host_cmd_ds_11n_addba_rsp { + u8 add_rsp_result; + u8 peer_mac_addr[ETH_ALEN]; + u8 dialog_token; + __le16 status_code; + __le16 block_ack_param_set; + __le16 block_ack_tmo; + __le16 ssn; +} __packed; + +struct host_cmd_ds_11n_delba { + u8 del_result; + u8 peer_mac_addr[ETH_ALEN]; + __le16 del_ba_param_set; + __le16 reason_code; + u8 reserved; +} __packed; + +struct host_cmd_ds_11n_batimeout { + u8 tid; + u8 peer_mac_addr[ETH_ALEN]; + u8 origninator; +} __packed; + +struct host_cmd_ds_11n_cfg { + __le16 action; + __le16 ht_tx_cap; + __le16 ht_tx_info; + __le16 misc_config; /* Needed for 802.11AC cards only */ +} __packed; + +struct host_cmd_ds_txbuf_cfg { + __le16 action; + __le16 buff_size; + __le16 mp_end_port; /* SDIO only, reserved for other interfaces */ + __le16 reserved3; +} __packed; + +struct host_cmd_ds_amsdu_aggr_ctrl { + __le16 action; + __le16 enable; + __le16 curr_buf_size; +} __packed; + +struct host_cmd_ds_sta_deauth { + u8 mac[ETH_ALEN]; + __le16 reason; +} __packed; + +struct nxpwifi_ie_types_sta_info { + struct nxpwifi_ie_types_header header; + u8 mac[ETH_ALEN]; + u8 power_mfg_status; + s8 rssi; +}; + +struct host_cmd_ds_sta_list { + __le16 sta_count; + u8 tlv[]; +} __packed; + +struct nxpwifi_ie_types_pwr_capability { + struct nxpwifi_ie_types_header header; + s8 min_pwr; + s8 max_pwr; +}; + +struct nxpwifi_ie_types_local_pwr_constraint { + struct nxpwifi_ie_types_header header; + u8 chan; + u8 constraint; +}; + +struct nxpwifi_ie_types_wmm_param_set { + struct nxpwifi_ie_types_header header; + u8 wmm_ie[]; +} __packed; + +struct nxpwifi_ie_types_mgmt_frame { + struct nxpwifi_ie_types_header header; + __le16 frame_control; + u8 frame_contents[]; +}; + +struct nxpwifi_ie_types_wmm_queue_status { + struct nxpwifi_ie_types_header header; + u8 queue_index; + u8 disabled; + __le16 medium_time; + u8 flow_required; + u8 flow_created; + u32 reserved; +}; + +struct ieee_types_wmm_info { + /* + * WMM Info element - Vendor Specific Header: + * element_id [221/0xdd] + * Len [7] + * Oui [00:50:f2] + * OuiType [2] + * OuiSubType [0] + * Version [1] + */ + struct ieee80211_vendor_ie vend_hdr; + u8 oui_subtype; + u8 version; + + u8 qos_info_bitmap; +} __packed; + +struct host_cmd_ds_wmm_get_status { + u8 queue_status_tlv[sizeof(struct nxpwifi_ie_types_wmm_queue_status) * + IEEE80211_NUM_ACS]; + u8 wmm_param_tlv[sizeof(struct ieee80211_wmm_param_ie) + 2]; +} __packed; + +struct nxpwifi_wmm_ac_status { + u8 disabled; + u8 flow_required; + u8 flow_created; +}; + +struct nxpwifi_ie_types_htcap { + struct nxpwifi_ie_types_header header; + struct ieee80211_ht_cap ht_cap; +} __packed; + +struct nxpwifi_ie_types_vhtcap { + struct nxpwifi_ie_types_header header; + struct ieee80211_vht_cap vht_cap; +} __packed; + +struct nxpwifi_ie_types_aid { + struct nxpwifi_ie_types_header header; + __le16 aid; +} __packed; + +struct nxpwifi_ie_types_oper_mode_ntf { + struct nxpwifi_ie_types_header header; + u8 oper_mode; +} __packed; + +/* VHT Operations element */ +struct nxpwifi_ie_types_vht_oper { + struct nxpwifi_ie_types_header header; + u8 chan_width; + u8 chan_center_freq_1; + u8 chan_center_freq_2; + /* Basic MCS set map, each 2 bits stands for a NSS */ + __le16 basic_mcs_map; +} __packed; + +struct nxpwifi_ie_types_wmmcap { + struct nxpwifi_ie_types_header header; + struct nxpwifi_types_wmm_info wmm_info; +} __packed; + +struct nxpwifi_ie_types_htinfo { + struct nxpwifi_ie_types_header header; + struct ieee80211_ht_operation ht_oper; +} __packed; + +struct nxpwifi_ie_types_2040bssco { + struct nxpwifi_ie_types_header header; + u8 bss_co_2040; +} __packed; + +struct nxpwifi_ie_types_extcap { + struct nxpwifi_ie_types_header header; + u8 ext_capab[]; +} __packed; + +struct host_cmd_ds_mem_access { + __le16 action; + __le16 reserved; + __le32 addr; + __le32 value; +} __packed; + +struct nxpwifi_ie_types_qos_info { + struct nxpwifi_ie_types_header header; + u8 qos_info; +} __packed; + +struct host_cmd_ds_mac_reg_access { + __le16 action; + __le16 offset; + __le32 value; +} __packed; + +struct host_cmd_ds_bbp_reg_access { + __le16 action; + __le16 offset; + u8 value; + u8 reserved[3]; +} __packed; + +struct host_cmd_ds_rf_reg_access { + __le16 action; + __le16 offset; + u8 value; + u8 reserved[3]; +} __packed; + +struct host_cmd_ds_pmic_reg_access { + __le16 action; + __le16 offset; + u8 value; + u8 reserved[3]; +} __packed; + +struct host_cmd_ds_802_11_eeprom_access { + __le16 action; + + __le16 offset; + __le16 byte_count; + u8 value; +} __packed; + +struct nxpwifi_assoc_event { + u8 sta_addr[ETH_ALEN]; + __le16 type; + __le16 len; + __le16 frame_control; + __le16 cap_info; + __le16 listen_interval; + u8 data[]; +} __packed; + +struct host_cmd_ds_sys_config { + __le16 action; + u8 tlv[]; +}; + +struct host_cmd_11ac_vht_cfg { + __le16 action; + u8 band_config; + u8 misc_config; + __le32 cap_info; + __le32 mcs_tx_set; + __le32 mcs_rx_set; +} __packed; + +struct host_cmd_tlv_akmp { + struct nxpwifi_ie_types_header header; + __le16 key_mgmt; + __le16 key_mgmt_operation; +} __packed; + +struct host_cmd_tlv_pwk_cipher { + struct nxpwifi_ie_types_header header; + __le16 proto; + u8 cipher; + u8 reserved; +} __packed; + +struct host_cmd_tlv_gwk_cipher { + struct nxpwifi_ie_types_header header; + u8 cipher; + u8 reserved; +} __packed; + +struct host_cmd_tlv_passphrase { + struct nxpwifi_ie_types_header header; + u8 passphrase[]; +} __packed; + +struct host_cmd_tlv_wep_key { + struct nxpwifi_ie_types_header header; + u8 key_index; + u8 is_default; + u8 key[]; +}; + +struct host_cmd_tlv_auth_type { + struct nxpwifi_ie_types_header header; + u8 auth_type; + u8 pwe_derivation; + u8 transition_disable; +} __packed; + +struct host_cmd_tlv_encrypt_protocol { + struct nxpwifi_ie_types_header header; + __le16 proto; +} __packed; + +struct host_cmd_tlv_ssid { + struct nxpwifi_ie_types_header header; + u8 ssid[]; +} __packed; + +struct host_cmd_tlv_rates { + struct nxpwifi_ie_types_header header; + u8 rates[]; +} __packed; + +struct nxpwifi_ie_types_bssid_list { + struct nxpwifi_ie_types_header header; + u8 bssid[ETH_ALEN]; +} __packed; + +struct host_cmd_tlv_bcast_ssid { + struct nxpwifi_ie_types_header header; + u8 bcast_ctl; +} __packed; + +struct host_cmd_tlv_beacon_period { + struct nxpwifi_ie_types_header header; + __le16 period; +} __packed; + +struct host_cmd_tlv_dtim_period { + struct nxpwifi_ie_types_header header; + u8 period; +} __packed; + +struct host_cmd_tlv_frag_threshold { + struct nxpwifi_ie_types_header header; + __le16 frag_thr; +} __packed; + +struct host_cmd_tlv_rts_threshold { + struct nxpwifi_ie_types_header header; + __le16 rts_thr; +} __packed; + +struct host_cmd_tlv_retry_limit { + struct nxpwifi_ie_types_header header; + u8 limit; +} __packed; + +struct host_cmd_tlv_mac_addr { + struct nxpwifi_ie_types_header header; + u8 mac_addr[ETH_ALEN]; +} __packed; + +struct host_cmd_tlv_channel_band { + struct nxpwifi_ie_types_header header; + u8 band_config; + u8 channel; +} __packed; + +struct host_cmd_tlv_ageout_timer { + struct nxpwifi_ie_types_header header; + __le32 sta_ao_timer; +} __packed; + +struct host_cmd_tlv_power_constraint { + struct nxpwifi_ie_types_header header; + u8 constraint; +} __packed; + +struct nxpwifi_ie_types_btcoex_scan_time { + struct nxpwifi_ie_types_header header; + u8 coex_scan; + u8 reserved; + __le16 min_scan_time; + __le16 max_scan_time; +} __packed; + +struct nxpwifi_ie_types_btcoex_aggr_win_size { + struct nxpwifi_ie_types_header header; + u8 coex_win_size; + u8 tx_win_size; + u8 rx_win_size; + u8 reserved; +} __packed; + +struct nxpwifi_ie_types_robust_coex { + struct nxpwifi_ie_types_header header; + __le32 mode; +} __packed; + +#define NXPWIFI_VERSION_STR_LENGTH 128 + +struct host_cmd_ds_version_ext { + u8 version_str_sel; + char version_str[NXPWIFI_VERSION_STR_LENGTH]; +} __packed; + +struct host_cmd_ds_mgmt_frame_reg { + __le16 action; + __le32 mask; +} __packed; + +struct host_cmd_ds_remain_on_chan { + __le16 action; + u8 status; + u8 reserved; + u8 band_cfg; + u8 channel; + __le32 duration; +} __packed; + +struct host_cmd_ds_802_11_ibss_status { + __le16 action; + __le16 enable; + u8 bssid[ETH_ALEN]; + __le16 beacon_interval; + __le16 atim_window; + __le16 use_g_rate_protect; +} __packed; + +struct nxpwifi_fw_mef_entry { + u8 mode; + u8 action; + __le16 exprsize; + u8 expr[]; +} __packed; + +struct host_cmd_ds_mef_cfg { + __le32 criteria; + __le16 num_entries; + u8 mef_entry_data[]; +} __packed; + +#define CONNECTION_TYPE_INFRA 0 +#define CONNECTION_TYPE_AP 2 + +struct host_cmd_ds_set_bss_mode { + u8 con_type; +} __packed; + +struct host_cmd_ds_pcie_details { + /* TX buffer descriptor ring address */ + __le32 txbd_addr_lo; + __le32 txbd_addr_hi; + /* TX buffer descriptor ring count */ + __le32 txbd_count; + + /* RX buffer descriptor ring address */ + __le32 rxbd_addr_lo; + __le32 rxbd_addr_hi; + /* RX buffer descriptor ring count */ + __le32 rxbd_count; + + /* Event buffer descriptor ring address */ + __le32 evtbd_addr_lo; + __le32 evtbd_addr_hi; + /* Event buffer descriptor ring count */ + __le32 evtbd_count; + + /* Sleep cookie buffer physical address */ + __le32 sleep_cookie_addr_lo; + __le32 sleep_cookie_addr_hi; +} __packed; + +struct nxpwifi_ie_types_rssi_threshold { + struct nxpwifi_ie_types_header header; + u8 abs_value; + u8 evt_freq; +} __packed; + +#define NXPWIFI_DFS_REC_HDR_LEN 8 +#define NXPWIFI_DFS_REC_HDR_NUM 10 +#define NXPWIFI_BIN_COUNTER_LEN 7 + +struct nxpwifi_radar_det_event { + __le32 detect_count; + u8 reg_domain; /*1=fcc, 2=etsi, 3=mic*/ + u8 det_type; /*0=none, 1=pw(chirp), 2=pri(radar)*/ + __le16 pw_chirp_type; + u8 pw_chirp_idx; + u8 pw_value; + u8 pri_radar_type; + u8 pri_bincnt; + u8 bin_counter[NXPWIFI_BIN_COUNTER_LEN]; + u8 num_dfs_records; + u8 dfs_record_hdr[NXPWIFI_DFS_REC_HDR_NUM][NXPWIFI_DFS_REC_HDR_LEN]; + __le32 passed; +} __packed; + +struct nxpwifi_ie_types_multi_chan_info { + struct nxpwifi_ie_types_header header; + __le16 status; + u8 tlv_buffer[]; +} __packed; + +struct nxpwifi_ie_types_mc_group_info { + struct nxpwifi_ie_types_header header; + u8 chan_group_id; + u8 chan_buf_weight; + u8 band_config; + u8 chan_num; + __le32 chan_time; + __le32 reserved; + union { + u8 sdio_func_num; + u8 usb_ep_num; + } hid_num; + u8 intf_num; + u8 bss_type_numlist[]; +} __packed; + +#define MEAS_RPT_MAP_RADAR_MASK 0x08 +#define MEAS_RPT_MAP_RADAR_SHIFT_BIT 3 + +struct nxpwifi_ie_types_chan_rpt_data { + struct nxpwifi_ie_types_header header; + u8 meas_rpt_map; +} __packed; + +struct host_cmd_ds_802_11_subsc_evt { + __le16 action; + __le16 events; +} __packed; + +struct chan_switch_result { + u8 cur_chan; + u8 status; + u8 reason; +} __packed; + +struct nxpwifi_ie { + __le16 ie_index; + __le16 mgmt_subtype_mask; + __le16 ie_length; + u8 ie_buffer[IEEE_MAX_IE_SIZE]; +} __packed; + +#define MAX_MGMT_IE_INDEX 16 +struct nxpwifi_ie_list { + __le16 type; + __le16 len; + struct nxpwifi_ie ie_list[MAX_MGMT_IE_INDEX]; +} __packed; + +struct coalesce_filt_field_param { + u8 operation; + u8 operand_len; + __le16 offset; + u8 operand_byte_stream[4]; +}; + +struct coalesce_receive_filt_rule { + struct nxpwifi_ie_types_header header; + u8 num_of_fields; + u8 pkt_type; + __le16 max_coalescing_delay; + struct coalesce_filt_field_param params[]; +} __packed; + +struct host_cmd_ds_coalesce_cfg { + __le16 action; + __le16 num_of_rules; + u8 rule_data[]; +} __packed; + +struct host_cmd_ds_multi_chan_policy { + __le16 action; + __le16 policy; +} __packed; + +struct host_cmd_ds_robust_coex { + __le16 action; + __le16 reserved; +} __packed; + +struct host_cmd_ds_wakeup_reason { + __le16 wakeup_reason; +} __packed; + +struct host_cmd_ds_gtk_rekey_params { + __le16 action; + u8 kck[NL80211_KCK_LEN]; + u8 kek[NL80211_KEK_LEN]; + __le32 replay_ctr_low; + __le32 replay_ctr_high; +} __packed; + +struct host_cmd_ds_chan_region_cfg { + __le16 action; +} __packed; + +struct host_cmd_ds_pkt_aggr_ctrl { + __le16 action; + __le16 enable; + __le16 tx_aggr_max_size; + __le16 tx_aggr_max_num; + __le16 tx_aggr_align; +} __packed; + +struct host_cmd_ds_sta_configure { + __le16 action; + u8 tlv_buffer[]; +} __packed; + +struct nxpwifi_ie_types_sta_flag { + struct nxpwifi_ie_types_header header; + __le32 sta_flags; +} __packed; + +struct host_cmd_ds_add_station { + __le16 action; + __le16 aid; + u8 peer_mac[ETH_ALEN]; + __le32 listen_interval; + __le16 cap_info; + u8 tlv[]; +} __packed; + +struct host_cmd_11ax_cfg { + __le16 action; + u8 band_config; + u8 tlv[]; +} __packed; + +struct host_cmd_11ax_cmd { + __le16 action; + __le16 sub_id; + u8 val[]; +} __packed; + +struct nxpwifi_802_11_net_monitor { + u32 enable_net_mon; + u32 filter_flag; + u32 band; + u32 channel; + u32 chan_bandwidth; +}; + +struct band_config { + /* Band: 00=2.4, 01=5, 10=6 GHz */ + u8 chan_band : 2; + /* Width: 00=20, 10=40, 11=80 MHz */ + u8 chan_width : 2; + /* Sec offset: 00=None, 01=Above, 11=Below */ + u8 chan_2O_ffset : 2; + /* Chan sel: 00=manual, 01=ACS, 02=Adoption */ + u8 scan_mode : 2; +} __packed; + +struct chan_band_param { + struct band_config band_cfg; + u8 chan_number; +} __packed; + +struct nxpwifi_ie_types_chan_band_list { + struct nxpwifi_ie_types_header header; + struct chan_band_param chan_band_param[]; +} __packed; + +struct host_cmd_ds_802_11_net_monitor { + __le16 action; + __le16 enable_net_mon; + __le16 filter_flag; + struct nxpwifi_ie_types_chan_band_list monitor_chan; +} __packed; + +struct host_cmd_twt_cfg { + __le16 action; + __le16 sub_id; + u8 val[]; +} __packed; + +struct host_cmd_ds_command { + __le16 command; + __le16 size; + __le16 seq_num; + __le16 result; + union { + struct host_cmd_ds_get_hw_spec hw_spec; + struct host_cmd_ds_mac_control mac_ctrl; + struct host_cmd_ds_802_11_mac_address mac_addr; + struct host_cmd_ds_mac_multicast_adr mc_addr; + struct host_cmd_ds_802_11_get_log get_log; + struct host_cmd_ds_802_11_rssi_info rssi_info; + struct host_cmd_ds_802_11_rssi_info_rsp rssi_info_rsp; + struct host_cmd_ds_802_11_snmp_mib smib; + struct host_cmd_ds_tx_rate_query tx_rate; + struct host_cmd_ds_tx_rate_cfg tx_rate_cfg; + struct host_cmd_ds_txpwr_cfg txp_cfg; + struct host_cmd_ds_rf_tx_pwr txp; + struct host_cmd_ds_rf_ant_mimo ant_mimo; + struct host_cmd_ds_rf_ant_siso ant_siso; + struct host_cmd_ds_802_11_ps_mode_enh psmode_enh; + struct host_cmd_ds_802_11_hs_cfg_enh opt_hs_cfg; + struct host_cmd_ds_802_11_scan scan; + struct host_cmd_ds_802_11_scan_ext ext_scan; + struct host_cmd_ds_802_11_scan_rsp scan_resp; + struct host_cmd_ds_802_11_bg_scan_config bg_scan_config; + struct host_cmd_ds_802_11_bg_scan_query bg_scan_query; + struct host_cmd_ds_802_11_bg_scan_query_rsp bg_scan_query_resp; + struct host_cmd_ds_802_11_associate associate; + struct host_cmd_ds_802_11_associate_rsp associate_rsp; + struct host_cmd_ds_802_11_deauthenticate deauth; + struct host_cmd_ds_802_11d_domain_info domain_info; + struct host_cmd_ds_802_11d_domain_info_rsp domain_info_resp; + struct host_cmd_ds_11n_addba_req add_ba_req; + struct host_cmd_ds_11n_addba_rsp add_ba_rsp; + struct host_cmd_ds_11n_delba del_ba; + struct host_cmd_ds_txbuf_cfg tx_buf; + struct host_cmd_ds_amsdu_aggr_ctrl amsdu_aggr_ctrl; + struct host_cmd_ds_11n_cfg htcfg; + struct host_cmd_ds_wmm_get_status get_wmm_status; + struct host_cmd_ds_802_11_key_material key_material; + struct host_cmd_ds_version_ext verext; + struct host_cmd_ds_mgmt_frame_reg reg_mask; + struct host_cmd_ds_remain_on_chan roc_cfg; + struct host_cmd_ds_802_11_ibss_status ibss_coalescing; + struct host_cmd_ds_mef_cfg mef_cfg; + struct host_cmd_ds_mem_access mem; + struct host_cmd_ds_mac_reg_access mac_reg; + struct host_cmd_ds_bbp_reg_access bbp_reg; + struct host_cmd_ds_rf_reg_access rf_reg; + struct host_cmd_ds_pmic_reg_access pmic_reg; + struct host_cmd_ds_set_bss_mode bss_mode; + struct host_cmd_ds_pcie_details pcie_host_spec; + struct host_cmd_ds_802_11_eeprom_access eeprom; + struct host_cmd_ds_802_11_subsc_evt subsc_evt; + struct host_cmd_ds_sys_config uap_sys_config; + struct host_cmd_ds_sta_deauth sta_deauth; + struct host_cmd_ds_sta_list sta_list; + struct host_cmd_11ac_vht_cfg vht_cfg; + struct host_cmd_ds_coalesce_cfg coalesce_cfg; + struct host_cmd_ds_chan_rpt_req chan_rpt_req; + struct host_cmd_sdio_sp_rx_aggr_cfg sdio_rx_aggr_cfg; + struct host_cmd_ds_multi_chan_policy mc_policy; + struct host_cmd_ds_robust_coex coex; + struct host_cmd_ds_wakeup_reason hs_wakeup_reason; + struct host_cmd_ds_gtk_rekey_params rekey; + struct host_cmd_ds_chan_region_cfg reg_cfg; + struct host_cmd_ds_pkt_aggr_ctrl pkt_aggr_ctrl; + struct host_cmd_ds_sta_configure sta_cfg; + struct host_cmd_ds_add_station sta_info; + struct host_cmd_11ax_cfg ax_cfg; + struct host_cmd_11ax_cmd ax_cmd; + struct host_cmd_ds_802_11_net_monitor net_mon; + struct host_cmd_twt_cfg twt_cfg; + } params; +} __packed; + +struct nxpwifi_opt_sleep_confirm { + __le16 command; + __le16 size; + __le16 seq_num; + __le16 result; + __le16 action; + __le16 resp_ctrl; +} __packed; + +#define VDLL_IND_TYPE_REQ 0 +#define VDLL_IND_TYPE_OFFSET 1 +#define VDLL_IND_TYPE_ERR_SIG 2 +#define VDLL_IND_TYPE_ERR_ID 3 +#define VDLL_IND_TYPE_SEC_ERR_ID 4 +#define VDLL_IND_TYPE_INTF_RESET 5 + +struct vdll_ind_event { + __le16 type; + __le16 vdll_id; + __le32 offset; + __le16 block_len; +} __packed; +#endif /* !_NXPWIFI_FW_H_ */ diff --git a/drivers/net/wireless/nxp/nxpwifi/ie.c b/drivers/net/wireless/nxp/nxpwifi/ie.c new file mode 100644 index 000000000000..158755c0c905 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/ie.c @@ -0,0 +1,480 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: management element handling - set/delete elements. + * + * Copyright 2011-2024 NXP + */ + +#include "main.h" +#include "cmdevt.h" + +/* Return true if the IE index is used by another interface. */ +static bool +nxpwifi_ie_index_used_by_other_intf(struct nxpwifi_private *priv, u16 idx) +{ + int i; + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_ie *ie; + + for (i = 0; i < adapter->priv_num; i++) { + if (adapter->priv[i] != priv) { + ie = &adapter->priv[i]->mgmt_ie[idx]; + if (ie->mgmt_subtype_mask && ie->ie_length) + return true; + } + } + + return false; +} + +/* Pick an unused IE index for a new element. */ +static int +nxpwifi_ie_get_autoidx(struct nxpwifi_private *priv, u16 subtype_mask, + struct nxpwifi_ie *ie, u16 *index) +{ + u16 mask, len, i; + + for (i = 0; i < priv->adapter->max_mgmt_ie_index; i++) { + mask = le16_to_cpu(priv->mgmt_ie[i].mgmt_subtype_mask); + len = le16_to_cpu(ie->ie_length); + + if (mask == NXPWIFI_AUTO_IDX_MASK) + continue; + + if (mask == subtype_mask) { + if (len > IEEE_MAX_IE_SIZE) + continue; + + *index = i; + return 0; + } + + if (!priv->mgmt_ie[i].ie_length) { + if (nxpwifi_ie_index_used_by_other_intf(priv, i)) + continue; + + *index = i; + return 0; + } + } + + return -ENOENT; +} + +/* Build IE list and resolve AUTO index before sending to FW. */ +static int +nxpwifi_update_autoindex_ies(struct nxpwifi_private *priv, + struct nxpwifi_ie_list *ie_list) +{ + u16 travel_len, index, mask; + s16 input_len, tlv_len; + struct nxpwifi_ie *ie; + u8 *tmp; + + input_len = le16_to_cpu(ie_list->len); + travel_len = sizeof(struct nxpwifi_ie_types_header); + + ie_list->len = 0; + + while (input_len >= sizeof(struct nxpwifi_ie_types_header)) { + ie = (struct nxpwifi_ie *)(((u8 *)ie_list) + travel_len); + tlv_len = le16_to_cpu(ie->ie_length); + travel_len += tlv_len + NXPWIFI_IE_HDR_SIZE; + + if (input_len < tlv_len + NXPWIFI_IE_HDR_SIZE) + return -EINVAL; + index = le16_to_cpu(ie->ie_index); + mask = le16_to_cpu(ie->mgmt_subtype_mask); + + if (index == NXPWIFI_AUTO_IDX_MASK) { + /* automatic addition */ + if (nxpwifi_ie_get_autoidx(priv, mask, ie, &index)) + return -ENOENT; + if (index == NXPWIFI_AUTO_IDX_MASK) + return -EINVAL; + + tmp = (u8 *)&priv->mgmt_ie[index].ie_buffer; + memcpy(tmp, &ie->ie_buffer, le16_to_cpu(ie->ie_length)); + priv->mgmt_ie[index].ie_length = ie->ie_length; + priv->mgmt_ie[index].ie_index = cpu_to_le16(index); + priv->mgmt_ie[index].mgmt_subtype_mask = + cpu_to_le16(mask); + + ie->ie_index = cpu_to_le16(index); + } else { + if (mask != NXPWIFI_DELETE_MASK) + return -EINVAL; + /* + * Check if this index is being used on any + * other interface. + */ + if (nxpwifi_ie_index_used_by_other_intf(priv, index)) + return -EPERM; + + ie->ie_length = 0; + memcpy(&priv->mgmt_ie[index], ie, + sizeof(struct nxpwifi_ie)); + } + + le16_unaligned_add_cpu + (&ie_list->len, + le16_to_cpu(priv->mgmt_ie[index].ie_length) + + NXPWIFI_IE_HDR_SIZE); + input_len -= tlv_len + NXPWIFI_IE_HDR_SIZE; + } + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) + return nxpwifi_send_cmd(priv, HOST_CMD_UAP_SYS_CONFIG, + HOST_ACT_GEN_SET, + UAP_CUSTOM_IE_I, ie_list, true); + + return 0; +} + +/* Pack beacon/probe/assoc IEs into one list and update auto-assigned indices. */ +static int +nxpwifi_update_uap_custom_ie(struct nxpwifi_private *priv, + struct nxpwifi_ie *beacon_ie, u16 *beacon_idx, + struct nxpwifi_ie *pr_ie, u16 *probe_idx, + struct nxpwifi_ie *ar_ie, u16 *assoc_idx) +{ + struct nxpwifi_ie_list *ap_custom_ie; + u8 *pos; + u16 len; + int ret; + + ap_custom_ie = kzalloc_obj(*ap_custom_ie, GFP_KERNEL); + if (!ap_custom_ie) + return -ENOMEM; + + ap_custom_ie->type = cpu_to_le16(TLV_TYPE_MGMT_IE); + pos = (u8 *)ap_custom_ie->ie_list; + + if (beacon_ie) { + len = sizeof(struct nxpwifi_ie) - IEEE_MAX_IE_SIZE + + le16_to_cpu(beacon_ie->ie_length); + memcpy(pos, beacon_ie, len); + pos += len; + le16_unaligned_add_cpu(&ap_custom_ie->len, len); + } + if (pr_ie) { + len = sizeof(struct nxpwifi_ie) - IEEE_MAX_IE_SIZE + + le16_to_cpu(pr_ie->ie_length); + memcpy(pos, pr_ie, len); + pos += len; + le16_unaligned_add_cpu(&ap_custom_ie->len, len); + } + if (ar_ie) { + len = sizeof(struct nxpwifi_ie) - IEEE_MAX_IE_SIZE + + le16_to_cpu(ar_ie->ie_length); + memcpy(pos, ar_ie, len); + pos += len; + le16_unaligned_add_cpu(&ap_custom_ie->len, len); + } + + ret = nxpwifi_update_autoindex_ies(priv, ap_custom_ie); + + pos = (u8 *)(&ap_custom_ie->ie_list[0].ie_index); + if (beacon_ie && *beacon_idx == NXPWIFI_AUTO_IDX_MASK) { + /* save beacon element index after auto-indexing */ + *beacon_idx = le16_to_cpu(ap_custom_ie->ie_list[0].ie_index); + len = sizeof(*beacon_ie) - IEEE_MAX_IE_SIZE + + le16_to_cpu(beacon_ie->ie_length); + pos += len; + } + if (pr_ie && le16_to_cpu(pr_ie->ie_index) == NXPWIFI_AUTO_IDX_MASK) { + /* save probe resp element index after auto-indexing */ + *probe_idx = *((u16 *)pos); + len = sizeof(*pr_ie) - IEEE_MAX_IE_SIZE + + le16_to_cpu(pr_ie->ie_length); + pos += len; + } + if (ar_ie && le16_to_cpu(ar_ie->ie_index) == NXPWIFI_AUTO_IDX_MASK) + /* save assoc resp element index after auto-indexing */ + *assoc_idx = *((u16 *)pos); + + kfree(ap_custom_ie); + return ret; +} + +/* Append vendor IE (if present) into nxpwifi_ie, allocating as needed. */ +static int nxpwifi_update_vs_ie(const u8 *ies, int ies_len, + struct nxpwifi_ie **ie_ptr, u16 mask, + unsigned int oui, u8 oui_type) +{ + struct element *vs_ie; + struct nxpwifi_ie *ie = *ie_ptr; + const u8 *vendor_ie; + + vendor_ie = cfg80211_find_vendor_ie(oui, oui_type, ies, ies_len); + if (vendor_ie) { + if (!*ie_ptr) { + *ie_ptr = kzalloc_obj(struct nxpwifi_ie, GFP_KERNEL); + if (!*ie_ptr) + return -ENOMEM; + ie = *ie_ptr; + } + + vs_ie = (struct element *)vendor_ie; + if (le16_to_cpu(ie->ie_length) + vs_ie->datalen + 2 > + IEEE_MAX_IE_SIZE) + return -EINVAL; + memcpy(ie->ie_buffer + le16_to_cpu(ie->ie_length), + vs_ie, vs_ie->datalen + 2); + le16_unaligned_add_cpu(&ie->ie_length, vs_ie->datalen + 2); + ie->mgmt_subtype_mask = cpu_to_le16(mask); + ie->ie_index = cpu_to_le16(NXPWIFI_AUTO_IDX_MASK); + } + + *ie_ptr = ie; + return 0; +} + +/* Parse beacon/probe/assoc IEs from cfg80211 and push them to FW. */ +static int nxpwifi_set_mgmt_beacon_data_ies(struct nxpwifi_private *priv, + struct cfg80211_beacon_data *data) +{ + struct nxpwifi_ie *beacon_ie = NULL, *pr_ie = NULL, *ar_ie = NULL; + u16 beacon_idx = NXPWIFI_AUTO_IDX_MASK, pr_idx = NXPWIFI_AUTO_IDX_MASK; + u16 ar_idx = NXPWIFI_AUTO_IDX_MASK; + int ret = 0; + + if (data->beacon_ies && data->beacon_ies_len) { + nxpwifi_update_vs_ie(data->beacon_ies, data->beacon_ies_len, + &beacon_ie, MGMT_MASK_BEACON, + WLAN_OUI_MICROSOFT, + WLAN_OUI_TYPE_MICROSOFT_WPS); + nxpwifi_update_vs_ie(data->beacon_ies, data->beacon_ies_len, + &beacon_ie, MGMT_MASK_BEACON, + WLAN_OUI_WFA, WLAN_OUI_TYPE_WFA_P2P); + } + + if (data->proberesp_ies && data->proberesp_ies_len) { + nxpwifi_update_vs_ie(data->proberesp_ies, + data->proberesp_ies_len, &pr_ie, + MGMT_MASK_PROBE_RESP, WLAN_OUI_MICROSOFT, + WLAN_OUI_TYPE_MICROSOFT_WPS); + nxpwifi_update_vs_ie(data->proberesp_ies, + data->proberesp_ies_len, &pr_ie, + MGMT_MASK_PROBE_RESP, + WLAN_OUI_WFA, WLAN_OUI_TYPE_WFA_P2P); + } + + if (data->assocresp_ies && data->assocresp_ies_len) { + nxpwifi_update_vs_ie(data->assocresp_ies, + data->assocresp_ies_len, &ar_ie, + MGMT_MASK_ASSOC_RESP | + MGMT_MASK_REASSOC_RESP, + WLAN_OUI_MICROSOFT, + WLAN_OUI_TYPE_MICROSOFT_WPS); + nxpwifi_update_vs_ie(data->assocresp_ies, + data->assocresp_ies_len, &ar_ie, + MGMT_MASK_ASSOC_RESP | + MGMT_MASK_REASSOC_RESP, WLAN_OUI_WFA, + WLAN_OUI_TYPE_WFA_P2P); + } + + if (beacon_ie || pr_ie || ar_ie) { + ret = nxpwifi_update_uap_custom_ie(priv, beacon_ie, + &beacon_idx, pr_ie, + &pr_idx, ar_ie, &ar_idx); + if (ret) + goto done; + } + + priv->beacon_idx = beacon_idx; + priv->proberesp_idx = pr_idx; + priv->assocresp_idx = ar_idx; + +done: + kfree(beacon_ie); + kfree(pr_ie); + kfree(ar_ie); + + return ret; +} + +/* Parse head/tail IEs from cfg80211_beacon_data and send them to FW. */ +static int nxpwifi_uap_parse_tail_ies(struct nxpwifi_private *priv, + struct cfg80211_beacon_data *info) +{ + struct nxpwifi_ie *gen_ie; + struct element *hdr; + struct ieee80211_vendor_ie *vendorhdr; + u16 gen_idx = NXPWIFI_AUTO_IDX_MASK, ie_len = 0; + int left_len, parsed_len = 0; + unsigned int token_len; + int ret = 0; + + if (!info->tail || !info->tail_len) + return 0; + + gen_ie = kzalloc_obj(*gen_ie, GFP_KERNEL); + if (!gen_ie) + return -ENOMEM; + + left_len = info->tail_len; + + /* Skip IEs generated by FW from bss configuration to avoid duplicates. */ + while (left_len > sizeof(struct element)) { + hdr = (void *)(info->tail + parsed_len); + token_len = hdr->datalen + sizeof(struct element); + if (token_len > left_len) { + ret = -EINVAL; + goto done; + } + + switch (hdr->id) { + case WLAN_EID_SSID: + case WLAN_EID_SUPP_RATES: + case WLAN_EID_COUNTRY: + case WLAN_EID_PWR_CONSTRAINT: + case WLAN_EID_ERP_INFO: + case WLAN_EID_EXT_SUPP_RATES: + case WLAN_EID_HT_CAPABILITY: + case WLAN_EID_HT_OPERATION: + case WLAN_EID_VHT_CAPABILITY: + break; + case WLAN_EID_VENDOR_SPECIFIC: + /* Skip only Microsoft WMM element */ + if (cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT, + WLAN_OUI_TYPE_MICROSOFT_WMM, + (const u8 *)hdr, + token_len)) + break; + fallthrough; + default: + if (ie_len + token_len > IEEE_MAX_IE_SIZE) { + ret = -EINVAL; + goto done; + } + memcpy(gen_ie->ie_buffer + ie_len, hdr, token_len); + ie_len += token_len; + break; + } + left_len -= token_len; + parsed_len += token_len; + } + + /* + * parse only WPA vendor element from tail, WMM element is configured by + * bss_config command + */ + vendorhdr = (void *)cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT, + WLAN_OUI_TYPE_MICROSOFT_WPA, + info->tail, info->tail_len); + if (vendorhdr) { + token_len = vendorhdr->len + sizeof(struct element); + if (ie_len + token_len > IEEE_MAX_IE_SIZE) { + ret = -EINVAL; + goto done; + } + memcpy(gen_ie->ie_buffer + ie_len, vendorhdr, token_len); + ie_len += token_len; + } + + if (!ie_len) + goto done; + + gen_ie->ie_index = cpu_to_le16(gen_idx); + gen_ie->mgmt_subtype_mask = cpu_to_le16(MGMT_MASK_BEACON | + MGMT_MASK_PROBE_RESP | + MGMT_MASK_ASSOC_RESP); + gen_ie->ie_length = cpu_to_le16(ie_len); + + ret = nxpwifi_update_uap_custom_ie(priv, gen_ie, &gen_idx, NULL, + NULL, NULL, NULL); + + if (ret) + goto done; + + priv->gen_idx = gen_idx; + + done: + kfree(gen_ie); + return ret; +} + +/* Parse head/tail/beacon/probe/assoc IEs and program the FW. */ +int nxpwifi_set_mgmt_ies(struct nxpwifi_private *priv, + struct cfg80211_beacon_data *info) +{ + int ret; + + ret = nxpwifi_uap_parse_tail_ies(priv, info); + + if (ret) + return ret; + + return nxpwifi_set_mgmt_beacon_data_ies(priv, info); +} + +/* Remove previously set management IEs. */ +int nxpwifi_del_mgmt_ies(struct nxpwifi_private *priv) +{ + struct nxpwifi_ie *beacon_ie = NULL, *pr_ie = NULL; + struct nxpwifi_ie *ar_ie = NULL, *gen_ie = NULL; + int ret = 0; + + if (priv->gen_idx != NXPWIFI_AUTO_IDX_MASK) { + gen_ie = kmalloc_obj(*gen_ie, GFP_KERNEL); + if (!gen_ie) + return -ENOMEM; + + gen_ie->ie_index = cpu_to_le16(priv->gen_idx); + gen_ie->mgmt_subtype_mask = cpu_to_le16(NXPWIFI_DELETE_MASK); + gen_ie->ie_length = 0; + ret = nxpwifi_update_uap_custom_ie(priv, gen_ie, &priv->gen_idx, + NULL, &priv->proberesp_idx, + NULL, &priv->assocresp_idx); + if (ret) + goto done; + + priv->gen_idx = NXPWIFI_AUTO_IDX_MASK; + } + + if (priv->beacon_idx != NXPWIFI_AUTO_IDX_MASK) { + beacon_ie = kmalloc_obj(*beacon_ie, GFP_KERNEL); + if (!beacon_ie) { + ret = -ENOMEM; + goto done; + } + beacon_ie->ie_index = cpu_to_le16(priv->beacon_idx); + beacon_ie->mgmt_subtype_mask = cpu_to_le16(NXPWIFI_DELETE_MASK); + beacon_ie->ie_length = 0; + } + if (priv->proberesp_idx != NXPWIFI_AUTO_IDX_MASK) { + pr_ie = kmalloc_obj(*pr_ie, GFP_KERNEL); + if (!pr_ie) { + ret = -ENOMEM; + goto done; + } + pr_ie->ie_index = cpu_to_le16(priv->proberesp_idx); + pr_ie->mgmt_subtype_mask = cpu_to_le16(NXPWIFI_DELETE_MASK); + pr_ie->ie_length = 0; + } + if (priv->assocresp_idx != NXPWIFI_AUTO_IDX_MASK) { + ar_ie = kmalloc_obj(*ar_ie, GFP_KERNEL); + if (!ar_ie) { + ret = -ENOMEM; + goto done; + } + ar_ie->ie_index = cpu_to_le16(priv->assocresp_idx); + ar_ie->mgmt_subtype_mask = cpu_to_le16(NXPWIFI_DELETE_MASK); + ar_ie->ie_length = 0; + } + + if (beacon_ie || pr_ie || ar_ie) + ret = nxpwifi_update_uap_custom_ie(priv, + beacon_ie, &priv->beacon_idx, + pr_ie, &priv->proberesp_idx, + ar_ie, &priv->assocresp_idx); + +done: + kfree(gen_ie); + kfree(beacon_ie); + kfree(pr_ie); + kfree(ar_ie); + + return ret; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/init.c b/drivers/net/wireless/nxp/nxpwifi/init.c new file mode 100644 index 000000000000..eb8510265b40 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/init.c @@ -0,0 +1,607 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: HW/FW initialization + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cmdevt.h" +#include "wmm.h" +#include "11n.h" + +/* Add a BSS priority node to the adapter list. */ +static int nxpwifi_add_bss_prio_tbl(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_bss_prio_node *bss_prio; + struct nxpwifi_bss_prio_tbl *tbl = adapter->bss_prio_tbl; + + bss_prio = kzalloc_obj(*bss_prio, GFP_KERNEL); + if (!bss_prio) + return -ENOMEM; + + bss_prio->priv = priv; + INIT_LIST_HEAD(&bss_prio->list); + + spin_lock_bh(&tbl[priv->bss_priority].bss_prio_lock); + list_add_tail(&bss_prio->list, &tbl[priv->bss_priority].bss_prio_head); + spin_unlock_bh(&tbl[priv->bss_priority].bss_prio_lock); + + return 0; +} + +static void wakeup_timer_fn(struct timer_list *t) +{ + struct nxpwifi_adapter *adapter = timer_container_of(adapter, t, wakeup_timer); + + nxpwifi_dbg(adapter, ERROR, "Firmware wakeup failed\n"); + adapter->hw_status = NXPWIFI_HW_STATUS_RESET; + nxpwifi_cancel_all_pending_cmd(adapter); + + if (adapter->if_ops.card_reset) + adapter->if_ops.card_reset(adapter); +} + +/* Initialize priv defaults and lists. */ +int nxpwifi_init_priv(struct nxpwifi_private *priv) +{ + u32 i; + + priv->media_connected = false; + eth_broadcast_addr(priv->curr_addr); + priv->port_open = false; + priv->usb_port = NXPWIFI_USB_EP_DATA; + priv->pkt_tx_ctrl = 0; + priv->bss_mode = NL80211_IFTYPE_UNSPECIFIED; + priv->data_rate = 0; /* Initially indicate the rate as auto */ + priv->is_data_rate_auto = true; + priv->bcn_avg_factor = DEFAULT_BCN_AVG_FACTOR; + priv->data_avg_factor = DEFAULT_DATA_AVG_FACTOR; + + priv->auth_flag = 0; + priv->auth_alg = WLAN_AUTH_NONE; + + priv->sec_info.wep_enabled = 0; + priv->sec_info.authentication_mode = NL80211_AUTHTYPE_OPEN_SYSTEM; + priv->sec_info.encryption_mode = 0; + for (i = 0; i < ARRAY_SIZE(priv->wep_key); i++) + memset(&priv->wep_key[i], 0, sizeof(struct nxpwifi_wep_key)); + priv->wep_key_curr_index = 0; + priv->curr_pkt_filter = HOST_ACT_MAC_DYNAMIC_BW_ENABLE | + HOST_ACT_MAC_RX_ON | HOST_ACT_MAC_TX_ON | + HOST_ACT_MAC_ETHERNETII_ENABLE; + + priv->beacon_period = 100; /* beacon interval */ + priv->attempted_bss_desc = NULL; + memset(&priv->curr_bss_params, 0, sizeof(priv->curr_bss_params)); + priv->listen_interval = NXPWIFI_DEFAULT_LISTEN_INTERVAL; + + memset(&priv->prev_ssid, 0, sizeof(priv->prev_ssid)); + memset(&priv->prev_bssid, 0, sizeof(priv->prev_bssid)); + memset(&priv->assoc_rsp_buf, 0, sizeof(priv->assoc_rsp_buf)); + priv->assoc_rsp_size = 0; + priv->atim_window = 0; + priv->tx_power_level = 0; + priv->max_tx_power_level = 0; + priv->min_tx_power_level = 0; + priv->tx_ant = 0; + priv->rx_ant = 0; + priv->tx_rate = 0; + priv->rxpd_htinfo = 0; + priv->rxpd_rate = 0; + priv->rate_bitmap = 0; + priv->data_rssi_last = 0; + priv->data_rssi_avg = 0; + priv->data_nf_avg = 0; + priv->data_nf_last = 0; + priv->bcn_rssi_last = 0; + priv->bcn_rssi_avg = 0; + priv->bcn_nf_avg = 0; + priv->bcn_nf_last = 0; + memset(&priv->wpa_ie, 0, sizeof(priv->wpa_ie)); + memset(&priv->aes_key, 0, sizeof(priv->aes_key)); + priv->wpa_ie_len = 0; + priv->wpa_is_gtk_set = false; + + memset(&priv->assoc_tlv_buf, 0, sizeof(priv->assoc_tlv_buf)); + priv->assoc_tlv_buf_len = 0; + memset(&priv->wps, 0, sizeof(priv->wps)); + memset(&priv->gen_ie_buf, 0, sizeof(priv->gen_ie_buf)); + priv->gen_ie_buf_len = 0; + memset(priv->vs_ie, 0, sizeof(priv->vs_ie)); + + priv->wmm_required = true; + priv->wmm_enabled = false; + priv->wmm_qosinfo = 0; + priv->curr_bcn_buf = NULL; + priv->curr_bcn_size = 0; + priv->wps_ie = NULL; + priv->wps_ie_len = 0; + priv->ap_11n_enabled = 0; + memset(&priv->roc_cfg, 0, sizeof(priv->roc_cfg)); + + priv->scan_block = false; + + priv->csa_chan = 0; + priv->csa_expire_time = 0; + priv->del_list_idx = 0; + priv->hs2_enabled = false; + memcpy(priv->tos_to_tid_inv, tos_to_tid_inv, MAX_NUM_TID); + + nxpwifi_init_11h_params(priv); + + return nxpwifi_add_bss_prio_tbl(priv); +} + +/* Allocate command buffer and sleep-confirm skb. */ +static int nxpwifi_allocate_adapter(struct nxpwifi_adapter *adapter) +{ + int ret; + + /* Allocate command buffer */ + ret = nxpwifi_alloc_cmd_buffer(adapter); + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "%s: failed to alloc cmd buffer\n", + __func__); + return ret; + } + + adapter->sleep_cfm = + dev_alloc_skb(sizeof(struct nxpwifi_opt_sleep_confirm) + + INTF_HEADER_LEN); + + if (!adapter->sleep_cfm) { + nxpwifi_dbg(adapter, ERROR, + "%s: failed to alloc sleep cfm\t" + " cmd buffer\n", __func__); + return -ENOMEM; + } + skb_reserve(adapter->sleep_cfm, INTF_HEADER_LEN); + + return 0; +} + +/* Initialize adapter defaults and WMM parameters. */ +static void nxpwifi_init_adapter(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_opt_sleep_confirm *sleep_cfm_buf = NULL; + + skb_put(adapter->sleep_cfm, sizeof(struct nxpwifi_opt_sleep_confirm)); + + adapter->cmd_sent = false; + adapter->data_sent = true; + + adapter->intf_hdr_len = INTF_HEADER_LEN; + + adapter->cmd_resp_received = false; + adapter->event_received = false; + adapter->data_received = false; + adapter->assoc_resp_received = false; + adapter->priv_link_lost = NULL; + adapter->host_mlme_link_lost = false; + + clear_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags); + + adapter->hw_status = NXPWIFI_HW_STATUS_INITIALIZING; + + adapter->ps_mode = NXPWIFI_802_11_POWER_MODE_CAM; + adapter->ps_state = PS_STATE_AWAKE; + adapter->need_to_wakeup = false; + + adapter->scan_mode = HOST_BSS_MODE_ANY; + adapter->specific_scan_time = NXPWIFI_SPECIFIC_SCAN_CHAN_TIME; + adapter->active_scan_time = NXPWIFI_ACTIVE_SCAN_CHAN_TIME; + adapter->passive_scan_time = NXPWIFI_PASSIVE_SCAN_CHAN_TIME; + adapter->scan_chan_gap_time = NXPWIFI_DEF_SCAN_CHAN_GAP_TIME; + + adapter->scan_probes = 1; + + adapter->multiple_dtim = 1; + + /* default value in firmware will be used */ + adapter->local_listen_interval = 0; + + adapter->is_deep_sleep = false; + + adapter->delay_null_pkt = false; + adapter->delay_to_ps = 1000; + adapter->enhanced_ps_mode = PS_MODE_AUTO; + + /* Disable NULL Pkg generation by default */ + adapter->gen_null_pkt = false; + /* Disable pps/uapsd mode by default */ + adapter->pps_uapsd_mode = false; + adapter->pm_wakeup_card_req = false; + + adapter->pm_wakeup_fw_try = false; + + adapter->curr_tx_buf_size = NXPWIFI_TX_DATA_BUF_SIZE_2K; + + clear_bit(NXPWIFI_IS_HS_CONFIGURED, &adapter->work_flags); + adapter->hs_cfg.conditions = cpu_to_le32(HS_CFG_COND_DEF); + adapter->hs_cfg.gpio = HS_CFG_GPIO_DEF; + adapter->hs_cfg.gap = HS_CFG_GAP_DEF; + adapter->hs_activated = false; + + memset(adapter->event_body, 0, sizeof(adapter->event_body)); + adapter->hw_dot_11n_dev_cap = 0; + adapter->hw_dev_mcs_support = 0; + adapter->sec_chan_offset = 0; + + nxpwifi_wmm_init(adapter); + atomic_set(&adapter->tx_hw_pending, 0); + + sleep_cfm_buf = (struct nxpwifi_opt_sleep_confirm *) + adapter->sleep_cfm->data; + memset(sleep_cfm_buf, 0, adapter->sleep_cfm->len); + sleep_cfm_buf->command = cpu_to_le16(HOST_CMD_802_11_PS_MODE_ENH); + sleep_cfm_buf->size = cpu_to_le16(adapter->sleep_cfm->len); + sleep_cfm_buf->result = 0; + sleep_cfm_buf->action = cpu_to_le16(SLEEP_CONFIRM); + sleep_cfm_buf->resp_ctrl = cpu_to_le16(RESP_NEEDED); + + memset(&adapter->sleep_period, 0, sizeof(adapter->sleep_period)); + adapter->tx_lock_flag = false; + adapter->null_pkt_interval = 0; + adapter->fw_bands = 0; + adapter->fw_release_number = 0; + adapter->fw_cap_info = 0; + memset(&adapter->upld_buf, 0, sizeof(adapter->upld_buf)); + adapter->event_cause = 0; + adapter->region_code = 0; + adapter->bcn_miss_time_out = DEFAULT_BCN_MISS_TIMEOUT; + memset(&adapter->arp_filter, 0, sizeof(adapter->arp_filter)); + adapter->arp_filter_size = 0; + adapter->max_mgmt_ie_index = MAX_MGMT_IE_INDEX; + adapter->key_api_major_ver = 0; + adapter->key_api_minor_ver = 0; + eth_broadcast_addr(adapter->perm_addr); + adapter->iface_limit.sta_intf = NXPWIFI_MAX_STA_NUM; + adapter->iface_limit.uap_intf = NXPWIFI_MAX_UAP_NUM; + adapter->active_scan_triggered = false; + timer_setup(&adapter->wakeup_timer, wakeup_timer_fn, 0); + adapter->devdump_len = 0; + memset(&adapter->vdll_ctrl, 0, sizeof(adapter->vdll_ctrl)); + adapter->vdll_ctrl.skb = dev_alloc_skb(NXPWIFI_SIZE_OF_CMD_BUFFER); + atomic_set(&adapter->iface_changing, 0); +} + +/* Update trans_start for each Tx queue. */ +void nxpwifi_set_trans_start(struct net_device *dev) +{ + int i; + + for (i = 0; i < dev->num_tx_queues; i++) + txq_trans_cond_update(netdev_get_tx_queue(dev, i)); + + netif_trans_update(dev); +} + +/* Wake all netdev Tx queues. */ +void nxpwifi_wake_up_net_dev_queue(struct net_device *netdev, + struct nxpwifi_adapter *adapter) +{ + spin_lock_bh(&adapter->queue_lock); + netif_tx_wake_all_queues(netdev); + spin_unlock_bh(&adapter->queue_lock); +} + +/* Stop all netdev Tx queues. */ +void nxpwifi_stop_net_dev_queue(struct net_device *netdev, + struct nxpwifi_adapter *adapter) +{ + spin_lock_bh(&adapter->queue_lock); + netif_tx_stop_all_queues(netdev); + spin_unlock_bh(&adapter->queue_lock); +} + +/* Invalidate list heads. */ +static void nxpwifi_invalidate_lists(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_private *priv; + s32 i, j; + + list_del(&adapter->cmd_free_q); + list_del(&adapter->cmd_pending_q); + list_del(&adapter->scan_pending_q); + + for (i = 0; i < adapter->priv_num; i++) + list_del(&adapter->bss_prio_tbl[i].bss_prio_head); + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + for (j = 0; j < MAX_NUM_TID; ++j) { + list_del(&priv->wmm.tid_tbl_ptr[j].ra_list); + list_del(&priv->tx_ba_stream_tbl_ptr[j]); + list_del(&priv->rx_reorder_tbl_ptr[j]); + } + list_del(&priv->sta_list); + } +} + +/* Cancel pending work, stop timers, and free adapter buffers. */ +static void +nxpwifi_adapter_cleanup(struct nxpwifi_adapter *adapter) +{ + timer_delete(&adapter->wakeup_timer); + nxpwifi_cancel_all_pending_cmd(adapter); + wake_up_interruptible(&adapter->cmd_wait_q.wait); + wake_up_interruptible(&adapter->hs_activate_wait_q); + if (adapter->vdll_ctrl.vdll_mem) { + vfree(adapter->vdll_ctrl.vdll_mem); + adapter->vdll_ctrl.vdll_mem = NULL; + adapter->vdll_ctrl.vdll_len = 0; + } + if (adapter->vdll_ctrl.skb) { + dev_kfree_skb_any(adapter->vdll_ctrl.skb); + adapter->vdll_ctrl.skb = NULL; + } +} + +void nxpwifi_free_cmd_buffers(struct nxpwifi_adapter *adapter) +{ + nxpwifi_invalidate_lists(adapter); + + /* Free command buffer */ + nxpwifi_dbg(adapter, INFO, "info: free cmd buffer\n"); + nxpwifi_free_cmd_buffer(adapter); + + if (adapter->sleep_cfm) + dev_kfree_skb_any(adapter->sleep_cfm); +} + +/* Initialize locks and list heads. */ +void nxpwifi_init_lock_list(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_private *priv; + s32 i, j; + + spin_lock_init(&adapter->int_lock); + spin_lock_init(&adapter->nxpwifi_cmd_lock); + spin_lock_init(&adapter->queue_lock); + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + spin_lock_init(&priv->wmm.ra_list_spinlock); + spin_lock_init(&priv->curr_bcn_buf_lock); + spin_lock_init(&priv->sta_list_spinlock); + } + + /* Initialize cmd_free_q */ + INIT_LIST_HEAD(&adapter->cmd_free_q); + /* Initialize cmd_pending_q */ + INIT_LIST_HEAD(&adapter->cmd_pending_q); + /* Initialize scan_pending_q */ + INIT_LIST_HEAD(&adapter->scan_pending_q); + + spin_lock_init(&adapter->cmd_free_q_lock); + spin_lock_init(&adapter->cmd_pending_q_lock); + spin_lock_init(&adapter->scan_pending_q_lock); + + skb_queue_head_init(&adapter->rx_mlme_q); + skb_queue_head_init(&adapter->rx_data_q); + skb_queue_head_init(&adapter->tx_data_q); + + for (i = 0; i < adapter->priv_num; ++i) { + INIT_LIST_HEAD(&adapter->bss_prio_tbl[i].bss_prio_head); + spin_lock_init(&adapter->bss_prio_tbl[i].bss_prio_lock); + } + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + for (j = 0; j < MAX_NUM_TID; ++j) { + INIT_LIST_HEAD(&priv->wmm.tid_tbl_ptr[j].ra_list); + INIT_LIST_HEAD(&priv->tx_ba_stream_tbl_ptr[j]); + INIT_LIST_HEAD(&priv->rx_reorder_tbl_ptr[j]); + spin_lock_init(&priv->tx_ba_stream_tbl_lock[j]); + spin_lock_init(&priv->rx_reorder_tbl_lock[j]); + } + INIT_LIST_HEAD(&priv->sta_list); + skb_queue_head_init(&priv->bypass_txq); + + spin_lock_init(&priv->ack_status_lock); + xa_init_flags(&priv->ack_status_frames, XA_FLAGS_ALLOC); + } +} + +/* Init firmware: alloc resources, init adapter/privs, send STA init. */ +int nxpwifi_init_fw(struct nxpwifi_adapter *adapter) +{ + int ret; + struct nxpwifi_private *priv; + u8 i; + bool first_sta = true; + + adapter->hw_status = NXPWIFI_HW_STATUS_INITIALIZING; + + /* Allocate memory for member of adapter structure */ + ret = nxpwifi_allocate_adapter(adapter); + if (ret) + return ret; + + /* Initialize adapter structure */ + nxpwifi_init_adapter(adapter); + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + + /* Initialize private structure */ + ret = nxpwifi_init_priv(priv); + if (ret) + return ret; + } + + for (i = 0; i < adapter->priv_num; i++) { + ret = nxpwifi_sta_init_cmd(adapter->priv[i], + first_sta, true); + if (ret) + return ret; + + first_sta = false; + } + spin_lock_bh(&adapter->cmd_pending_q_lock); + WARN_ON(!list_empty(&adapter->cmd_pending_q)); + spin_unlock_bh(&adapter->cmd_pending_q_lock); + adapter->hw_status = NXPWIFI_HW_STATUS_READY; + + return 0; +} + +/* Remove all BSS priority nodes for this priv. */ +static void nxpwifi_delete_bss_prio_tbl(struct nxpwifi_private *priv) +{ + int i; + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_bss_prio_node *bssprio_node, *tmp_node; + struct list_head *head; + spinlock_t *lock; /* bss priority lock */ + + for (i = 0; i < adapter->priv_num; ++i) { + head = &adapter->bss_prio_tbl[i].bss_prio_head; + lock = &adapter->bss_prio_tbl[i].bss_prio_lock; + nxpwifi_dbg(adapter, INFO, + "info: delete BSS priority table,\t" + "bss_type = %d, bss_num = %d, i = %d,\t" + "head = %p\n", + priv->bss_type, priv->bss_num, i, head); + + { + spin_lock_bh(lock); + list_for_each_entry_safe(bssprio_node, tmp_node, head, + list) { + if (bssprio_node->priv == priv) { + nxpwifi_dbg(adapter, INFO, + "info: Delete\t" + "node %p, next = %p\n", + bssprio_node, tmp_node); + list_del(&bssprio_node->list); + kfree(bssprio_node); + } + } + spin_unlock_bh(lock); + } + } +} + +/* Free per-priv resources and BSS priority entries. */ +void nxpwifi_free_priv(struct nxpwifi_private *priv) +{ + nxpwifi_clean_txrx(priv); + nxpwifi_delete_bss_prio_tbl(priv); + nxpwifi_free_curr_bcn(priv); +} + +/* Shutdown driver: stop work, drain queues, free resources. */ +void +nxpwifi_shutdown_drv(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_private *priv; + s32 i; + struct sk_buff *skb; + + /* nxpwifi already shutdown */ + if (adapter->hw_status == NXPWIFI_HW_STATUS_NOT_READY) + return; + + /* cancel current command */ + if (adapter->curr_cmd) { + nxpwifi_dbg(adapter, WARN, + "curr_cmd is still in processing\n"); + timer_delete_sync(&adapter->cmd_timer); + nxpwifi_recycle_cmd_node(adapter, adapter->curr_cmd); + adapter->curr_cmd = NULL; + } + + /* shut down nxpwifi */ + nxpwifi_dbg(adapter, MSG, + "info: shutdown nxpwifi...\n"); + + /* Clean up Tx/Rx queues and delete BSS priority table */ + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + + nxpwifi_abort_cac(priv); + nxpwifi_free_priv(priv); + } + + atomic_set(&adapter->tx_queued, 0); + while ((skb = skb_dequeue(&adapter->tx_data_q))) + nxpwifi_write_data_complete(adapter, skb, 0, 0); + + while ((skb = skb_dequeue(&adapter->rx_mlme_q))) + dev_kfree_skb_any(skb); + + while ((skb = skb_dequeue(&adapter->rx_data_q))) { + struct nxpwifi_rxinfo *rx_info = NXPWIFI_SKB_RXCB(skb); + + atomic_dec(&adapter->rx_pending); + priv = adapter->priv[rx_info->bss_num]; + if (priv) + priv->stats.rx_dropped++; + + dev_kfree_skb_any(skb); + } + + nxpwifi_adapter_cleanup(adapter); + + adapter->hw_status = NXPWIFI_HW_STATUS_NOT_READY; +} + +/* Download FW if needed; check winner and wait until ready. */ +int nxpwifi_dnld_fw(struct nxpwifi_adapter *adapter, + struct nxpwifi_fw_image *pmfw) +{ + int ret; + u32 poll_num = 1; + + /* check if firmware is already running */ + ret = adapter->if_ops.check_fw_status(adapter, poll_num); + if (!ret) { + nxpwifi_dbg(adapter, MSG, + "WLAN FW already running! Skip FW dnld\n"); + return 0; + } + + /* check if we are the winner for downloading FW */ + if (adapter->if_ops.check_winner_status) { + adapter->winner = 0; + ret = adapter->if_ops.check_winner_status(adapter); + + poll_num = MAX_FIRMWARE_POLL_TRIES; + if (ret) { + nxpwifi_dbg(adapter, MSG, + "WLAN read winner status failed!\n"); + return ret; + } + + if (!adapter->winner) { + nxpwifi_dbg(adapter, MSG, + "WLAN is not the winner! Skip FW dnld\n"); + goto poll_fw; + } + } + + if (pmfw) { + /* Download firmware with helper */ + ret = adapter->if_ops.prog_fw(adapter, pmfw); + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "prog_fw failed ret=%#x\n", ret); + return ret; + } + } + +poll_fw: + /* Check if the firmware is downloaded successfully or not */ + ret = adapter->if_ops.check_fw_status(adapter, poll_num); + if (ret) + nxpwifi_dbg(adapter, ERROR, + "FW failed to be active in time\n"); + + return ret; +} +EXPORT_SYMBOL_GPL(nxpwifi_dnld_fw); diff --git a/drivers/net/wireless/nxp/nxpwifi/join.c b/drivers/net/wireless/nxp/nxpwifi/join.c new file mode 100644 index 000000000000..359006e2c391 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/join.c @@ -0,0 +1,787 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: association and ad-hoc start/join + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cmdevt.h" +#include "wmm.h" +#include "11n.h" +#include "11ac.h" +#include "11ax.h" + +#define CAPINFO_MASK (~(BIT(15) | BIT(14) | BIT(12) | BIT(11) | BIT(9))) + +/* Append generic IE as pass-through TLV for join */ +static int +nxpwifi_cmd_append_generic_ie(struct nxpwifi_private *priv, u8 **buffer) +{ + int ret_len = 0; + struct nxpwifi_ie_types_header ie_header; + + /* Null Checks */ + if (!buffer) + return 0; + if (!(*buffer)) + return 0; + + /* + * If there is a generic element buffer setup, append it to the return + * parameter buffer pointer. + */ + if (priv->gen_ie_buf_len) { + nxpwifi_dbg(priv->adapter, INFO, + "info: %s: append generic element len %d to %p\n", + __func__, priv->gen_ie_buf_len, *buffer); + + /* Wrap the generic element buffer with a pass through TLV type */ + ie_header.type = cpu_to_le16(TLV_TYPE_PASSTHROUGH); + ie_header.len = cpu_to_le16(priv->gen_ie_buf_len); + memcpy(*buffer, &ie_header, sizeof(ie_header)); + + /* + * Increment the return size and the return buffer pointer + * param + */ + *buffer += sizeof(ie_header); + ret_len += sizeof(ie_header); + + /* + * Copy the generic element buffer to the output buffer, advance + * pointer + */ + memcpy(*buffer, priv->gen_ie_buf, priv->gen_ie_buf_len); + + /* + * Increment the return size and the return buffer pointer + * param + */ + *buffer += priv->gen_ie_buf_len; + ret_len += priv->gen_ie_buf_len; + + /* Reset the generic element buffer */ + priv->gen_ie_buf_len = 0; + } + + /* return the length appended to the buffer */ + return ret_len; +} + +/* Append TSF timestamp (AP TSF and local RX TSF) for reassoc */ +static int +nxpwifi_cmd_append_tsf_tlv(struct nxpwifi_private *priv, u8 **buffer, + struct nxpwifi_bssdescriptor *bss_desc) +{ + struct nxpwifi_ie_types_tsf_timestamp tsf_tlv; + __le64 tsf_val; + + /* Null Checks */ + if (!buffer) + return 0; + if (!*buffer) + return 0; + + memset(&tsf_tlv, 0x00, sizeof(struct nxpwifi_ie_types_tsf_timestamp)); + + tsf_tlv.header.type = cpu_to_le16(TLV_TYPE_TSFTIMESTAMP); + tsf_tlv.header.len = cpu_to_le16(2 * sizeof(tsf_val)); + + memcpy(*buffer, &tsf_tlv, sizeof(tsf_tlv.header)); + *buffer += sizeof(tsf_tlv.header); + + /* TSF at the time when beacon/probe_response was received */ + tsf_val = cpu_to_le64(bss_desc->fw_tsf); + memcpy(*buffer, &tsf_val, sizeof(tsf_val)); + *buffer += sizeof(tsf_val); + + tsf_val = cpu_to_le64(bss_desc->timestamp); + + nxpwifi_dbg(priv->adapter, INFO, + "info: %s: TSF offset calc: %016llx - %016llx\n", + __func__, bss_desc->timestamp, bss_desc->fw_tsf); + + memcpy(*buffer, &tsf_val, sizeof(tsf_val)); + *buffer += sizeof(tsf_val); + + return sizeof(tsf_tlv.header) + (2 * sizeof(tsf_val)); +} + +/* Compute intersection of two rate sets; rate1 updated in-place */ +static int nxpwifi_get_common_rates(struct nxpwifi_private *priv, u8 *rate1, + u32 rate1_size, u8 *rate2, u32 rate2_size) +{ + int ret; + u8 *ptr = rate1, *tmp; + u32 i, j; + + tmp = kmemdup(rate1, rate1_size, GFP_KERNEL); + if (!tmp) + return -ENOMEM; + + memset(rate1, 0, rate1_size); + + for (i = 0; i < rate2_size && rate2[i]; i++) { + for (j = 0; j < rate1_size && tmp[j]; j++) { + /* + * Check common rate, excluding the bit for + * basic rate + */ + if ((rate2[i] & 0x7F) == (tmp[j] & 0x7F)) { + *rate1++ = tmp[j]; + break; + } + } + } + + nxpwifi_dbg(priv->adapter, INFO, "info: Tx data rate set to %#x\n", + priv->data_rate); + + if (!priv->is_data_rate_auto) { + while (*ptr) { + if ((*ptr & 0x7f) == priv->data_rate) { + ret = 0; + goto done; + } + ptr++; + } + nxpwifi_dbg(priv->adapter, ERROR, + "previously set fixed data rate %#x\t" + "is not compatible with the network\n", + priv->data_rate); + + ret = -EPERM; + goto done; + } + + ret = 0; +done: + kfree(tmp); + return ret; +} + +/* Build common rates from BSS descriptor into out_rates */ +static int +nxpwifi_setup_rates_from_bssdesc(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc, + u8 *out_rates, u32 *out_rates_size) +{ + u8 card_rates[NXPWIFI_SUPPORTED_RATES]; + u32 card_rates_size; + int ret; + + /* Copy AP supported rates */ + memcpy(out_rates, bss_desc->supported_rates, NXPWIFI_SUPPORTED_RATES); + /* Get the STA supported rates */ + card_rates_size = nxpwifi_get_active_data_rates(priv, card_rates); + /* Get the common rates between AP and STA supported rates */ + ret = nxpwifi_get_common_rates(priv, out_rates, NXPWIFI_SUPPORTED_RATES, + card_rates, card_rates_size); + if (ret) { + *out_rates_size = 0; + nxpwifi_dbg(priv->adapter, ERROR, + "%s: cannot get common rates\n", + __func__); + } else { + *out_rates_size = + min_t(size_t, strlen(out_rates), NXPWIFI_SUPPORTED_RATES); + } + + return ret; +} + +/* Append WPS IE as pass-through TLV for join */ +static int +nxpwifi_cmd_append_wps_ie(struct nxpwifi_private *priv, u8 **buffer) +{ + int ret_len = 0; + struct nxpwifi_ie_types_header ie_header; + + if (!buffer || !*buffer) + return 0; + + /* + * If there is a wps element buffer setup, append it to the return + * parameter buffer pointer. + */ + if (priv->wps_ie_len) { + nxpwifi_dbg(priv->adapter, CMD, + "cmd: append wps element %d to %p\n", + priv->wps_ie_len, *buffer); + + /* Wrap the generic element buffer with a pass through TLV type */ + ie_header.type = cpu_to_le16(TLV_TYPE_PASSTHROUGH); + ie_header.len = cpu_to_le16(priv->wps_ie_len); + memcpy(*buffer, &ie_header, sizeof(ie_header)); + *buffer += sizeof(ie_header); + ret_len += sizeof(ie_header); + + memcpy(*buffer, priv->wps_ie, priv->wps_ie_len); + *buffer += priv->wps_ie_len; + ret_len += priv->wps_ie_len; + } + + kfree(priv->wps_ie); + priv->wps_ie_len = 0; + return ret_len; +} + +/* Append WPA/WPA2 RSN IE TLV */ +static int nxpwifi_append_rsn_ie_wpa_wpa2(struct nxpwifi_private *priv, + u8 **buffer) +{ + struct nxpwifi_ie_types_rsn_param_set *rsn_ie_tlv; + int rsn_ie_len; + + if (!buffer || !(*buffer)) + return 0; + + rsn_ie_tlv = (struct nxpwifi_ie_types_rsn_param_set *)(*buffer); + rsn_ie_tlv->header.type = cpu_to_le16((u16)priv->wpa_ie[0]); + rsn_ie_tlv->header.type = + cpu_to_le16(le16_to_cpu(rsn_ie_tlv->header.type) & 0x00FF); + rsn_ie_tlv->header.len = cpu_to_le16((u16)priv->wpa_ie[1]); + rsn_ie_tlv->header.len = cpu_to_le16(le16_to_cpu(rsn_ie_tlv->header.len) + & 0x00FF); + if (le16_to_cpu(rsn_ie_tlv->header.len) <= (sizeof(priv->wpa_ie) - 2)) + memcpy(rsn_ie_tlv->rsn_ie, &priv->wpa_ie[2], + le16_to_cpu(rsn_ie_tlv->header.len)); + else + return -ENOMEM; + + rsn_ie_len = sizeof(rsn_ie_tlv->header) + + le16_to_cpu(rsn_ie_tlv->header.len); + *buffer += rsn_ie_len; + + return rsn_ie_len; +} + +/* Build 802.11 association command and required TLVs */ +int nxpwifi_cmd_802_11_associate(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + struct nxpwifi_bssdescriptor *bss_desc) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_ds_802_11_associate *assoc = &cmd->params.associate; + struct nxpwifi_ie_types_host_mlme *host_mlme_tlv; + struct nxpwifi_ie_types_ssid_param_set *ssid_tlv; + struct nxpwifi_ie_types_phy_param_set *phy_tlv; + struct nxpwifi_ie_types_ss_param_set *ss_tlv; + struct nxpwifi_ie_types_rates_param_set *rates_tlv; + struct nxpwifi_ie_types_auth_type *auth_tlv; + struct nxpwifi_ie_types_sae_pwe_mode *sae_pwe_tlv; + struct nxpwifi_ie_types_chan_list_param_set *chan_tlv; + u8 rates[NXPWIFI_SUPPORTED_RATES]; + u32 rates_size; + u16 tmp_cap; + u8 *pos; + int rsn_ie_len = 0; + int ret; + + pos = (u8 *)assoc; + + cmd->command = cpu_to_le16(HOST_CMD_802_11_ASSOCIATE); + + /* Save so we know which BSS Desc to use in the response handler */ + priv->attempted_bss_desc = bss_desc; + + memcpy(assoc->peer_sta_addr, + bss_desc->mac_address, sizeof(assoc->peer_sta_addr)); + pos += sizeof(assoc->peer_sta_addr); + + /* Set the listen interval */ + assoc->listen_interval = cpu_to_le16(priv->listen_interval); + /* Set the beacon period */ + assoc->beacon_period = cpu_to_le16(bss_desc->beacon_period); + + pos += sizeof(assoc->cap_info_bitmap); + pos += sizeof(assoc->listen_interval); + pos += sizeof(assoc->beacon_period); + pos += sizeof(assoc->dtim_period); + + host_mlme_tlv = (struct nxpwifi_ie_types_host_mlme *)pos; + host_mlme_tlv->header.type = cpu_to_le16(TLV_TYPE_HOST_MLME); + host_mlme_tlv->header.len = cpu_to_le16(sizeof(host_mlme_tlv->host_mlme)); + host_mlme_tlv->host_mlme = 1; + pos += sizeof(host_mlme_tlv->header) + sizeof(host_mlme_tlv->host_mlme); + + ssid_tlv = (struct nxpwifi_ie_types_ssid_param_set *)pos; + ssid_tlv->header.type = cpu_to_le16(WLAN_EID_SSID); + ssid_tlv->header.len = cpu_to_le16((u16)bss_desc->ssid.ssid_len); + memcpy(ssid_tlv->ssid, bss_desc->ssid.ssid, + le16_to_cpu(ssid_tlv->header.len)); + pos += sizeof(ssid_tlv->header) + le16_to_cpu(ssid_tlv->header.len); + + phy_tlv = (struct nxpwifi_ie_types_phy_param_set *)pos; + phy_tlv->header.type = cpu_to_le16(WLAN_EID_DS_PARAMS); + phy_tlv->header.len = cpu_to_le16(sizeof(phy_tlv->fh_ds.ds_param_set)); + memcpy(&phy_tlv->fh_ds.ds_param_set, + &bss_desc->phy_param_set.ds_param_set.current_chan, + sizeof(phy_tlv->fh_ds.ds_param_set)); + pos += sizeof(phy_tlv->header) + le16_to_cpu(phy_tlv->header.len); + + ss_tlv = (struct nxpwifi_ie_types_ss_param_set *)pos; + ss_tlv->header.type = cpu_to_le16(WLAN_EID_CF_PARAMS); + ss_tlv->header.len = cpu_to_le16(sizeof(ss_tlv->cf_ibss.cf_param_set)); + pos += sizeof(ss_tlv->header) + le16_to_cpu(ss_tlv->header.len); + + /* Get the common rates supported between the driver and the BSS Desc */ + ret = nxpwifi_setup_rates_from_bssdesc(priv, bss_desc, + rates, &rates_size); + if (ret) + return ret; + + /* Save the data rates into Current BSS state structure */ + priv->curr_bss_params.num_of_rates = rates_size; + memcpy(&priv->curr_bss_params.data_rates, rates, rates_size); + + /* Setup the Rates TLV in the association command */ + rates_tlv = (struct nxpwifi_ie_types_rates_param_set *)pos; + rates_tlv->header.type = cpu_to_le16(WLAN_EID_SUPP_RATES); + rates_tlv->header.len = cpu_to_le16((u16)rates_size); + memcpy(rates_tlv->rates, rates, rates_size); + pos += sizeof(rates_tlv->header) + rates_size; + nxpwifi_dbg(adapter, INFO, "info: ASSOC_CMD: rates size = %d\n", + rates_size); + + /* Add the Authentication type */ + auth_tlv = (struct nxpwifi_ie_types_auth_type *)pos; + auth_tlv->header.type = cpu_to_le16(TLV_TYPE_AUTH_TYPE); + auth_tlv->header.len = cpu_to_le16(sizeof(auth_tlv->auth_type)); + if (priv->sec_info.wep_enabled) + auth_tlv->auth_type = + cpu_to_le16((u16)priv->sec_info.authentication_mode); + else + auth_tlv->auth_type = cpu_to_le16(NL80211_AUTHTYPE_OPEN_SYSTEM); + + pos += sizeof(auth_tlv->header) + le16_to_cpu(auth_tlv->header.len); + + if (priv->sec_info.authentication_mode == WLAN_AUTH_SAE) { + auth_tlv->auth_type = cpu_to_le16(NXPWIFI_AUTHTYPE_SAE); + if (bss_desc->bcn_rsnx_ie && + bss_desc->bcn_rsnx_ie->datalen && + (bss_desc->bcn_rsnx_ie->data[0] & + WLAN_RSNX_CAPA_SAE_H2E)) { + sae_pwe_tlv = + (struct nxpwifi_ie_types_sae_pwe_mode *)pos; + sae_pwe_tlv->header.type = + cpu_to_le16(TLV_TYPE_SAE_PWE_MODE); + sae_pwe_tlv->header.len = + cpu_to_le16(sizeof(sae_pwe_tlv->pwe[0])); + sae_pwe_tlv->pwe[0] = bss_desc->bcn_rsnx_ie->data[0]; + pos += sizeof(sae_pwe_tlv->header) + + sizeof(sae_pwe_tlv->pwe[0]); + } + } + + if (IS_SUPPORT_MULTI_BANDS(adapter) && + !(ISSUPP_11NENABLED(adapter->fw_cap_info) && + !bss_desc->disable_11n && + (priv->config_bands & BAND_GN || + priv->config_bands & BAND_AN) && + bss_desc->bcn_ht_cap)) { + /* + * Append a channel TLV for the channel the attempted AP was + * found on + */ + chan_tlv = (struct nxpwifi_ie_types_chan_list_param_set *)pos; + chan_tlv->header.type = cpu_to_le16(TLV_TYPE_CHANLIST); + chan_tlv->header.len = + cpu_to_le16(sizeof(struct nxpwifi_chan_scan_param_set)); + + memset(chan_tlv->chan_scan_param, 0x00, + sizeof(struct nxpwifi_chan_scan_param_set)); + chan_tlv->chan_scan_param[0].chan_number = + (bss_desc->phy_param_set.ds_param_set.current_chan); + nxpwifi_dbg(adapter, INFO, "info: Assoc: TLV Chan = %d\n", + chan_tlv->chan_scan_param[0].chan_number); + + chan_tlv->chan_scan_param[0].band_cfg = + nxpwifi_band_to_radio_type((u8)bss_desc->bss_band); + + nxpwifi_dbg(adapter, INFO, "info: Assoc: TLV Band = %d\n", + chan_tlv->chan_scan_param[0].band_cfg); + pos += sizeof(chan_tlv->header) + + sizeof(struct nxpwifi_chan_scan_param_set); + } + + if (!priv->wps.session_enable) { + if (priv->sec_info.wpa_enabled || priv->sec_info.wpa2_enabled) + rsn_ie_len = nxpwifi_append_rsn_ie_wpa_wpa2(priv, &pos); + + if (rsn_ie_len == -ENOMEM) + return -ENOMEM; + } + + if (ISSUPP_11NENABLED(adapter->fw_cap_info) && + !bss_desc->disable_11n && + (priv->config_bands & BAND_GN || + priv->config_bands & BAND_AN)) + nxpwifi_cmd_append_11n_tlv(priv, bss_desc, &pos); + + if (ISSUPP_11ACENABLED(adapter->fw_cap_info) && + !bss_desc->disable_11n && !bss_desc->disable_11ac && + (priv->config_bands & BAND_GAC || + priv->config_bands & BAND_AAC)) + nxpwifi_cmd_append_11ac_tlv(priv, bss_desc, &pos); + + if (ISSUPP_11AXENABLED(adapter->fw_cap_ext) && + nxpwifi_11ax_bandconfig_allowed(priv, bss_desc)) + nxpwifi_cmd_append_11ax_tlv(priv, bss_desc, &pos); + + /* Append vendor specific element TLV */ + nxpwifi_cmd_append_vsie_tlv(priv, NXPWIFI_VSIE_MASK_ASSOC, &pos); + + nxpwifi_wmm_process_association_req(priv, &pos, &bss_desc->wmm_ie, + bss_desc->bcn_ht_cap); + + if (priv->wps.session_enable && priv->wps_ie_len) + nxpwifi_cmd_append_wps_ie(priv, &pos); + + nxpwifi_cmd_append_generic_ie(priv, &pos); + + nxpwifi_cmd_append_tsf_tlv(priv, &pos, bss_desc); + + nxpwifi_11h_process_join(priv, &pos, bss_desc); + + cmd->size = cpu_to_le16((u16)(pos - (u8 *)assoc) + S_DS_GEN); + + /* Set the Capability info at last */ + tmp_cap = bss_desc->cap_info_bitmap; + + if (priv->config_bands == BAND_B) + tmp_cap &= ~WLAN_CAPABILITY_SHORT_SLOT_TIME; + + tmp_cap &= CAPINFO_MASK; + nxpwifi_dbg(adapter, INFO, + "info: ASSOC_CMD: tmp_cap=%4X CAPINFO_MASK=%4lX\n", + tmp_cap, CAPINFO_MASK); + assoc->cap_info_bitmap = cpu_to_le16(tmp_cap); + + return ret; +} + +static const char *assoc_failure_reason_to_str(u16 cap_info) +{ + switch (cap_info) { + case CONNECT_ERR_AUTH_ERR_STA_FAILURE: + return "CONNECT_ERR_AUTH_ERR_STA_FAILURE"; + case CONNECT_ERR_AUTH_MSG_UNHANDLED: + return "CONNECT_ERR_AUTH_MSG_UNHANDLED"; + case CONNECT_ERR_ASSOC_ERR_TIMEOUT: + return "CONNECT_ERR_ASSOC_ERR_TIMEOUT"; + case CONNECT_ERR_ASSOC_ERR_AUTH_REFUSED: + return "CONNECT_ERR_ASSOC_ERR_AUTH_REFUSED"; + case CONNECT_ERR_STA_FAILURE: + return "CONNECT_ERR_STA_FAILURE"; + } + + return "Unknown connect failure"; +} + +/* + * Handle association command response. + * Parse cap_info/status_code/AID and copy IEs, update connection state, + * WMM and HT/HE parameters, queues and filters. On failure, return the + * IEEE status code or a mapped timeout error. + */ +int nxpwifi_ret_802_11_associate(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int ret = 0; + struct ieee_types_assoc_rsp *assoc_rsp; + struct nxpwifi_bssdescriptor *bss_desc; + bool enable_data = true; + u16 cap_info, status_code, aid; + const u8 *ie_ptr; + struct ieee80211_ht_operation *assoc_resp_ht_oper; + struct ieee80211_mgmt *hdr; + + if (!priv->attempted_bss_desc) { + nxpwifi_dbg(adapter, ERROR, + "%s: failed, association terminated by host\n", + __func__); + goto done; + } + + hdr = (struct ieee80211_mgmt *)&resp->params; + if (!memcmp(hdr->bssid, priv->attempted_bss_desc->mac_address, + ETH_ALEN)) + assoc_rsp = (struct ieee_types_assoc_rsp *)&hdr->u.assoc_resp; + else + assoc_rsp = (struct ieee_types_assoc_rsp *)&resp->params; + + cap_info = le16_to_cpu(assoc_rsp->cap_info_bitmap); + status_code = le16_to_cpu(assoc_rsp->status_code); + aid = le16_to_cpu(assoc_rsp->a_id); + + if ((aid & (BIT(15) | BIT(14))) != (BIT(15) | BIT(14))) + nxpwifi_dbg(adapter, ERROR, + "invalid AID value 0x%x; bits 15:14 not set\n", aid); + + aid &= ~(BIT(15) | BIT(14)); + + priv->assoc_rsp_size = min(le16_to_cpu(resp->size) - S_DS_GEN, + sizeof(priv->assoc_rsp_buf)); + + assoc_rsp->a_id = cpu_to_le16(aid); + memcpy(priv->assoc_rsp_buf, &resp->params, priv->assoc_rsp_size); + + if (status_code) { + adapter->dbg.num_cmd_assoc_failure++; + nxpwifi_dbg(adapter, ERROR, + "ASSOC_RESP: failed,\t" + "status code=%d err=%#x a_id=%#x\n", + status_code, cap_info, + le16_to_cpu(assoc_rsp->a_id)); + + nxpwifi_dbg(adapter, ERROR, "assoc failure: reason %s\n", + assoc_failure_reason_to_str(cap_info)); + if (cap_info == CONNECT_ERR_ASSOC_ERR_TIMEOUT) { + if (status_code == NXPWIFI_ASSOC_CMD_FAILURE_AUTH) { + ret = WLAN_STATUS_AUTH_TIMEOUT; + nxpwifi_dbg(adapter, ERROR, + "ASSOC_RESP: AUTH timeout\n"); + } else { + ret = WLAN_STATUS_UNSPECIFIED_FAILURE; + nxpwifi_dbg(adapter, ERROR, + "ASSOC_RESP: UNSPECIFIED failure\n"); + } + + priv->assoc_rsp_size = 0; + } else { + ret = status_code; + } + + goto done; + } + + /* Send a Media Connected event, according to the Spec */ + priv->media_connected = true; + + adapter->ps_state = PS_STATE_AWAKE; + adapter->pps_uapsd_mode = false; + adapter->tx_lock_flag = false; + + /* Set the attempted BSSID Index to current */ + bss_desc = priv->attempted_bss_desc; + + nxpwifi_dbg(adapter, INFO, "info: ASSOC_RESP: %s\n", + bss_desc->ssid.ssid); + + /* Make a copy of current BSSID descriptor */ + memcpy(&priv->curr_bss_params.bss_descriptor, + bss_desc, sizeof(struct nxpwifi_bssdescriptor)); + + /* Update curr_bss_params */ + priv->curr_bss_params.bss_descriptor.channel = + bss_desc->phy_param_set.ds_param_set.current_chan; + + priv->curr_bss_params.band = (u8)bss_desc->bss_band; + + if (bss_desc->wmm_ie.element_id == WLAN_EID_VENDOR_SPECIFIC) + priv->curr_bss_params.wmm_enabled = true; + else + priv->curr_bss_params.wmm_enabled = false; + + if ((priv->wmm_required || bss_desc->bcn_ht_cap) && + priv->curr_bss_params.wmm_enabled) + priv->wmm_enabled = true; + else + priv->wmm_enabled = false; + + priv->curr_bss_params.wmm_uapsd_enabled = false; + + priv->curr_bss_params.wmm_uapsd_enabled = priv->wmm_enabled && + (bss_desc->wmm_ie.qos_info & IEEE80211_WMM_IE_AP_QOSINFO_UAPSD); + + /* Store the bandwidth information from assoc response */ + ie_ptr = cfg80211_find_ie(WLAN_EID_HT_OPERATION, assoc_rsp->ie_buffer, + priv->assoc_rsp_size + - sizeof(struct ieee_types_assoc_rsp)); + if (ie_ptr) { + assoc_resp_ht_oper = (struct ieee80211_ht_operation *)(ie_ptr + + sizeof(struct element)); + priv->assoc_resp_ht_param = assoc_resp_ht_oper->ht_param; + priv->ht_param_present = true; + } else { + priv->ht_param_present = false; + } + + nxpwifi_dbg(adapter, INFO, + "info: ASSOC_RESP: curr_pkt_filter is %#x\n", + priv->curr_pkt_filter); + if (priv->sec_info.wpa_enabled || priv->sec_info.wpa2_enabled) + priv->wpa_is_gtk_set = false; + + if (priv->wmm_enabled) { + /* Don't re-enable carrier until we get the WMM_GET_STATUS event */ + enable_data = false; + } else { + /* Since WMM is not enabled, setup the queues with the defaults */ + nxpwifi_wmm_setup_queue_priorities(priv, NULL); + nxpwifi_wmm_setup_ac_downgrade(priv); + } + + if (enable_data) + nxpwifi_dbg(adapter, INFO, + "info: post association, re-enabling data flow\n"); + + /* Reset SNR/NF/RSSI values */ + priv->data_rssi_last = 0; + priv->data_nf_last = 0; + priv->data_rssi_avg = 0; + priv->data_nf_avg = 0; + priv->bcn_rssi_last = 0; + priv->bcn_nf_last = 0; + priv->bcn_rssi_avg = 0; + priv->bcn_nf_avg = 0; + priv->rxpd_rate = 0; + priv->rxpd_htinfo = 0; + + nxpwifi_save_curr_bcn(priv); + + adapter->dbg.num_cmd_assoc_success++; + + nxpwifi_dbg(adapter, MSG, "assoc: associated with %pM\n", + priv->attempted_bss_desc->mac_address); + + /* Add the ra_list here for infra mode as there will be only 1 ra always */ + nxpwifi_ralist_add(priv, + priv->curr_bss_params.bss_descriptor.mac_address); + + netif_carrier_on(priv->netdev); + nxpwifi_wake_up_net_dev_queue(priv->netdev, adapter); + + if (priv->sec_info.wpa_enabled || priv->sec_info.wpa2_enabled) + priv->scan_block = true; + else + priv->port_open = true; + +done: + /* Need to indicate IOCTL complete */ + if (adapter->curr_cmd->wait_q_enabled) { + if (ret) + adapter->cmd_wait_q.status = -1; + else + adapter->cmd_wait_q.status = 0; + } + + return ret; +} + +/* Associate to the specified BSS (STA only) */ +int nxpwifi_associate(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc) +{ + /* + * Return error if the adapter is not STA role or table entry + * is not marked as infra. + */ + if ((GET_BSS_ROLE(priv) != NXPWIFI_BSS_ROLE_STA) || + bss_desc->bss_mode != NL80211_IFTYPE_STATION) + return -EINVAL; + + if (ISSUPP_11ACENABLED(priv->adapter->fw_cap_info) && + !bss_desc->disable_11n && !bss_desc->disable_11ac && + priv->config_bands & BAND_AAC) + nxpwifi_set_11ac_ba_params(priv); + else + nxpwifi_set_ba_params(priv); + + /* + * Clear any past association response stored for application + * retrieval + */ + priv->assoc_rsp_size = 0; + + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_ASSOCIATE, + HOST_ACT_GEN_SET, 0, bss_desc, true); +} + +/* Send deauth to disconnect from an infrastructure BSS */ +static int nxpwifi_deauthenticate_infra(struct nxpwifi_private *priv, u8 *mac) +{ + u8 mac_address[ETH_ALEN]; + int ret; + + if (!mac || is_zero_ether_addr(mac)) + memcpy(mac_address, + priv->curr_bss_params.bss_descriptor.mac_address, + ETH_ALEN); + else + memcpy(mac_address, mac, ETH_ALEN); + + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_DEAUTHENTICATE, + HOST_ACT_GEN_SET, 0, mac_address, true); + + return ret; +} + +/* Disconnect from the current BSS (STA/P2P/AP) */ +int nxpwifi_deauthenticate(struct nxpwifi_private *priv, u8 *mac) +{ + int ret = 0; + + if (!priv->media_connected) + return 0; + + priv->auth_flag = 0; + priv->auth_alg = WLAN_AUTH_NONE; + priv->host_mlme_reg = false; + priv->mgmt_frame_mask = 0; + + ret = nxpwifi_send_cmd(priv, HOST_CMD_MGMT_FRAME_REG, + HOST_ACT_GEN_SET, 0, + &priv->mgmt_frame_mask, false); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "could not unregister mgmt frame rx\n"); + return ret; + } + + switch (priv->bss_mode) { + case NL80211_IFTYPE_STATION: + ret = nxpwifi_deauthenticate_infra(priv, mac); + if (ret) + cfg80211_disconnected(priv->netdev, 0, NULL, 0, + true, GFP_KERNEL); + break; + case NL80211_IFTYPE_AP: + ret = nxpwifi_send_cmd(priv, HOST_CMD_UAP_BSS_STOP, + HOST_ACT_GEN_SET, 0, NULL, true); + break; + default: + break; + } + + return ret; +} + +/* Deauthenticate/disconnect from all BSS. */ +void nxpwifi_deauthenticate_all(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_private *priv; + int i; + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + nxpwifi_deauthenticate(priv, NULL); + } +} +EXPORT_SYMBOL_GPL(nxpwifi_deauthenticate_all); + +/* Convert band to radio type used in channel TLV. */ +u8 nxpwifi_band_to_radio_type(u16 config_bands) +{ + if (config_bands & BAND_A || config_bands & BAND_AN || + config_bands & BAND_AAC || config_bands & BAND_AAX) + return HOST_SCAN_RADIO_TYPE_A; + + return HOST_SCAN_RADIO_TYPE_BG; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/main.c b/drivers/net/wireless/nxp/nxpwifi/main.c new file mode 100644 index 000000000000..a7c6d592e2ff --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/main.c @@ -0,0 +1,1673 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP Wireless LAN device driver: major functions + * + * Copyright 2011-2024 NXP + */ + +#include + +#include "main.h" +#include "cmdevt.h" +#include "wmm.h" +#include "cfg80211.h" +#include "11n.h" + +#define VERSION "1.0" + +static unsigned int debug_mask = NXPWIFI_DEFAULT_DEBUG_MASK; + +char driver_version[] = "nxpwifi " VERSION " (%s) "; + +const u16 nxpwifi_1d_to_wmm_queue[8] = { 1, 0, 0, 1, 2, 2, 3, 3 }; + +/* Optional RF calibration data file */ +static const char *cal_data_name = "nxp/cal_data.conf"; + +/* Register device; init adapter/privs/if_ops/locks; cleanup on fail. */ +static struct nxpwifi_adapter *nxpwifi_register(void *card, struct device *dev, + struct nxpwifi_if_ops *if_ops) +{ + struct nxpwifi_adapter *adapter; + int ret = 0; + int i; + + adapter = kzalloc_obj(*adapter, GFP_KERNEL); + if (!adapter) + return ERR_PTR(-ENOMEM); + + adapter->dev = dev; + adapter->card = card; + + /* Save interface specific operations in adapter */ + memmove(&adapter->if_ops, if_ops, sizeof(struct nxpwifi_if_ops)); + adapter->debug_mask = debug_mask; + + /* card specific initialization has been deferred until now .. */ + if (adapter->if_ops.init_if) { + ret = adapter->if_ops.init_if(adapter); + if (ret) + goto error; + } + + adapter->priv_num = 0; + + for (i = 0; i < NXPWIFI_MAX_BSS_NUM; i++) { + /* Allocate memory for private structure */ + adapter->priv[i] = + kzalloc_obj(struct nxpwifi_private, GFP_KERNEL); + if (!adapter->priv[i]) { + ret = -ENOMEM; + goto error; + } + + adapter->priv[i]->adapter = adapter; + adapter->priv_num++; + } + nxpwifi_init_lock_list(adapter); + + timer_setup(&adapter->cmd_timer, nxpwifi_cmd_timeout_func, 0); + + if (ret) + return ERR_PTR(ret); + else + return adapter; + +error: + nxpwifi_dbg(adapter, ERROR, + "info: leave %s with error\n", __func__); + + for (i = 0; i < adapter->priv_num; i++) + kfree(adapter->priv[i]); + + kfree(adapter); + + return ERR_PTR(ret); +} + +/* Unregister device; free timers, beacons, privs, nd_info, adapter. */ +static void nxpwifi_unregister(struct nxpwifi_adapter *adapter) +{ + s32 i; + + if (adapter->if_ops.cleanup_if) + adapter->if_ops.cleanup_if(adapter); + + timer_delete_sync(&adapter->cmd_timer); + + /* Free private structures */ + for (i = 0; i < adapter->priv_num; i++) { + nxpwifi_free_curr_bcn(adapter->priv[i]); + kfree(adapter->priv[i]); + } + + if (adapter->nd_info) { + for (i = 0 ; i < adapter->nd_info->n_matches ; i++) + kfree(adapter->nd_info->matches[i]); + kfree(adapter->nd_info); + adapter->nd_info = NULL; + } + + kfree(adapter->regd); + + kfree(adapter); +} + +static void nxpwifi_queue_rx_work(struct nxpwifi_adapter *adapter) +{ + queue_work(adapter->rx_workqueue, &adapter->rx_work); +} + +static void nxpwifi_process_rx(struct nxpwifi_adapter *adapter) +{ + struct sk_buff *skb; + struct nxpwifi_rxinfo *rx_info; + + if (atomic_read(&adapter->iface_changing) || + atomic_read(&adapter->rx_ba_teardown_pending)) + return; + + /* Check for Rx data */ + while ((skb = skb_dequeue(&adapter->rx_data_q))) { + atomic_dec(&adapter->rx_pending); + if (adapter->delay_main_work && + (atomic_read(&adapter->rx_pending) < LOW_RX_PENDING)) { + adapter->delay_main_work = false; + nxpwifi_queue_work(adapter, &adapter->main_work); + } + rx_info = NXPWIFI_SKB_RXCB(skb); + if (rx_info->buf_type == NXPWIFI_TYPE_AGGR_DATA) { + if (adapter->if_ops.deaggr_pkt) + adapter->if_ops.deaggr_pkt(adapter, skb); + dev_kfree_skb_any(skb); + } else { + nxpwifi_handle_rx_packet(adapter, skb); + } + } +} + +static void maybe_quirk_fw_disable_ds(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_private *priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_STA); + struct nxpwifi_ver_ext ver_ext; + + if (test_and_set_bit(NXPWIFI_IS_REQUESTING_FW_VEREXT, &adapter->work_flags)) + return; + + memset(&ver_ext, 0, sizeof(ver_ext)); + ver_ext.version_str_sel = 1; + if (nxpwifi_send_cmd(priv, HOST_CMD_VERSION_EXT, + HOST_ACT_GEN_GET, 0, &ver_ext, false)) { + nxpwifi_dbg(priv->adapter, MSG, + "Checking hardware revision failed.\n"); + } +} + +static void nxpwifi_handle_irq_status(struct nxpwifi_adapter *adapter, u8 istat) +{ + if (adapter->hs_activated) + nxpwifi_process_hs_config(adapter); + if (adapter->if_ops.process_int_status) + adapter->if_ops.process_int_status(adapter, istat); +} + +static bool nxpwifi_drain_tx(struct nxpwifi_adapter *adapter) +{ + bool ret = false; + + if ((adapter->scan_chan_gap_enabled || !adapter->scan_processing) && + !adapter->data_sent && !skb_queue_empty(&adapter->tx_data_q)) { + if (adapter->hs_activated_manually) { + nxpwifi_cancel_hs(nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY), + NXPWIFI_ASYNC_CMD); + adapter->hs_activated_manually = false; + } + + nxpwifi_process_tx_queue(adapter); + if (adapter->hs_activated) { + clear_bit(NXPWIFI_IS_HS_CONFIGURED, + &adapter->work_flags); + nxpwifi_hs_activated_event + (nxpwifi_get_priv + (adapter, NXPWIFI_BSS_ROLE_ANY), + false); + } + ret = true; + } + + if ((adapter->scan_chan_gap_enabled || + !adapter->scan_processing) && + !adapter->data_sent && + !nxpwifi_bypass_txlist_empty(adapter)) { + if (adapter->hs_activated_manually) { + nxpwifi_cancel_hs(nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY), + NXPWIFI_ASYNC_CMD); + adapter->hs_activated_manually = false; + } + nxpwifi_process_bypass_tx(adapter); + if (adapter->hs_activated) { + clear_bit(NXPWIFI_IS_HS_CONFIGURED, + &adapter->work_flags); + nxpwifi_hs_activated_event + (nxpwifi_get_priv + (adapter, NXPWIFI_BSS_ROLE_ANY), + false); + } + ret = true; + } + + if ((adapter->scan_chan_gap_enabled || + !adapter->scan_processing) && + !adapter->data_sent && !nxpwifi_wmm_lists_empty(adapter)) { + if (adapter->hs_activated_manually) { + nxpwifi_cancel_hs(nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY), + NXPWIFI_ASYNC_CMD); + adapter->hs_activated_manually = false; + } + + nxpwifi_wmm_process_tx(adapter); + if (adapter->hs_activated) { + clear_bit(NXPWIFI_IS_HS_CONFIGURED, + &adapter->work_flags); + nxpwifi_hs_activated_event + (nxpwifi_get_priv + (adapter, NXPWIFI_BSS_ROLE_ANY), + false); + } + ret = true; + } + + return ret; +} + +static bool nxpwifi_handle_rx(struct nxpwifi_adapter *adapter) +{ + if (adapter->rx_work_enabled && adapter->data_received) { + nxpwifi_queue_rx_work(adapter); + return true; + } + + return false; +} + +static bool nxpwifi_handle_cmd_response(struct nxpwifi_adapter *adapter) +{ + /* Check for Cmd Resp */ + if (adapter->cmd_resp_received) { + adapter->cmd_resp_received = false; + nxpwifi_process_cmdresp(adapter); + return true; + } + + return false; +} + +static bool nxpwifi_handle_events(struct nxpwifi_adapter *adapter) +{ + if (adapter->event_received) { + adapter->event_received = false; + nxpwifi_process_event(adapter); + return true; + } + + return false; +} + +static bool nxpwifi_tx_has_pending(struct nxpwifi_adapter *adapter) +{ + return !skb_queue_empty(&adapter->tx_data_q) || + !nxpwifi_bypass_txlist_empty(adapter) || + !nxpwifi_wmm_lists_empty(adapter); +} + +static bool nxpwifi_cmd_has_pending(struct nxpwifi_adapter *adapter) +{ + return !list_empty(&adapter->cmd_pending_q); +} + +static bool nxpwifi_events_has_pending(struct nxpwifi_adapter *adapter) +{ + return adapter->event_received; +} + +static bool nxpwifi_should_wakeup_card(struct nxpwifi_adapter *adapter) +{ + if (adapter->ps_state != PS_STATE_SLEEP) + return false; + + if (!adapter->pm_wakeup_card_req || adapter->pm_wakeup_fw_try) + return false; + + return is_command_pending(adapter) || nxpwifi_tx_has_pending(adapter); +} + +static bool nxpwifi_should_exit_main_loop(struct nxpwifi_adapter *adapter) +{ + if (adapter->pm_wakeup_fw_try) + return true; + + if (adapter->ps_state == PS_STATE_PRE_SLEEP) + nxpwifi_check_ps_cond(adapter); + + if (adapter->ps_state != PS_STATE_AWAKE) + return true; + + if (adapter->tx_lock_flag) + return true; + + if ((!adapter->scan_chan_gap_enabled && adapter->scan_processing) || + adapter->data_sent || !nxpwifi_tx_has_pending(adapter)) { + if (adapter->cmd_sent || adapter->curr_cmd || + !is_command_pending(adapter)) + return true; + } + + return false; +} + +static void nxpwifi_wakeup_card(struct nxpwifi_adapter *adapter) +{ + adapter->pm_wakeup_fw_try = true; + mod_timer(&adapter->wakeup_timer, jiffies + (HZ * 3)); + adapter->if_ops.wakeup(adapter); +} + +static void nxpwifi_handle_vdll_download(struct nxpwifi_adapter *adapter) +{ + if (!adapter->cmd_sent && adapter->vdll_ctrl.pending_block) { + struct vdll_dnld_ctrl *ctrl = &adapter->vdll_ctrl; + + nxpwifi_download_vdll_block(adapter, ctrl->pending_block, + ctrl->pending_block_len); + ctrl->pending_block = NULL; + } +} + +static void nxpwifi_finish_delayed_null_pkt(struct nxpwifi_adapter *adapter) +{ + if (!adapter->delay_null_pkt) + return; + + if (adapter->cmd_sent || adapter->curr_cmd || is_command_pending(adapter)) + return; + + if (nxpwifi_tx_has_pending(adapter)) + return; + + if (!nxpwifi_send_null_packet(nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_STA), + NXPWIFI_TxPD_POWER_MGMT_NULL_PACKET | + NXPWIFI_TxPD_POWER_MGMT_LAST_PACKET)) { + adapter->delay_null_pkt = false; + adapter->ps_state = PS_STATE_SLEEP; + } +} + +static bool nxpwifi_pump_command(struct nxpwifi_adapter *adapter) +{ + if (!adapter->cmd_sent && !adapter->curr_cmd) { + if (!nxpwifi_exec_next_cmd(adapter)) + return true; + } + + return false; +} + +/* Main loop: IRQ/RX/CMD/EVENT; wake card; TX; PS null; exit if idle. */ +void nxpwifi_main_process(struct nxpwifi_adapter *adapter) +{ + unsigned long flags; + + /* Check if virtual interface changing */ + if (atomic_read(&adapter->iface_changing)) { + nxpwifi_dbg(adapter, + INFO, "main_process skipped due to iface_changing"); + return; + } + + for (;;) { + bool did_work = false; + u8 istat = 0; + + if (adapter->hw_status == NXPWIFI_HW_STATUS_NOT_READY) + break; + + /* + * For non-USB interfaces, If we process interrupts first, it + * would increase RX pending even further. Avoid this by + * checking if rx_pending has crossed high threshold and + * schedule rx work queue and then process interrupts. + * For USB interface, there are no interrupts. We already have + * HIGH_RX_PENDING check in usb.c + */ + if (atomic_read(&adapter->rx_pending) >= HIGH_RX_PENDING) { + adapter->delay_main_work = true; + nxpwifi_queue_rx_work(adapter); + break; + } + + /* + * Snapshot-and-clear the interrupt status. + * + * Take the same lock as the producer (nxpwifi_sdio_interrupt()) uses + * when OR-ing new bits into adapter->int_status. We atomically grab + * what has accumulated and clear it, so this consumer owns this batch. + */ + spin_lock_irqsave(&adapter->int_lock, flags); + istat = adapter->int_status; + adapter->int_status = 0; + spin_unlock_irqrestore(&adapter->int_lock, flags); + + /* Handle pending interrupt if any */ + if (istat) { + nxpwifi_handle_irq_status(adapter, istat); + did_work = true; + } + + did_work |= nxpwifi_handle_rx(adapter); + + if (nxpwifi_should_wakeup_card(adapter)) { + nxpwifi_wakeup_card(adapter); + continue; + } + + if (IS_CARD_RX_RCVD(adapter)) { + /* Card has responded, clear wakeup state and update power state */ + adapter->data_received = false; + adapter->pm_wakeup_fw_try = false; + timer_delete(&adapter->wakeup_timer); + if (adapter->ps_state == PS_STATE_SLEEP) + adapter->ps_state = PS_STATE_AWAKE; + } else { + if (nxpwifi_should_exit_main_loop(adapter)) + break; + } + + did_work |= nxpwifi_handle_events(adapter); + + did_work |= nxpwifi_handle_cmd_response(adapter); + + /* Check if we need to confirm Sleep Request received previously */ + if (adapter->ps_state == PS_STATE_PRE_SLEEP) + nxpwifi_check_ps_cond(adapter); + + /* + * The ps_state may have been changed during processing of + * Sleep Request event. + */ + if (adapter->ps_state != PS_STATE_AWAKE) + continue; + + if (adapter->tx_lock_flag) + continue; + + nxpwifi_handle_vdll_download(adapter); + + did_work |= nxpwifi_pump_command(adapter); + + did_work |= nxpwifi_drain_tx(adapter); + + /* + * Attempt to send delayed null packet. + * If successful, firmware will enter sleep and ps_state will be updated. + * We check ps_state here to determine if main loop can safely exit. + */ + nxpwifi_finish_delayed_null_pkt(adapter); + + if (adapter->ps_state == PS_STATE_SLEEP) + break; + /* + * Step 3) Cooperative preemption point. + * cond_resched() yields ONLY if need_resched() is set. Placing it + * BEFORE the final check improves fairness: it lets ksdioirqd (RT/FIFO) + * or other producers run and set new int_status bits. Immediately + * after we return here, we perform the final "net cast" (Step 4) to + * decide if we should continue or return. + */ + + cond_resched(); + + /* + * Step 4) Exit decision with lost-kick closure. + * + * We consider exiting ONLY when this round did no real work. + * Rationale: + * - If did_work == true: we will loop anyway; at Step 1 we will + * re-snapshot int_status, so there's no need to re-check now. + * - If did_work == false: we appear idle and may return. But during + * our execution window, a producer may have just set int_status and + * queue_work(); since this work is still running, queue_work() + * returns false (no second instance queued). If we return now, + * we'd leave unprocessed status with no pending work => lost-kick. + * + * Therefore, perform a single final check: if *anything* is pending, + * continue looping; otherwise, break and return. + */ + if (!did_work) { + bool more = false; + unsigned long flags; + /* 4a) New IRQ bits raced in while we were running? */ + spin_lock_irqsave(&adapter->int_lock, flags); + more |= adapter->int_status != 0; + spin_unlock_irqrestore(&adapter->int_lock, flags); + /* 4b) Any other sources still pending? (driver-specific) */ + more |= nxpwifi_tx_has_pending(adapter); + more |= nxpwifi_cmd_has_pending(adapter); + more |= nxpwifi_events_has_pending(adapter); + + if (!more) + break; /* Truly quiescent now: safe to return. */ + /* else: loop back to Step 1 to consume what just arrived. */ + } + /* If did_work == true, we loop unconditionally and re-snapshot. */ + }; +} + +/* Free adapter via nxpwifi_unregister(). */ +static void nxpwifi_free_adapter(struct nxpwifi_adapter *adapter) +{ + if (!adapter) { + pr_err("%s: adapter is NULL\n", __func__); + return; + } + + nxpwifi_unregister(adapter); + pr_debug("info: %s: free adapter\n", __func__); +} + +/* Destroy main and RX workqueues. */ +static void nxpwifi_terminate_workqueue(struct nxpwifi_adapter *adapter) +{ + if (adapter->workqueue) { + destroy_workqueue(adapter->workqueue); + adapter->workqueue = NULL; + } + + if (adapter->rx_workqueue) { + destroy_workqueue(adapter->rx_workqueue); + adapter->rx_workqueue = NULL; + } +} + +/* FW bring-up: download, enable IRQ, init FW; cfg80211+ifaces; cleanup. */ +static int _nxpwifi_fw_dpc(const struct firmware *firmware, void *context) +{ + int ret = 0; + char fmt[64]; + struct nxpwifi_adapter *adapter = context; + struct nxpwifi_fw_image fw; + bool init_failed = false; + struct wireless_dev *wdev; + struct completion *fw_done = adapter->fw_done; + + if (!firmware) { + nxpwifi_dbg(adapter, ERROR, + "Failed to get firmware %s\n", adapter->fw_name); + ret = -EINVAL; + goto err_dnld_fw; + } + + memset(&fw, 0, sizeof(struct nxpwifi_fw_image)); + adapter->firmware = firmware; + fw.fw_buf = (u8 *)adapter->firmware->data; + fw.fw_len = adapter->firmware->size; + + if (adapter->if_ops.dnld_fw) + ret = adapter->if_ops.dnld_fw(adapter, &fw); + else + ret = nxpwifi_dnld_fw(adapter, &fw); + + if (ret) + goto err_dnld_fw; + + nxpwifi_dbg(adapter, MSG, "WLAN FW is active\n"); + + /* Load optional calibration data */ + ret = request_firmware(&adapter->cal_data, cal_data_name, adapter->dev); + if (ret) { + nxpwifi_dbg(adapter, INFO, "no %s, using default cal\n", + cal_data_name); + adapter->cal_data = NULL; + } + + /* enable host interrupt after fw dnld is successful */ + if (adapter->if_ops.enable_int) { + ret = adapter->if_ops.enable_int(adapter); + if (ret) + goto err_dnld_fw; + } + + ret = nxpwifi_init_fw(adapter); + if (ret) + goto err_init_fw; + + maybe_quirk_fw_disable_ds(adapter); + + if (!adapter->wiphy) { + if (nxpwifi_register_cfg80211(adapter)) { + nxpwifi_dbg(adapter, ERROR, + "cannot register with cfg80211\n"); + goto err_init_fw; + } + } + + if (nxpwifi_init_channel_scan_gap(adapter)) { + nxpwifi_dbg(adapter, ERROR, + "could not init channel stats table\n"); + goto err_init_chan_scan; + } + + rtnl_lock(); + /* Create station interface by default */ + wdev = nxpwifi_add_virtual_intf(adapter->wiphy, "mlan%d", NET_NAME_ENUM, + NL80211_IFTYPE_STATION, NULL); + if (IS_ERR(wdev)) { + nxpwifi_dbg(adapter, ERROR, + "cannot create default STA interface\n"); + rtnl_unlock(); + goto err_add_intf; + } + + wdev = nxpwifi_add_virtual_intf(adapter->wiphy, "uap%d", NET_NAME_ENUM, + NL80211_IFTYPE_AP, NULL); + if (IS_ERR(wdev)) { + nxpwifi_dbg(adapter, ERROR, + "cannot create AP interface\n"); + rtnl_unlock(); + goto err_add_intf; + } + + rtnl_unlock(); + + nxpwifi_drv_get_driver_version(adapter, fmt, sizeof(fmt) - 1); + nxpwifi_dbg(adapter, MSG, "driver_version = %s\n", fmt); + adapter->is_up = true; + goto done; + +err_add_intf: + vfree(adapter->chan_stats); +err_init_chan_scan: + wiphy_unregister(adapter->wiphy); + wiphy_free(adapter->wiphy); +err_init_fw: + if (adapter->if_ops.disable_int) + adapter->if_ops.disable_int(adapter); +err_dnld_fw: + nxpwifi_dbg(adapter, ERROR, + "info: %s: unregister device\n", __func__); + if (adapter->if_ops.unregister_dev) + adapter->if_ops.unregister_dev(adapter); + + set_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags); + nxpwifi_terminate_workqueue(adapter); + + if (adapter->hw_status == NXPWIFI_HW_STATUS_READY) { + pr_debug("info: %s: shutdown nxpwifi\n", __func__); + nxpwifi_shutdown_drv(adapter); + nxpwifi_free_cmd_buffers(adapter); + } + + init_failed = true; +done: + if (adapter->cal_data) { + release_firmware(adapter->cal_data); + adapter->cal_data = NULL; + } + if (adapter->firmware) { + release_firmware(adapter->firmware); + adapter->firmware = NULL; + } + if (init_failed) + nxpwifi_free_adapter(adapter); + + /* Tell all current and future waiters we're finished */ + complete_all(fw_done); + + return ret; +} + +static void nxpwifi_fw_dpc(const struct firmware *firmware, void *context) +{ + _nxpwifi_fw_dpc(firmware, context); +} + +/* Request firmware (sync/async) and start HW init. */ +static int nxpwifi_init_hw_fw(struct nxpwifi_adapter *adapter, + bool req_fw_nowait) +{ + int ret; + + if (req_fw_nowait) { + ret = request_firmware_nowait(THIS_MODULE, 1, adapter->fw_name, + adapter->dev, GFP_KERNEL, adapter, + nxpwifi_fw_dpc); + } else { + ret = request_firmware(&adapter->firmware, + adapter->fw_name, + adapter->dev); + } + + if (ret < 0) + nxpwifi_dbg(adapter, ERROR, "request_firmware%s error %d\n", + req_fw_nowait ? "_nowait" : "", ret); + return ret; +} + +/* ndo_open: bring carrier down. */ +static int +nxpwifi_open(struct net_device *dev) +{ + netif_carrier_off(dev); + + return 0; +} + +/* ndo_stop: abort scan/sched-scan if running. */ +static int +nxpwifi_close(struct net_device *dev) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + + if (priv->scan_request) { + struct cfg80211_scan_info info = { + .aborted = true, + }; + + nxpwifi_dbg(priv->adapter, INFO, + "aborting scan on ndo_stop\n"); + cfg80211_scan_done(priv->scan_request, &info); + priv->scan_request = NULL; + priv->scan_aborting = true; + } + + if (priv->sched_scanning) { + nxpwifi_dbg(priv->adapter, INFO, + "aborting bgscan on ndo_stop\n"); + nxpwifi_stop_bg_scan(priv); + cfg80211_sched_scan_stopped(priv->wdev.wiphy, 0); + } + + return 0; +} + +static bool +nxpwifi_bypass_tx_queue(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct ethhdr *eth_hdr = (struct ethhdr *)skb->data; + + if (eth_hdr->h_proto == htons(ETH_P_PAE) || + nxpwifi_is_skb_mgmt_frame(skb)) { + nxpwifi_dbg(priv->adapter, DATA, + "bypass txqueue; eth type %#x, mgmt %d\n", + ntohs(eth_hdr->h_proto), + nxpwifi_is_skb_mgmt_frame(skb)); + if (eth_hdr->h_proto == htons(ETH_P_PAE)) + nxpwifi_dbg(priv->adapter, MSG, + "key: send EAPOL to %pM\n", + eth_hdr->h_dest); + return true; + } + + return false; +} + +/* Queue SKB (WMM or bypass) and schedule main work. */ +void nxpwifi_queue_tx_pkt(struct nxpwifi_private *priv, struct sk_buff *skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct netdev_queue *txq; + int index = nxpwifi_1d_to_wmm_queue[skb->priority]; + + if (atomic_inc_return(&priv->wmm_tx_pending[index]) >= MAX_TX_PENDING) { + txq = netdev_get_tx_queue(priv->netdev, index); + if (!netif_tx_queue_stopped(txq)) { + netif_tx_stop_queue(txq); + nxpwifi_dbg(adapter, DATA, + "stop queue: %d\n", index); + } + } + + if (nxpwifi_bypass_tx_queue(priv, skb)) { + atomic_inc(&adapter->tx_pending); + atomic_inc(&adapter->bypass_tx_pending); + nxpwifi_wmm_add_buf_bypass_txqueue(priv, skb); + } else { + atomic_inc(&adapter->tx_pending); + nxpwifi_wmm_add_buf_txqueue(priv, skb); + } + + nxpwifi_queue_work(adapter, &adapter->main_work); +} + +struct sk_buff * +nxpwifi_clone_skb_for_tx_status(struct nxpwifi_private *priv, + struct sk_buff *skb, u8 flag, u64 *cookie) +{ + struct sk_buff *orig_skb = skb; + struct nxpwifi_txinfo *tx_info, *orig_tx_info; + u32 id32 = 0; + int ret; + + skb = skb_clone(skb, GFP_ATOMIC); + if (skb) { + spin_lock_bh(&priv->ack_status_lock); + /* + * Use XArray to allocate IDs in the range 1..0x0F. + * Limit ensures the allocated token ID is always within this + * range. + */ + ret = xa_alloc(&priv->ack_status_frames, &id32, orig_skb, + XA_LIMIT(1, 0x0f), GFP_ATOMIC); + spin_unlock_bh(&priv->ack_status_lock); + + if (ret == 0) { + tx_info = NXPWIFI_SKB_TXCB(skb); + tx_info->ack_frame_id = id32; + tx_info->flags |= flag; + orig_tx_info = NXPWIFI_SKB_TXCB(orig_skb); + orig_tx_info->ack_frame_id = id32; + orig_tx_info->flags |= flag; + + if (flag == NXPWIFI_BUF_FLAG_ACTION_TX_STATUS && cookie) + orig_tx_info->cookie = *cookie; + + } else if (skb_shared(skb)) { + kfree_skb(orig_skb); + } else { + kfree_skb(skb); + skb = orig_skb; + } + } else { + /* couldn't clone -- lose tx status ... */ + skb = orig_skb; + } + + return skb; +} + +/* ndo_start_xmit: fix headroom, fill TXCB, timestamp, enqueue. */ +static netdev_tx_t +nxpwifi_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + struct sk_buff *new_skb; + struct nxpwifi_txinfo *tx_info; + bool multicast; + + nxpwifi_dbg(priv->adapter, DATA, + "data: %lu BSS(%d-%d): Data <= kernel\n", + jiffies, priv->bss_type, priv->bss_num); + + if (test_bit(NXPWIFI_SURPRISE_REMOVED, &priv->adapter->work_flags)) { + kfree_skb(skb); + priv->stats.tx_dropped++; + return 0; + } + if (!skb->len || skb->len > ETH_FRAME_LEN) { + nxpwifi_dbg(priv->adapter, ERROR, + "Tx: bad skb len %d\n", skb->len); + kfree_skb(skb); + priv->stats.tx_dropped++; + return 0; + } + if (skb_headroom(skb) < NXPWIFI_MIN_DATA_HEADER_LEN) { + nxpwifi_dbg(priv->adapter, DATA, + "data: Tx: insufficient skb headroom %d\n", + skb_headroom(skb)); + /* Insufficient skb headroom - allocate a new skb */ + new_skb = + skb_realloc_headroom(skb, NXPWIFI_MIN_DATA_HEADER_LEN); + if (unlikely(!new_skb)) { + nxpwifi_dbg(priv->adapter, ERROR, + "Tx: cannot alloca new_skb\n"); + kfree_skb(skb); + priv->stats.tx_dropped++; + return 0; + } + kfree_skb(skb); + skb = new_skb; + nxpwifi_dbg(priv->adapter, INFO, + "info: new skb headroomd %d\n", + skb_headroom(skb)); + } + + tx_info = NXPWIFI_SKB_TXCB(skb); + memset(tx_info, 0, sizeof(*tx_info)); + tx_info->bss_num = priv->bss_num; + tx_info->bss_type = priv->bss_type; + tx_info->pkt_len = skb->len; + + multicast = is_multicast_ether_addr(skb->data); + + if (unlikely(!multicast && sk_requests_wifi_status(skb->sk) && + priv->adapter->fw_api_ver == NXPWIFI_FW_V15)) + skb = nxpwifi_clone_skb_for_tx_status(priv, + skb, + NXPWIFI_BUF_FLAG_EAPOL_TX_STATUS, NULL); + + /* + * Record the current time the packet was queued; used to + * determine the amount of time the packet was queued in + * the driver before it was sent to the firmware. + * The delay is then sent along with the packet to the + * firmware for aggregate delay calculation for stats and + * MSDU lifetime expiry. + */ + __net_timestamp(skb); + + nxpwifi_queue_tx_pkt(priv, skb); + + return 0; +} + +int nxpwifi_set_mac_address(struct nxpwifi_private *priv, + struct net_device *dev, bool external, + u8 *new_mac) +{ + int ret; + u64 mac_addr, old_mac_addr; + + old_mac_addr = ether_addr_to_u64(priv->curr_addr); + + if (external) { + mac_addr = ether_addr_to_u64(new_mac); + } else { + /* Internal mac address change */ + if (priv->bss_type == NXPWIFI_BSS_TYPE_ANY) + return -EOPNOTSUPP; + + mac_addr = old_mac_addr; + + if (priv->adapter->priv[0] != priv) { + /* Set mac address based on bss_type/bss_num */ + mac_addr ^= BIT_ULL(priv->bss_type + 8); + mac_addr += priv->bss_num; + } + } + + u64_to_ether_addr(mac_addr, priv->curr_addr); + + /* Send request to firmware */ + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_MAC_ADDRESS, + HOST_ACT_GEN_SET, 0, NULL, true); + + if (ret) { + u64_to_ether_addr(old_mac_addr, priv->curr_addr); + nxpwifi_dbg(priv->adapter, ERROR, + "set mac address failed: ret=%d\n", ret); + return ret; + } + + eth_hw_addr_set(dev, priv->curr_addr); + return 0; +} + +/* ndo_set_mac_address: set MAC via firmware. */ +static int +nxpwifi_ndo_set_mac_address(struct net_device *dev, void *addr) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + struct sockaddr *hw_addr = addr; + + return nxpwifi_set_mac_address(priv, dev, true, hw_addr->sa_data); +} + +/* ndo_set_rx_mode: promisc/allmulti or program multicast. */ +static void nxpwifi_set_multicast_list(struct net_device *dev) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + struct nxpwifi_multicast_list mcast_list; + + if (dev->flags & IFF_PROMISC) { + mcast_list.mode = NXPWIFI_PROMISC_MODE; + } else if (dev->flags & IFF_ALLMULTI || + netdev_mc_count(dev) > NXPWIFI_MAX_MULTICAST_LIST_SIZE) { + mcast_list.mode = NXPWIFI_ALL_MULTI_MODE; + } else { + mcast_list.mode = NXPWIFI_MULTICAST_MODE; + mcast_list.num_multicast_addr = + nxpwifi_copy_mcast_addr(&mcast_list, dev); + } + nxpwifi_request_set_multicast_list(priv, &mcast_list); +} + +/* ndo_tx_timeout: account; reset card on threshold. */ +static void +nxpwifi_tx_timeout(struct net_device *dev, unsigned int txqueue) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + + priv->num_tx_timeout++; + priv->tx_timeout_cnt++; + nxpwifi_dbg(priv->adapter, ERROR, + "%lu : Tx timeout(#%d), bss_type-num = %d-%d\n", + jiffies, priv->tx_timeout_cnt, priv->bss_type, + priv->bss_num); + nxpwifi_set_trans_start(dev); + + if (priv->tx_timeout_cnt > TX_TIMEOUT_THRESHOLD && + priv->adapter->if_ops.card_reset) { + nxpwifi_dbg(priv->adapter, ERROR, + "tx_timeout_cnt exceeds threshold.\t" + "Triggering card reset!\n"); + priv->adapter->if_ops.card_reset(priv->adapter); + } +} + +void nxpwifi_upload_device_dump(struct nxpwifi_adapter *adapter) +{ + /* + * Dump all the memory data into single file, a userspace script will + * be used to split all the memory data to multiple files + */ + nxpwifi_dbg(adapter, MSG, + "== nxpwifi dump information to /sys/class/devcoredump start\n"); + dev_coredumpv(adapter->dev, adapter->devdump_data, adapter->devdump_len, + GFP_KERNEL); + nxpwifi_dbg(adapter, MSG, + "== nxpwifi dump information to /sys/class/devcoredump end\n"); + + /* + * Device dump data will be freed in device coredump release function + * after 5 min. Here reset adapter->devdump_data and ->devdump_len + * to avoid it been accidentally reused. + */ + adapter->devdump_data = NULL; + adapter->devdump_len = 0; +} +EXPORT_SYMBOL_GPL(nxpwifi_upload_device_dump); + +void nxpwifi_drv_info_dump(struct nxpwifi_adapter *adapter) +{ + char *p; + char drv_version[64]; + struct sdio_mmc_card *sdio_card; + struct nxpwifi_private *priv; + int i, idx; + struct netdev_queue *txq; + struct nxpwifi_debug_info *debug_info; + + nxpwifi_dbg(adapter, MSG, "===nxpwifi driverinfo dump start===\n"); + + p = adapter->devdump_data; + strscpy(p, "========Start dump driverinfo========\n", NXPWIFI_FW_DUMP_SIZE); + p += strlen("========Start dump driverinfo========\n"); + p += sprintf(p, "driver_name = "); + p += sprintf(p, "\"nxpwifi\"\n"); + + nxpwifi_drv_get_driver_version(adapter, drv_version, + sizeof(drv_version) - 1); + p += sprintf(p, "driver_version = %s\n", drv_version); + + p += sprintf(p, "tx_pending = %d\n", + atomic_read(&adapter->tx_pending)); + p += sprintf(p, "rx_pending = %d\n", + atomic_read(&adapter->rx_pending)); + + if (adapter->iface_type == NXPWIFI_SDIO) { + sdio_card = (struct sdio_mmc_card *)adapter->card; + p += sprintf(p, "\nmp_rd_bitmap=0x%x curr_rd_port=0x%x\n", + sdio_card->mp_rd_bitmap, sdio_card->curr_rd_port); + p += sprintf(p, "mp_wr_bitmap=0x%x curr_wr_port=0x%x\n", + sdio_card->mp_wr_bitmap, sdio_card->curr_wr_port); + } + + for (i = 0; i < adapter->priv_num; i++) { + if (!adapter->priv[i]->netdev) + continue; + priv = adapter->priv[i]; + p += sprintf(p, "\n[interface : \"%s\"]\n", + priv->netdev->name); + p += sprintf(p, "wmm_tx_pending[0] = %d\n", + atomic_read(&priv->wmm_tx_pending[0])); + p += sprintf(p, "wmm_tx_pending[1] = %d\n", + atomic_read(&priv->wmm_tx_pending[1])); + p += sprintf(p, "wmm_tx_pending[2] = %d\n", + atomic_read(&priv->wmm_tx_pending[2])); + p += sprintf(p, "wmm_tx_pending[3] = %d\n", + atomic_read(&priv->wmm_tx_pending[3])); + p += sprintf(p, "media_state=\"%s\"\n", !priv->media_connected ? + "Disconnected" : "Connected"); + p += sprintf(p, "carrier %s\n", (netif_carrier_ok(priv->netdev) + ? "on" : "off")); + for (idx = 0; idx < priv->netdev->num_tx_queues; idx++) { + txq = netdev_get_tx_queue(priv->netdev, idx); + p += sprintf(p, "tx queue %d:%s ", idx, + netif_tx_queue_stopped(txq) ? + "stopped" : "started"); + } + p += sprintf(p, "\n%s: num_tx_timeout = %d\n", + priv->netdev->name, priv->num_tx_timeout); + } + + if (adapter->iface_type == NXPWIFI_SDIO) { + p += sprintf(p, "\n=== %s register dump===\n", "SDIO"); + if (adapter->if_ops.reg_dump) + p += adapter->if_ops.reg_dump(adapter, p); + } + p += sprintf(p, "\n=== more debug information\n"); + debug_info = kzalloc_obj(*debug_info, GFP_KERNEL); + if (debug_info) { + for (i = 0; i < adapter->priv_num; i++) { + if (!adapter->priv[i]->netdev) + continue; + priv = adapter->priv[i]; + nxpwifi_get_debug_info(priv, debug_info); + p += nxpwifi_debug_info_to_buffer(priv, p, debug_info); + break; + } + kfree(debug_info); + } + + p += sprintf(p, "\n========End dump========\n"); + nxpwifi_dbg(adapter, MSG, "===nxpwifi driverinfo dump end===\n"); + adapter->devdump_len = p - (char *)adapter->devdump_data; +} +EXPORT_SYMBOL_GPL(nxpwifi_drv_info_dump); + +void nxpwifi_prepare_fw_dump_info(struct nxpwifi_adapter *adapter) +{ + u8 idx; + char *fw_dump_ptr; + u32 dump_len = 0; + + for (idx = 0; idx < adapter->num_mem_types; idx++) { + struct memory_type_mapping *entry = + &adapter->mem_type_mapping_tbl[idx]; + + if (entry->mem_ptr) { + dump_len += (strlen("========Start dump ") + + strlen(entry->mem_name) + + strlen("========\n") + + (entry->mem_size + 1) + + strlen("\n========End dump========\n")); + } + } + + if (dump_len + 1 + adapter->devdump_len > NXPWIFI_FW_DUMP_SIZE) { + /* Realloc in case buffer overflow */ + fw_dump_ptr = vzalloc(dump_len + 1 + adapter->devdump_len); + nxpwifi_dbg(adapter, MSG, "Realloc device dump data.\n"); + if (!fw_dump_ptr) { + vfree(adapter->devdump_data); + nxpwifi_dbg(adapter, ERROR, + "vzalloc devdump data failure!\n"); + return; + } + + memmove(fw_dump_ptr, adapter->devdump_data, + adapter->devdump_len); + vfree(adapter->devdump_data); + adapter->devdump_data = fw_dump_ptr; + } + + fw_dump_ptr = (char *)adapter->devdump_data + adapter->devdump_len; + + for (idx = 0; idx < adapter->num_mem_types; idx++) { + struct memory_type_mapping *entry = + &adapter->mem_type_mapping_tbl[idx]; + + if (entry->mem_ptr) { + fw_dump_ptr += sprintf(fw_dump_ptr, "========Start dump "); + fw_dump_ptr += sprintf(fw_dump_ptr, "%s", entry->mem_name); + fw_dump_ptr += sprintf(fw_dump_ptr, "========\n"); + memcpy(fw_dump_ptr, entry->mem_ptr, entry->mem_size); + fw_dump_ptr += entry->mem_size; + fw_dump_ptr += sprintf(fw_dump_ptr, "\n========End dump========\n"); + } + } + + adapter->devdump_len = fw_dump_ptr - (char *)adapter->devdump_data; + + for (idx = 0; idx < adapter->num_mem_types; idx++) { + struct memory_type_mapping *entry = + &adapter->mem_type_mapping_tbl[idx]; + + vfree(entry->mem_ptr); + entry->mem_ptr = NULL; + entry->mem_size = 0; + } +} +EXPORT_SYMBOL_GPL(nxpwifi_prepare_fw_dump_info); + +/* ndo_get_stats: return netdev stats. */ +static struct net_device_stats *nxpwifi_get_stats(struct net_device *dev) +{ + struct nxpwifi_private *priv = nxpwifi_netdev_get_priv(dev); + + return &priv->stats; +} + +static u16 +nxpwifi_netdev_select_wmm_queue(struct net_device *dev, struct sk_buff *skb, + struct net_device *sb_dev) +{ + skb->priority = cfg80211_classify8021d(skb, NULL); + return nxpwifi_1d_to_wmm_queue[skb->priority]; +} + +/* Network device handlers */ +static const struct net_device_ops nxpwifi_netdev_ops = { + .ndo_open = nxpwifi_open, + .ndo_stop = nxpwifi_close, + .ndo_start_xmit = nxpwifi_hard_start_xmit, + .ndo_set_mac_address = nxpwifi_ndo_set_mac_address, + .ndo_validate_addr = eth_validate_addr, + .ndo_tx_timeout = nxpwifi_tx_timeout, + .ndo_get_stats = nxpwifi_get_stats, + .ndo_set_rx_mode = nxpwifi_set_multicast_list, + .ndo_select_queue = nxpwifi_netdev_select_wmm_queue, +}; + +/* Init per-interface defaults: ops, addrs, mgmt IEs, stats. */ +void nxpwifi_init_priv_params(struct nxpwifi_private *priv, + struct net_device *dev) +{ + dev->netdev_ops = &nxpwifi_netdev_ops; + dev->needs_free_netdev = true; + /* Initialize private structure */ + priv->current_key_index = 0; + priv->media_connected = false; + memset(priv->mgmt_ie, 0, + sizeof(struct nxpwifi_ie) * MAX_MGMT_IE_INDEX); + priv->beacon_idx = NXPWIFI_AUTO_IDX_MASK; + priv->proberesp_idx = NXPWIFI_AUTO_IDX_MASK; + priv->assocresp_idx = NXPWIFI_AUTO_IDX_MASK; + priv->gen_idx = NXPWIFI_AUTO_IDX_MASK; + priv->num_tx_timeout = 0; + if (is_valid_ether_addr(dev->dev_addr)) + ether_addr_copy(priv->curr_addr, dev->dev_addr); + else + ether_addr_copy(priv->curr_addr, priv->adapter->perm_addr); + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA || + GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + priv->hist_data = kmalloc_obj(*priv->hist_data, GFP_KERNEL); + if (priv->hist_data) + nxpwifi_hist_data_reset(priv); + } +} + +/* Return true if any command is pending. */ +int is_command_pending(struct nxpwifi_adapter *adapter) +{ + int is_cmd_pend_q_empty; + + spin_lock_bh(&adapter->cmd_pending_q_lock); + is_cmd_pend_q_empty = list_empty(&adapter->cmd_pending_q); + spin_unlock_bh(&adapter->cmd_pending_q_lock); + + return !is_cmd_pend_q_empty; +} + +/* Host MLME work: deliver RX; handle assoc/link-loss. */ +static void nxpwifi_host_mlme_work(struct wiphy *wiphy, struct wiphy_work *work) +{ + struct nxpwifi_adapter *adapter = + container_of(work, struct nxpwifi_adapter, host_mlme_work); + struct sk_buff *skb; + struct nxpwifi_rxinfo *rx_info; + struct nxpwifi_private *priv; + + if (test_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags)) + return; + + while ((skb = skb_dequeue(&adapter->rx_mlme_q))) { + rx_info = NXPWIFI_SKB_RXCB(skb); + priv = adapter->priv[rx_info->bss_num]; + cfg80211_rx_mlme_mgmt(priv->netdev, + skb->data, + rx_info->pkt_len); + } + + /* Check for host mlme disconnection */ + if (adapter->host_mlme_link_lost) { + if (adapter->priv_link_lost) { + nxpwifi_reset_connect_state(adapter->priv_link_lost, + WLAN_REASON_DEAUTH_LEAVING, + true); + adapter->priv_link_lost = NULL; + } + adapter->host_mlme_link_lost = false; + } + + /* Check for host mlme Assoc Resp */ + if (adapter->assoc_resp_received) { + nxpwifi_process_assoc_resp(adapter); + adapter->assoc_resp_received = false; + } +} + +/* RX work: process RX queue. */ +static void nxpwifi_rx_work(struct work_struct *work) +{ + struct nxpwifi_adapter *adapter = + container_of(work, struct nxpwifi_adapter, rx_work); + + if (test_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags)) + return; + nxpwifi_process_rx(adapter); +} + +/* Main work: run nxpwifi_main_process(). */ +static void nxpwifi_main_work(struct work_struct *work) +{ + struct nxpwifi_adapter *adapter = + container_of(work, struct nxpwifi_adapter, main_work); + + if (test_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags)) + return; + nxpwifi_main_process(adapter); +} + +/* Teardown: disable IRQs, stop queues, shutdown, remove ifaces, unreg. */ +static void nxpwifi_uninit_sw(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_private *priv; + int i; + + /* + * We can no longer handle interrupts once we start doing the teardown + * below. + */ + if (adapter->if_ops.disable_int) + adapter->if_ops.disable_int(adapter); + + set_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags); + nxpwifi_terminate_workqueue(adapter); + adapter->int_status = 0; + + /* Stop data */ + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + if (priv->netdev) { + nxpwifi_stop_net_dev_queue(priv->netdev, adapter); + netif_carrier_off(priv->netdev); + netif_device_detach(priv->netdev); + } + } + + nxpwifi_dbg(adapter, CMD, "cmd: calling nxpwifi_shutdown_drv...\n"); + nxpwifi_shutdown_drv(adapter); + nxpwifi_dbg(adapter, CMD, "cmd: nxpwifi_shutdown_drv done\n"); + + if (atomic_read(&adapter->rx_pending) || + atomic_read(&adapter->tx_pending) || + atomic_read(&adapter->cmd_pending)) { + nxpwifi_dbg(adapter, ERROR, + "rx_pending=%d, tx_pending=%d,\t" + "cmd_pending=%d\n", + atomic_read(&adapter->rx_pending), + atomic_read(&adapter->tx_pending), + atomic_read(&adapter->cmd_pending)); + } + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + rtnl_lock(); + if (priv->netdev && + priv->wdev.iftype != NL80211_IFTYPE_UNSPECIFIED) { + /* + * Close the netdev now, because if we do it later, the + * netdev notifiers will need to acquire the wiphy lock + * again --> deadlock. + */ + dev_close(priv->wdev.netdev); + wiphy_lock(adapter->wiphy); + nxpwifi_del_virtual_intf(adapter->wiphy, &priv->wdev); + wiphy_unlock(adapter->wiphy); + } + rtnl_unlock(); + } + + wiphy_unregister(adapter->wiphy); + wiphy_free(adapter->wiphy); + adapter->wiphy = NULL; + + vfree(adapter->chan_stats); + nxpwifi_free_cmd_buffers(adapter); +} + +/* Shut down SW/FW and mark device down. */ +void nxpwifi_shutdown_sw(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_private *priv; + + if (!adapter) + return; + + wait_for_completion(adapter->fw_done); + /* Caller should ensure we aren't suspending while this happens */ + reinit_completion(adapter->fw_done); + + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + nxpwifi_deauthenticate(priv, NULL); + + nxpwifi_init_shutdown_fw(priv, NXPWIFI_FUNC_SHUTDOWN); + + nxpwifi_uninit_sw(adapter); + adapter->is_up = false; +} +EXPORT_SYMBOL_GPL(nxpwifi_shutdown_sw); + +/* Re-init adapter SW and bring device up. */ +int +nxpwifi_reinit_sw(struct nxpwifi_adapter *adapter) +{ + int ret = 0; + + nxpwifi_init_lock_list(adapter); + if (adapter->if_ops.up_dev) + adapter->if_ops.up_dev(adapter); + + adapter->hw_status = NXPWIFI_HW_STATUS_INITIALIZING; + clear_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags); + clear_bit(NXPWIFI_IS_SUSPENDED, &adapter->work_flags); + adapter->hs_activated = false; + clear_bit(NXPWIFI_IS_CMD_TIMEDOUT, &adapter->work_flags); + init_waitqueue_head(&adapter->hs_activate_wait_q); + init_waitqueue_head(&adapter->cmd_wait_q.wait); + adapter->cmd_wait_q.status = 0; + adapter->scan_wait_q_woken = false; + + if (num_possible_cpus() > 1) + adapter->rx_work_enabled = true; + + adapter->workqueue = + alloc_workqueue("NXPWIFI_WORK_QUEUE", + WQ_HIGHPRI | WQ_MEM_RECLAIM | WQ_UNBOUND, 0); + if (!adapter->workqueue) { + ret = -ENOMEM; + goto err_kmalloc; + } + + INIT_WORK(&adapter->main_work, nxpwifi_main_work); + + if (adapter->rx_work_enabled) { + adapter->rx_workqueue = alloc_workqueue("NXPWIFI_RX_WORK_QUEUE", + WQ_HIGHPRI | + WQ_MEM_RECLAIM | + WQ_UNBOUND, 0); + if (!adapter->rx_workqueue) { + ret = -ENOMEM; + goto err_kmalloc; + } + INIT_WORK(&adapter->rx_work, nxpwifi_rx_work); + } + + wiphy_work_init(&adapter->host_mlme_work, nxpwifi_host_mlme_work); + + /* + * Register the device. Fill up the private data structure with + * relevant information from the card. Some code extracted from + * nxpwifi_register_dev() + */ + nxpwifi_dbg(adapter, INFO, "%s, nxpwifi_init_hw_fw()...\n", __func__); + + ret = nxpwifi_init_hw_fw(adapter, false); + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "%s: firmware init failed\n", __func__); + goto err_init_fw; + } + + /* _nxpwifi_fw_dpc() does its own cleanup */ + ret = _nxpwifi_fw_dpc(adapter->firmware, adapter); + if (ret) { + pr_err("Failed to bring up adapter: %d\n", ret); + return ret; + } + nxpwifi_dbg(adapter, INFO, "%s, successful\n", __func__); + + return ret; + +err_init_fw: + nxpwifi_dbg(adapter, ERROR, "info: %s: unregister device\n", __func__); + if (adapter->if_ops.unregister_dev) + adapter->if_ops.unregister_dev(adapter); + +err_kmalloc: + set_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags); + nxpwifi_terminate_workqueue(adapter); + if (adapter->hw_status == NXPWIFI_HW_STATUS_READY) { + nxpwifi_dbg(adapter, ERROR, + "info: %s: shutdown nxpwifi\n", __func__); + nxpwifi_shutdown_drv(adapter); + nxpwifi_free_cmd_buffers(adapter); + } + + complete_all(adapter->fw_done); + nxpwifi_dbg(adapter, INFO, "%s, error\n", __func__); + + return ret; +} +EXPORT_SYMBOL_GPL(nxpwifi_reinit_sw); + +/* Add card: register adapter, workqueues, device; request FW (async). */ +int +nxpwifi_add_card(void *card, struct completion *fw_done, + struct nxpwifi_if_ops *if_ops, u8 iface_type, + struct device *dev) +{ + struct nxpwifi_adapter *adapter; + int ret = 0; + + adapter = nxpwifi_register(card, dev, if_ops); + if (IS_ERR(adapter)) { + ret = PTR_ERR(adapter); + pr_err("%s: adapter register failed %d\n", __func__, ret); + goto err_init_sw; + } + + adapter->iface_type = iface_type; + adapter->fw_done = fw_done; + + adapter->hw_status = NXPWIFI_HW_STATUS_INITIALIZING; + clear_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags); + clear_bit(NXPWIFI_IS_SUSPENDED, &adapter->work_flags); + adapter->hs_activated = false; + init_waitqueue_head(&adapter->hs_activate_wait_q); + init_waitqueue_head(&adapter->cmd_wait_q.wait); + adapter->cmd_wait_q.status = 0; + adapter->scan_wait_q_woken = false; + + if (num_possible_cpus() > 1) + adapter->rx_work_enabled = true; + + adapter->workqueue = + alloc_workqueue("NXPWIFI_WORK_QUEUE", + WQ_HIGHPRI | WQ_MEM_RECLAIM | WQ_UNBOUND, 0); + if (!adapter->workqueue) { + ret = -ENOMEM; + goto err_kmalloc; + } + + INIT_WORK(&adapter->main_work, nxpwifi_main_work); + + if (adapter->rx_work_enabled) { + adapter->rx_workqueue = alloc_workqueue("NXPWIFI_RX_WORK_QUEUE", + WQ_HIGHPRI | + WQ_MEM_RECLAIM | + WQ_UNBOUND, 0); + if (!adapter->rx_workqueue) { + ret = -ENOMEM; + goto err_kmalloc; + } + + INIT_WORK(&adapter->rx_work, nxpwifi_rx_work); + } + + wiphy_work_init(&adapter->host_mlme_work, nxpwifi_host_mlme_work); + + /* + * Register the device. Fill up the private data structure with relevant + * information from the card. + */ + ret = adapter->if_ops.register_dev(adapter); + if (ret) { + pr_err("%s: failed to register nxpwifi device\n", __func__); + goto err_registerdev; + } + + ret = nxpwifi_init_hw_fw(adapter, true); + if (ret) { + pr_err("%s: firmware init failed\n", __func__); + goto err_init_fw; + } + + return ret; + +err_init_fw: + pr_debug("info: %s: unregister device\n", __func__); + if (adapter->if_ops.unregister_dev) + adapter->if_ops.unregister_dev(adapter); +err_registerdev: + set_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags); + +err_kmalloc: + nxpwifi_terminate_workqueue(adapter); + + if (adapter->hw_status == NXPWIFI_HW_STATUS_READY) { + pr_debug("info: %s: shutdown nxpwifi\n", __func__); + nxpwifi_shutdown_drv(adapter); + nxpwifi_free_cmd_buffers(adapter); + } + + nxpwifi_free_adapter(adapter); + +err_init_sw: + + return ret; +} +EXPORT_SYMBOL_GPL(nxpwifi_add_card); + +/* Remove card: teardown SW, unregister device, free adapter. */ +void nxpwifi_remove_card(struct nxpwifi_adapter *adapter) +{ + if (!adapter) + return; + + if (adapter->is_up) + nxpwifi_uninit_sw(adapter); + + /* Unregister device */ + nxpwifi_dbg(adapter, INFO, + "info: unregister device\n"); + if (adapter->if_ops.unregister_dev) + adapter->if_ops.unregister_dev(adapter); + /* Free adapter structure */ + nxpwifi_dbg(adapter, INFO, + "info: free adapter\n"); + nxpwifi_free_adapter(adapter); +} +EXPORT_SYMBOL_GPL(nxpwifi_remove_card); + +void _nxpwifi_dbg(const struct nxpwifi_adapter *adapter, int mask, + const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + + if (!(adapter->debug_mask & mask)) + return; + + va_start(args, fmt); + + vaf.fmt = fmt; + vaf.va = &args; + + if (adapter->dev) + dev_info(adapter->dev, "%pV", &vaf); + else + pr_info("%pV", &vaf); + + va_end(args); +} +EXPORT_SYMBOL_GPL(_nxpwifi_dbg); + +/* Module init: init debugfs if enabled. */ +static int +nxpwifi_init_module(void) +{ +#ifdef CONFIG_DEBUG_FS + nxpwifi_debugfs_init(); +#endif + return 0; +} + +/* Module exit: remove debugfs if enabled. */ +static void +nxpwifi_cleanup_module(void) +{ +#ifdef CONFIG_DEBUG_FS + nxpwifi_debugfs_remove(); +#endif +} + +module_init(nxpwifi_init_module); +module_exit(nxpwifi_cleanup_module); + +MODULE_AUTHOR("NXP International Ltd."); +MODULE_DESCRIPTION("NXP WiFi Driver version " VERSION); +MODULE_VERSION(VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/wireless/nxp/nxpwifi/main.h b/drivers/net/wireless/nxp/nxpwifi/main.h new file mode 100644 index 000000000000..565e5528a614 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/main.h @@ -0,0 +1,1427 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * nxpwifi: main data structures and prototypes + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_MAIN_H_ +#define _NXPWIFI_MAIN_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "sdio.h" + +extern char driver_version[]; + +struct nxpwifi_adapter; +struct nxpwifi_private; + +/* command type */ +enum { + NXPWIFI_ASYNC_CMD, + NXPWIFI_SYNC_CMD +}; + +#define NXPWIFI_MAX_AP 64 + +#define NXPWIFI_MAX_PKTS_TXQ 16 + +#define NXPWIFI_DEFAULT_WATCHDOG_TIMEOUT (5 * HZ) + +#define NXPWIFI_TIMER_10S 10000 +#define NXPWIFI_TIMER_1S 1000 + +#define MAX_TX_PENDING 400 +#define LOW_TX_PENDING 380 + +#define HIGH_RX_PENDING 50 +#define LOW_RX_PENDING 20 + +#define NXPWIFI_UPLD_SIZE (2312) + +#define MAX_EVENT_SIZE 2048 + +#define NXPWIFI_FW_DUMP_SIZE (2 * 1024 * 1024) + +#define ARP_FILTER_MAX_BUF_SIZE 68 + +#define NXPWIFI_KEY_BUFFER_SIZE 16 +#define NXPWIFI_DEFAULT_LISTEN_INTERVAL 10 +#define NXPWIFI_MAX_REGION_CODE 9 + +#define DEFAULT_BCN_AVG_FACTOR 8 +#define DEFAULT_DATA_AVG_FACTOR 8 + +#define FIRST_VALID_CHANNEL 0xff + +#define DEFAULT_BCN_MISS_TIMEOUT 5 + +#define MAX_SCAN_BEACON_BUFFER 8000 + +#define SCAN_BEACON_ENTRY_PAD 6 + +#define NXPWIFI_PASSIVE_SCAN_CHAN_TIME 110 +#define NXPWIFI_ACTIVE_SCAN_CHAN_TIME 40 +#define NXPWIFI_SPECIFIC_SCAN_CHAN_TIME 40 +#define NXPWIFI_DEF_SCAN_CHAN_GAP_TIME 50 + +#define SCAN_RSSI(RSSI) (0x100 - ((u8)(RSSI))) + +#define NXPWIFI_MAX_TOTAL_SCAN_TIME (NXPWIFI_TIMER_10S - NXPWIFI_TIMER_1S) + +#define WPA_GTK_OUI_OFFSET 2 +#define RSN_GTK_OUI_OFFSET 2 + +#define NXPWIFI_OUI_NOT_PRESENT 0 +#define NXPWIFI_OUI_PRESENT 1 + +#define PKT_TYPE_MGMT 0xE5 +#define PKT_TYPE_802DOT11 0x05 +/* check if any data / resp / event is received from card */ +#define IS_CARD_RX_RCVD(adapter) ({ \ + typeof(adapter) (_adapter) = adapter; \ + ((_adapter)->cmd_resp_received || \ + (_adapter)->event_received || \ + (_adapter)->data_received); \ + }) + +#define NXPWIFI_TYPE_DATA 0 +#define NXPWIFI_TYPE_CMD 1 +#define NXPWIFI_TYPE_EVENT 3 +#define NXPWIFI_TYPE_VDLL 4 +#define NXPWIFI_TYPE_AGGR_DATA 10 + +#define MAX_BITMAP_RATES_SIZE 18 + +#define MAX_CHANNEL_BAND_BG 14 +#define MAX_CHANNEL_BAND_A 165 + +#define MAX_FREQUENCY_BAND_BG 2484 + +#define NXPWIFI_EVENT_HEADER_LEN 4 +#define NXPWIFI_UAP_EVENT_EXTRA_HEADER 2 + +#define NXPWIFI_TYPE_LEN 4 +#define NXPWIFI_USB_TYPE_CMD 0xF00DFACE +#define NXPWIFI_USB_TYPE_DATA 0xBEADC0DE +#define NXPWIFI_USB_TYPE_EVENT 0xBEEFFACE + +/* tx_timeout threshold to trigger card reset */ +#define TX_TIMEOUT_THRESHOLD 6 + +#define NXPWIFI_DRV_INFO_SIZE_MAX 0x40000 + +/* address alignment helper */ +#define NXPWIFI_ALIGN_ADDR(p, a) ({ \ + typeof(a) (_a) = a; \ + (((long)(p) + (_a) - 1) & ~((_a) - 1)); \ + }) + +#define NXPWIFI_MAC_LOCAL_ADMIN_BIT 41 + +/* bit helper */ +#define MBIT(x) (((u32)1) << (x)) + +/* enum nxpwifi_debug_level - nxp wifi debug level */ +enum NXPWIFI_DEBUG_LEVEL { + NXPWIFI_DBG_MSG = 0x00000001, + NXPWIFI_DBG_FATAL = 0x00000002, + NXPWIFI_DBG_ERROR = 0x00000004, + NXPWIFI_DBG_DATA = 0x00000008, + NXPWIFI_DBG_CMD = 0x00000010, + NXPWIFI_DBG_EVENT = 0x00000020, + NXPWIFI_DBG_INTR = 0x00000040, + NXPWIFI_DBG_IOCTL = 0x00000080, + NXPWIFI_DBG_MPA_D = 0x00008000, + NXPWIFI_DBG_DAT_D = 0x00010000, + NXPWIFI_DBG_CMD_D = 0x00020000, + NXPWIFI_DBG_EVT_D = 0x00040000, + NXPWIFI_DBG_FW_D = 0x00080000, + NXPWIFI_DBG_IF_D = 0x00100000, + NXPWIFI_DBG_ENTRY = 0x10000000, + NXPWIFI_DBG_WARN = 0x20000000, + NXPWIFI_DBG_INFO = 0x40000000, + NXPWIFI_DBG_DUMP = 0x80000000, + NXPWIFI_DBG_ANY = 0xffffffff +}; + +#define NXPWIFI_DEFAULT_DEBUG_MASK (NXPWIFI_DBG_MSG | \ + NXPWIFI_DBG_FATAL | \ + NXPWIFI_DBG_ERROR) + +__printf(3, 4) +void _nxpwifi_dbg(const struct nxpwifi_adapter *adapter, int mask, + const char *fmt, ...); +#define nxpwifi_dbg(adapter, mask, fmt, ...) \ + _nxpwifi_dbg(adapter, NXPWIFI_DBG_##mask, fmt, ##__VA_ARGS__) + +#define DEBUG_DUMP_DATA_MAX_LEN 128 +#define nxpwifi_dbg_dump(adapter, dbg_mask, str, buf, len) \ +do { \ + if ((adapter)->debug_mask & NXPWIFI_DBG_##dbg_mask) \ + print_hex_dump(KERN_DEBUG, str, \ + DUMP_PREFIX_OFFSET, 16, 1, \ + buf, len, false); \ +} while (0) + +/* Min BGSCAN interval 15 second */ +#define NXPWIFI_BGSCAN_INTERVAL 15000 +/* bgscan interval (ms) and default repeat count */ +#define NXPWIFI_BGSCAN_REPEAT_COUNT 6 + +struct nxpwifi_dbg { + u32 num_cmd_host_to_card_failure; + u32 num_cmd_sleep_cfm_host_to_card_failure; + u32 num_tx_host_to_card_failure; + u32 num_event_deauth; + u32 num_event_disassoc; + u32 num_event_link_lost; + u32 num_cmd_deauth; + u32 num_cmd_assoc_success; + u32 num_cmd_assoc_failure; + u32 num_tx_timeout; + u16 timeout_cmd_id; + u16 timeout_cmd_act; + u16 last_cmd_id[DBG_CMD_NUM]; + u16 last_cmd_act[DBG_CMD_NUM]; + u16 last_cmd_index; + u16 last_cmd_resp_id[DBG_CMD_NUM]; + u16 last_cmd_resp_index; + u16 last_event[DBG_CMD_NUM]; + u16 last_event_index; + u32 last_mp_wr_bitmap[NXPWIFI_DBG_SDIO_MP_NUM]; + u32 last_mp_wr_ports[NXPWIFI_DBG_SDIO_MP_NUM]; + u32 last_mp_wr_len[NXPWIFI_DBG_SDIO_MP_NUM]; + u32 last_mp_curr_wr_port[NXPWIFI_DBG_SDIO_MP_NUM]; + u8 last_sdio_mp_index; +}; + +enum NXPWIFI_HARDWARE_STATUS { + NXPWIFI_HW_STATUS_READY, + NXPWIFI_HW_STATUS_INITIALIZING, + NXPWIFI_HW_STATUS_RESET, + NXPWIFI_HW_STATUS_NOT_READY +}; + +enum NXPWIFI_802_11_POWER_MODE { + NXPWIFI_802_11_POWER_MODE_CAM, + NXPWIFI_802_11_POWER_MODE_PSP +}; + +struct nxpwifi_tx_param { + u32 next_pkt_len; +}; + +enum NXPWIFI_PS_STATE { + PS_STATE_AWAKE, + PS_STATE_PRE_SLEEP, + PS_STATE_SLEEP_CFM, + PS_STATE_SLEEP +}; + +enum nxpwifi_iface_type { + NXPWIFI_SDIO +}; + +struct nxpwifi_add_ba_param { + u32 tx_win_size; + u32 rx_win_size; + u32 timeout; + u8 tx_amsdu; + u8 rx_amsdu; +}; + +struct nxpwifi_tx_aggr { + u8 ampdu_user; + u8 ampdu_ap; + u8 amsdu; +}; + +enum nxpwifi_ba_status { + BA_SETUP_NONE = 0, + BA_SETUP_INPROGRESS, + BA_SETUP_COMPLETE +}; + +struct nxpwifi_ra_list_tbl { + struct list_head list; + struct sk_buff_head skb_head; + u8 ra[ETH_ALEN]; + u32 is_11n_enabled; + u16 max_amsdu; + u16 ba_pkt_count; + u8 ba_packet_thr; + enum nxpwifi_ba_status ba_status; + u8 amsdu_in_ampdu; + u16 total_pkt_count; + bool tx_paused; +}; + +struct nxpwifi_tid_tbl { + struct list_head ra_list; +}; + +#define WMM_HIGHEST_PRIORITY 7 +#define HIGH_PRIO_TID 7 +#define LOW_PRIO_TID 0 +#define NO_PKT_PRIO_TID -1 +#define NXPWIFI_WMM_DRV_DELAY_MAX 510 + +struct nxpwifi_wmm_desc { + struct nxpwifi_tid_tbl tid_tbl_ptr[MAX_NUM_TID]; + u32 packets_out[MAX_NUM_TID]; + u32 pkts_paused[MAX_NUM_TID]; + /* protects ra_list */ + spinlock_t ra_list_spinlock; + struct nxpwifi_wmm_ac_status ac_status[IEEE80211_NUM_ACS]; + enum nxpwifi_wmm_ac_e ac_down_graded_vals[IEEE80211_NUM_ACS]; + u32 drv_pkt_delay_max; + u8 queue_priority[IEEE80211_NUM_ACS]; + u32 user_pri_pkt_tx_ctrl[WMM_HIGHEST_PRIORITY + 1]; /* UP: 0 to 7 */ + /* number of queued TX packets */ + atomic_t tx_pkts_queued; + /* highest priority currently queued */ + atomic_t highest_queued_prio; +}; + +struct nxpwifi_802_11_security { + u8 wpa_enabled; + u8 wpa2_enabled; + u8 wep_enabled; + u32 authentication_mode; + u8 is_authtype_auto; + u32 encryption_mode; +}; + +struct ieee_types_vendor_specific { + struct ieee80211_vendor_ie vend_hdr; + u8 data[IEEE_MAX_IE_SIZE - sizeof(struct ieee80211_vendor_ie)]; +} __packed; + +struct nxpwifi_bssdescriptor { + u8 mac_address[ETH_ALEN]; + struct cfg80211_ssid ssid; + u32 privacy; + s32 rssi; + u32 channel; + u32 freq; + u16 beacon_period; + u8 erp_flags; + u32 bss_mode; + u8 supported_rates[NXPWIFI_SUPPORTED_RATES]; + u8 data_rates[NXPWIFI_SUPPORTED_RATES]; + u16 bss_band; + u64 fw_tsf; + u64 timestamp; + union ieee_types_phy_param_set phy_param_set; + struct ieee_types_cf_param_set cf_param_set; + u16 cap_info_bitmap; + struct ieee80211_wmm_param_ie wmm_ie; + u8 disable_11n; + struct ieee80211_ht_cap *bcn_ht_cap; + u16 ht_cap_offset; + struct ieee80211_ht_operation *bcn_ht_oper; + u16 ht_info_offset; + u8 *bcn_bss_co_2040; + u16 bss_co_2040_offset; + u8 *bcn_ext_cap; + u16 ext_cap_offset; + struct ieee80211_vht_cap *bcn_vht_cap; + u16 vht_cap_offset; + struct ieee80211_vht_operation *bcn_vht_oper; + u16 vht_info_offset; + struct ieee_types_oper_mode_ntf *oper_mode; + u16 oper_mode_offset; + u8 disable_11ac; + struct ieee80211_he_cap_elem *bcn_he_cap; + u16 he_cap_offset; + struct ieee80211_he_operation *bcn_he_oper; + u16 he_info_offset; + u8 disable_11ax; + struct ieee_types_vendor_specific *bcn_wpa_ie; + u16 wpa_offset; + struct element *bcn_rsn_ie; + u16 rsn_offset; + struct element *bcn_rsnx_ie; + u16 rsnx_offset; + u8 *beacon_buf; + u32 beacon_buf_size; + u8 sensed_11h; + u8 local_constraint; + u8 chan_sw_ie_present; +}; + +struct nxpwifi_current_bss_params { + struct nxpwifi_bssdescriptor bss_descriptor; + bool wmm_enabled; + bool wmm_uapsd_enabled; + u8 band; + u32 num_of_rates; + u8 data_rates[NXPWIFI_SUPPORTED_RATES]; +}; + +struct nxpwifi_sleep_period { + u16 period; + u16 reserved; +}; + +struct nxpwifi_wep_key { + u32 length; + u32 key_index; + u32 key_length; + u8 key_material[NXPWIFI_KEY_BUFFER_SIZE]; +}; + +#define MAX_REGION_CHANNEL_NUM 2 + +struct nxpwifi_chan_freq_power { + u16 channel; + u32 freq; + u16 max_tx_power; + u8 unsupported; +}; + +enum state_11d_t { + DISABLE_11D = 0, + ENABLE_11D = 1, +}; + +#define NXPWIFI_MAX_TRIPLET_802_11D 83 + +struct nxpwifi_802_11d_domain_reg { + u8 dfs_region; + u8 country_code[IEEE80211_COUNTRY_STRING_LEN]; + u8 no_of_triplet; + struct ieee80211_country_ie_triplet + triplet[NXPWIFI_MAX_TRIPLET_802_11D]; +}; + +struct nxpwifi_vendor_spec_cfg_ie { + u16 mask; + u16 flag; + u8 ie[NXPWIFI_MAX_VSIE_LEN]; +}; + +struct wps { + u8 session_enable; +}; + +struct nxpwifi_roc_cfg { + u64 cookie; + struct ieee80211_channel chan; +}; + +enum nxpwifi_iface_work_flags { + NXPWIFI_IFACE_WORK_DEVICE_DUMP, + NXPWIFI_IFACE_WORK_CARD_RESET, +}; + +enum nxpwifi_adapter_work_flags { + NXPWIFI_SURPRISE_REMOVED, + NXPWIFI_IS_CMD_TIMEDOUT, + NXPWIFI_IS_SUSPENDED, + NXPWIFI_IS_HS_CONFIGURED, + NXPWIFI_IS_HS_ENABLING, + NXPWIFI_IS_REQUESTING_FW_VEREXT, +}; + +struct nxpwifi_band_config { + u8 chan_band:2; + u8 chan_width:2; + u8 chan2_offset:2; + u8 scan_mode:2; +} __packed; + +struct nxpwifi_channel_band { + struct nxpwifi_band_config band_config; + u8 channel; +}; + +struct nxpwifi_private { + struct nxpwifi_adapter *adapter; + u8 bss_type; + u8 bss_role; + u8 bss_priority; + u8 bss_num; + u8 bss_started; + u8 auth_flag; + u16 auth_alg; + u8 frame_type; + u8 curr_addr[ETH_ALEN]; + u8 media_connected; + u8 port_open; + u8 usb_port; + u32 num_tx_timeout; + /* track consecutive timeout */ + u8 tx_timeout_cnt; + struct net_device *netdev; + struct net_device_stats stats; + u32 curr_pkt_filter; + u32 bss_mode; + u32 pkt_tx_ctrl; + u16 tx_power_level; + u8 max_tx_power_level; + u8 min_tx_power_level; + u32 tx_ant; + u32 rx_ant; + u8 tx_rate; + u8 tx_htinfo; + u8 rxpd_htinfo; + u8 rxpd_rate; + u16 rate_bitmap; + u16 bitmap_rates[MAX_BITMAP_RATES_SIZE]; + u32 data_rate; + u8 is_data_rate_auto; + u16 bcn_avg_factor; + u16 data_avg_factor; + s16 data_rssi_last; + s16 data_nf_last; + s16 data_rssi_avg; + s16 data_nf_avg; + s16 bcn_rssi_last; + s16 bcn_nf_last; + s16 bcn_rssi_avg; + s16 bcn_nf_avg; + struct nxpwifi_bssdescriptor *attempted_bss_desc; + struct cfg80211_ssid prev_ssid; + u8 prev_bssid[ETH_ALEN]; + struct nxpwifi_current_bss_params curr_bss_params; + u16 beacon_period; + u8 dtim_period; + u16 listen_interval; + u16 atim_window; + struct nxpwifi_802_11_security sec_info; + struct nxpwifi_wep_key wep_key[NUM_WEP_KEYS]; + u16 wep_key_curr_index; + u8 wpa_ie[256]; + u16 wpa_ie_len; + u8 wpa_is_gtk_set; + struct host_cmd_ds_802_11_key_material aes_key; + u8 *wps_ie; + u16 wps_ie_len; + u8 wmm_required; + bool wmm_enabled; + u8 wmm_qosinfo; + struct nxpwifi_wmm_desc wmm; + atomic_t wmm_tx_pending[IEEE80211_NUM_ACS]; + struct list_head sta_list; + /* spin lock for associated station list */ + spinlock_t sta_list_spinlock; + struct list_head tx_ba_stream_tbl_ptr[MAX_NUM_TID]; + /* spin lock for tx_ba_stream_tbl_ptr queue */ + struct spinlock tx_ba_stream_tbl_lock[MAX_NUM_TID]; + struct nxpwifi_tx_aggr aggr_prio_tbl[MAX_NUM_TID]; + struct nxpwifi_add_ba_param add_ba_param; + u16 rx_seq[MAX_NUM_TID]; + u8 tos_to_tid_inv[MAX_NUM_TID]; + struct list_head rx_reorder_tbl_ptr[MAX_NUM_TID]; + /* spin lock for rx_reorder_tbl_ptr queue */ + struct spinlock rx_reorder_tbl_lock[MAX_NUM_TID]; +#define NXPWIFI_ASSOC_RSP_BUF_SIZE 500 + u8 assoc_rsp_buf[NXPWIFI_ASSOC_RSP_BUF_SIZE]; + u32 assoc_rsp_size; + struct cfg80211_bss *req_bss; + +#define NXPWIFI_GENIE_BUF_SIZE 256 + u8 gen_ie_buf[NXPWIFI_GENIE_BUF_SIZE]; + u8 gen_ie_buf_len; + + struct nxpwifi_vendor_spec_cfg_ie vs_ie[NXPWIFI_MAX_VSIE_NUM]; + +#define NXPWIFI_ASSOC_TLV_BUF_SIZE 256 + u8 assoc_tlv_buf[NXPWIFI_ASSOC_TLV_BUF_SIZE]; + u8 assoc_tlv_buf_len; + + u8 *curr_bcn_buf; + u32 curr_bcn_size; + /* spin lock for beacon buffer */ + spinlock_t curr_bcn_buf_lock; + struct wireless_dev wdev; + struct nxpwifi_chan_freq_power cfp; + u32 versionstrsel; + char version_str[NXPWIFI_VERSION_STR_LENGTH]; +#ifdef CONFIG_DEBUG_FS + struct dentry *dfs_dev_dir; +#endif + u16 current_key_index; + struct cfg80211_scan_request *scan_request; + u8 cfg_bssid[6]; + struct wps wps; + u8 scan_block; + s32 cqm_rssi_thold; + u32 cqm_rssi_hyst; + u8 subsc_evt_rssi_state; + struct nxpwifi_ds_misc_subsc_evt async_subsc_evt_storage; + struct nxpwifi_ie mgmt_ie[MAX_MGMT_IE_INDEX]; + u16 beacon_idx; + u16 proberesp_idx; + u16 assocresp_idx; + u16 gen_idx; + u8 ap_11n_enabled; + u8 ap_11ac_enabled; + u8 ap_11ax_enabled; + u16 config_bands; + /* 11AX */ + u8 user_he_cap_len; + u8 user_he_cap[HE_CAP_MAX_SIZE]; + u8 user_2g_he_cap_len; + u8 user_2g_he_cap[HE_CAP_MAX_SIZE]; + bool host_mlme_reg; + u32 mgmt_frame_mask; + struct nxpwifi_roc_cfg roc_cfg; + bool scan_aborting; + u8 sched_scanning; + u8 csa_chan; + unsigned long csa_expire_time; + u8 del_list_idx; + bool hs2_enabled; + struct nxpwifi_uap_bss_param bss_cfg; + struct cfg80211_chan_def bss_chandef; + struct station_parameters *sta_params; + struct xarray ack_status_frames; + /* spin lock for ack status */ + spinlock_t ack_status_lock; + /** rx histogram data */ + struct nxpwifi_histogram_data *hist_data; + struct cfg80211_chan_def dfs_chandef; + struct wiphy_work reset_conn_state_work; + struct wiphy_delayed_work dfs_cac_work; + struct wiphy_delayed_work dfs_chan_sw_work; + bool uap_stop_tx; + struct cfg80211_ap_update ap_update_info; + struct nxpwifi_11h_intf_state state_11h; + struct nxpwifi_ds_mem_rw mem_rw; + struct sk_buff_head bypass_txq; + struct nxpwifi_user_scan_chan hidden_chan[NXPWIFI_USER_SCAN_CHAN_MAX]; + u8 assoc_resp_ht_param; + bool ht_param_present; + u16 last_deauth_reason; +}; + +struct nxpwifi_tx_ba_stream_tbl { + struct list_head list; + struct rcu_head rcu; + int tid; + u8 ra[ETH_ALEN]; + enum nxpwifi_ba_status ba_status; + u8 amsdu; +}; + +struct nxpwifi_rx_reorder_tbl; + +struct reorder_tmr_cnxt { + struct timer_list timer; + struct nxpwifi_rx_reorder_tbl *ptr; + struct nxpwifi_private *priv; + u8 timer_is_set; +}; + +struct nxpwifi_rx_reorder_tbl { + struct list_head list; + struct list_head tmp_list; + struct rcu_head rcu; + int tid; + u8 ta[ETH_ALEN]; + int init_win; + int start_win; + int win_size; + void **rx_reorder_ptr; + struct reorder_tmr_cnxt timer_context; + u8 amsdu; + u8 flags; +}; + +struct nxpwifi_bss_prio_node { + struct list_head list; + struct nxpwifi_private *priv; +}; + +struct nxpwifi_bss_prio_tbl { + struct list_head bss_prio_head; + spinlock_t bss_prio_lock; /* protects BSS priority */ + struct nxpwifi_bss_prio_node *bss_prio_cur; +}; + +struct cmd_ctrl_node { + struct list_head list; + struct nxpwifi_private *priv; + u32 cmd_no; + u32 cmd_flag; + struct sk_buff *cmd_skb; + struct sk_buff *resp_skb; + void *data_buf; + u32 wait_q_enabled; + struct sk_buff *skb; + u8 *condition; + u8 cmd_wait_q_woken; + int (*cmd_resp)(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf); +}; + +struct nxpwifi_bss_priv { + u16 band; + u64 fw_tsf; +}; + +struct nxpwifi_station_stats { + u64 last_rx; + s8 rssi; + u64 rx_bytes; + u64 tx_bytes; + u32 rx_packets; + u32 tx_packets; + u32 tx_failed; + u8 last_tx_rate; + u8 last_tx_htinfo; +}; + +/*AP - side structure tracking associated STA info */ +struct nxpwifi_sta_node { + struct list_head list; + struct rcu_head rcu; + u8 mac_addr[ETH_ALEN]; + u8 is_wmm_enabled; + u8 is_11n_enabled; + u8 is_11ac_enabled; + u8 is_11ax_enabled; + u8 ampdu_sta[MAX_NUM_TID]; + u16 rx_seq[MAX_NUM_TID]; + u16 max_amsdu; + struct nxpwifi_station_stats stats; + u8 tx_pause; +}; + +#define NXPWIFI_TYPE_AGGR_DATA_V2 11 +#define NXPWIFI_BUS_AGGR_MODE_LEN_V2 (2) +#define NXPWIFI_BUS_AGGR_MAX_LEN 16000 +#define NXPWIFI_BUS_AGGR_MAX_NUM 10 +struct bus_aggr_params { + u16 enable; + u16 mode; + u16 tx_aggr_max_size; + u16 tx_aggr_max_num; + u16 tx_aggr_align; +}; + +struct vdll_dnld_ctrl { + u8 *pending_block; + u16 pending_block_len; + u8 *vdll_mem; + u32 vdll_len; + struct sk_buff *skb; +}; + +struct nxpwifi_if_ops { + int (*init_if)(struct nxpwifi_adapter *adapter); + void (*cleanup_if)(struct nxpwifi_adapter *adapter); + int (*check_fw_status)(struct nxpwifi_adapter *adapter, u32 poll_num); + int (*check_winner_status)(struct nxpwifi_adapter *adapter); + int (*prog_fw)(struct nxpwifi_adapter *adapter, + struct nxpwifi_fw_image *fw); + int (*register_dev)(struct nxpwifi_adapter *adapter); + void (*unregister_dev)(struct nxpwifi_adapter *adapter); + int (*enable_int)(struct nxpwifi_adapter *adapter); + void (*disable_int)(struct nxpwifi_adapter *adapter); + int (*process_int_status)(struct nxpwifi_adapter *adapter, u8 istat); + int (*host_to_card)(struct nxpwifi_adapter *adapter, u8 type, + struct sk_buff *skb, + struct nxpwifi_tx_param *tx_param); + int (*wakeup)(struct nxpwifi_adapter *adapter); + int (*wakeup_complete)(struct nxpwifi_adapter *adapter); + + /* interface-specific operations */ + void (*update_mp_end_port)(struct nxpwifi_adapter *adapter, u16 port); + void (*cleanup_mpa_buf)(struct nxpwifi_adapter *adapter); + int (*cmdrsp_complete)(struct nxpwifi_adapter *adapter, + struct sk_buff *skb); + int (*event_complete)(struct nxpwifi_adapter *adapter, + struct sk_buff *skb); + int (*dnld_fw)(struct nxpwifi_adapter *adapter, + struct nxpwifi_fw_image *fw); + void (*card_reset)(struct nxpwifi_adapter *adapter); + int (*reg_dump)(struct nxpwifi_adapter *adapter, char *drv_buf); + void (*device_dump)(struct nxpwifi_adapter *adapter); + void (*deaggr_pkt)(struct nxpwifi_adapter *adapter, + struct sk_buff *skb); + void (*up_dev)(struct nxpwifi_adapter *adapter); +}; + +struct nxpwifi_adapter { + u8 iface_type; + unsigned int debug_mask; + struct nxpwifi_iface_comb iface_limit; + struct nxpwifi_iface_comb curr_iface_comb; + struct nxpwifi_private *priv[NXPWIFI_MAX_BSS_NUM]; + u8 priv_num; + const struct firmware *firmware; + char fw_name[32]; + int winner; + struct device *dev; + struct wiphy *wiphy; + u8 perm_addr[ETH_ALEN]; + unsigned long work_flags; + u32 fw_release_number; + u8 intf_hdr_len; + void *card; + struct nxpwifi_if_ops if_ops; + atomic_t bypass_tx_pending; + atomic_t rx_pending; + atomic_t tx_pending; + atomic_t cmd_pending; + atomic_t tx_hw_pending; + struct workqueue_struct *workqueue; + struct work_struct main_work; + struct workqueue_struct *rx_workqueue; + struct work_struct rx_work; + struct wiphy_work host_mlme_work; + bool rx_work_enabled; + bool rx_processing; + bool delay_main_work; + atomic_t rx_ba_teardown_pending; + atomic_t iface_changing; + struct nxpwifi_bss_prio_tbl bss_prio_tbl[NXPWIFI_MAX_BSS_NUM]; + u32 nxpwifi_processing; + u16 tx_buf_size; + u16 curr_tx_buf_size; + /* SDIO single port rx aggregation capability */ + bool host_disable_sdio_rx_aggr; + bool sdio_rx_aggr_enable; + u16 sdio_rx_block_size; + u32 ioport; + enum NXPWIFI_HARDWARE_STATUS hw_status; + u16 number_of_antenna; + u32 fw_cap_info; + u32 fw_cap_ext; + u16 user_htstream; + u64 uuid_lo; + u64 uuid_hi; + /* interrupt lock */ + spinlock_t int_lock; + u8 int_status; + u32 event_cause; + struct sk_buff *event_skb; + u8 upld_buf[NXPWIFI_UPLD_SIZE]; + u8 data_sent; + u8 cmd_sent; + u8 cmd_resp_received; + bool event_received; + u8 data_received; + u8 assoc_resp_received; + struct nxpwifi_private *priv_link_lost; + u8 host_mlme_link_lost; + u16 seq_num; + struct cmd_ctrl_node *cmd_pool; + struct cmd_ctrl_node *curr_cmd; + /* spin lock for command */ + spinlock_t nxpwifi_cmd_lock; + struct timer_list cmd_timer; + struct list_head cmd_free_q; + spinlock_t cmd_free_q_lock; /* protects cmd_free_q */ + struct list_head cmd_pending_q; + spinlock_t cmd_pending_q_lock; /* protects cmd_pending_q */ + struct list_head scan_pending_q; + spinlock_t scan_pending_q_lock; /* protects scan_pending_q */ + struct sk_buff_head tx_data_q; + atomic_t tx_queued; + u32 scan_processing; + u16 region_code; + struct nxpwifi_802_11d_domain_reg domain_reg; + u16 scan_probes; + u32 scan_mode; + u16 specific_scan_time; + u16 active_scan_time; + u16 passive_scan_time; + u16 scan_chan_gap_time; + u16 fw_bands; + u8 tx_lock_flag; + struct nxpwifi_sleep_period sleep_period; + u16 ps_mode; + u32 ps_state; + u8 need_to_wakeup; + u16 multiple_dtim; + u16 local_listen_interval; + u16 null_pkt_interval; + struct sk_buff *sleep_cfm; + u16 bcn_miss_time_out; + u8 is_deep_sleep; + u8 delay_null_pkt; + u16 delay_to_ps; + u16 enhanced_ps_mode; + u8 pm_wakeup_card_req; + u16 gen_null_pkt; + u16 pps_uapsd_mode; + u32 pm_wakeup_fw_try; + struct timer_list wakeup_timer; + struct nxpwifi_hs_config_param hs_cfg; + u8 hs_activated; + u8 hs_activated_manually; + u16 hs_activate_wait_q_woken; + wait_queue_head_t hs_activate_wait_q; + u8 event_body[MAX_EVENT_SIZE]; + u32 hw_dot_11n_dev_cap; + u8 hw_dev_mcs_support; + u8 hw_mpdu_density; + u8 user_dev_mcs_support; + u8 sec_chan_offset; + struct nxpwifi_dbg dbg; + u8 arp_filter[ARP_FILTER_MAX_BUF_SIZE]; + u32 arp_filter_size; + struct nxpwifi_wait_queue cmd_wait_q; + u8 scan_wait_q_woken; + spinlock_t queue_lock; /* protects TX queues */ + u8 dfs_region; + u8 country_code[IEEE80211_COUNTRY_STRING_LEN]; + u16 max_mgmt_ie_index; + const struct firmware *cal_data; + /* 11AC capability fields */ + u32 is_hw_11ac_capable; + u32 hw_dot_11ac_dev_cap; + u32 hw_dot_11ac_mcs_support; + u32 usr_dot_11ac_dev_cap_bg; + u32 usr_dot_11ac_dev_cap_a; + u32 usr_dot_11ac_mcs_support; + /* 11AX capability fields */ + u8 is_hw_11ax_capable; + u8 hw_he_cap_len; + u8 hw_he_cap[HE_CAP_MAX_SIZE]; + u8 hw_2g_he_cap_len; + u8 hw_2g_he_cap[HE_CAP_MAX_SIZE]; + atomic_t pending_bridged_pkts; + struct completion *fw_done; /* FW init completion */ + bool is_up; + bool ext_scan; + u8 fw_api_ver; + u8 fw_hotfix_ver; + u8 key_api_major_ver, key_api_minor_ver; + u8 max_sta_conn; + struct memory_type_mapping *mem_type_mapping_tbl; + u8 num_mem_types; + bool scan_chan_gap_enabled; + struct sk_buff_head rx_mlme_q; + struct sk_buff_head rx_data_q; + struct nxpwifi_chan_stats *chan_stats; + u32 num_in_chan_stats; + int survey_idx; + u8 coex_scan; + u8 coex_min_scan_time; + u8 coex_max_scan_time; + u8 coex_win_size; + u8 coex_tx_win_size; + u8 coex_rx_win_size; + u8 active_scan_triggered; + bool usb_mc_status; + bool usb_mc_setup; + struct cfg80211_wowlan_nd_info *nd_info; + struct ieee80211_regdomain *regd; + /* Aggregation parameters*/ + struct bus_aggr_params bus_aggr; + void *devdump_data; /* device dump storage */ + int devdump_len; /* device dump length */ + bool ignore_btcoex_events; + struct vdll_dnld_ctrl vdll_ctrl; + u64 roc_cookie_counter; + u32 enable_net_mon; + bool wowlan_enabled; + bool chandef_valid; + struct cfg80211_chan_def chandef; + atomic_t uap_count; +}; + +void nxpwifi_process_tx_queue(struct nxpwifi_adapter *adapter); + +void nxpwifi_init_lock_list(struct nxpwifi_adapter *adapter); + +void nxpwifi_set_trans_start(struct net_device *dev); + +void nxpwifi_stop_net_dev_queue(struct net_device *netdev, + struct nxpwifi_adapter *adapter); + +void nxpwifi_wake_up_net_dev_queue(struct net_device *netdev, + struct nxpwifi_adapter *adapter); + +int nxpwifi_init_priv(struct nxpwifi_private *priv); +void nxpwifi_free_priv(struct nxpwifi_private *priv); + +int nxpwifi_init_fw(struct nxpwifi_adapter *adapter); + +void nxpwifi_shutdown_drv(struct nxpwifi_adapter *adapter); + +int nxpwifi_dnld_fw(struct nxpwifi_adapter *adapter, + struct nxpwifi_fw_image *fw); + +int nxpwifi_recv_packet(struct nxpwifi_private *priv, struct sk_buff *skb); +int nxpwifi_uap_recv_packet(struct nxpwifi_private *priv, + struct sk_buff *skb); + +void nxpwifi_host_mlme_disconnect(struct nxpwifi_private *priv, + u16 reason_code, u8 *sa); + +int nxpwifi_process_mgmt_packet(struct nxpwifi_private *priv, + struct sk_buff *skb); +int nxpwifi_recv_packet_to_monif(struct nxpwifi_private *priv, + struct sk_buff *skb); +int nxpwifi_complete_cmd(struct nxpwifi_adapter *adapter, + struct cmd_ctrl_node *cmd_node); + +void nxpwifi_cmd_timeout_func(struct timer_list *t); + +int nxpwifi_get_debug_info(struct nxpwifi_private *priv, + struct nxpwifi_debug_info *info); + +int nxpwifi_alloc_cmd_buffer(struct nxpwifi_adapter *adapter); +void nxpwifi_free_cmd_buffer(struct nxpwifi_adapter *adapter); +void nxpwifi_free_cmd_buffers(struct nxpwifi_adapter *adapter); +void nxpwifi_cancel_all_pending_cmd(struct nxpwifi_adapter *adapter); +void nxpwifi_cancel_pending_scan_cmd(struct nxpwifi_adapter *adapter); +void nxpwifi_cancel_scan(struct nxpwifi_adapter *adapter); + +void nxpwifi_recycle_cmd_node(struct nxpwifi_adapter *adapter, + struct cmd_ctrl_node *cmd_node); + +void nxpwifi_insert_cmd_to_pending_q(struct nxpwifi_adapter *adapter, + struct cmd_ctrl_node *cmd_node); + +int nxpwifi_exec_next_cmd(struct nxpwifi_adapter *adapter); +int nxpwifi_process_cmdresp(struct nxpwifi_adapter *adapter); +void nxpwifi_process_assoc_resp(struct nxpwifi_adapter *adapter); +int nxpwifi_handle_rx_packet(struct nxpwifi_adapter *adapter, + struct sk_buff *skb); +int nxpwifi_process_tx(struct nxpwifi_private *priv, struct sk_buff *skb, + struct nxpwifi_tx_param *tx_param); +int nxpwifi_send_null_packet(struct nxpwifi_private *priv, u8 flags); +int nxpwifi_write_data_complete(struct nxpwifi_adapter *adapter, + struct sk_buff *skb, int aggr, int status); +void nxpwifi_clean_txrx(struct nxpwifi_private *priv); +u8 nxpwifi_check_last_packet_indication(struct nxpwifi_private *priv); +void nxpwifi_check_ps_cond(struct nxpwifi_adapter *adapter); +void nxpwifi_process_sleep_confirm_resp(struct nxpwifi_adapter *adapter, + u8 *pbuf, u32 upld_len); +void nxpwifi_process_hs_config(struct nxpwifi_adapter *adapter); +void nxpwifi_hs_activated_event(struct nxpwifi_private *priv, + u8 activated); +int nxpwifi_set_hs_params(struct nxpwifi_private *priv, u16 action, + int cmd_type, struct nxpwifi_ds_hs_cfg *hs_cfg); +int nxpwifi_ret_802_11_hs_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp); +int nxpwifi_process_rx_packet(struct nxpwifi_private *priv, + struct sk_buff *skb); +int nxpwifi_process_sta_rx_packet(struct nxpwifi_private *priv, + struct sk_buff *skb); +int nxpwifi_process_uap_rx_packet(struct nxpwifi_private *priv, + struct sk_buff *skb); +int nxpwifi_handle_uap_rx_forward(struct nxpwifi_private *priv, + struct sk_buff *skb); +void nxpwifi_delete_all_station_list(struct nxpwifi_private *priv); +void nxpwifi_wmm_del_peer_ra_list(struct nxpwifi_private *priv, + const u8 *ra_addr); +void nxpwifi_process_sta_txpd(struct nxpwifi_private *priv, + struct sk_buff *skb); +void nxpwifi_process_uap_txpd(struct nxpwifi_private *priv, + struct sk_buff *skb); +int nxpwifi_cmd_802_11_scan(struct host_cmd_ds_command *cmd, + struct nxpwifi_scan_cmd_config *scan_cfg); +void nxpwifi_queue_scan_cmd(struct nxpwifi_private *priv, + struct cmd_ctrl_node *cmd_node); +int nxpwifi_ret_802_11_scan(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp); +int nxpwifi_associate(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc); +int nxpwifi_cmd_802_11_associate(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + struct nxpwifi_bssdescriptor *bss_desc); +int nxpwifi_ret_802_11_associate(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp); +u8 nxpwifi_band_to_radio_type(u16 config_bands); +int nxpwifi_deauthenticate(struct nxpwifi_private *priv, u8 *mac); +void nxpwifi_deauthenticate_all(struct nxpwifi_adapter *adapter); +int nxpwifi_cmd_802_11_bg_scan_query(struct host_cmd_ds_command *cmd); +struct nxpwifi_chan_freq_power *nxpwifi_get_cfp(struct nxpwifi_private *priv, + u8 band, u16 channel, u32 freq); +u32 nxpwifi_index_to_data_rate(struct nxpwifi_private *priv, + u8 index, u8 ht_info); +u32 nxpwifi_index_to_acs_data_rate(struct nxpwifi_private *priv, + u8 index, u8 ht_info); +int nxpwifi_cmd_append_vsie_tlv(struct nxpwifi_private *priv, u16 vsie_mask, + u8 **buffer); +u32 nxpwifi_get_active_data_rates(struct nxpwifi_private *priv, + u8 *rates); +u32 nxpwifi_get_supported_rates(struct nxpwifi_private *priv, u8 *rates); +u32 nxpwifi_get_rates_from_cfg80211(struct nxpwifi_private *priv, + u8 *rates, u8 radio_type); +u8 nxpwifi_is_rate_auto(struct nxpwifi_private *priv); +extern u16 region_code_index[NXPWIFI_MAX_REGION_CODE]; +void nxpwifi_save_curr_bcn(struct nxpwifi_private *priv); +void nxpwifi_free_curr_bcn(struct nxpwifi_private *priv); +int is_command_pending(struct nxpwifi_adapter *adapter); +void nxpwifi_init_priv_params(struct nxpwifi_private *priv, + struct net_device *dev); +void nxpwifi_set_ba_params(struct nxpwifi_private *priv); +void nxpwifi_update_ampdu_txwinsize(struct nxpwifi_adapter *pmadapter); +void nxpwifi_set_11ac_ba_params(struct nxpwifi_private *priv); +int nxpwifi_cmd_802_11_scan_ext(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + void *data_buf); +int nxpwifi_ret_802_11_scan_ext(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp); +int nxpwifi_handle_event_ext_scan_report(struct nxpwifi_private *priv, + void *buf); +int nxpwifi_cmd_802_11_bg_scan_config(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + void *data_buf); +int nxpwifi_stop_bg_scan(struct nxpwifi_private *priv); + +/* check if RA-based queuing */ +static inline u8 +nxpwifi_queuing_ra_based(struct nxpwifi_private *priv) +{ + /* In STA mode DA==RA; subject to future revision */ + if (priv->bss_mode == NL80211_IFTYPE_STATION && + (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA)) + return false; + + return true; +} + +/* copy rates from src to dest */ +static inline u32 +nxpwifi_copy_rates(u8 *dest, u32 pos, u8 *src, int len) +{ + int i; + + for (i = 0; i < len && src[i]; i++, pos++) { + if (pos >= NXPWIFI_SUPPORTED_RATES) + break; + dest[pos] = src[i]; + } + + return pos; +} + +/* return priv matching the given BSS type and number */ +static inline struct nxpwifi_private * +nxpwifi_get_priv_by_id(struct nxpwifi_adapter *adapter, + u8 bss_num, u8 bss_type) +{ + int i; + + for (i = 0; i < adapter->priv_num; i++) { + if (adapter->priv[i]->bss_mode == + NL80211_IFTYPE_UNSPECIFIED) + continue; + if (adapter->priv[i]->bss_num == bss_num && + adapter->priv[i]->bss_type == bss_type) + break; + } + return ((i < adapter->priv_num) ? adapter->priv[i] : NULL); +} + +/* return first priv matching BSS role */ +static inline struct nxpwifi_private * +nxpwifi_get_priv(struct nxpwifi_adapter *adapter, + enum nxpwifi_bss_role bss_role) +{ + int i; + + for (i = 0; i < adapter->priv_num; i++) { + if (bss_role == NXPWIFI_BSS_ROLE_ANY || + GET_BSS_ROLE(adapter->priv[i]) == bss_role) + break; + } + + return ((i < adapter->priv_num) ? adapter->priv[i] : NULL); +} + +/* find unused BSS number for new interface */ +static inline u8 +nxpwifi_get_unused_bss_num(struct nxpwifi_adapter *adapter, u8 bss_type) +{ + u8 i, j; + int index[NXPWIFI_MAX_BSS_NUM]; + + memset(index, 0, sizeof(index)); + for (i = 0; i < adapter->priv_num; i++) + if (adapter->priv[i]->bss_type == bss_type && + !(adapter->priv[i]->bss_mode == + NL80211_IFTYPE_UNSPECIFIED)) { + index[adapter->priv[i]->bss_num] = 1; + } + for (j = 0; j < NXPWIFI_MAX_BSS_NUM; j++) + if (!index[j]) + return j; + return -ENOENT; +} + +/* return unused private entry for requested bss type */ +static inline struct nxpwifi_private * +nxpwifi_get_unused_priv_by_bss_type(struct nxpwifi_adapter *adapter, + u8 bss_type) +{ + u8 i; + + for (i = 0; i < adapter->priv_num; i++) + if (adapter->priv[i]->bss_mode == + NL80211_IFTYPE_UNSPECIFIED) { + adapter->priv[i]->bss_num = + nxpwifi_get_unused_bss_num(adapter, bss_type); + break; + } + + return ((i < adapter->priv_num) ? adapter->priv[i] : NULL); +} + +/* return private structure attached to netdev */ +static inline struct nxpwifi_private * +nxpwifi_netdev_get_priv(struct net_device *dev) +{ + return (struct nxpwifi_private *)(*(unsigned long *)netdev_priv(dev)); +} + +/* return true if skb contains a management frame */ +static inline bool nxpwifi_is_skb_mgmt_frame(struct sk_buff *skb) +{ + return (get_unaligned_le32(skb->data) == PKT_TYPE_MGMT); +} + +/* channel closed by CSA */ +static inline u8 +nxpwifi_11h_get_csa_closed_channel(struct nxpwifi_private *priv) +{ + if (!priv->csa_chan) + return 0; + + /* clear CSA if DFS switch timeout expired */ + if (time_after(jiffies, priv->csa_expire_time)) { + priv->csa_chan = 0; + priv->csa_expire_time = 0; + } + + return priv->csa_chan; +} + +static inline u8 nxpwifi_is_any_intf_active(struct nxpwifi_private *priv) +{ + struct nxpwifi_private *priv_tmp; + int i; + + for (i = 0; i < priv->adapter->priv_num; i++) { + priv_tmp = priv->adapter->priv[i]; + if ((GET_BSS_ROLE(priv_tmp) == NXPWIFI_BSS_ROLE_UAP && + priv_tmp->bss_started) || + (GET_BSS_ROLE(priv_tmp) == NXPWIFI_BSS_ROLE_STA && + priv_tmp->media_connected)) + return 1; + } + + return 0; +} + +int nxpwifi_init_shutdown_fw(struct nxpwifi_private *priv, + u32 func_init_shutdown); + +int nxpwifi_add_card(void *card, struct completion *fw_done, + struct nxpwifi_if_ops *if_ops, u8 iface_type, + struct device *dev); +void nxpwifi_remove_card(struct nxpwifi_adapter *adapter); + +void nxpwifi_get_version(struct nxpwifi_adapter *adapter, char *version, + int maxlen); +int +nxpwifi_request_set_multicast_list(struct nxpwifi_private *priv, + struct nxpwifi_multicast_list *mcast_list); +int nxpwifi_copy_mcast_addr(struct nxpwifi_multicast_list *mlist, + struct net_device *dev); +int nxpwifi_wait_queue_complete(struct nxpwifi_adapter *adapter, + struct cmd_ctrl_node *cmd_queued); +int nxpwifi_bss_start(struct nxpwifi_private *priv, struct cfg80211_bss *bss, + struct cfg80211_ssid *req_ssid); +int nxpwifi_cancel_hs(struct nxpwifi_private *priv, int cmd_type); +bool nxpwifi_enable_hs(struct nxpwifi_adapter *adapter); +int nxpwifi_disable_auto_ds(struct nxpwifi_private *priv); +int nxpwifi_drv_get_data_rate(struct nxpwifi_private *priv, u32 *rate); + +int nxpwifi_scan_networks(struct nxpwifi_private *priv, + const struct nxpwifi_user_scan_cfg *user_scan_in); +int nxpwifi_set_radio(struct nxpwifi_private *priv, u8 option); + +int nxpwifi_set_encode(struct nxpwifi_private *priv, struct key_params *kp, + const u8 *key, int key_len, u8 key_index, + const u8 *mac_addr, int disable); + +int nxpwifi_set_gen_ie(struct nxpwifi_private *priv, const u8 *ie, int ie_len); + +int nxpwifi_get_ver_ext(struct nxpwifi_private *priv, u32 version_str_sel); + +int nxpwifi_remain_on_chan_cfg(struct nxpwifi_private *priv, u16 action, + struct ieee80211_channel *chan, + unsigned int duration); + +int nxpwifi_get_stats_info(struct nxpwifi_private *priv, + struct nxpwifi_ds_get_stats *log); + +int nxpwifi_reg_write(struct nxpwifi_private *priv, u32 reg_type, + u32 reg_offset, u32 reg_value); + +int nxpwifi_reg_read(struct nxpwifi_private *priv, u32 reg_type, + u32 reg_offset, u32 *value); + +int nxpwifi_eeprom_read(struct nxpwifi_private *priv, u16 offset, u16 bytes, + u8 *value); + +int nxpwifi_set_11n_httx_cfg(struct nxpwifi_private *priv, int data); + +int nxpwifi_get_11n_httx_cfg(struct nxpwifi_private *priv, int *data); + +int nxpwifi_set_tx_rate_cfg(struct nxpwifi_private *priv, int tx_rate_index); + +int nxpwifi_get_tx_rate_cfg(struct nxpwifi_private *priv, int *tx_rate_index); + +int nxpwifi_drv_set_power(struct nxpwifi_private *priv, u32 *ps_mode); + +int nxpwifi_drv_get_driver_version(struct nxpwifi_adapter *adapter, + char *version, int max_len); + +int nxpwifi_set_tx_power(struct nxpwifi_private *priv, + struct nxpwifi_power_cfg *power_cfg); + +void nxpwifi_main_process(struct nxpwifi_adapter *adapter); + +void nxpwifi_queue_tx_pkt(struct nxpwifi_private *priv, struct sk_buff *skb); + +int nxpwifi_get_bss_info(struct nxpwifi_private *priv, + struct nxpwifi_bss_info *info); +int nxpwifi_fill_new_bss_desc(struct nxpwifi_private *priv, + struct cfg80211_bss *bss, + struct nxpwifi_bssdescriptor *bss_desc); +int nxpwifi_update_bss_desc_with_ie(struct nxpwifi_adapter *adapter, + struct nxpwifi_bssdescriptor *bss_entry); +int nxpwifi_check_network_compatibility(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc); + +u8 nxpwifi_chan_type_to_sec_chan_offset(enum nl80211_channel_type chan_type); +u8 nxpwifi_get_chan_type(struct nxpwifi_private *priv); + +struct wireless_dev *nxpwifi_add_virtual_intf(struct wiphy *wiphy, + const char *name, + unsigned char name_assign_type, + enum nl80211_iftype type, + struct vif_params *params); +int nxpwifi_del_virtual_intf(struct wiphy *wiphy, struct wireless_dev *wdev); + +int nxpwifi_add_wowlan_magic_pkt_filter(struct nxpwifi_adapter *adapter); + +int nxpwifi_set_mgmt_ies(struct nxpwifi_private *priv, + struct cfg80211_beacon_data *data); +int nxpwifi_del_mgmt_ies(struct nxpwifi_private *priv); +u8 *nxpwifi_11d_code_2_region(u8 code); +void nxpwifi_init_11h_params(struct nxpwifi_private *priv); +int nxpwifi_is_11h_active(struct nxpwifi_private *priv); +int nxpwifi_11h_activate(struct nxpwifi_private *priv, bool flag); +void nxpwifi_11h_process_join(struct nxpwifi_private *priv, u8 **buffer, + struct nxpwifi_bssdescriptor *bss_desc); +int nxpwifi_11h_handle_event_chanswann(struct nxpwifi_private *priv); + +extern const struct ethtool_ops nxpwifi_ethtool_ops; + +void nxpwifi_del_all_sta_list(struct nxpwifi_private *priv); +void nxpwifi_del_sta_entry(struct nxpwifi_private *priv, const u8 *mac); +void +nxpwifi_set_sta_ht_cap(struct nxpwifi_private *priv, const u8 *ies, + int ies_len, struct nxpwifi_sta_node *node); +struct nxpwifi_sta_node * +nxpwifi_add_sta_entry(struct nxpwifi_private *priv, const u8 *mac); +struct nxpwifi_sta_node * +nxpwifi_get_sta_entry(struct nxpwifi_private *priv, const u8 *mac); +struct nxpwifi_sta_node * +nxpwifi_get_sta_entry_rcu(struct nxpwifi_private *priv, const u8 *mac); +int nxpwifi_init_channel_scan_gap(struct nxpwifi_adapter *adapter); + +int nxpwifi_cmd_issue_chan_report_request(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + void *data_buf); +int nxpwifi_11h_handle_chanrpt_ready(struct nxpwifi_private *priv, + struct sk_buff *skb); + +void nxpwifi_parse_tx_status_event(struct nxpwifi_private *priv, + void *event_body); + +struct sk_buff * +nxpwifi_clone_skb_for_tx_status(struct nxpwifi_private *priv, + struct sk_buff *skb, u8 flag, u64 *cookie); +void nxpwifi_reset_conn_state_work(struct wiphy *wiphy, struct wiphy_work *work); +void nxpwifi_dfs_cac_work(struct wiphy *wiphy, struct wiphy_work *work); +void nxpwifi_dfs_chan_sw_work(struct wiphy *wiphy, struct wiphy_work *work); +void nxpwifi_abort_cac(struct nxpwifi_private *priv); +int nxpwifi_stop_radar_detection(struct nxpwifi_private *priv, + struct cfg80211_chan_def *chandef); +int nxpwifi_11h_handle_radar_detected(struct nxpwifi_private *priv, + struct sk_buff *skb); + +void nxpwifi_hist_data_set(struct nxpwifi_private *priv, u8 rx_rate, s8 snr, + s8 nflr); +void nxpwifi_hist_data_reset(struct nxpwifi_private *priv); +void nxpwifi_hist_data_add(struct nxpwifi_private *priv, + u8 rx_rate, s8 snr, s8 nflr); +u8 nxpwifi_adjust_data_rate(struct nxpwifi_private *priv, + u8 rx_rate, u8 ht_info); + +void nxpwifi_drv_info_dump(struct nxpwifi_adapter *adapter); +void nxpwifi_prepare_fw_dump_info(struct nxpwifi_adapter *adapter); +void nxpwifi_upload_device_dump(struct nxpwifi_adapter *adapter); +void *nxpwifi_alloc_dma_align_buf(int rx_len, gfp_t flags); +void nxpwifi_fw_dump_event(struct nxpwifi_private *priv); +int nxpwifi_get_wakeup_reason(struct nxpwifi_private *priv, u16 action, + int cmd_type, + struct nxpwifi_ds_wakeup_reason *wakeup_reason); +int nxpwifi_get_chan_info(struct nxpwifi_private *priv, + struct nxpwifi_channel_band *channel_band); +void nxpwifi_coex_ampdu_rxwinsize(struct nxpwifi_adapter *adapter); +void nxpwifi_11n_delba(struct nxpwifi_private *priv, int tid); +int nxpwifi_send_domain_info_cmd_fw(struct wiphy *wiphy, enum nl80211_band band); +int nxpwifi_set_mac_address(struct nxpwifi_private *priv, + struct net_device *dev, + bool external, u8 *new_mac); +void nxpwifi_devdump_tmo_func(unsigned long function_context); + +#ifdef CONFIG_DEBUG_FS +void nxpwifi_debugfs_init(void); +void nxpwifi_debugfs_remove(void); + +void nxpwifi_dev_debugfs_init(struct nxpwifi_private *priv); +void nxpwifi_dev_debugfs_remove(struct nxpwifi_private *priv); +#endif +int nxpwifi_reinit_sw(struct nxpwifi_adapter *adapter); +void nxpwifi_shutdown_sw(struct nxpwifi_adapter *adapter); +#endif /* !_NXPWIFI_MAIN_H_ */ diff --git a/drivers/net/wireless/nxp/nxpwifi/scan.c b/drivers/net/wireless/nxp/nxpwifi/scan.c new file mode 100644 index 000000000000..bb3ce2b6f4b9 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/scan.c @@ -0,0 +1,2695 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: scan ioctl and command handling + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cmdevt.h" +#include "11n.h" +#include "11ac.h" +#include "11ax.h" +#include "cfg80211.h" + +/* The maximum number of channels the firmware can scan per command */ +#define NXPWIFI_MAX_CHANNELS_PER_SPECIFIC_SCAN 14 + +#define NXPWIFI_DEF_CHANNELS_PER_SCAN_CMD 4 + +/* Memory needed to store a max sized Channel List TLV for a firmware scan */ +#define CHAN_TLV_MAX_SIZE (sizeof(struct nxpwifi_ie_types_header) \ + + (NXPWIFI_MAX_CHANNELS_PER_SPECIFIC_SCAN \ + * sizeof(struct nxpwifi_chan_scan_param_set))) + +/* Memory needed to store supported rate */ +#define RATE_TLV_MAX_SIZE (sizeof(struct nxpwifi_ie_types_rates_param_set) \ + + HOSTCMD_SUPPORTED_RATES) + +/* Memory needed to store a max number/size WildCard SSID TLV for a firmware scan */ +#define WILDCARD_SSID_TLV_MAX_SIZE \ + (NXPWIFI_MAX_SSID_LIST_LENGTH * \ + (sizeof(struct nxpwifi_ie_types_wildcard_ssid_params) \ + + IEEE80211_MAX_SSID_LEN)) + +/* Maximum memory needed for a nxpwifi_scan_cmd_config with all TLVs at max */ +#define MAX_SCAN_CFG_ALLOC (sizeof(struct nxpwifi_scan_cmd_config) \ + + sizeof(struct nxpwifi_ie_types_num_probes) \ + + sizeof(struct nxpwifi_ie_types_htcap) \ + + sizeof(struct nxpwifi_ie_types_vhtcap) \ + + sizeof(struct nxpwifi_ie_types_he_cap) \ + + CHAN_TLV_MAX_SIZE \ + + RATE_TLV_MAX_SIZE \ + + WILDCARD_SSID_TLV_MAX_SIZE) + +union nxpwifi_scan_cmd_config_tlv { + /* Scan configuration (variable length) */ + struct nxpwifi_scan_cmd_config config; + /* Max allocated block */ + u8 config_alloc_buf[MAX_SCAN_CFG_ALLOC]; +}; + +#define NXPWIFI_WPA_CIPHER_SUITE_TKIP SUITE(WLAN_OUI_MICROSOFT, 2) +#define NXPWIFI_WPA_CIPHER_SUITE_CCMP SUITE(WLAN_OUI_MICROSOFT, 4) + +static void +_dbg_security_flags(int log_level, const char *func, const char *desc, + struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc) +{ + _nxpwifi_dbg(priv->adapter, log_level, + "info: %s: %s:\twpa_ie=%#x wpa2_ie=%#x WEP=%s WPA=%s WPA2=%s\tEncMode=%#x privacy=%#x\n", + func, desc, + bss_desc->bcn_wpa_ie ? + bss_desc->bcn_wpa_ie->vend_hdr.element_id : 0, + bss_desc->bcn_rsn_ie ? + bss_desc->bcn_rsn_ie->id : 0, + priv->sec_info.wep_enabled ? "e" : "d", + priv->sec_info.wpa_enabled ? "e" : "d", + priv->sec_info.wpa2_enabled ? "e" : "d", + priv->sec_info.encryption_mode, + bss_desc->privacy); +} + +#define dbg_security_flags(mask, desc, priv, bss_desc) \ + _dbg_security_flags(NXPWIFI_DBG_##mask, __func__, desc, priv, bss_desc) + +/* Parse a WPA/RSN element and check whether its PTK list contains the OUI */ +static u8 +nxpwifi_search_oui_in_ie(struct ie_body *iebody, u8 *oui) +{ + u8 count; + + count = iebody->ptk_cnt[0]; + + /* + * PTK may contain multiple OUIs; iterate through the list and compare + * each one + */ + while (count) { + if (!memcmp(iebody->ptk_body, oui, sizeof(iebody->ptk_body))) + return NXPWIFI_OUI_PRESENT; + + --count; + if (count) + iebody = (struct ie_body *)((u8 *)iebody + + sizeof(iebody->ptk_body)); + } + + pr_debug("info: %s: OUI is not found in PTK\n", __func__); + return NXPWIFI_OUI_NOT_PRESENT; +} + +/* Check whether the RSN IE is present and if its PTK list contains the OUI */ +static u8 +nxpwifi_is_rsn_oui_present(struct nxpwifi_bssdescriptor *bss_desc, + u32 cipher) +{ + struct ie_body *iebody; + u8 ret = NXPWIFI_OUI_NOT_PRESENT; + __be32 oui = cpu_to_be32(cipher); + + if (bss_desc->bcn_rsn_ie) { + iebody = (struct ie_body *) + (((u8 *)bss_desc->bcn_rsn_ie->data) + + RSN_GTK_OUI_OFFSET); + ret = nxpwifi_search_oui_in_ie(iebody, (u8 *)&oui); + if (ret) + return ret; + } + return ret; +} + +/* Check if the WPA IE exists and whether its PTK list contains the OUI */ +static u8 +nxpwifi_is_wpa_oui_present(struct nxpwifi_bssdescriptor *bss_desc, u32 cipher) +{ + struct ie_body *iebody; + u8 ret = NXPWIFI_OUI_NOT_PRESENT; + __be32 oui = cpu_to_be32(cipher); + + if (bss_desc->bcn_wpa_ie) { + iebody = (struct ie_body *)((u8 *)bss_desc->bcn_wpa_ie->data + + WPA_GTK_OUI_OFFSET); + ret = nxpwifi_search_oui_in_ie(iebody, (u8 *)&oui); + if (ret) + return ret; + } + return ret; +} + +/* Check whether both driver and BSS operate with no security */ +static bool +nxpwifi_is_bss_no_sec(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc) +{ + if (!priv->sec_info.wep_enabled && !priv->sec_info.wpa_enabled && + !priv->sec_info.wpa2_enabled && + !bss_desc->bcn_rsn_ie && + !bss_desc->bcn_wpa_ie && + !priv->sec_info.encryption_mode && !bss_desc->privacy) { + return true; + } + return false; +} + +/* Check whether static WEP is enabled and the BSS privacy setting matches */ +static bool +nxpwifi_is_bss_static_wep(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc) +{ + if (priv->sec_info.wep_enabled && !priv->sec_info.wpa_enabled && + !priv->sec_info.wpa2_enabled && bss_desc->privacy) { + return true; + } + return false; +} + +/* Check whether WPA is enabled and the BSS contains a WPA IE */ +static bool +nxpwifi_is_bss_wpa(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc) +{ + if (!priv->sec_info.wep_enabled && priv->sec_info.wpa_enabled && + !priv->sec_info.wpa2_enabled && + bss_desc->bcn_wpa_ie) { + dbg_security_flags(INFO, "WPA", priv, bss_desc); + return true; + } + return false; +} + +/* Check whether WPA2 is enabled and the BSS includes an RSN IE */ +static bool +nxpwifi_is_bss_wpa2(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc) +{ + if (!priv->sec_info.wep_enabled && !priv->sec_info.wpa_enabled && + priv->sec_info.wpa2_enabled && + bss_desc->bcn_rsn_ie) { + /* + * Some APs (e.g., WRT54G) may omit the privacy bit even when + * using WPA2 + */ + dbg_security_flags(ERROR, "WPA2", priv, bss_desc); + return true; + } + return false; +} + +/* Check dynamic WEP: enabled in driver, privacy set, and no WPA/RSN IE present */ +static bool +nxpwifi_is_bss_dynamic_wep(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc) +{ + if (!priv->sec_info.wep_enabled && !priv->sec_info.wpa_enabled && + !priv->sec_info.wpa2_enabled && + !bss_desc->bcn_wpa_ie && + !bss_desc->bcn_rsn_ie && + priv->sec_info.encryption_mode && bss_desc->privacy) { + dbg_security_flags(INFO, "dynamic", priv, bss_desc); + return true; + } + return false; +} + +/* + * Check whether a scanned network is compatible with the driver's security + * configuration. The decision considers WEP, WPA, WPA2, privacy settings, + * and whether HT must be disabled when required (e.g., no AES). + * + * General rules: + * - Open networks: always compatible. + * - WPA-only: compatible; HT disabled if AES is not supported. + * - WPA2-only: compatible; HT disabled if AES is not supported. + * - Static WEP: compatible; HT disabled. + * - Dynamic WEP: compatible when privacy is enabled. + * + * Note: Compatibility is not enforced during roaming except for security mode. + */ +static int +nxpwifi_is_network_compatible(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc, u32 mode) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + bss_desc->disable_11n = false; + + /* Skip compatibility checks while roaming */ + if (priv->media_connected && + priv->bss_mode == NL80211_IFTYPE_STATION && + bss_desc->bss_mode == NL80211_IFTYPE_STATION) + return 0; + + if (priv->wps.session_enable) { + nxpwifi_dbg(adapter, IOCTL, + "info: return success directly in WPS period\n"); + return 0; + } + + if (bss_desc->chan_sw_ie_present) { + nxpwifi_dbg(adapter, INFO, + "Don't connect to AP with WLAN_EID_CHANNEL_SWITCH\n"); + return -EPERM; + } + + if (bss_desc->bss_mode == mode) { + if (nxpwifi_is_bss_no_sec(priv, bss_desc)) { + return 0; + } else if (nxpwifi_is_bss_static_wep(priv, bss_desc)) { + nxpwifi_dbg(adapter, INFO, + "info: Disable 11n in WEP mode.\n"); + bss_desc->disable_11n = true; + return 0; + } else if (nxpwifi_is_bss_wpa(priv, bss_desc)) { + if (((priv->config_bands & BAND_GN || + priv->config_bands & BAND_AN) && + bss_desc->bcn_ht_cap) && + !nxpwifi_is_wpa_oui_present(bss_desc, + NXPWIFI_WPA_CIPHER_SUITE_CCMP)) { + if (nxpwifi_is_wpa_oui_present + (bss_desc, NXPWIFI_WPA_CIPHER_SUITE_TKIP)) { + nxpwifi_dbg(adapter, INFO, + "info: Disable 11n if AES\t" + "is not supported by AP\n"); + bss_desc->disable_11n = true; + } else { + return -EINVAL; + } + } + return 0; + } else if (nxpwifi_is_bss_wpa2(priv, bss_desc)) { + if (((priv->config_bands & BAND_GN || + priv->config_bands & BAND_AN) && + bss_desc->bcn_ht_cap) && + !nxpwifi_is_rsn_oui_present(bss_desc, + WLAN_CIPHER_SUITE_CCMP)) { + if (nxpwifi_is_rsn_oui_present + (bss_desc, WLAN_CIPHER_SUITE_TKIP)) { + nxpwifi_dbg(adapter, INFO, + "info: Disable 11n if AES\t" + "is not supported by AP\n"); + bss_desc->disable_11n = true; + } else if (nxpwifi_is_rsn_oui_present + (bss_desc, WLAN_CIPHER_SUITE_GCMP_256) || + nxpwifi_is_rsn_oui_present + (bss_desc, WLAN_CIPHER_SUITE_CCMP_256)) { + return 0; + } else { + return -EINVAL; + } + } + return 0; + } else if (nxpwifi_is_bss_dynamic_wep(priv, bss_desc)) { + return 0; + } + + /* Security mismatch */ + dbg_security_flags(ERROR, "failed", priv, bss_desc); + return -EINVAL; + } + + return -EINVAL; +} + +/* + * Build the channel list for scanning based on region and band settings. + * Used when a scan request does not specify its own channel list. + */ +static int +nxpwifi_scan_create_channel_list(struct nxpwifi_private *priv, + const struct nxpwifi_user_scan_cfg + *user_scan_in, + struct nxpwifi_chan_scan_param_set + *scan_chan_list, + u8 filtered_scan) +{ + enum nl80211_band band; + struct ieee80211_supported_band *sband; + struct ieee80211_channel *ch; + struct nxpwifi_adapter *adapter = priv->adapter; + int chan_idx = 0, i; + u16 scan_time = 0; + + if (user_scan_in) + scan_time = (u16)user_scan_in->chan_list[0].scan_time; + + for (band = 0; (band < NUM_NL80211_BANDS) ; band++) { + if (!priv->wdev.wiphy->bands[band]) + continue; + + sband = priv->wdev.wiphy->bands[band]; + + for (i = 0; (i < sband->n_channels) ; i++) { + ch = &sband->channels[i]; + if (ch->flags & IEEE80211_CHAN_DISABLED) + continue; + scan_chan_list[chan_idx].band_cfg = band; + + if (scan_time) + scan_chan_list[chan_idx].max_scan_time = + cpu_to_le16(scan_time); + else if ((ch->flags & IEEE80211_CHAN_NO_IR) || + (ch->flags & IEEE80211_CHAN_RADAR)) + scan_chan_list[chan_idx].max_scan_time = + cpu_to_le16(adapter->passive_scan_time); + else + scan_chan_list[chan_idx].max_scan_time = + cpu_to_le16(adapter->active_scan_time); + + if (ch->flags & IEEE80211_CHAN_NO_IR) + scan_chan_list[chan_idx].chan_scan_mode_bmap |= + (NXPWIFI_PASSIVE_SCAN | NXPWIFI_HIDDEN_SSID_REPORT); + else + scan_chan_list[chan_idx].chan_scan_mode_bmap &= + ~NXPWIFI_PASSIVE_SCAN; + + scan_chan_list[chan_idx].chan_number = (u32)ch->hw_value; + scan_chan_list[chan_idx].chan_scan_mode_bmap |= + NXPWIFI_DISABLE_CHAN_FILT; + + if (filtered_scan && + !((ch->flags & IEEE80211_CHAN_NO_IR) || + (ch->flags & IEEE80211_CHAN_RADAR))) + scan_chan_list[chan_idx].max_scan_time = + cpu_to_le16(adapter->specific_scan_time); + + chan_idx++; + } + } + return chan_idx; +} + +/* + * Build the channel-list TLV for bgscan based on region and band settings. + */ +static int +nxpwifi_bgscan_create_channel_list(struct nxpwifi_private *priv, + const struct nxpwifi_bg_scan_cfg + *bgscan_cfg_in, + struct nxpwifi_chan_scan_param_set + *scan_chan_list) +{ + enum nl80211_band band; + struct ieee80211_supported_band *sband; + struct ieee80211_channel *ch; + struct nxpwifi_adapter *adapter = priv->adapter; + int chan_idx = 0, i; + u16 scan_time = 0, specific_scan_time = adapter->specific_scan_time; + + if (bgscan_cfg_in) + scan_time = (u16)bgscan_cfg_in->chan_list[0].scan_time; + + for (band = 0; (band < NUM_NL80211_BANDS); band++) { + if (!priv->wdev.wiphy->bands[band]) + continue; + + sband = priv->wdev.wiphy->bands[band]; + + for (i = 0; (i < sband->n_channels) ; i++) { + ch = &sband->channels[i]; + if (ch->flags & IEEE80211_CHAN_DISABLED) + continue; + scan_chan_list[chan_idx].band_cfg = band; + + if (scan_time) + scan_chan_list[chan_idx].max_scan_time = + cpu_to_le16(scan_time); + else if (ch->flags & IEEE80211_CHAN_NO_IR) + scan_chan_list[chan_idx].max_scan_time = + cpu_to_le16(adapter->passive_scan_time); + else + scan_chan_list[chan_idx].max_scan_time = + cpu_to_le16(specific_scan_time); + + if (ch->flags & IEEE80211_CHAN_NO_IR) + scan_chan_list[chan_idx].chan_scan_mode_bmap |= + NXPWIFI_PASSIVE_SCAN; + else + scan_chan_list[chan_idx].chan_scan_mode_bmap &= + ~NXPWIFI_PASSIVE_SCAN; + + scan_chan_list[chan_idx].chan_number = (u32)ch->hw_value; + chan_idx++; + } + } + return chan_idx; +} + +/* Append the rate TLV to the scan configuration command */ +static int +nxpwifi_append_rate_tlv(struct nxpwifi_private *priv, + struct nxpwifi_scan_cmd_config *scan_cfg_out, + u8 radio) +{ + struct nxpwifi_ie_types_rates_param_set *rates_tlv; + u8 rates[NXPWIFI_SUPPORTED_RATES], *tlv_pos; + u32 rates_size; + + memset(rates, 0, sizeof(rates)); + + tlv_pos = (u8 *)scan_cfg_out->tlv_buf + scan_cfg_out->tlv_buf_len; + + if (priv->scan_request) + rates_size = nxpwifi_get_rates_from_cfg80211(priv, rates, + radio); + else + rates_size = nxpwifi_get_supported_rates(priv, rates); + + nxpwifi_dbg(priv->adapter, CMD, + "info: SCAN_CMD: Rates size = %d\n", + rates_size); + rates_tlv = (struct nxpwifi_ie_types_rates_param_set *)tlv_pos; + rates_tlv->header.type = cpu_to_le16(WLAN_EID_SUPP_RATES); + rates_tlv->header.len = cpu_to_le16((u16)rates_size); + memcpy(rates_tlv->rates, rates, rates_size); + scan_cfg_out->tlv_buf_len += sizeof(rates_tlv->header) + rates_size; + + return rates_size; +} + +/* + * Build and send multiple scan commands by chunking channel TLVs per scan + * limit. + */ +static int +nxpwifi_scan_channel_list(struct nxpwifi_private *priv, + u32 max_chan_per_scan, u8 filtered_scan, + struct nxpwifi_scan_cmd_config *scan_cfg_out, + struct nxpwifi_ie_types_chan_list_param_set *tlv_o, + struct nxpwifi_chan_scan_param_set *scan_chan_list) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int ret = 0; + struct nxpwifi_chan_scan_param_set *tmp_chan_list; + u32 tlv_idx, rates_size, cmd_no; + u32 total_scan_time; + u32 done_early; + u8 radio_type; + + if (!scan_cfg_out || !tlv_o || !scan_chan_list) { + nxpwifi_dbg(priv->adapter, ERROR, + "info: Scan: Null detect: %p, %p, %p\n", + scan_cfg_out, tlv_o, scan_chan_list); + return -EINVAL; + } + + /* Check csa channel expiry before preparing scan list */ + nxpwifi_11h_get_csa_closed_channel(priv); + + tlv_o->header.type = cpu_to_le16(TLV_TYPE_CHANLIST); + + tmp_chan_list = scan_chan_list; + + /* + * Iterate through the channel list and send a firmware scan command for + * each group of max_chan_per_scan channels, or individually for + * channels 1, 6, and 11 when configured. + */ + while (tmp_chan_list->chan_number) { + tlv_idx = 0; + total_scan_time = 0; + radio_type = 0; + tlv_o->header.len = 0; + done_early = false; + + /* + * Build the channel TLV for the scan command. Continue adding + * channel TLVs until one of the following conditions is met: + * - tlv_idx reaches the maximum allowed per scan command + * - the next channel is 0 (end of the desired channel list) + * - done_early is set (used for per-channel scanning of 1, 6, + * and 11) + */ + while (tlv_idx < max_chan_per_scan && + tmp_chan_list->chan_number && !done_early) { + if (tmp_chan_list->chan_number == priv->csa_chan) { + tmp_chan_list++; + continue; + } + + radio_type = tmp_chan_list->band_cfg; + nxpwifi_dbg(priv->adapter, INFO, + "info: Scan: Chan(%3d), Band(%d),\t" + "Mode(%d, %d), Dur(%d)\n", + tmp_chan_list->chan_number, + tmp_chan_list->band_cfg, + tmp_chan_list->chan_scan_mode_bmap + & NXPWIFI_PASSIVE_SCAN, + (tmp_chan_list->chan_scan_mode_bmap + & NXPWIFI_DISABLE_CHAN_FILT) >> 1, + le16_to_cpu(tmp_chan_list->max_scan_time)); + + /* Copy the current channel TLV into the command being prepared */ + memcpy(&tlv_o->chan_scan_param[tlv_idx], tmp_chan_list, + sizeof(*tlv_o->chan_scan_param)); + + /* + * Increment the TLV header length by the size + * appended + */ + le16_unaligned_add_cpu(&tlv_o->header.len, + sizeof(*tlv_o->chan_scan_param)); + + /* + * The tlv buffer length is set to the number of bytes + * of the between the channel tlv pointer and the start + * of the tlv buffer. This compensates for any TLVs + * that were appended before the channel list. + */ + scan_cfg_out->tlv_buf_len = + (u32)((u8 *)tlv_o - scan_cfg_out->tlv_buf); + + scan_cfg_out->tlv_buf_len += + (sizeof(tlv_o->header) + + le16_to_cpu(tlv_o->header.len)); + + /* Advance the index for the channel TLV being constructed. */ + tlv_idx++; + + /* Count the total scan time per command */ + total_scan_time += + le16_to_cpu(tmp_chan_list->max_scan_time); + + done_early = false; + + /* + * Stop the loop if the current channel is one of 1, 6, + * or 11 and no SSID or BSSID filter is applied. + */ + if (!filtered_scan && + (tmp_chan_list->chan_number == 1 || + tmp_chan_list->chan_number == 6 || + tmp_chan_list->chan_number == 11)) + done_early = true; + + /* Advance the tmp pointer to the next channel to be scanned. */ + tmp_chan_list++; + + /* + * Stop the loop if the next channel is one of 1, 6, + * or 11. This causes that channel to be scanned alone + * in the next iteration. + */ + if (!filtered_scan && + (tmp_chan_list->chan_number == 1 || + tmp_chan_list->chan_number == 6 || + tmp_chan_list->chan_number == 11)) + done_early = true; + } + + /* Ensure the total scan time does not exceed the scan-command timeout. */ + if (total_scan_time > NXPWIFI_MAX_TOTAL_SCAN_TIME) { + nxpwifi_dbg(priv->adapter, ERROR, + "total scan time %dms\t" + "is over limit (%dms), scan skipped\n", + total_scan_time, + NXPWIFI_MAX_TOTAL_SCAN_TIME); + ret = -EINVAL; + break; + } + + rates_size = nxpwifi_append_rate_tlv(priv, scan_cfg_out, + radio_type); + + if (priv->adapter->ext_scan) + cmd_no = HOST_CMD_802_11_SCAN_EXT; + else + cmd_no = HOST_CMD_802_11_SCAN; + + ret = nxpwifi_send_cmd(priv, cmd_no, HOST_ACT_GEN_SET, + 0, scan_cfg_out, false); + + /* + * The rate element is updated for each scan command, but the + * same starting pointer is reused, so the previous rate element + * in scan_cfg_out->buf is overwritten. + */ + scan_cfg_out->tlv_buf_len -= + sizeof(struct nxpwifi_ie_types_header) + rates_size; + + if (ret) { + nxpwifi_cancel_pending_scan_cmd(adapter); + break; + } + } + + return ret; +} + +/* + * Build final scan config from user params, disabling missing filters and using + * defaults. + */ +static void +nxpwifi_config_scan(struct nxpwifi_private *priv, + const struct nxpwifi_user_scan_cfg *user_scan_in, + struct nxpwifi_scan_cmd_config *scan_cfg_out, + struct nxpwifi_ie_types_chan_list_param_set **chan_list_out, + struct nxpwifi_chan_scan_param_set *scan_chan_list, + u8 *max_chan_per_scan, u8 *filtered_scan, + u8 *scan_current_only) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_ie_types_num_probes *num_probes_tlv; + struct nxpwifi_ie_types_scan_chan_gap *chan_gap_tlv; + struct nxpwifi_ie_types_random_mac *random_mac_tlv; + struct nxpwifi_ie_types_wildcard_ssid_params *wildcard_ssid_tlv; + struct nxpwifi_ie_types_bssid_list *bssid_tlv; + struct nxpwifi_ie_types_extcap *ext_cap; + u8 *ext_capab = NULL; + u8 *tlv_pos; + u32 num_probes; + u32 ssid_len; + u32 chan_idx; + u32 scan_time; + u32 scan_type; + u16 scan_dur; + u8 channel; + u8 radio_type; + int i, vsid; + u8 ssid_filter; + struct nxpwifi_ie_types_htcap *ht_cap; + struct nxpwifi_ie_types_bss_mode *bss_mode; + struct nxpwifi_ie_types_vhtcap *vht_cap; + struct nxpwifi_ie_types_he_cap *he_cap; + + /* + * tlv_buf_len is recalculated for each scan command. TLVs added in this + * routine are preserved because the send routine appends channel TLVs + * at chan_list_out. The difference between chan_list_out and the start + * of the TLV buffer determines the size of the TLVs added here. + */ + scan_cfg_out->tlv_buf_len = 0; + + /* + * Running TLV pointer. It is assigned to chan_list_out at the end of + * the function so later routines know where channel TLVs can be + * appended in the command buffer. + */ + tlv_pos = scan_cfg_out->tlv_buf; + + /* + * Initialize the scan as un-filtered; the flag is later set to TRUE + * below if a SSID or BSSID filter is sent in the command + */ + *filtered_scan = false; + + /* + * Initialize the scan as not being only on the current channel. If + * the channel list is customized, only contains one channel, and is + * the active channel, this is set true and data flow is not halted. + */ + *scan_current_only = false; + + if (user_scan_in) { + u8 tmpaddr[ETH_ALEN]; + + /* + * Default the ssid_filter flag to TRUE, set false under + * certain wildcard conditions and qualified by the existence + * of an SSID list before marking the scan as filtered + */ + ssid_filter = true; + + /* + * Set the BSS type scan filter, use Adapter setting if + * unset + */ + scan_cfg_out->bss_mode = + (u8)(user_scan_in->bss_mode ?: adapter->scan_mode); + + /* + * Set the number of probes to send, use Adapter setting + * if unset + */ + num_probes = user_scan_in->num_probes ?: adapter->scan_probes; + + /* + * Set the BSSID filter to the incoming configuration, + * if non-zero. If not set, it will remain disabled + * (all zeros). + */ + memcpy(scan_cfg_out->specific_bssid, + user_scan_in->specific_bssid, + sizeof(scan_cfg_out->specific_bssid)); + + memcpy(tmpaddr, scan_cfg_out->specific_bssid, ETH_ALEN); + + if (adapter->ext_scan && + !is_zero_ether_addr(tmpaddr)) { + bssid_tlv = + (struct nxpwifi_ie_types_bssid_list *)tlv_pos; + bssid_tlv->header.type = cpu_to_le16(TLV_TYPE_BSSID); + bssid_tlv->header.len = cpu_to_le16(ETH_ALEN); + memcpy(bssid_tlv->bssid, user_scan_in->specific_bssid, + ETH_ALEN); + tlv_pos += sizeof(struct nxpwifi_ie_types_bssid_list); + } + + for (i = 0; i < user_scan_in->num_ssids; i++) { + ssid_len = user_scan_in->ssid_list[i].ssid_len; + + wildcard_ssid_tlv = + (struct nxpwifi_ie_types_wildcard_ssid_params *) + tlv_pos; + wildcard_ssid_tlv->header.type = + cpu_to_le16(TLV_TYPE_WILDCARDSSID); + wildcard_ssid_tlv->header.len = + cpu_to_le16((u16)(ssid_len + sizeof(u8))); + + /* + * max_ssid_length = 0 tells firmware to perform + * specific scan for the SSID filled, whereas + * max_ssid_length = IEEE80211_MAX_SSID_LEN is for + * wildcard scan. + */ + if (ssid_len) + wildcard_ssid_tlv->max_ssid_length = 0; + else + wildcard_ssid_tlv->max_ssid_length = + IEEE80211_MAX_SSID_LEN; + + if (!memcmp(user_scan_in->ssid_list[i].ssid, + "DIRECT-", 7)) + wildcard_ssid_tlv->max_ssid_length = 0xfe; + + memcpy(wildcard_ssid_tlv->ssid, + user_scan_in->ssid_list[i].ssid, ssid_len); + + tlv_pos += (sizeof(wildcard_ssid_tlv->header) + + le16_to_cpu(wildcard_ssid_tlv->header.len)); + + nxpwifi_dbg(adapter, INFO, + "info: scan: ssid[%d]: %s, %d\n", + i, wildcard_ssid_tlv->ssid, + wildcard_ssid_tlv->max_ssid_length); + + /* + * Empty wildcard ssid with a maxlen will match many or + * potentially all SSIDs (maxlen == 32), therefore do + * not treat the scan as + * filtered. + */ + if (!ssid_len && wildcard_ssid_tlv->max_ssid_length) + ssid_filter = false; + } + + /* + * The default number of channels sent in the command is low to + * ensure the response buffer from the firmware does not + * truncate scan results. That is not an issue with an SSID + * or BSSID filter applied to the scan results in the firmware. + */ + memcpy(tmpaddr, scan_cfg_out->specific_bssid, ETH_ALEN); + if ((i && ssid_filter) || + !is_zero_ether_addr(tmpaddr)) + *filtered_scan = true; + + if (user_scan_in->scan_chan_gap) { + nxpwifi_dbg(adapter, INFO, + "info: scan: channel gap = %d\n", + user_scan_in->scan_chan_gap); + *max_chan_per_scan = + NXPWIFI_MAX_CHANNELS_PER_SPECIFIC_SCAN; + + chan_gap_tlv = (void *)tlv_pos; + chan_gap_tlv->header.type = + cpu_to_le16(TLV_TYPE_SCAN_CHANNEL_GAP); + chan_gap_tlv->header.len = + cpu_to_le16(sizeof(chan_gap_tlv->chan_gap)); + chan_gap_tlv->chan_gap = + cpu_to_le16((user_scan_in->scan_chan_gap)); + tlv_pos += + sizeof(struct nxpwifi_ie_types_scan_chan_gap); + } + + if (!is_zero_ether_addr(user_scan_in->random_mac)) { + random_mac_tlv = (void *)tlv_pos; + random_mac_tlv->header.type = + cpu_to_le16(TLV_TYPE_RANDOM_MAC); + random_mac_tlv->header.len = + cpu_to_le16(sizeof(random_mac_tlv->mac)); + ether_addr_copy(random_mac_tlv->mac, + user_scan_in->random_mac); + tlv_pos += + sizeof(struct nxpwifi_ie_types_random_mac); + } + } else { + scan_cfg_out->bss_mode = (u8)adapter->scan_mode; + num_probes = adapter->scan_probes; + } + + /* + * If a specific BSSID or SSID is used, the number of channels in the + * scan command will be increased to the absolute maximum. + */ + if (*filtered_scan) { + *max_chan_per_scan = NXPWIFI_MAX_CHANNELS_PER_SPECIFIC_SCAN; + } else { + if (!priv->media_connected) + *max_chan_per_scan = NXPWIFI_DEF_CHANNELS_PER_SCAN_CMD; + else + *max_chan_per_scan = + NXPWIFI_DEF_CHANNELS_PER_SCAN_CMD / 2; + } + + if (adapter->ext_scan) { + bss_mode = (struct nxpwifi_ie_types_bss_mode *)tlv_pos; + bss_mode->header.type = cpu_to_le16(TLV_TYPE_BSS_MODE); + bss_mode->header.len = cpu_to_le16(sizeof(bss_mode->bss_mode)); + bss_mode->bss_mode = scan_cfg_out->bss_mode; + tlv_pos += sizeof(bss_mode->header) + + le16_to_cpu(bss_mode->header.len); + } + + /* + * If the input config or adapter has the number of Probes set, + * add tlv + */ + if (num_probes) { + nxpwifi_dbg(adapter, INFO, + "info: scan: num_probes = %d\n", + num_probes); + + num_probes_tlv = (struct nxpwifi_ie_types_num_probes *)tlv_pos; + num_probes_tlv->header.type = cpu_to_le16(TLV_TYPE_NUMPROBES); + num_probes_tlv->header.len = + cpu_to_le16(sizeof(num_probes_tlv->num_probes)); + num_probes_tlv->num_probes = cpu_to_le16((u16)num_probes); + + tlv_pos += sizeof(num_probes_tlv->header) + + le16_to_cpu(num_probes_tlv->header.len); + } + + if (ISSUPP_11NENABLED(priv->adapter->fw_cap_info) && + (priv->config_bands & BAND_GN || + priv->config_bands & BAND_AN)) { + ht_cap = (struct nxpwifi_ie_types_htcap *)tlv_pos; + memset(ht_cap, 0, sizeof(struct nxpwifi_ie_types_htcap)); + ht_cap->header.type = cpu_to_le16(WLAN_EID_HT_CAPABILITY); + ht_cap->header.len = + cpu_to_le16(sizeof(struct ieee80211_ht_cap)); + radio_type = + nxpwifi_band_to_radio_type(priv->config_bands); + nxpwifi_fill_cap_info(priv, radio_type, &ht_cap->ht_cap); + tlv_pos += sizeof(struct nxpwifi_ie_types_htcap); + } + + if (ISSUPP_11ACENABLED(adapter->fw_cap_info) && + (priv->config_bands & BAND_AAC)) { + vht_cap = (struct nxpwifi_ie_types_vhtcap *)tlv_pos; + memset(vht_cap, 0, sizeof(struct nxpwifi_ie_types_vhtcap)); + vht_cap->header.type = cpu_to_le16(WLAN_EID_VHT_CAPABILITY); + vht_cap->header.len = cpu_to_le16(sizeof(struct ieee80211_vht_cap)); + nxpwifi_fill_vht_cap_tlv(priv, &vht_cap->vht_cap, priv->config_bands); + tlv_pos += sizeof(*vht_cap); + } + + if (ISSUPP_11AXENABLED(adapter->fw_cap_ext) && + (priv->config_bands & BAND_GAX || + priv->config_bands & BAND_AAX)) { + he_cap = (struct nxpwifi_ie_types_he_cap *)tlv_pos; + memset(he_cap, 0, sizeof(struct nxpwifi_ie_types_he_cap)); + tlv_pos += nxpwifi_fill_he_cap_tlv(priv, he_cap, priv->config_bands); + } + + if (nxpwifi_is_sta_11ax_twt_req_supported(priv)) { + for (vsid = 0; vsid < NXPWIFI_MAX_VSIE_NUM; vsid++) { + if (priv->vs_ie[vsid].mask & NXPWIFI_VSIE_MASK_SCAN) { + ext_capab = (u8 *)cfg80211_find_ie(WLAN_EID_EXT_CAPABILITY, + priv->vs_ie[vsid].ie, + sizeof(priv->vs_ie[vsid].ie)); + break; + } + } + + if (ext_capab) { + ext_capab += 2; + } else { + ext_cap = (struct nxpwifi_ie_types_extcap *)tlv_pos; + memset(ext_cap, 0, sizeof(struct nxpwifi_ie_types_extcap) + + NXPWIFI_EXT_CAPAB_IE_LEN); + ext_cap->header.type = cpu_to_le16(WLAN_EID_EXT_CAPABILITY); + ext_cap->header.len = cpu_to_le16(NXPWIFI_EXT_CAPAB_IE_LEN); + ext_capab = ext_cap->ext_capab; + tlv_pos += sizeof(struct nxpwifi_ie_types_extcap) + + le16_to_cpu(ext_cap->header.len); + } + + ext_capab[9] |= WLAN_EXT_CAPA10_TWT_REQUESTER_SUPPORT; + } + + /* Append vendor specific element TLV */ + nxpwifi_cmd_append_vsie_tlv(priv, NXPWIFI_VSIE_MASK_SCAN, &tlv_pos); + + /* + * Set the channel TLV output pointer to the end of the newly added TLVs + * (SSID, num_probes). Channel TLVs for each scan will be appended after + * these, preserving previously added TLVs. + */ + *chan_list_out = + (struct nxpwifi_ie_types_chan_list_param_set *)tlv_pos; + + if (user_scan_in && user_scan_in->chan_list[0].chan_number) { + nxpwifi_dbg(adapter, INFO, + "info: Scan: Using supplied channel list\n"); + + for (chan_idx = 0; + chan_idx < NXPWIFI_USER_SCAN_CHAN_MAX && + user_scan_in->chan_list[chan_idx].chan_number; + chan_idx++) { + channel = user_scan_in->chan_list[chan_idx].chan_number; + scan_chan_list[chan_idx].chan_number = channel; + + radio_type = + user_scan_in->chan_list[chan_idx].radio_type; + scan_chan_list[chan_idx].band_cfg = radio_type; + + scan_type = user_scan_in->chan_list[chan_idx].scan_type; + + if (scan_type == NXPWIFI_SCAN_TYPE_PASSIVE) + scan_chan_list[chan_idx].chan_scan_mode_bmap |= + (NXPWIFI_PASSIVE_SCAN | + NXPWIFI_HIDDEN_SSID_REPORT); + else + scan_chan_list[chan_idx].chan_scan_mode_bmap &= + ~NXPWIFI_PASSIVE_SCAN; + + scan_chan_list[chan_idx].chan_scan_mode_bmap |= + NXPWIFI_DISABLE_CHAN_FILT; + + scan_time = user_scan_in->chan_list[chan_idx].scan_time; + + if (scan_time) { + scan_dur = (u16)scan_time; + } else { + if (scan_type == NXPWIFI_SCAN_TYPE_PASSIVE) + scan_dur = adapter->passive_scan_time; + else if (*filtered_scan) + scan_dur = adapter->specific_scan_time; + else + scan_dur = adapter->active_scan_time; + } + + scan_chan_list[chan_idx].min_scan_time = + cpu_to_le16(scan_dur); + scan_chan_list[chan_idx].max_scan_time = + cpu_to_le16(scan_dur); + } + + /* Check if we are only scanning the current channel */ + if (chan_idx == 1 && + user_scan_in->chan_list[0].chan_number == + priv->curr_bss_params.bss_descriptor.channel) { + *scan_current_only = true; + nxpwifi_dbg(adapter, INFO, + "info: Scan: Scanning current channel only\n"); + } + } else { + nxpwifi_dbg(adapter, INFO, + "info: Scan: Creating full region channel list\n"); + nxpwifi_scan_create_channel_list(priv, user_scan_in, + scan_chan_list, + *filtered_scan); + } +} + +/* Parse the beacon buffer and update the BSS descriptor fields. */ +int nxpwifi_update_bss_desc_with_ie(struct nxpwifi_adapter *adapter, + struct nxpwifi_bssdescriptor *bss_entry) +{ + u8 element_id; + u16 elem_size = sizeof(struct element); + struct ieee_types_fh_param_set *fh_param_set; + struct ieee_types_ds_param_set *ds_param_set; + struct ieee_types_cf_param_set *cf_param_set; + u8 *current_ptr; + u8 *rate; + u8 element_len; + u16 total_ie_len; + u8 bytes_to_copy; + u8 rate_size; + u8 found_data_rate_ie; + u32 bytes_left; + struct ieee_types_vendor_specific *vendor_ie; + const u8 wpa_oui[4] = { 0x00, 0x50, 0xf2, 0x01 }; + const u8 wmm_oui[4] = { 0x00, 0x50, 0xf2, 0x02 }; + struct element *elem; + + found_data_rate_ie = false; + rate_size = 0; + current_ptr = bss_entry->beacon_buf; + bytes_left = bss_entry->beacon_buf_size; + + /* Process variable element */ + while (bytes_left >= 2) { + element_id = *current_ptr; + element_len = *(current_ptr + 1); + total_ie_len = element_len + elem_size; + + if (bytes_left < total_ie_len) { + nxpwifi_dbg(adapter, ERROR, + "err: InterpretIE: in processing\t" + "element, bytes left < element length\n"); + return -EINVAL; + } + switch (element_id) { + case WLAN_EID_SSID: + if (element_len > IEEE80211_MAX_SSID_LEN) + return -EINVAL; + bss_entry->ssid.ssid_len = element_len; + memcpy(bss_entry->ssid.ssid, (current_ptr + 2), + element_len); + nxpwifi_dbg(adapter, INFO, + "info: InterpretIE: ssid: %-32s\n", + bss_entry->ssid.ssid); + break; + + case WLAN_EID_SUPP_RATES: + if (element_len > NXPWIFI_SUPPORTED_RATES) + return -EINVAL; + memcpy(bss_entry->data_rates, current_ptr + 2, + element_len); + memcpy(bss_entry->supported_rates, current_ptr + 2, + element_len); + rate_size = element_len; + found_data_rate_ie = true; + break; + + case WLAN_EID_FH_PARAMS: + if (total_ie_len < sizeof(*fh_param_set)) + return -EINVAL; + fh_param_set = + (struct ieee_types_fh_param_set *)current_ptr; + memcpy(&bss_entry->phy_param_set.fh_param_set, + fh_param_set, + sizeof(struct ieee_types_fh_param_set)); + break; + + case WLAN_EID_DS_PARAMS: + if (total_ie_len < sizeof(*ds_param_set)) + return -EINVAL; + ds_param_set = + (struct ieee_types_ds_param_set *)current_ptr; + + bss_entry->channel = ds_param_set->current_chan; + + memcpy(&bss_entry->phy_param_set.ds_param_set, + ds_param_set, + sizeof(struct ieee_types_ds_param_set)); + break; + + case WLAN_EID_CF_PARAMS: + if (total_ie_len < sizeof(*cf_param_set)) + return -EINVAL; + cf_param_set = + (struct ieee_types_cf_param_set *)current_ptr; + memcpy(&bss_entry->cf_param_set, + cf_param_set, + sizeof(struct ieee_types_cf_param_set)); + break; + + case WLAN_EID_ERP_INFO: + if (!element_len) + return -EINVAL; + bss_entry->erp_flags = *(current_ptr + 2); + break; + + case WLAN_EID_PWR_CONSTRAINT: + if (!element_len) + return -EINVAL; + bss_entry->local_constraint = *(current_ptr + 2); + bss_entry->sensed_11h = true; + break; + + case WLAN_EID_CHANNEL_SWITCH: + bss_entry->chan_sw_ie_present = true; + fallthrough; + case WLAN_EID_PWR_CAPABILITY: + case WLAN_EID_TPC_REPORT: + case WLAN_EID_QUIET: + bss_entry->sensed_11h = true; + break; + + case WLAN_EID_EXT_SUPP_RATES: + /* + * Only process extended supported rate + * if data rate is already found. + * Data rate element should come before + * extended supported rate element + */ + if (found_data_rate_ie) { + if ((element_len + rate_size) > + NXPWIFI_SUPPORTED_RATES) + bytes_to_copy = + (NXPWIFI_SUPPORTED_RATES - + rate_size); + else + bytes_to_copy = element_len; + + rate = (u8 *)bss_entry->data_rates; + rate += rate_size; + memcpy(rate, current_ptr + 2, bytes_to_copy); + + rate = (u8 *)bss_entry->supported_rates; + rate += rate_size; + memcpy(rate, current_ptr + 2, bytes_to_copy); + } + break; + + case WLAN_EID_VENDOR_SPECIFIC: + vendor_ie = (struct ieee_types_vendor_specific *) + current_ptr; + + /* 802.11 requires at least 3-byte OUI. */ + if (element_len < sizeof(vendor_ie->vend_hdr.oui)) + return -EINVAL; + + /* Not long enough for a match? Skip it. */ + if (element_len < sizeof(wpa_oui)) + break; + + if (!memcmp(&vendor_ie->vend_hdr.oui, wpa_oui, + sizeof(wpa_oui))) { + bss_entry->bcn_wpa_ie = + (struct ieee_types_vendor_specific *) + current_ptr; + bss_entry->wpa_offset = + (u16)(current_ptr - + bss_entry->beacon_buf); + } else if (!memcmp(&vendor_ie->vend_hdr.oui, wmm_oui, + sizeof(wmm_oui))) { + if (total_ie_len == + sizeof(struct ieee80211_wmm_param_ie) || + total_ie_len == + sizeof(struct ieee_types_wmm_info)) + /* + * Only accept and copy the WMM element if + * it matches the size expected for the + * WMM Info element or the WMM Parameter element. + */ + memcpy((u8 *)&bss_entry->wmm_ie, + current_ptr, total_ie_len); + } + break; + case WLAN_EID_RSN: + bss_entry->bcn_rsn_ie = + (struct element *)current_ptr; + bss_entry->rsn_offset = + (u16)(current_ptr - bss_entry->beacon_buf); + break; + case WLAN_EID_RSNX: + bss_entry->bcn_rsnx_ie = + (struct element *)current_ptr; + bss_entry->rsnx_offset = + (u16)(current_ptr - bss_entry->beacon_buf); + break; + case WLAN_EID_HT_CAPABILITY: + bss_entry->bcn_ht_cap = + (struct ieee80211_ht_cap *)(current_ptr + + elem_size); + bss_entry->ht_cap_offset = + (u16)(current_ptr + elem_size - + bss_entry->beacon_buf); + break; + case WLAN_EID_HT_OPERATION: + bss_entry->bcn_ht_oper = + (struct ieee80211_ht_operation *)(current_ptr + + elem_size); + bss_entry->ht_info_offset = + (u16)(current_ptr + elem_size - + bss_entry->beacon_buf); + break; + case WLAN_EID_VHT_CAPABILITY: + bss_entry->disable_11ac = false; + bss_entry->bcn_vht_cap = (void *)(current_ptr + + elem_size); + bss_entry->vht_cap_offset = + (u16)((u8 *)bss_entry->bcn_vht_cap - + bss_entry->beacon_buf); + break; + case WLAN_EID_VHT_OPERATION: + bss_entry->bcn_vht_oper = + (void *)(current_ptr + elem_size); + bss_entry->vht_info_offset = + (u16)((u8 *)bss_entry->bcn_vht_oper - + bss_entry->beacon_buf); + break; + case WLAN_EID_BSS_COEX_2040: + bss_entry->bcn_bss_co_2040 = current_ptr; + bss_entry->bss_co_2040_offset = + (u16)(current_ptr - bss_entry->beacon_buf); + break; + case WLAN_EID_EXT_CAPABILITY: + bss_entry->bcn_ext_cap = current_ptr; + bss_entry->ext_cap_offset = + (u16)(current_ptr - bss_entry->beacon_buf); + break; + case WLAN_EID_OPMODE_NOTIF: + bss_entry->oper_mode = (void *)current_ptr; + bss_entry->oper_mode_offset = + (u16)(current_ptr - bss_entry->beacon_buf); + break; + case WLAN_EID_EXTENSION: + elem = (struct element *)current_ptr; + + switch (elem->data[0]) { + case WLAN_EID_EXT_HE_CAPABILITY: + bss_entry->disable_11ax = false; + bss_entry->bcn_he_cap = + (void *)(current_ptr + elem_size + 1); + bss_entry->he_cap_offset = + (u16)((u8 *)bss_entry->bcn_he_cap - + bss_entry->beacon_buf); + break; + case WLAN_EID_EXT_HE_OPERATION: + bss_entry->bcn_he_oper = + (void *)(current_ptr + elem_size + 1); + bss_entry->he_info_offset = + (u16)((u8 *)bss_entry->bcn_he_oper - + bss_entry->beacon_buf); + break; + default: + break; + } + break; + default: + break; + } + + current_ptr += total_ie_len; + bytes_left -= total_ie_len; + + } /* while (bytes_left > 2) */ + return 0; +} + +/* Convert the radio-type scan parameter to the join command's band config. */ +static u8 +nxpwifi_radio_type_to_band(u8 radio_type) +{ + switch (radio_type) { + case HOST_SCAN_RADIO_TYPE_A: + return BAND_A; + case HOST_SCAN_RADIO_TYPE_BG: + default: + return BAND_G; + } +} + +/* Internal helper to start a scan using the given configuration. */ +int nxpwifi_scan_networks(struct nxpwifi_private *priv, + const struct nxpwifi_user_scan_cfg *user_scan_in) +{ + int ret; + struct nxpwifi_adapter *adapter = priv->adapter; + struct cmd_ctrl_node *cmd_node; + union nxpwifi_scan_cmd_config_tlv *scan_cfg_out; + struct nxpwifi_ie_types_chan_list_param_set *chan_list_out; + struct nxpwifi_chan_scan_param_set *scan_chan_list; + u8 filtered_scan; + u8 scan_current_chan_only; + u8 max_chan_per_scan; + + if (adapter->scan_processing) { + nxpwifi_dbg(adapter, WARN, + "cmd: Scan already in process...\n"); + return -EBUSY; + } + + if (priv->scan_block) { + nxpwifi_dbg(adapter, WARN, + "cmd: Scan is blocked during association...\n"); + return -EBUSY; + } + + if (test_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags) || + test_bit(NXPWIFI_IS_CMD_TIMEDOUT, &adapter->work_flags)) { + nxpwifi_dbg(adapter, ERROR, + "Ignore scan. Card removed or firmware in bad state\n"); + return -EPERM; + } + + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + adapter->scan_processing = true; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + + scan_cfg_out = kzalloc_obj(union nxpwifi_scan_cmd_config_tlv, + GFP_KERNEL); + if (!scan_cfg_out) { + ret = -ENOMEM; + goto done; + } + + scan_chan_list = kzalloc_objs(struct nxpwifi_chan_scan_param_set, + NXPWIFI_USER_SCAN_CHAN_MAX, GFP_KERNEL); + if (!scan_chan_list) { + kfree(scan_cfg_out); + ret = -ENOMEM; + goto done; + } + + nxpwifi_config_scan(priv, user_scan_in, &scan_cfg_out->config, + &chan_list_out, scan_chan_list, &max_chan_per_scan, + &filtered_scan, &scan_current_chan_only); + + ret = nxpwifi_scan_channel_list(priv, max_chan_per_scan, filtered_scan, + &scan_cfg_out->config, chan_list_out, + scan_chan_list); + + /* Get scan command from scan_pending_q and put to cmd_pending_q */ + if (!ret) { + spin_lock_bh(&adapter->scan_pending_q_lock); + if (!list_empty(&adapter->scan_pending_q)) { + cmd_node = list_first_entry(&adapter->scan_pending_q, + struct cmd_ctrl_node, list); + list_del(&cmd_node->list); + spin_unlock_bh(&adapter->scan_pending_q_lock); + nxpwifi_insert_cmd_to_pending_q(adapter, cmd_node); + nxpwifi_queue_work(adapter, &adapter->main_work); + + /* Perform internal scan synchronously */ + if (!priv->scan_request) { + nxpwifi_dbg(adapter, INFO, + "wait internal scan\n"); + nxpwifi_wait_queue_complete(adapter, cmd_node); + } + } else { + spin_unlock_bh(&adapter->scan_pending_q_lock); + } + } + + kfree(scan_cfg_out); + kfree(scan_chan_list); +done: + if (ret) { + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + adapter->scan_processing = false; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + } + return ret; +} + +/* + * Build the firmware scan command from the given configuration, including + * fixed fields and TLVs, and set the command ID, size, and endianness. + */ +int nxpwifi_cmd_802_11_scan(struct host_cmd_ds_command *cmd, + struct nxpwifi_scan_cmd_config *scan_cfg) +{ + struct host_cmd_ds_802_11_scan *scan_cmd = &cmd->params.scan; + + /* Set fixed field variables in scan command */ + scan_cmd->bss_mode = scan_cfg->bss_mode; + memcpy(scan_cmd->bssid, scan_cfg->specific_bssid, + sizeof(scan_cmd->bssid)); + memcpy(scan_cmd->tlv_buffer, scan_cfg->tlv_buf, scan_cfg->tlv_buf_len); + + cmd->command = cpu_to_le16(HOST_CMD_802_11_SCAN); + + /* Size is equal to the sizeof(fixed portions) + the TLV len + header */ + cmd->size = cpu_to_le16((u16)(sizeof(scan_cmd->bss_mode) + + sizeof(scan_cmd->bssid) + + scan_cfg->tlv_buf_len + S_DS_GEN)); + + return 0; +} + +/* Check compatibility of the requested network with current driver settings. */ +int nxpwifi_check_network_compatibility(struct nxpwifi_private *priv, + struct nxpwifi_bssdescriptor *bss_desc) +{ + int ret = 0; + + if (!bss_desc) + return -EINVAL; + + if ((nxpwifi_get_cfp(priv, (u8)bss_desc->bss_band, + (u16)bss_desc->channel, 0))) { + switch (priv->bss_mode) { + case NL80211_IFTYPE_STATION: + ret = nxpwifi_is_network_compatible(priv, bss_desc, + priv->bss_mode); + if (ret) + nxpwifi_dbg(priv->adapter, ERROR, + "Incompatible network settings\n"); + break; + default: + ret = 0; + } + } + + return ret; +} + +/* Check if the SSID length is zero or all bytes are zero. */ +static bool nxpwifi_is_hidden_ssid(struct cfg80211_ssid *ssid) +{ + int idx; + + for (idx = 0; idx < ssid->ssid_len; idx++) { + if (ssid->ssid[idx]) + return false; + } + + return true; +} + +/* Find hidden SSIDs on passive channels and save those channels for active scan. */ +static int nxpwifi_save_hidden_ssid_channels(struct nxpwifi_private *priv, + struct cfg80211_bss *bss) +{ + struct nxpwifi_bssdescriptor *bss_desc; + int ret; + int chid; + + /* Allocate and fill new bss descriptor */ + bss_desc = kzalloc_obj(*bss_desc, GFP_KERNEL); + if (!bss_desc) + return -ENOMEM; + + ret = nxpwifi_fill_new_bss_desc(priv, bss, bss_desc); + if (ret) + goto done; + + if (nxpwifi_is_hidden_ssid(&bss_desc->ssid)) { + nxpwifi_dbg(priv->adapter, INFO, "found hidden SSID\n"); + for (chid = 0 ; chid < NXPWIFI_USER_SCAN_CHAN_MAX; chid++) { + if (priv->hidden_chan[chid].chan_number == + bss->channel->hw_value) + break; + + if (!priv->hidden_chan[chid].chan_number) { + priv->hidden_chan[chid].chan_number = + bss->channel->hw_value; + priv->hidden_chan[chid].radio_type = + bss->channel->band; + priv->hidden_chan[chid].scan_type = + NXPWIFI_SCAN_TYPE_ACTIVE; + break; + } + } + } + +done: + /* Free beacon_ie allocated by nxpwifi_fill_new_bss_desc(). */ + kfree(bss_desc->beacon_buf); + kfree(bss_desc); + return ret; +} + +static int nxpwifi_update_curr_bss_params(struct nxpwifi_private *priv, + struct cfg80211_bss *bss) +{ + struct nxpwifi_bssdescriptor *bss_desc; + int ret; + + /* Allocate and fill new bss descriptor */ + bss_desc = kzalloc_obj(*bss_desc, GFP_KERNEL); + if (!bss_desc) + return -ENOMEM; + + ret = nxpwifi_fill_new_bss_desc(priv, bss, bss_desc); + if (ret) + goto done; + + ret = nxpwifi_check_network_compatibility(priv, bss_desc); + if (ret) + goto done; + + spin_lock_bh(&priv->curr_bcn_buf_lock); + /* Make a copy of current BSSID descriptor */ + memcpy(&priv->curr_bss_params.bss_descriptor, bss_desc, + sizeof(priv->curr_bss_params.bss_descriptor)); + + /* beacon_ie will be copied to its own buffer in nxpwifi_save_curr_bcn(). */ + nxpwifi_save_curr_bcn(priv); + spin_unlock_bh(&priv->curr_bcn_buf_lock); + +done: + /* Free beacon_ie allocated by nxpwifi_fill_new_bss_desc(). */ + kfree(bss_desc->beacon_buf); + kfree(bss_desc); + return ret; +} + +static int +nxpwifi_parse_single_response_buf(struct nxpwifi_private *priv, u8 **bss_info, + u32 *bytes_left, u64 fw_tsf, const u8 *radio_type, + bool ext_scan, s32 rssi_val) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_chan_freq_power *cfp; + struct cfg80211_bss *bss; + u8 bssid[ETH_ALEN]; + s32 rssi; + const u8 *ie_buf; + size_t ie_len; + u16 channel = 0; + u16 beacon_size = 0; + u32 curr_bcn_bytes; + u32 freq; + u16 beacon_period; + u16 cap_info_bitmap; + u8 *current_ptr; + u64 timestamp; + struct nxpwifi_fixed_bcn_param *bcn_param; + struct nxpwifi_bss_priv *bss_priv; + + if (*bytes_left >= sizeof(beacon_size)) { + /* Extract & convert beacon size from command buffer */ + beacon_size = get_unaligned_le16((*bss_info)); + *bytes_left -= sizeof(beacon_size); + *bss_info += sizeof(beacon_size); + } + + if (!beacon_size || beacon_size > *bytes_left) { + *bss_info += *bytes_left; + *bytes_left = 0; + return -EINVAL; + } + + /* + * Initialize the current working beacon pointer for this BSS + * iteration + */ + current_ptr = *bss_info; + + /* Advance the return beacon pointer past the current beacon */ + *bss_info += beacon_size; + *bytes_left -= beacon_size; + + curr_bcn_bytes = beacon_size; + + /* + * First 5 fields are bssid, RSSI(for legacy scan only), + * time stamp, beacon interval, and capability information + */ + if (curr_bcn_bytes < ETH_ALEN + sizeof(u8) + + sizeof(struct nxpwifi_fixed_bcn_param)) { + nxpwifi_dbg(adapter, ERROR, + "InterpretIE: not enough bytes left\n"); + return -EINVAL; + } + + memcpy(bssid, current_ptr, ETH_ALEN); + current_ptr += ETH_ALEN; + curr_bcn_bytes -= ETH_ALEN; + + if (!ext_scan) { + rssi = (s32)*current_ptr; + rssi = (-rssi) * 100; /* Convert dBm to mBm */ + current_ptr += sizeof(u8); + curr_bcn_bytes -= sizeof(u8); + nxpwifi_dbg(adapter, INFO, + "info: InterpretIE: RSSI=%d\n", rssi); + } else { + rssi = rssi_val; + } + + bcn_param = (struct nxpwifi_fixed_bcn_param *)current_ptr; + current_ptr += sizeof(*bcn_param); + curr_bcn_bytes -= sizeof(*bcn_param); + + timestamp = le64_to_cpu(bcn_param->timestamp); + beacon_period = le16_to_cpu(bcn_param->beacon_period); + + cap_info_bitmap = le16_to_cpu(bcn_param->cap_info_bitmap); + nxpwifi_dbg(adapter, INFO, + "info: InterpretIE: capabilities=0x%X\n", + cap_info_bitmap); + + /* Rest of the current buffer are element's */ + ie_buf = current_ptr; + ie_len = curr_bcn_bytes; + nxpwifi_dbg(adapter, INFO, + "info: InterpretIE: IELength for this AP = %d\n", + curr_bcn_bytes); + + while (curr_bcn_bytes >= sizeof(struct element)) { + u8 element_id, element_len; + + element_id = *current_ptr; + element_len = *(current_ptr + 1); + if (curr_bcn_bytes < element_len + + sizeof(struct element)) { + nxpwifi_dbg(adapter, ERROR, + "%s: bytes left < element length\n", __func__); + return -EFAULT; + } + if (element_id == WLAN_EID_DS_PARAMS) { + channel = *(current_ptr + + sizeof(struct element)); + break; + } + + current_ptr += element_len + sizeof(struct element); + curr_bcn_bytes -= element_len + + sizeof(struct element); + } + + if (channel) { + struct ieee80211_channel *chan; + struct nxpwifi_bssdescriptor *bss_desc; + u8 band; + + /* Skip entry if on csa closed channel */ + if (channel == priv->csa_chan) { + nxpwifi_dbg(adapter, WARN, + "Dropping entry on csa closed channel\n"); + return 0; + } + + band = BAND_G; + if (radio_type) + band = nxpwifi_radio_type_to_band(*radio_type & + (BIT(0) | BIT(1))); + + cfp = nxpwifi_get_cfp(priv, band, channel, 0); + + freq = cfp ? cfp->freq : 0; + + chan = ieee80211_get_channel(priv->wdev.wiphy, freq); + + if (chan && !(chan->flags & IEEE80211_CHAN_DISABLED)) { + bss = cfg80211_inform_bss(priv->wdev.wiphy, chan, + CFG80211_BSS_FTYPE_UNKNOWN, + bssid, timestamp, + cap_info_bitmap, + beacon_period, + ie_buf, ie_len, rssi, + GFP_ATOMIC); + if (bss) { + bss_priv = (struct nxpwifi_bss_priv *)bss->priv; + bss_priv->band = band; + bss_priv->fw_tsf = fw_tsf; + bss_desc = + &priv->curr_bss_params.bss_descriptor; + if (priv->media_connected && + !memcmp(bssid, bss_desc->mac_address, + ETH_ALEN)) + nxpwifi_update_curr_bss_params(priv, + bss); + + if ((chan->flags & IEEE80211_CHAN_RADAR) || + (chan->flags & IEEE80211_CHAN_NO_IR)) { + nxpwifi_dbg(adapter, INFO, + "radar or passive channel %d\n", + channel); + nxpwifi_save_hidden_ssid_channels(priv, + bss); + } + + cfg80211_put_bss(priv->wdev.wiphy, bss); + } + } + } else { + nxpwifi_dbg(adapter, WARN, "missing BSS channel element\n"); + } + + return 0; +} + +static void nxpwifi_complete_scan(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + adapter->survey_idx = 0; + if (adapter->curr_cmd->wait_q_enabled) { + adapter->cmd_wait_q.status = 0; + if (!priv->scan_request) { + nxpwifi_dbg(adapter, INFO, + "complete internal scan\n"); + nxpwifi_complete_cmd(adapter, adapter->curr_cmd); + } + } +} + +/* Find hidden SSIDs on passive channels and run active scans on them. */ +static int +nxpwifi_active_scan_req_for_passive_chan(struct nxpwifi_private *priv) +{ + int ret; + struct nxpwifi_adapter *adapter = priv->adapter; + u8 id = 0; + struct nxpwifi_user_scan_cfg *user_scan_cfg; + + if (adapter->active_scan_triggered || !priv->scan_request || + priv->scan_aborting) { + adapter->active_scan_triggered = false; + return 0; + } + + if (!priv->hidden_chan[0].chan_number) { + nxpwifi_dbg(adapter, INFO, "No BSS with hidden SSID found on DFS channels\n"); + return 0; + } + user_scan_cfg = kzalloc_obj(*user_scan_cfg, GFP_KERNEL); + + if (!user_scan_cfg) + return -ENOMEM; + + for (id = 0; id < NXPWIFI_USER_SCAN_CHAN_MAX; id++) { + if (!priv->hidden_chan[id].chan_number) + break; + memcpy(&user_scan_cfg->chan_list[id], + &priv->hidden_chan[id], + sizeof(struct nxpwifi_user_scan_chan)); + } + + adapter->active_scan_triggered = true; + if (priv->scan_request->flags & NL80211_SCAN_FLAG_RANDOM_ADDR) + ether_addr_copy(user_scan_cfg->random_mac, + priv->scan_request->mac_addr); + user_scan_cfg->num_ssids = priv->scan_request->n_ssids; + user_scan_cfg->ssid_list = priv->scan_request->ssids; + + ret = nxpwifi_scan_networks(priv, user_scan_cfg); + kfree(user_scan_cfg); + + memset(&priv->hidden_chan, 0, sizeof(priv->hidden_chan)); + + if (ret) + nxpwifi_dbg(adapter, ERROR, "scan failed: %d\n", ret); + + return ret; +} + +static void nxpwifi_check_next_scan_command(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct cmd_ctrl_node *cmd_node; + + spin_lock_bh(&adapter->scan_pending_q_lock); + if (list_empty(&adapter->scan_pending_q)) { + spin_unlock_bh(&adapter->scan_pending_q_lock); + + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + adapter->scan_processing = false; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + + nxpwifi_active_scan_req_for_passive_chan(priv); + + if (!adapter->ext_scan) + nxpwifi_complete_scan(priv); + + if (priv->scan_request) { + struct cfg80211_scan_info info = { + .aborted = false, + }; + + nxpwifi_dbg(adapter, INFO, + "info: notifying scan done\n"); + cfg80211_scan_done(priv->scan_request, &info); + priv->scan_request = NULL; + priv->scan_aborting = false; + } else { + priv->scan_aborting = false; + nxpwifi_dbg(adapter, INFO, + "info: scan already aborted\n"); + } + } else if ((priv->scan_aborting && !priv->scan_request) || + priv->scan_block) { + spin_unlock_bh(&adapter->scan_pending_q_lock); + + nxpwifi_cancel_pending_scan_cmd(adapter); + + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + adapter->scan_processing = false; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + + if (!adapter->active_scan_triggered) { + if (priv->scan_request) { + struct cfg80211_scan_info info = { + .aborted = true, + }; + + nxpwifi_dbg(adapter, INFO, + "info: aborting scan\n"); + cfg80211_scan_done(priv->scan_request, &info); + priv->scan_request = NULL; + priv->scan_aborting = false; + } else { + priv->scan_aborting = false; + nxpwifi_dbg(adapter, INFO, + "info: scan already aborted\n"); + } + } + } else { + /* Move a scan command from scan_pending_q to cmd_pending_q. */ + cmd_node = list_first_entry(&adapter->scan_pending_q, + struct cmd_ctrl_node, list); + list_del(&cmd_node->list); + spin_unlock_bh(&adapter->scan_pending_q_lock); + nxpwifi_insert_cmd_to_pending_q(adapter, cmd_node); + } +} + +void nxpwifi_cancel_scan(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_private *priv; + int i; + + nxpwifi_cancel_pending_scan_cmd(adapter); + + if (adapter->scan_processing) { + spin_lock_bh(&adapter->nxpwifi_cmd_lock); + adapter->scan_processing = false; + spin_unlock_bh(&adapter->nxpwifi_cmd_lock); + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + if (priv->scan_request) { + struct cfg80211_scan_info info = { + .aborted = true, + }; + + nxpwifi_dbg(adapter, INFO, + "info: aborting scan\n"); + cfg80211_scan_done(priv->scan_request, &info); + priv->scan_request = NULL; + priv->scan_aborting = false; + } + } + } +} + +/* + * Handle the scan command response. + * + * The scan response buffer has the following layout: + * + * ------------------------------------------------------------- + * | Header (4 * t_u16): standard command response header | + * ------------------------------------------------------------- + * | BufSize (t_u16): size of the BSS description data | + * ------------------------------------------------------------- + * | NumOfSet (t_u8): number of returned BSS descriptions | + * ------------------------------------------------------------- + * | BSS description data (variable, size = BufSize) | + * ------------------------------------------------------------- + * | TLV data (variable, size = cmd_size - fixed fields) | + * ------------------------------------------------------------- + */ +int nxpwifi_ret_802_11_scan(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp) +{ + int ret = 0; + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_ds_802_11_scan_rsp *scan_rsp; + u8 *tlv_data; + const struct nxpwifi_ie_types_tsf_timestamp *tsf_tlv; + u8 *bss_info; + u32 scan_resp_size; + u32 bytes_left; + u32 idx; + u32 tlv_buf_size; + const struct nxpwifi_ie_types_chan_band_list_param_set *chan_band_tlv; + const struct chan_band_param_set *chan_band; + u8 is_bgscan_resp; + __le64 fw_tsf = 0; + const u8 *radio_type; + struct cfg80211_wowlan_nd_match *pmatch; + struct cfg80211_sched_scan_request *nd_config = NULL; + + is_bgscan_resp = (le16_to_cpu(resp->command) + == HOST_CMD_802_11_BG_SCAN_QUERY); + if (is_bgscan_resp) + scan_rsp = &resp->params.bg_scan_query_resp.scan_resp; + else + scan_rsp = &resp->params.scan_resp; + + if (scan_rsp->number_of_sets > NXPWIFI_MAX_AP) { + nxpwifi_dbg(adapter, ERROR, + "SCAN_RESP: too many AP returned (%d)\n", + scan_rsp->number_of_sets); + ret = -EINVAL; + goto check_next_scan; + } + + /* Check csa channel expiry before parsing scan response */ + nxpwifi_11h_get_csa_closed_channel(priv); + + bytes_left = le16_to_cpu(scan_rsp->bss_descript_size); + nxpwifi_dbg(adapter, INFO, + "info: SCAN_RESP: bss_descript_size %d\n", + bytes_left); + + scan_resp_size = le16_to_cpu(resp->size); + + nxpwifi_dbg(adapter, INFO, + "info: SCAN_RESP: returned %d APs before parsing\n", + scan_rsp->number_of_sets); + + bss_info = scan_rsp->bss_desc_and_tlv_buffer; + + /* + * TLV buffer size = scan_resp_size minus the fixed fields, BSS + * description data, and the command response header (S_DS_GEN). + */ + tlv_buf_size = scan_resp_size - (bytes_left + + sizeof(scan_rsp->bss_descript_size) + + sizeof(scan_rsp->number_of_sets) + + S_DS_GEN); + + tlv_data = (scan_rsp->bss_desc_and_tlv_buffer + + bytes_left); + + /* Find timestamp TLV */ + { + const struct nxpwifi_tlv *t; + + t = nxpwifi_find_tlv(TLV_TYPE_TSFTIMESTAMP, tlv_data, tlv_buf_size); + tsf_tlv = (const struct nxpwifi_ie_types_tsf_timestamp *)t; + } + + /* Find channel-band list TLV */ + { + const struct nxpwifi_tlv *t; + + t = nxpwifi_find_tlv(TLV_TYPE_CHANNELBANDLIST, tlv_data, + tlv_buf_size); + chan_band_tlv = + (const struct nxpwifi_ie_types_chan_band_list_param_set *)t; + } + +#ifdef CONFIG_PM + if (priv->wdev.wiphy->wowlan_config) + nd_config = priv->wdev.wiphy->wowlan_config->nd_config; +#endif + + if (nd_config) { + adapter->nd_info = + kzalloc_flex(*adapter->nd_info, matches, + scan_rsp->number_of_sets, GFP_ATOMIC); + + if (adapter->nd_info) + adapter->nd_info->n_matches = scan_rsp->number_of_sets; + } + + for (idx = 0; idx < scan_rsp->number_of_sets && bytes_left; idx++) { + /* + * If a TSF TLV is present, save its TSF value in fw_tsf. This + * is the firmware TSF at the time the beacon or probe response + * was received. + */ + if (tsf_tlv) + memcpy(&fw_tsf, &tsf_tlv->tsf_data[idx * TSF_DATA_SIZE], + sizeof(fw_tsf)); + + if (chan_band_tlv) { + chan_band = &chan_band_tlv->chan_band_param[idx]; + radio_type = &chan_band->radio_type; + } else { + radio_type = NULL; + } + + if (chan_band_tlv && adapter->nd_info) { + adapter->nd_info->matches[idx] = + kzalloc(sizeof(*pmatch) + sizeof(u32), + GFP_ATOMIC); + + pmatch = adapter->nd_info->matches[idx]; + + if (pmatch) { + pmatch->n_channels = 1; + pmatch->channels[0] = chan_band->chan_number; + } + } + + ret = nxpwifi_parse_single_response_buf(priv, &bss_info, + &bytes_left, + le64_to_cpu(fw_tsf), + radio_type, false, 0); + if (ret) + goto check_next_scan; + } + +check_next_scan: + nxpwifi_check_next_scan_command(priv); + return ret; +} + +/* + * Prepare the extended scan command using the provided scan configuration + * and build the structure to be sent to firmware. + */ +int nxpwifi_cmd_802_11_scan_ext(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + void *data_buf) +{ + struct host_cmd_ds_802_11_scan_ext *ext_scan = &cmd->params.ext_scan; + struct nxpwifi_scan_cmd_config *scan_cfg = data_buf; + + memcpy(ext_scan->tlv_buffer, scan_cfg->tlv_buf, scan_cfg->tlv_buf_len); + + cmd->command = cpu_to_le16(HOST_CMD_802_11_SCAN_EXT); + + /* Size is equal to the sizeof(fixed portions) + the TLV len + header */ + cmd->size = cpu_to_le16((u16)(sizeof(ext_scan->reserved) + + scan_cfg->tlv_buf_len + S_DS_GEN)); + + return 0; +} + +/* Prepare the background scan config command to send to firmware. */ +int nxpwifi_cmd_802_11_bg_scan_config(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + void *data_buf) +{ + struct host_cmd_ds_802_11_bg_scan_config *bgscan_config = + &cmd->params.bg_scan_config; + struct nxpwifi_bg_scan_cfg *bgscan_cfg_in = data_buf; + u8 *tlv_pos = bgscan_config->tlv; + u8 num_probes; + u32 ssid_len, chan_idx, scan_time, scan_type, scan_dur, chan_num; + int i; + struct nxpwifi_ie_types_num_probes *num_probes_tlv; + struct nxpwifi_ie_types_repeat_count *repeat_count_tlv; + struct nxpwifi_ie_types_min_rssi_threshold *rssi_threshold_tlv; + struct nxpwifi_ie_types_bgscan_start_later *start_later_tlv; + struct nxpwifi_ie_types_wildcard_ssid_params *wildcard_ssid_tlv; + struct nxpwifi_ie_types_chan_list_param_set *tlv_l; + struct nxpwifi_chan_scan_param_set *temp_chan; + + cmd->command = cpu_to_le16(HOST_CMD_802_11_BG_SCAN_CONFIG); + cmd->size = cpu_to_le16(sizeof(*bgscan_config) + S_DS_GEN); + + bgscan_config->action = cpu_to_le16(bgscan_cfg_in->action); + bgscan_config->enable = bgscan_cfg_in->enable; + bgscan_config->bss_type = bgscan_cfg_in->bss_type; + bgscan_config->scan_interval = + cpu_to_le32(bgscan_cfg_in->scan_interval); + bgscan_config->report_condition = + cpu_to_le32(bgscan_cfg_in->report_condition); + + /* stop sched scan */ + if (!bgscan_config->enable) + return 0; + + bgscan_config->chan_per_scan = bgscan_cfg_in->chan_per_scan; + + num_probes = (bgscan_cfg_in->num_probes ? + bgscan_cfg_in->num_probes : priv->adapter->scan_probes); + + if (num_probes) { + num_probes_tlv = (struct nxpwifi_ie_types_num_probes *)tlv_pos; + num_probes_tlv->header.type = cpu_to_le16(TLV_TYPE_NUMPROBES); + num_probes_tlv->header.len = + cpu_to_le16(sizeof(num_probes_tlv->num_probes)); + num_probes_tlv->num_probes = cpu_to_le16((u16)num_probes); + + tlv_pos += sizeof(num_probes_tlv->header) + + le16_to_cpu(num_probes_tlv->header.len); + } + + if (bgscan_cfg_in->repeat_count) { + repeat_count_tlv = + (struct nxpwifi_ie_types_repeat_count *)tlv_pos; + repeat_count_tlv->header.type = + cpu_to_le16(TLV_TYPE_REPEAT_COUNT); + repeat_count_tlv->header.len = + cpu_to_le16(sizeof(repeat_count_tlv->repeat_count)); + repeat_count_tlv->repeat_count = + cpu_to_le16(bgscan_cfg_in->repeat_count); + + tlv_pos += sizeof(repeat_count_tlv->header) + + le16_to_cpu(repeat_count_tlv->header.len); + } + + if (bgscan_cfg_in->rssi_threshold) { + rssi_threshold_tlv = + (struct nxpwifi_ie_types_min_rssi_threshold *)tlv_pos; + rssi_threshold_tlv->header.type = + cpu_to_le16(TLV_TYPE_RSSI_LOW); + rssi_threshold_tlv->header.len = + cpu_to_le16(sizeof(rssi_threshold_tlv->rssi_threshold)); + rssi_threshold_tlv->rssi_threshold = + cpu_to_le16(bgscan_cfg_in->rssi_threshold); + + tlv_pos += sizeof(rssi_threshold_tlv->header) + + le16_to_cpu(rssi_threshold_tlv->header.len); + } + + for (i = 0; i < bgscan_cfg_in->num_ssids; i++) { + ssid_len = bgscan_cfg_in->ssid_list[i].ssid.ssid_len; + + wildcard_ssid_tlv = + (struct nxpwifi_ie_types_wildcard_ssid_params *)tlv_pos; + wildcard_ssid_tlv->header.type = + cpu_to_le16(TLV_TYPE_WILDCARDSSID); + wildcard_ssid_tlv->header.len = + cpu_to_le16((u16)(ssid_len + sizeof(u8))); + + /* + * max_ssid_length = 0 tells firmware to scan only for the given + * SSID. max_ssid_length = IEEE80211_MAX_SSID_LEN triggers a + * wildcard scan. + */ + if (ssid_len) + wildcard_ssid_tlv->max_ssid_length = 0; + else + wildcard_ssid_tlv->max_ssid_length = + IEEE80211_MAX_SSID_LEN; + + memcpy(wildcard_ssid_tlv->ssid, + bgscan_cfg_in->ssid_list[i].ssid.ssid, ssid_len); + + tlv_pos += (sizeof(wildcard_ssid_tlv->header) + + le16_to_cpu(wildcard_ssid_tlv->header.len)); + } + + tlv_l = (struct nxpwifi_ie_types_chan_list_param_set *)tlv_pos; + + if (bgscan_cfg_in->chan_list[0].chan_number) { + nxpwifi_dbg(priv->adapter, INFO, "info: bgscan: Using supplied channel list\n"); + + tlv_l->header.type = cpu_to_le16(TLV_TYPE_CHANLIST); + + for (chan_idx = 0; + chan_idx < NXPWIFI_BG_SCAN_CHAN_MAX && + bgscan_cfg_in->chan_list[chan_idx].chan_number; + chan_idx++) { + temp_chan = &tlv_l->chan_scan_param[chan_idx]; + + /* Increment the TLV header length by size appended */ + le16_unaligned_add_cpu(&tlv_l->header.len, + sizeof(*tlv_l->chan_scan_param)); + + temp_chan->chan_number = + bgscan_cfg_in->chan_list[chan_idx].chan_number; + temp_chan->band_cfg = + bgscan_cfg_in->chan_list[chan_idx].radio_type; + + scan_type = + bgscan_cfg_in->chan_list[chan_idx].scan_type; + + if (scan_type == NXPWIFI_SCAN_TYPE_PASSIVE) + temp_chan->chan_scan_mode_bmap |= + NXPWIFI_PASSIVE_SCAN; + else + temp_chan->chan_scan_mode_bmap &= + ~NXPWIFI_PASSIVE_SCAN; + + scan_time = bgscan_cfg_in->chan_list[chan_idx].scan_time; + + if (scan_time) { + scan_dur = (u16)scan_time; + } else { + scan_dur = (scan_type == + NXPWIFI_SCAN_TYPE_PASSIVE) ? + priv->adapter->passive_scan_time : + priv->adapter->specific_scan_time; + } + + temp_chan->min_scan_time = cpu_to_le16(scan_dur); + temp_chan->max_scan_time = cpu_to_le16(scan_dur); + } + } else { + nxpwifi_dbg(priv->adapter, INFO, + "info: bgscan: Creating full region channel list\n"); + chan_num = + nxpwifi_bgscan_create_channel_list + (priv, bgscan_cfg_in, + tlv_l->chan_scan_param); + le16_unaligned_add_cpu(&tlv_l->header.len, + chan_num * + sizeof(*tlv_l->chan_scan_param)); + } + + tlv_pos += (sizeof(tlv_l->header) + + le16_to_cpu(tlv_l->header.len)); + + if (bgscan_cfg_in->start_later) { + start_later_tlv = + (struct nxpwifi_ie_types_bgscan_start_later *)tlv_pos; + start_later_tlv->header.type = + cpu_to_le16(TLV_TYPE_BGSCAN_START_LATER); + start_later_tlv->header.len = + cpu_to_le16(sizeof(start_later_tlv->start_later)); + start_later_tlv->start_later = + cpu_to_le16(bgscan_cfg_in->start_later); + + tlv_pos += sizeof(start_later_tlv->header) + + le16_to_cpu(start_later_tlv->header.len); + } + + /* Append vendor specific element TLV */ + nxpwifi_cmd_append_vsie_tlv(priv, NXPWIFI_VSIE_MASK_BGSCAN, &tlv_pos); + + le16_unaligned_add_cpu(&cmd->size, tlv_pos - bgscan_config->tlv); + + return 0; +} + +int nxpwifi_stop_bg_scan(struct nxpwifi_private *priv) +{ + struct nxpwifi_bg_scan_cfg *bgscan_cfg; + int ret; + + if (!priv->sched_scanning) { + nxpwifi_dbg(priv->adapter, MSG, "bgscan already stopped!\n"); + return 0; + } + + bgscan_cfg = kzalloc_obj(*bgscan_cfg, GFP_KERNEL); + if (!bgscan_cfg) + return -ENOMEM; + + bgscan_cfg->bss_type = NXPWIFI_BSS_MODE_INFRA; + bgscan_cfg->action = NXPWIFI_BGSCAN_ACT_SET; + bgscan_cfg->enable = false; + + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_BG_SCAN_CONFIG, + HOST_ACT_GEN_SET, 0, bgscan_cfg, true); + if (!ret) + priv->sched_scanning = false; + + kfree(bgscan_cfg); + return ret; +} + +static void +nxpwifi_update_chan_statistics(struct nxpwifi_private *priv, + struct nxpwifi_ietypes_chanstats *tlv_stat) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u8 i, num_chan; + struct nxpwifi_fw_chan_stats *fw_chan_stats; + struct nxpwifi_chan_stats chan_stats; + + fw_chan_stats = (void *)((u8 *)tlv_stat + + sizeof(struct nxpwifi_ie_types_header)); + num_chan = le16_to_cpu(tlv_stat->header.len) / + sizeof(struct nxpwifi_chan_stats); + + for (i = 0 ; i < num_chan; i++) { + if (adapter->survey_idx >= adapter->num_in_chan_stats) { + nxpwifi_dbg(adapter, WARN, + "FW reported too many channel results (max %d)\n", + adapter->num_in_chan_stats); + return; + } + chan_stats.chan_num = fw_chan_stats->chan_num; + chan_stats.bandcfg = fw_chan_stats->bandcfg; + chan_stats.flags = fw_chan_stats->flags; + chan_stats.noise = fw_chan_stats->noise; + chan_stats.total_bss = le16_to_cpu(fw_chan_stats->total_bss); + chan_stats.cca_scan_dur = + le16_to_cpu(fw_chan_stats->cca_scan_dur); + chan_stats.cca_busy_dur = + le16_to_cpu(fw_chan_stats->cca_busy_dur); + nxpwifi_dbg(adapter, INFO, + "chan=%d, noise=%d, total_network=%d scan_duration=%d, busy_duration=%d\n", + chan_stats.chan_num, + chan_stats.noise, + chan_stats.total_bss, + chan_stats.cca_scan_dur, + chan_stats.cca_busy_dur); + memcpy(&adapter->chan_stats[adapter->survey_idx++], &chan_stats, + sizeof(struct nxpwifi_chan_stats)); + fw_chan_stats++; + } +} + +/* Handle the extended scan command response. */ +int nxpwifi_ret_802_11_scan_ext(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_ds_802_11_scan_ext *ext_scan_resp; + struct nxpwifi_ie_types_header *tlv; + struct nxpwifi_ietypes_chanstats *tlv_stat; + u16 buf_left, type, len; + + struct host_cmd_ds_command *cmd_ptr; + struct cmd_ctrl_node *cmd_node; + bool complete_scan = false; + + nxpwifi_dbg(adapter, INFO, "info: EXT scan returns successfully\n"); + + ext_scan_resp = &resp->params.ext_scan; + + tlv = (void *)ext_scan_resp->tlv_buffer; + buf_left = le16_to_cpu(resp->size) - (sizeof(*ext_scan_resp) + S_DS_GEN); + + while (buf_left >= sizeof(struct nxpwifi_ie_types_header)) { + type = le16_to_cpu(tlv->type); + len = le16_to_cpu(tlv->len); + + if (buf_left < (sizeof(struct nxpwifi_ie_types_header) + len)) { + nxpwifi_dbg(adapter, ERROR, + "error processing scan response TLVs"); + break; + } + + switch (type) { + case TLV_TYPE_CHANNEL_STATS: + tlv_stat = (void *)tlv; + nxpwifi_update_chan_statistics(priv, tlv_stat); + break; + default: + break; + } + + buf_left -= len + sizeof(struct nxpwifi_ie_types_header); + tlv = (void *)((u8 *)tlv + len + + sizeof(struct nxpwifi_ie_types_header)); + } + + spin_lock_bh(&adapter->cmd_pending_q_lock); + spin_lock_bh(&adapter->scan_pending_q_lock); + if (list_empty(&adapter->scan_pending_q)) { + complete_scan = true; + list_for_each_entry(cmd_node, &adapter->cmd_pending_q, list) { + cmd_ptr = (void *)cmd_node->cmd_skb->data; + if (le16_to_cpu(cmd_ptr->command) == + HOST_CMD_802_11_SCAN_EXT) { + nxpwifi_dbg(adapter, INFO, + "Scan pending in command pending list"); + complete_scan = false; + break; + } + } + } + spin_unlock_bh(&adapter->scan_pending_q_lock); + spin_unlock_bh(&adapter->cmd_pending_q_lock); + + if (complete_scan) + nxpwifi_complete_scan(priv); + + return 0; +} + +/* + * Handle the extended scan report event: parse the results and notify + * cfg80211. + */ +int nxpwifi_handle_event_ext_scan_report(struct nxpwifi_private *priv, + void *buf) +{ + int ret = 0; + struct nxpwifi_adapter *adapter = priv->adapter; + u8 *bss_info; + u32 bytes_left, bytes_left_for_tlv, idx; + u16 type, len; + struct nxpwifi_ie_types_data *tlv; + struct nxpwifi_ie_types_scan_rsp *scan_rsp_tlv; + struct nxpwifi_ie_types_scan_inf *scan_info_tlv; + u8 *radio_type; + u64 fw_tsf = 0; + s32 rssi = 0; + struct nxpwifi_event_scan_result *event_scan = buf; + u8 num_of_set = event_scan->num_of_set; + u8 *scan_resp = buf + sizeof(struct nxpwifi_event_scan_result); + u16 scan_resp_size = le16_to_cpu(event_scan->buf_size); + + if (num_of_set > NXPWIFI_MAX_AP) { + nxpwifi_dbg(adapter, ERROR, + "EXT_SCAN: Invalid number of AP returned (%d)!!\n", + num_of_set); + ret = -EINVAL; + goto check_next_scan; + } + + bytes_left = scan_resp_size; + nxpwifi_dbg(adapter, INFO, + "EXT_SCAN: size %d, returned %d APs...", + scan_resp_size, num_of_set); + nxpwifi_dbg_dump(adapter, CMD_D, "EXT_SCAN buffer:", buf, + scan_resp_size + + sizeof(struct nxpwifi_event_scan_result)); + + tlv = (struct nxpwifi_ie_types_data *)scan_resp; + + for (idx = 0; idx < num_of_set && bytes_left; idx++) { + type = le16_to_cpu(tlv->header.type); + len = le16_to_cpu(tlv->header.len); + if (bytes_left < sizeof(struct nxpwifi_ie_types_header) + len) { + nxpwifi_dbg(adapter, ERROR, + "EXT_SCAN: Error bytes left < TLV length\n"); + break; + } + scan_rsp_tlv = NULL; + scan_info_tlv = NULL; + bytes_left_for_tlv = bytes_left; + + /* + * BSS response TLV with beacon or probe response buffer + * at the initial position of each descriptor + */ + if (type != TLV_TYPE_BSS_SCAN_RSP) + break; + + bss_info = (u8 *)tlv; + scan_rsp_tlv = (struct nxpwifi_ie_types_scan_rsp *)tlv; + tlv = (struct nxpwifi_ie_types_data *)(tlv->data + len); + bytes_left_for_tlv -= + (len + sizeof(struct nxpwifi_ie_types_header)); + + while (bytes_left_for_tlv >= + sizeof(struct nxpwifi_ie_types_header) && + le16_to_cpu(tlv->header.type) != TLV_TYPE_BSS_SCAN_RSP) { + type = le16_to_cpu(tlv->header.type); + len = le16_to_cpu(tlv->header.len); + if (bytes_left_for_tlv < + sizeof(struct nxpwifi_ie_types_header) + len) { + nxpwifi_dbg(adapter, ERROR, + "EXT_SCAN: Error in processing TLV,\t" + "bytes left < TLV length\n"); + scan_rsp_tlv = NULL; + bytes_left_for_tlv = 0; + continue; + } + switch (type) { + case TLV_TYPE_BSS_SCAN_INFO: + scan_info_tlv = + (struct nxpwifi_ie_types_scan_inf *)tlv; + if (len != + sizeof(struct nxpwifi_ie_types_scan_inf) - + sizeof(struct nxpwifi_ie_types_header)) { + bytes_left_for_tlv = 0; + continue; + } + break; + default: + break; + } + tlv = (struct nxpwifi_ie_types_data *)(tlv->data + len); + bytes_left -= + (len + sizeof(struct nxpwifi_ie_types_header)); + bytes_left_for_tlv -= + (len + sizeof(struct nxpwifi_ie_types_header)); + } + + if (!scan_rsp_tlv) + break; + + /* + * Advance pointer to the beacon buffer length and + * update the bytes count so that the function + * wlan_interpret_bss_desc_with_ie() can handle the + * scan buffer withut any change + */ + bss_info += sizeof(u16); + bytes_left -= sizeof(u16); + + if (scan_info_tlv) { + rssi = (s32)(s16)(le16_to_cpu(scan_info_tlv->rssi)); + rssi *= 100; /* Convert dBm to mBm */ + nxpwifi_dbg(adapter, INFO, + "info: InterpretIE: RSSI=%d\n", rssi); + fw_tsf = le64_to_cpu(scan_info_tlv->tsf); + radio_type = &scan_info_tlv->radio_type; + } else { + radio_type = NULL; + } + ret = nxpwifi_parse_single_response_buf(priv, &bss_info, + &bytes_left, fw_tsf, + radio_type, true, rssi); + if (ret) + goto check_next_scan; + } + +check_next_scan: + if (!event_scan->more_event) + nxpwifi_check_next_scan_command(priv); + + return ret; +} + +/* + * Prepare the background scan query command. Sets the command ID, size, + * flush parameter, and fixes endianness. + */ +int nxpwifi_cmd_802_11_bg_scan_query(struct host_cmd_ds_command *cmd) +{ + struct host_cmd_ds_802_11_bg_scan_query *bg_query = + &cmd->params.bg_scan_query; + + cmd->command = cpu_to_le16(HOST_CMD_802_11_BG_SCAN_QUERY); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_802_11_bg_scan_query) + + S_DS_GEN); + + bg_query->flush = 1; + + return 0; +} + +/* Insert a scan command node into the scan_pending_q. */ +void +nxpwifi_queue_scan_cmd(struct nxpwifi_private *priv, + struct cmd_ctrl_node *cmd_node) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + cmd_node->wait_q_enabled = true; + cmd_node->condition = &adapter->scan_wait_q_woken; + spin_lock_bh(&adapter->scan_pending_q_lock); + list_add_tail(&cmd_node->list, &adapter->scan_pending_q); + spin_unlock_bh(&adapter->scan_pending_q_lock); +} + +/* Append a vendor-specific element TLV to the buffer. */ +int +nxpwifi_cmd_append_vsie_tlv(struct nxpwifi_private *priv, + u16 vsie_mask, u8 **buffer) +{ + int id, ret_len = 0; + struct nxpwifi_ie_types_vendor_param_set *vs_param_set; + + if (!buffer) + return 0; + if (!(*buffer)) + return 0; + + /* + * Traverse through the saved vendor specific element array and append + * the selected(scan/assoc) element as TLV to the command + */ + for (id = 0; id < NXPWIFI_MAX_VSIE_NUM; id++) { + if (priv->vs_ie[id].mask & vsie_mask) { + vs_param_set = + (struct nxpwifi_ie_types_vendor_param_set *) + *buffer; + vs_param_set->header.type = + cpu_to_le16(TLV_TYPE_PASSTHROUGH); + vs_param_set->header.len = + cpu_to_le16((((u16)priv->vs_ie[id].ie[1]) + & 0x00FF) + 2); + if (le16_to_cpu(vs_param_set->header.len) > + NXPWIFI_MAX_VSIE_LEN) { + nxpwifi_dbg(priv->adapter, ERROR, + "Invalid param length!\n"); + break; + } + + memcpy(vs_param_set->ie, priv->vs_ie[id].ie, + le16_to_cpu(vs_param_set->header.len)); + *buffer += le16_to_cpu(vs_param_set->header.len) + + sizeof(struct nxpwifi_ie_types_header); + ret_len += le16_to_cpu(vs_param_set->header.len) + + sizeof(struct nxpwifi_ie_types_header); + } + } + return ret_len; +} + +/* + * Save the beacon buffer of the current BSS descriptor. + * + * The buffer is preserved so it can be restored when the current SSID's + * beacon is missing, such as when: + * - the SSID was not found in the latest scan, or + * - the SSID was the last entry in the scan table and was overwritten. + */ +void +nxpwifi_save_curr_bcn(struct nxpwifi_private *priv) +{ + struct nxpwifi_bssdescriptor *curr_bss = + &priv->curr_bss_params.bss_descriptor; + + if (!curr_bss->beacon_buf_size) + return; + + /* allocate beacon buffer at 1st time; or if it's size has changed */ + if (!priv->curr_bcn_buf || + priv->curr_bcn_size != curr_bss->beacon_buf_size) { + priv->curr_bcn_size = curr_bss->beacon_buf_size; + + kfree(priv->curr_bcn_buf); + priv->curr_bcn_buf = kmalloc(curr_bss->beacon_buf_size, + GFP_ATOMIC); + if (!priv->curr_bcn_buf) + return; + } + + memcpy(priv->curr_bcn_buf, curr_bss->beacon_buf, + curr_bss->beacon_buf_size); + nxpwifi_dbg(priv->adapter, INFO, + "info: current beacon saved %d\n", + priv->curr_bcn_size); + + curr_bss->beacon_buf = priv->curr_bcn_buf; + + /* adjust the pointers in the current BSS descriptor */ + if (curr_bss->bcn_wpa_ie) + curr_bss->bcn_wpa_ie = + (struct ieee_types_vendor_specific *) + (curr_bss->beacon_buf + + curr_bss->wpa_offset); + + if (curr_bss->bcn_rsn_ie) + curr_bss->bcn_rsn_ie = + (struct element *)(curr_bss->beacon_buf + + curr_bss->rsn_offset); + + if (curr_bss->bcn_ht_cap) + curr_bss->bcn_ht_cap = (struct ieee80211_ht_cap *) + (curr_bss->beacon_buf + + curr_bss->ht_cap_offset); + + if (curr_bss->bcn_ht_oper) + curr_bss->bcn_ht_oper = (struct ieee80211_ht_operation *) + (curr_bss->beacon_buf + + curr_bss->ht_info_offset); + + if (curr_bss->bcn_vht_cap) + curr_bss->bcn_vht_cap = (void *)(curr_bss->beacon_buf + + curr_bss->vht_cap_offset); + + if (curr_bss->bcn_vht_oper) + curr_bss->bcn_vht_oper = (void *)(curr_bss->beacon_buf + + curr_bss->vht_info_offset); + + if (curr_bss->bcn_he_cap) + curr_bss->bcn_he_cap = (void *)(curr_bss->beacon_buf + + curr_bss->he_cap_offset); + + if (curr_bss->bcn_he_oper) + curr_bss->bcn_he_oper = (void *)(curr_bss->beacon_buf + + curr_bss->he_info_offset); + + if (curr_bss->bcn_bss_co_2040) + curr_bss->bcn_bss_co_2040 = + (curr_bss->beacon_buf + curr_bss->bss_co_2040_offset); + + if (curr_bss->bcn_ext_cap) + curr_bss->bcn_ext_cap = curr_bss->beacon_buf + + curr_bss->ext_cap_offset; + + if (curr_bss->oper_mode) + curr_bss->oper_mode = (void *)(curr_bss->beacon_buf + + curr_bss->oper_mode_offset); +} + +/* Free the beacon buffer in the current BSS descriptor. */ +void +nxpwifi_free_curr_bcn(struct nxpwifi_private *priv) +{ + kfree(priv->curr_bcn_buf); + priv->curr_bcn_buf = NULL; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/sdio.c b/drivers/net/wireless/nxp/nxpwifi/sdio.c new file mode 100644 index 000000000000..d8536354f093 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/sdio.c @@ -0,0 +1,2327 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP Wireless LAN device driver: SDIO specific handling + * + * Copyright 2011-2024 NXP + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "wmm.h" +#include "11n.h" +#include "sdio.h" + +#define SDIO_VERSION "1.0" + +/* Process deferred SDIO work items. */ +static void nxpwifi_sdio_work(struct work_struct *work); + +static struct nxpwifi_if_ops sdio_ops; + +static const struct nxpwifi_sdio_card_reg nxpwifi_reg_iw61x = { + .start_rd_port = 0, + .start_wr_port = 0, + .base_0_reg = 0xF8, + .base_1_reg = 0xF9, + .poll_reg = 0x5C, + .host_int_enable = UP_LD_HOST_INT_MASK | DN_LD_HOST_INT_MASK | + CMD_PORT_UPLD_INT_MASK | CMD_PORT_DNLD_INT_MASK, + .host_int_rsr_reg = 0x4, + .host_int_status_reg = 0x0C, + .host_int_mask_reg = 0x08, + .host_strap_reg = 0xF4, + .host_strap_mask = 0x01, + .host_strap_value = 0x00, + .status_reg_0 = 0xE8, + .status_reg_1 = 0xE9, + .sdio_int_mask = 0xff, + .data_port_mask = 0xffffffff, + .io_port_0_reg = 0xE4, + .io_port_1_reg = 0xE5, + .io_port_2_reg = 0xE6, + .max_mp_regs = 196, + .rd_bitmap_l = 0x10, + .rd_bitmap_u = 0x11, + .rd_bitmap_1l = 0x12, + .rd_bitmap_1u = 0x13, + .wr_bitmap_l = 0x14, + .wr_bitmap_u = 0x15, + .wr_bitmap_1l = 0x16, + .wr_bitmap_1u = 0x17, + .rd_len_p0_l = 0x18, + .rd_len_p0_u = 0x19, + .card_misc_cfg_reg = 0xd8, + .card_cfg_2_1_reg = 0xd9, + .cmd_rd_len_0 = 0xc0, + .cmd_rd_len_1 = 0xc1, + .cmd_rd_len_2 = 0xc2, + .cmd_rd_len_3 = 0xc3, + .cmd_cfg_0 = 0xc4, + .cmd_cfg_1 = 0xc5, + .cmd_cfg_2 = 0xc6, + .cmd_cfg_3 = 0xc7, + .fw_dump_host_ready = 0xcc, + .fw_dump_ctrl = 0xf9, + .fw_dump_start = 0xf1, + .fw_dump_end = 0xf8, + .func1_dump_reg_start = 0x10, + .func1_dump_reg_end = 0x17, + .func1_scratch_reg = 0xE8, + .func1_spec_reg_num = 13, + .func1_spec_reg_table = {0x08, 0x58, 0x5C, 0x5D, 0x60, + 0x61, 0x62, 0x64, 0x65, 0x66, + 0x68, 0x69, 0x6a}, +}; + +static const struct nxpwifi_sdio_device nxpwifi_sdio_iw61x = { + .firmware = IW61X_SDIO_FW_NAME, + .reg = &nxpwifi_reg_iw61x, + .max_ports = 32, + .mp_agg_pkt_limit = 16, + .tx_buf_size = NXPWIFI_TX_DATA_BUF_SIZE_4K, + .mp_tx_agg_buf_size = NXPWIFI_MP_AGGR_BSIZE_MAX, + .mp_rx_agg_buf_size = NXPWIFI_MP_AGGR_BSIZE_MAX, + .can_dump_fw = true, + .fw_dump_enh = true, + .can_ext_scan = true, +}; + +static struct memory_type_mapping generic_mem_type_map[] = { + {"DUMP", NULL, 0, 0xDD}, +}; + +static struct memory_type_mapping mem_type_mapping_tbl[] = { + {"ITCM", NULL, 0, 0xF0}, + {"DTCM", NULL, 0, 0xF1}, + {"SQRAM", NULL, 0, 0xF2}, + {"APU", NULL, 0, 0xF3}, + {"CIU", NULL, 0, 0xF4}, + {"ICU", NULL, 0, 0xF5}, + {"MAC", NULL, 0, 0xF6}, + {"EXT7", NULL, 0, 0xF7}, + {"EXT8", NULL, 0, 0xF8}, + {"EXT9", NULL, 0, 0xF9}, + {"EXT10", NULL, 0, 0xFA}, + {"EXT11", NULL, 0, 0xFB}, + {"EXT12", NULL, 0, 0xFC}, + {"EXT13", NULL, 0, 0xFD}, + {"EXTLAST", NULL, 0, 0xFE}, +}; + +/* Bind the SDIO function and start device registration. */ +static int +nxpwifi_sdio_probe(struct sdio_func *func, const struct sdio_device_id *id) +{ + int ret; + struct sdio_mmc_card *card = NULL; + + card = devm_kzalloc(&func->dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + init_completion(&card->fw_done); + + card->func = func; + + if (id->driver_data) { + struct nxpwifi_sdio_device *data = (void *)id->driver_data; + + card->firmware = data->firmware; + card->firmware_sdiouart = data->firmware_sdiouart; + card->reg = data->reg; + card->max_ports = data->max_ports; + card->mp_agg_pkt_limit = data->mp_agg_pkt_limit; + card->tx_buf_size = data->tx_buf_size; + card->mp_tx_agg_buf_size = data->mp_tx_agg_buf_size; + card->mp_rx_agg_buf_size = data->mp_rx_agg_buf_size; + card->can_dump_fw = data->can_dump_fw; + card->fw_dump_enh = data->fw_dump_enh; + card->can_ext_scan = data->can_ext_scan; + INIT_WORK(&card->work, nxpwifi_sdio_work); + } + + sdio_claim_host(func); + ret = sdio_enable_func(func); + sdio_release_host(func); + + if (ret) { + dev_err(&func->dev, "failed to enable function\n"); + return ret; + } + + ret = nxpwifi_add_card(card, &card->fw_done, &sdio_ops, + NXPWIFI_SDIO, &func->dev); + if (ret) { + dev_err(&func->dev, "add card failed\n"); + goto err_disable; + } + + return 0; + +err_disable: + sdio_claim_host(func); + sdio_disable_func(func); + sdio_release_host(func); + + return ret; +} + +/* Resume the SDIO function and cancel host sleep. */ +static int nxpwifi_sdio_resume(struct device *dev) +{ + struct sdio_func *func = dev_to_sdio_func(dev); + struct sdio_mmc_card *card; + struct nxpwifi_adapter *adapter; + + card = sdio_get_drvdata(func); + + if (unlikely(!card || !card->adapter)) { + dev_dbg(dev, "resume: %s not ready\n", !card ? "card" : "adapter"); + return -ENODEV; + } + + adapter = card->adapter; + + if (!test_bit(NXPWIFI_IS_SUSPENDED, &adapter->work_flags)) + return 0; + + clear_bit(NXPWIFI_IS_SUSPENDED, &adapter->work_flags); + + /* Disable Host Sleep */ + nxpwifi_cancel_hs(nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_STA), + NXPWIFI_SYNC_CMD); + + return 0; +} + +static int +nxpwifi_write_reg_locked(struct sdio_func *func, u32 reg, u8 data) +{ + int ret; + + sdio_writeb(func, data, reg, &ret); + return ret; +} + +static int +nxpwifi_write_reg(struct nxpwifi_adapter *adapter, u32 reg, u8 data) +{ + struct sdio_mmc_card *card = adapter->card; + int ret; + + sdio_claim_host(card->func); + ret = nxpwifi_write_reg_locked(card->func, reg, data); + sdio_release_host(card->func); + + return ret; +} + +static int +nxpwifi_read_reg(struct nxpwifi_adapter *adapter, u32 reg, u8 *data) +{ + struct sdio_mmc_card *card = adapter->card; + int ret; + u8 val; + + sdio_claim_host(card->func); + val = sdio_readb(card->func, reg, &ret); + sdio_release_host(card->func); + + *data = val; + + return ret; +} + +static int +nxpwifi_write_data_sync(struct nxpwifi_adapter *adapter, + u8 *buffer, u32 pkt_len, u32 port) +{ + struct sdio_mmc_card *card = adapter->card; + int ret; + u8 blk_mode = + (port & NXPWIFI_SDIO_BYTE_MODE_MASK) ? BYTE_MODE : BLOCK_MODE; + u32 blk_size = (blk_mode == BLOCK_MODE) ? NXPWIFI_SDIO_BLOCK_SIZE : 1; + u32 blk_cnt = + (blk_mode == + BLOCK_MODE) ? (pkt_len / + NXPWIFI_SDIO_BLOCK_SIZE) : pkt_len; + u32 ioport = (port & NXPWIFI_SDIO_IO_PORT_MASK); + + if (test_bit(NXPWIFI_IS_SUSPENDED, &adapter->work_flags)) { + nxpwifi_dbg(adapter, ERROR, + "%s: not allowed while suspended\n", __func__); + return -EPERM; + } + + sdio_claim_host(card->func); + + ret = sdio_writesb(card->func, ioport, buffer, blk_cnt * blk_size); + + sdio_release_host(card->func); + + return ret; +} + +static int nxpwifi_read_data_sync(struct nxpwifi_adapter *adapter, u8 *buffer, + u32 len, u32 port, u8 claim) +{ + struct sdio_mmc_card *card = adapter->card; + int ret; + u8 blk_mode = (port & NXPWIFI_SDIO_BYTE_MODE_MASK) ? BYTE_MODE + : BLOCK_MODE; + u32 blk_size = (blk_mode == BLOCK_MODE) ? NXPWIFI_SDIO_BLOCK_SIZE : 1; + u32 blk_cnt = (blk_mode == BLOCK_MODE) ? (len / NXPWIFI_SDIO_BLOCK_SIZE) + : len; + u32 ioport = (port & NXPWIFI_SDIO_IO_PORT_MASK); + + if (claim) + sdio_claim_host(card->func); + + ret = sdio_readsb(card->func, buffer, ioport, blk_cnt * blk_size); + + if (claim) + sdio_release_host(card->func); + + return ret; +} + +static int +nxpwifi_sdio_read_fw_status(struct nxpwifi_adapter *adapter, u16 *dat) +{ + struct sdio_mmc_card *card = adapter->card; + const struct nxpwifi_sdio_card_reg *reg = card->reg; + u8 fws0, fws1; + int ret; + + ret = nxpwifi_read_reg(adapter, reg->status_reg_0, &fws0); + if (ret) + return ret; + + ret = nxpwifi_read_reg(adapter, reg->status_reg_1, &fws1); + if (ret) + return ret; + + *dat = (u16)((fws1 << 8) | fws0); + return ret; +} + +static int nxpwifi_check_fw_status(struct nxpwifi_adapter *adapter, + u32 poll_num) +{ + int ret = 0; + u16 firmware_stat = 0; + + unsigned int timeout_us = poll_num * 100000; /* 100 ms * poll_num */ + /* + * Poll every 100 ms until firmware reports FIRMWARE_READY_SDIO. + * On timeout, read_poll_timeout() returns -ETIMEDOUT. + */ + ret = read_poll_timeout(nxpwifi_sdio_read_fw_status, ret, + (!ret && firmware_stat == FIRMWARE_READY_SDIO), + 100000, timeout_us, true, /* sleep */ + adapter, &firmware_stat); + + /* FW may appear ready; wait a bit to avoid early races. */ + if (firmware_stat == FIRMWARE_READY_SDIO) + msleep(100); + + return ret; +} + +static int nxpwifi_check_winner_status(struct nxpwifi_adapter *adapter) +{ + int ret; + u8 winner = 0; + struct sdio_mmc_card *card = adapter->card; + + ret = nxpwifi_read_reg(adapter, card->reg->status_reg_0, &winner); + if (ret) + return ret; + + if (winner) + adapter->winner = 0; + else + adapter->winner = 1; + + return ret; +} + +/* Remove the SDIO function and tear down the adapter. */ +static void +nxpwifi_sdio_remove(struct sdio_func *func) +{ + struct sdio_mmc_card *card; + struct nxpwifi_adapter *adapter; + struct nxpwifi_private *priv; + int ret = 0; + u16 firmware_stat; + + card = sdio_get_drvdata(func); + if (!card) + return; + + wait_for_completion(&card->fw_done); + + adapter = card->adapter; + if (!adapter || !adapter->priv_num) + return; + + ret = nxpwifi_sdio_read_fw_status(adapter, &firmware_stat); + if (!ret && firmware_stat == FIRMWARE_READY_SDIO) { + nxpwifi_deauthenticate_all(adapter); + + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + nxpwifi_disable_auto_ds(priv); + nxpwifi_init_shutdown_fw(priv, NXPWIFI_FUNC_SHUTDOWN); + } + + nxpwifi_remove_card(adapter); +} + +/* Suspend the SDIO function while keeping SDIO power. */ +static int nxpwifi_sdio_suspend(struct device *dev) +{ + struct sdio_func *func = dev_to_sdio_func(dev); + struct sdio_mmc_card *card; + struct nxpwifi_adapter *adapter; + mmc_pm_flag_t caps; + unsigned long flags = 0; + int ret = 0; + + caps = sdio_get_host_pm_caps(func); + + if (!(caps & MMC_PM_KEEP_POWER)) { + /* host lacks keep-power capability */ + dev_warn(dev, "suspend: host does not support MMC_PM_KEEP_POWER\n"); + return -EOPNOTSUPP; + } + + card = sdio_get_drvdata(func); + + if (!card) { + dev_warn(dev, "suspend: card not ready\n"); + return -ENODEV; + } + + /* Might still be loading firmware */ + wait_for_completion(&card->fw_done); + + adapter = card->adapter; + if (!adapter) { + dev_warn(dev, "suspend: adapter not ready\n"); + return -ENODEV; + } + + /* Enable the Host Sleep */ + if (!nxpwifi_enable_hs(adapter)) { + nxpwifi_dbg(adapter, ERROR, "suspend: enable host sleep failed\n"); + clear_bit(NXPWIFI_IS_HS_ENABLING, &adapter->work_flags); + return -ETIMEDOUT; + } + + flags |= MMC_PM_KEEP_POWER; + + if (adapter->wowlan_enabled && (caps & MMC_PM_WAKE_SDIO_IRQ)) + flags |= MMC_PM_WAKE_SDIO_IRQ; + + ret = sdio_set_host_pm_flags(func, flags); + + /* Indicate device suspended */ + set_bit(NXPWIFI_IS_SUSPENDED, &adapter->work_flags); + clear_bit(NXPWIFI_IS_HS_ENABLING, &adapter->work_flags); + + return ret; +} + +static void nxpwifi_sdio_coredump(struct device *dev) +{ + struct sdio_func *func = dev_to_sdio_func(dev); + struct sdio_mmc_card *card; + + card = sdio_get_drvdata(func); + if (!test_and_set_bit(NXPWIFI_IFACE_WORK_DEVICE_DUMP, + &card->work_flags)) + nxpwifi_queue_work(card->adapter, &card->work); +} + +/* WLAN IDs */ +static const struct sdio_device_id nxpwifi_ids[] = { + {SDIO_DEVICE(SDIO_VENDOR_ID_NXP, SDIO_DEVICE_ID_NXP_IW61X), + .driver_data = (unsigned long)&nxpwifi_sdio_iw61x}, + {}, +}; + +MODULE_DEVICE_TABLE(sdio, nxpwifi_ids); + +static const struct dev_pm_ops nxpwifi_sdio_pm_ops = { + .suspend = nxpwifi_sdio_suspend, + .resume = nxpwifi_sdio_resume, +}; + +static struct sdio_driver nxpwifi_sdio = { + .name = "nxpwifi_sdio", + .id_table = nxpwifi_ids, + .probe = nxpwifi_sdio_probe, + .remove = nxpwifi_sdio_remove, + .drv = { + .coredump = nxpwifi_sdio_coredump, + .pm = &nxpwifi_sdio_pm_ops, + } +}; + +static int nxpwifi_pm_wakeup_card(struct nxpwifi_adapter *adapter) +{ + nxpwifi_dbg(adapter, EVENT, "event: wakeup device...\n"); + + return nxpwifi_write_reg(adapter, CONFIGURATION_REG, HOST_POWER_UP); +} + +static int nxpwifi_pm_wakeup_card_complete(struct nxpwifi_adapter *adapter) +{ + nxpwifi_dbg(adapter, EVENT, "cmd: wakeup device completed\n"); + + return nxpwifi_write_reg(adapter, CONFIGURATION_REG, 0); +} + +/* SDIO wrapper for firmware download (claims host). */ +static int nxpwifi_sdio_dnld_fw(struct nxpwifi_adapter *adapter, + struct nxpwifi_fw_image *fw) +{ + struct sdio_mmc_card *card = adapter->card; + int ret; + + sdio_claim_host(card->func); + ret = nxpwifi_dnld_fw(adapter, fw); + sdio_release_host(card->func); + + return ret; +} + +static int nxpwifi_init_sdio_new_mode(struct nxpwifi_adapter *adapter) +{ + u8 reg; + struct sdio_mmc_card *card = adapter->card; + int ret; + + adapter->ioport = MEM_PORT; + + /* enable sdio new mode */ + ret = nxpwifi_read_reg(adapter, card->reg->card_cfg_2_1_reg, ®); + if (ret) + return ret; + ret = nxpwifi_write_reg(adapter, card->reg->card_cfg_2_1_reg, + reg | CMD53_NEW_MODE); + if (ret) + return ret; + + /* Configure cmd port and enable reading rx length from the register */ + ret = nxpwifi_read_reg(adapter, card->reg->cmd_cfg_0, ®); + if (ret) + return ret; + ret = nxpwifi_write_reg(adapter, card->reg->cmd_cfg_0, + reg | CMD_PORT_RD_LEN_EN); + if (ret) + return ret; + + /* + * Enable Dnld/Upld ready auto reset for cmd port after cmd53 is + * completed + */ + ret = nxpwifi_read_reg(adapter, card->reg->cmd_cfg_1, ®); + if (ret) + return ret; + ret = nxpwifi_write_reg(adapter, card->reg->cmd_cfg_1, + reg | CMD_PORT_AUTO_EN); + + return ret; +} + +/* Initialize SDIO IO ports and host-int behavior. */ +static int nxpwifi_init_sdio_ioport(struct nxpwifi_adapter *adapter) +{ + u8 reg; + struct sdio_mmc_card *card = adapter->card; + int ret; + + ret = nxpwifi_init_sdio_new_mode(adapter); + if (ret) + return ret; + + /* Set Host interrupt reset to read to clear */ + ret = nxpwifi_read_reg(adapter, card->reg->host_int_rsr_reg, ®); + if (ret) + return ret; + ret = nxpwifi_write_reg(adapter, card->reg->host_int_rsr_reg, + reg | card->reg->sdio_int_mask); + if (ret) + return ret; + + /* Dnld/Upld ready set to auto reset */ + ret = nxpwifi_read_reg(adapter, card->reg->card_misc_cfg_reg, ®); + if (ret) + return ret; + ret = nxpwifi_write_reg(adapter, card->reg->card_misc_cfg_reg, + reg | AUTO_RE_ENABLE_INT); + + return ret; +} + +static int nxpwifi_write_data_to_card(struct nxpwifi_adapter *adapter, + u8 *payload, u32 pkt_len, u32 port) +{ + u32 i = 0; + int ret; + + do { + ret = nxpwifi_write_data_sync(adapter, payload, pkt_len, port); + if (ret) { + i++; + nxpwifi_dbg(adapter, ERROR, "host_to_card, write iomem\t" + "(%d) failed: %d\n", i, ret); + if (nxpwifi_write_reg(adapter, CONFIGURATION_REG, 0x04)) + nxpwifi_dbg(adapter, ERROR, "write CFG reg failed\n"); + + if (i > MAX_WRITE_IOMEM_RETRY) + return ret; + } + } while (ret); + + return ret; +} + +static int nxpwifi_get_rd_port(struct nxpwifi_adapter *adapter, u8 *port) +{ + struct sdio_mmc_card *card = adapter->card; + const struct nxpwifi_sdio_card_reg *reg = card->reg; + u32 rd_bitmap = card->mp_rd_bitmap; + + if (!(rd_bitmap & reg->data_port_mask)) + return -EINVAL; + + if (!(card->mp_rd_bitmap & (1 << card->curr_rd_port))) + return -EINVAL; + + /* We are now handling the SDIO data ports */ + card->mp_rd_bitmap &= (u32)(~(1 << card->curr_rd_port)); + *port = card->curr_rd_port; + + if (++card->curr_rd_port == card->max_ports) + card->curr_rd_port = reg->start_rd_port; + + return 0; +} + +static int nxpwifi_get_wr_port_data(struct nxpwifi_adapter *adapter, u32 *port) +{ + struct sdio_mmc_card *card = adapter->card; + const struct nxpwifi_sdio_card_reg *reg = card->reg; + u32 wr_bitmap = card->mp_wr_bitmap; + + if (!(wr_bitmap & card->mp_data_port_mask)) { + adapter->data_sent = true; + return -EBUSY; + } + + if (card->mp_wr_bitmap & (1 << card->curr_wr_port)) { + card->mp_wr_bitmap &= (u32)(~(1 << card->curr_wr_port)); + *port = card->curr_wr_port; + if (++card->curr_wr_port == card->mp_end_port) + card->curr_wr_port = reg->start_wr_port; + } else { + adapter->data_sent = true; + return -EBUSY; + } + + return 0; +} + +static int +nxpwifi_sdio_poll_card_status(struct nxpwifi_adapter *adapter, u8 bits) +{ + struct sdio_mmc_card *card = adapter->card; + u32 tries; + u8 cs; + int ret; + + for (tries = 0; tries < MAX_POLL_TRIES; tries++) { + ret = nxpwifi_read_reg(adapter, card->reg->poll_reg, &cs); + if (ret) + break; + else if ((cs & bits) == bits) + return 0; + + usleep_range(10, 20); + } + + nxpwifi_dbg(adapter, ERROR, "poll card status failed, tries = %d\n", tries); + + return ret; +} + +/* Disable SDIO host interrupt and release IRQ. */ +static void nxpwifi_sdio_disable_host_int(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + struct sdio_func *func = card->func; + + sdio_claim_host(func); + nxpwifi_write_reg_locked(func, card->reg->host_int_mask_reg, 0); + sdio_release_irq(func); + sdio_release_host(func); +} + +static void nxpwifi_interrupt_status(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + u8 sdio_ireg; + unsigned long flags; + + if (nxpwifi_read_data_sync(adapter, card->mp_regs, + card->reg->max_mp_regs, + REG_PORT | NXPWIFI_SDIO_BYTE_MODE_MASK, 0)) { + nxpwifi_dbg(adapter, ERROR, "read mp_regs failed\n"); + return; + } + + sdio_ireg = card->mp_regs[card->reg->host_int_status_reg]; + if (sdio_ireg) { + nxpwifi_dbg(adapter, INTR, "intr: sdio_ireg = %#x\n", sdio_ireg); + spin_lock_irqsave(&adapter->int_lock, flags); + adapter->int_status |= sdio_ireg; + spin_unlock_irqrestore(&adapter->int_lock, flags); + } +} + +/* SDIO IRQ handler: snapshot status and schedule main work. */ +static void +nxpwifi_sdio_interrupt(struct sdio_func *func) +{ + struct nxpwifi_adapter *adapter; + struct sdio_mmc_card *card; + + card = sdio_get_drvdata(func); + + if (!card || !card->adapter) { + /* device-scoped error logging (rate-limited to avoid flood) */ + dev_err_ratelimited(&func->dev, "interrupt: missing card/adapter\n"); + return; + } + + adapter = card->adapter; + + if (!adapter->pps_uapsd_mode && adapter->ps_state == PS_STATE_SLEEP) + adapter->ps_state = PS_STATE_AWAKE; + + nxpwifi_interrupt_status(adapter); + nxpwifi_queue_work(adapter, &adapter->main_work); +} + +/* Enable SDIO host interrupt and claim IRQ. */ +static int nxpwifi_sdio_enable_host_int(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + struct sdio_func *func = card->func; + int ret; + + sdio_claim_host(func); + + /* Request the SDIO IRQ */ + ret = sdio_claim_irq(func, nxpwifi_sdio_interrupt); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "claim irq failed: ret=%d\n", ret); + goto done; + } + + /* Simply write the mask to the register */ + ret = nxpwifi_write_reg_locked(func, card->reg->host_int_mask_reg, + card->reg->host_int_enable); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "enable host interrupt failed\n"); + sdio_release_irq(func); + } + +done: + sdio_release_host(func); + return ret; +} + +static int nxpwifi_sdio_card_to_host(struct nxpwifi_adapter *adapter, + u32 *type, u8 *buffer, + u32 npayload, u32 ioport) +{ + int ret; + u32 nb; + + if (!buffer) + return -EINVAL; + + ret = nxpwifi_read_data_sync(adapter, buffer, npayload, ioport, 1); + + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "read iomem failed (ioport=%#x, len=%u): %d", + ioport, npayload, ret); + + return ret; + } + + nb = get_unaligned_le16((buffer)); + if (nb > npayload) { + nxpwifi_dbg(adapter, ERROR, + "invalid packet len: nb=%u > npayload=%u (ioport=%#x)", + nb, npayload, ioport); + return -EINVAL; + } + + *type = get_unaligned_le16((buffer + 2)); + + return ret; +} + +/* Download firmware using the helper protocol. */ +static int nxpwifi_prog_fw_w_helper(struct nxpwifi_adapter *adapter, + struct nxpwifi_fw_image *fw) +{ + struct sdio_mmc_card *card = adapter->card; + const struct nxpwifi_sdio_card_reg *reg = card->reg; + int ret; + u8 *firmware = fw->fw_buf; + u32 firmware_len = fw->fw_len; + u32 offset = 0; + u8 base0, base1; + u8 *fwbuf; + u16 len = 0; + u32 txlen, tx_blocks = 0, tries; + u32 i = 0; + + if (!firmware_len) { + nxpwifi_dbg(adapter, ERROR, + "firmware image not found! Terminating download\n"); + return -EINVAL; + } + + /* Assume that the allocated buffer is 8-byte aligned */ + fwbuf = kzalloc(NXPWIFI_UPLD_SIZE, GFP_KERNEL); + if (!fwbuf) + return -ENOMEM; + + sdio_claim_host(card->func); + + /* Perform firmware data transfer */ + do { + /* + * The host polls for the DN_LD_CARD_RDY and CARD_IO_READY + * bits + */ + ret = nxpwifi_sdio_poll_card_status(adapter, CARD_IO_READY | + DN_LD_CARD_RDY); + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "FW download with helper:\t" + "poll status timeout @ %d\n", offset); + goto done; + } + + /* More data? */ + if (offset >= firmware_len) + break; + + for (tries = 0; tries < MAX_POLL_TRIES; tries++) { + ret = nxpwifi_read_reg(adapter, reg->base_0_reg, + &base0); + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "dev BASE0 register read failed:\t" + "base0=%#04X(%d). Terminating dnld\n", + base0, base0); + goto done; + } + ret = nxpwifi_read_reg(adapter, reg->base_1_reg, + &base1); + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "dev BASE1 register read failed:\t" + "base1=%#04X(%d). Terminating dnld\n", + base1, base1); + goto done; + } + len = (u16)(((base1 & 0xff) << 8) | (base0 & 0xff)); + + if (len) + break; + + usleep_range(10, 20); + } + + if (!len) { + break; + } else if (len > NXPWIFI_UPLD_SIZE) { + nxpwifi_dbg(adapter, ERROR, + "FW dnld failed @ %d, invalid length %d\n", + offset, len); + ret = -EINVAL; + goto done; + } + + txlen = len; + + if (len & BIT(0)) { + i++; + if (i > MAX_WRITE_IOMEM_RETRY) { + nxpwifi_dbg(adapter, ERROR, + "FW dnld failed @ %d, over max retry\n", + offset); + ret = -EIO; + goto done; + } + nxpwifi_dbg(adapter, ERROR, + "CRC indicated by the helper:\t" + "len = 0x%04X, txlen = %d\n", len, txlen); + len &= ~BIT(0); + /* Setting this to 0 to resend from same offset */ + txlen = 0; + } else { + i = 0; + + /* + * Set blocksize to transfer - checking for last + * block + */ + if (firmware_len - offset < txlen) + txlen = firmware_len - offset; + + tx_blocks = (txlen + NXPWIFI_SDIO_BLOCK_SIZE - 1) + / NXPWIFI_SDIO_BLOCK_SIZE; + + /* Copy payload to buffer */ + memcpy(fwbuf, &firmware[offset], txlen); + } + + ret = nxpwifi_write_data_sync(adapter, fwbuf, tx_blocks * + NXPWIFI_SDIO_BLOCK_SIZE, + adapter->ioport); + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "FW download, write iomem (%d) failed @ %d\n", + i, offset); + if (nxpwifi_write_reg(adapter, CONFIGURATION_REG, 0x04)) + nxpwifi_dbg(adapter, ERROR, "write CFG reg failed\n"); + + goto done; + } + + offset += txlen; + } while (true); + + nxpwifi_dbg(adapter, MSG, "FW download complete (%u bytes)\n", offset); + + ret = 0; +done: + sdio_release_host(card->func); + kfree(fwbuf); + return ret; +} + +/* Deaggregate an SDIO RX aggregation packet. */ +static void nxpwifi_deaggr_sdio_pkt(struct nxpwifi_adapter *adapter, + struct sk_buff *skb) +{ + u32 total_pkt_len, pkt_len; + struct sk_buff *skb_deaggr; + u16 blk_size; + u8 blk_num; + u8 *data; + + data = skb->data; + total_pkt_len = skb->len; + + while (total_pkt_len >= (SDIO_HEADER_OFFSET + adapter->intf_hdr_len)) { + if (total_pkt_len < adapter->sdio_rx_block_size) + break; + blk_num = *(data + BLOCK_NUMBER_OFFSET); + blk_size = adapter->sdio_rx_block_size * blk_num; + if (blk_size > total_pkt_len) { + nxpwifi_dbg(adapter, ERROR, + "%s: error in blk_size,\t" + "blk_num=%d, blk_size=%d, total_pkt_len=%d\n", + __func__, blk_num, blk_size, total_pkt_len); + break; + } + pkt_len = get_unaligned_le16((data + + SDIO_HEADER_OFFSET)); + if ((pkt_len + SDIO_HEADER_OFFSET) > blk_size) { + nxpwifi_dbg(adapter, ERROR, + "%s: error in pkt_len,\t" + "pkt_len=%d, blk_size=%d\n", + __func__, pkt_len, blk_size); + break; + } + + skb_deaggr = nxpwifi_alloc_dma_align_buf(pkt_len, GFP_KERNEL); + if (!skb_deaggr) + break; + skb_put(skb_deaggr, pkt_len); + memcpy(skb_deaggr->data, data + SDIO_HEADER_OFFSET, pkt_len); + skb_pull(skb_deaggr, adapter->intf_hdr_len); + + nxpwifi_handle_rx_packet(adapter, skb_deaggr); + data += blk_size; + total_pkt_len -= blk_size; + } +} + +static void nxpwifi_decode_rx_packet(struct nxpwifi_adapter *adapter, + struct sk_buff *skb, u32 upld_typ) +{ + u8 *cmd_buf; + u16 pkt_len; + struct nxpwifi_rxinfo *rx_info; + + pkt_len = get_unaligned_le16(skb->data); + + if (upld_typ != NXPWIFI_TYPE_AGGR_DATA) { + skb_trim(skb, pkt_len); + skb_pull(skb, adapter->intf_hdr_len); + } + + switch (upld_typ) { + case NXPWIFI_TYPE_AGGR_DATA: + nxpwifi_dbg(adapter, DATA, + "Rx Aggr Data packet\n"); + rx_info = NXPWIFI_SKB_RXCB(skb); + rx_info->buf_type = NXPWIFI_TYPE_AGGR_DATA; + if (adapter->rx_work_enabled) { + skb_queue_tail(&adapter->rx_data_q, skb); + atomic_inc(&adapter->rx_pending); + adapter->data_received = true; + } else { + /* Deaggregate an SDIO RX aggregation packet. */ + nxpwifi_deaggr_sdio_pkt(adapter, skb); + dev_kfree_skb_any(skb); + } + break; + + case NXPWIFI_TYPE_DATA: + nxpwifi_dbg(adapter, DATA, "Rx Data packet\n"); + if (adapter->rx_work_enabled) { + skb_queue_tail(&adapter->rx_data_q, skb); + adapter->data_received = true; + atomic_inc(&adapter->rx_pending); + } else { + nxpwifi_handle_rx_packet(adapter, skb); + } + break; + + case NXPWIFI_TYPE_CMD: + nxpwifi_dbg(adapter, CMD, "Rx Cmd Response\n"); + /* take care of curr_cmd = NULL case */ + if (!adapter->curr_cmd) { + cmd_buf = adapter->upld_buf; + + if (adapter->ps_state == PS_STATE_SLEEP_CFM) + nxpwifi_process_sleep_confirm_resp(adapter, + skb->data, + skb->len); + + memcpy(cmd_buf, skb->data, + min_t(u32, NXPWIFI_SIZE_OF_CMD_BUFFER, + skb->len)); + + dev_kfree_skb_any(skb); + } else { + adapter->cmd_resp_received = true; + adapter->curr_cmd->resp_skb = skb; + } + break; + + case NXPWIFI_TYPE_EVENT: + nxpwifi_dbg(adapter, EVENT, "Rx Event\n"); + adapter->event_cause = get_unaligned_le32(skb->data); + + if (skb->len > NXPWIFI_EVENT_HEADER_LEN) { + u32 body_len = min_t(u32, skb->len - NXPWIFI_EVENT_HEADER_LEN, + MAX_EVENT_SIZE); + memcpy(adapter->event_body, skb->data + NXPWIFI_EVENT_HEADER_LEN, + body_len); + } + + /* event cause has been saved to adapter->event_cause */ + adapter->event_received = true; + adapter->event_skb = skb; + + break; + + default: + nxpwifi_dbg(adapter, ERROR, "unknown upload type %#x\n", upld_typ); + dev_kfree_skb_any(skb); + break; + } +} + +/* Receive path with SDIO multi-port aggregation. */ +static int nxpwifi_sdio_card_to_host_mp_aggr(struct nxpwifi_adapter *adapter, + u16 rx_len, u8 port) +{ + struct sdio_mmc_card *card = adapter->card; + s32 f_do_rx_aggr = 0; + s32 f_do_rx_cur = 0; + s32 f_aggr_cur = 0; + s32 f_post_aggr_cur = 0; + struct sk_buff *skb_deaggr; + struct sk_buff *skb = NULL; + u32 pkt_len, pkt_type, mport, pind; + u8 *curr_ptr; + int ret = 0; + + if (!card->mpa_rx.enabled) { + nxpwifi_dbg(adapter, WARN, "rx aggregation disabled\n"); + f_do_rx_cur = 1; + goto rx_curr_single; + } + + if (card->mp_rd_bitmap & card->reg->data_port_mask) { + /* Some more data RX pending */ + + if (MP_RX_AGGR_IN_PROGRESS(card)) { + if (MP_RX_AGGR_BUF_HAS_ROOM(card, rx_len)) { + f_aggr_cur = 1; + } else { + /* No room in Aggr buf, do rx aggr now */ + f_do_rx_aggr = 1; + f_post_aggr_cur = 1; + } + } else { + /* Rx aggr not in progress */ + f_aggr_cur = 1; + } + + } else { + /* No more data RX pending */ + + if (MP_RX_AGGR_IN_PROGRESS(card)) { + f_do_rx_aggr = 1; + if (MP_RX_AGGR_BUF_HAS_ROOM(card, rx_len)) + f_aggr_cur = 1; + else + /* No room in Aggr buf, do rx aggr now */ + f_do_rx_cur = 1; + } else { + f_do_rx_cur = 1; + } + } + + if (f_aggr_cur) { + /* Curr pkt can be aggregated */ + mp_rx_aggr_setup(card, rx_len, port); + + if (MP_RX_AGGR_PKT_LIMIT_REACHED(card) || + mp_rx_aggr_port_limit_reached(card)) { + /* No more pkts allowed in Aggr buf, rx it */ + f_do_rx_aggr = 1; + } + } + + if (f_do_rx_aggr) { + u32 port_count; + int i; + + /* do aggr RX now */ + for (i = 0, port_count = 0; i < card->max_ports; i++) + if (card->mpa_rx.ports & BIT(i)) + port_count++; + + /* + * Reading data from "start_port + 0" to "start_port + + * port_count -1", so decrease the count by 1 + */ + port_count--; + mport = (adapter->ioport | SDIO_MPA_ADDR_BASE | + (port_count << 8)) + card->mpa_rx.start_port; + + if (card->mpa_rx.pkt_cnt == 1) + mport = adapter->ioport + card->mpa_rx.start_port; + + ret = nxpwifi_read_data_sync(adapter, card->mpa_rx.buf, + card->mpa_rx.buf_len, mport, 1); + if (ret) + goto error; + + curr_ptr = card->mpa_rx.buf; + + for (pind = 0; pind < card->mpa_rx.pkt_cnt; pind++) { + u32 *len_arr = card->mpa_rx.len_arr; + + /* get curr PKT len & type */ + pkt_len = get_unaligned_le16(&curr_ptr[0]); + pkt_type = get_unaligned_le16(&curr_ptr[2]); + + /* copy pkt to deaggr buf */ + skb_deaggr = nxpwifi_alloc_dma_align_buf(len_arr[pind], + GFP_KERNEL); + if (!skb_deaggr) { + nxpwifi_dbg(adapter, ERROR, "skb allocation failure\t" + "drop pkt len=%d type=%d\n", + pkt_len, pkt_type); + curr_ptr += len_arr[pind]; + continue; + } + + skb_put(skb_deaggr, len_arr[pind]); + + if ((pkt_type == NXPWIFI_TYPE_DATA || + (pkt_type == NXPWIFI_TYPE_AGGR_DATA && + adapter->sdio_rx_aggr_enable)) && + pkt_len <= len_arr[pind]) { + memcpy(skb_deaggr->data, curr_ptr, pkt_len); + + skb_trim(skb_deaggr, pkt_len); + + nxpwifi_decode_rx_packet(adapter, skb_deaggr, + pkt_type); + } else { + nxpwifi_dbg(adapter, ERROR, + "drop wrong aggr pkt:\t" + "sdio_single_port_rx_aggr=%d\t" + "type=%d len=%d max_len=%d\n", + adapter->sdio_rx_aggr_enable, + pkt_type, pkt_len, len_arr[pind]); + dev_kfree_skb_any(skb_deaggr); + } + curr_ptr += len_arr[pind]; + } + MP_RX_AGGR_BUF_RESET(card); + } + +rx_curr_single: + if (f_do_rx_cur) { + skb = nxpwifi_alloc_dma_align_buf(rx_len, GFP_KERNEL); + if (!skb) { + nxpwifi_dbg(adapter, ERROR, + "single skb allocated fail,\t" + "drop pkt port=%d len=%d\n", port, rx_len); + ret = nxpwifi_sdio_card_to_host(adapter, &pkt_type, + card->mpa_rx.buf, + rx_len, + adapter->ioport + port); + if (ret) + goto error; + return 0; + } + + skb_put(skb, rx_len); + + ret = nxpwifi_sdio_card_to_host(adapter, &pkt_type, + skb->data, skb->len, + adapter->ioport + port); + if (ret) + goto error; + if (!adapter->sdio_rx_aggr_enable && + pkt_type == NXPWIFI_TYPE_AGGR_DATA) { + nxpwifi_dbg(adapter, ERROR, "drop wrong pkt type %d\t" + "current SDIO RX Aggr not enabled\n", + pkt_type); + dev_kfree_skb_any(skb); + return 0; + } + + nxpwifi_decode_rx_packet(adapter, skb, pkt_type); + } + if (f_post_aggr_cur) /* Curr pkt can be aggregated */ + mp_rx_aggr_setup(card, rx_len, port); + + return 0; +error: + if (MP_RX_AGGR_IN_PROGRESS(card)) + MP_RX_AGGR_BUF_RESET(card); + + if (f_do_rx_cur && skb) /* Single transfer pending. Free curr buff also */ + dev_kfree_skb_any(skb); + + return ret; +} + +static int nxpwifi_process_int_status(struct nxpwifi_adapter *adapter, u8 sdio_ireg) +{ + struct sdio_mmc_card *card = adapter->card; + const struct nxpwifi_sdio_card_reg *reg = card->reg; + int ret = 0; + struct sk_buff *skb; + u8 port; + u32 len_reg_l, len_reg_u; + u32 rx_blocks; + u16 rx_len; + u32 bitmap; + u8 cr; + + if (!sdio_ireg) + return ret; + + if (sdio_ireg & DN_LD_CMD_PORT_HOST_INT_STATUS && adapter->cmd_sent) + adapter->cmd_sent = false; + + if (sdio_ireg & UP_LD_CMD_PORT_HOST_INT_STATUS) { + u32 pkt_type; + + /* read the len of control packet */ + rx_len = card->mp_regs[reg->cmd_rd_len_1] << 8; + rx_len |= (u16)card->mp_regs[reg->cmd_rd_len_0]; + rx_blocks = DIV_ROUND_UP(rx_len, NXPWIFI_SDIO_BLOCK_SIZE); + if (rx_len <= adapter->intf_hdr_len || + (rx_blocks * NXPWIFI_SDIO_BLOCK_SIZE) > + NXPWIFI_RX_DATA_BUF_SIZE) + return -EINVAL; + rx_len = (u16)(rx_blocks * NXPWIFI_SDIO_BLOCK_SIZE); + + skb = nxpwifi_alloc_dma_align_buf(rx_len, GFP_KERNEL); + if (!skb) + return -ENOMEM; + + skb_put(skb, rx_len); + + ret = nxpwifi_sdio_card_to_host(adapter, &pkt_type, skb->data, + skb->len, adapter->ioport | + CMD_PORT_SLCT); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "failed to card_to_host"); + dev_kfree_skb_any(skb); + goto term_cmd; + } + + if (pkt_type != NXPWIFI_TYPE_CMD && + pkt_type != NXPWIFI_TYPE_EVENT) + nxpwifi_dbg(adapter, ERROR, "Received wrong packet on cmd port"); + + nxpwifi_decode_rx_packet(adapter, skb, pkt_type); + } + + if (sdio_ireg & DN_LD_HOST_INT_STATUS) { + bitmap = (u32)card->mp_regs[reg->wr_bitmap_l]; + bitmap |= ((u32)card->mp_regs[reg->wr_bitmap_u]) << 8; + bitmap |= ((u32)card->mp_regs[reg->wr_bitmap_1l]) << 16; + bitmap |= ((u32)card->mp_regs[reg->wr_bitmap_1u]) << 24; + card->mp_wr_bitmap = bitmap; + + nxpwifi_dbg(adapter, INTR, "intr: wr_bitmap=0x%x\n", card->mp_wr_bitmap); + if (adapter->data_sent && + (card->mp_wr_bitmap & card->mp_data_port_mask)) { + nxpwifi_dbg(adapter, INTR, "Tx DONE\n"); + adapter->data_sent = false; + } + } + + nxpwifi_dbg(adapter, INTR, "cmd_sent=%d data_sent=%d\n", + adapter->cmd_sent, adapter->data_sent); + if (sdio_ireg & UP_LD_HOST_INT_STATUS) { + bitmap = (u32)card->mp_regs[reg->rd_bitmap_l]; + bitmap |= ((u32)card->mp_regs[reg->rd_bitmap_u]) << 8; + bitmap |= ((u32)card->mp_regs[reg->rd_bitmap_1l]) << 16; + bitmap |= ((u32)card->mp_regs[reg->rd_bitmap_1u]) << 24; + card->mp_rd_bitmap = bitmap; + nxpwifi_dbg(adapter, INTR, "intr: rd_bitmap=0x%x\n", card->mp_rd_bitmap); + + while (true) { + ret = nxpwifi_get_rd_port(adapter, &port); + + if (ret) + break; + + len_reg_l = reg->rd_len_p0_l + (port << 1); + len_reg_u = reg->rd_len_p0_u + (port << 1); + rx_len = ((u16)card->mp_regs[len_reg_u]) << 8; + rx_len |= (u16)card->mp_regs[len_reg_l]; + rx_blocks = + (rx_len + NXPWIFI_SDIO_BLOCK_SIZE - + 1) / NXPWIFI_SDIO_BLOCK_SIZE; + if (rx_len <= adapter->intf_hdr_len || + (card->mpa_rx.enabled && + ((rx_blocks * NXPWIFI_SDIO_BLOCK_SIZE) > + card->mpa_rx.buf_size))) { + nxpwifi_dbg(adapter, ERROR, "invalid rx_len=%d\n", rx_len); + return -EINVAL; + } + + rx_len = (u16)(rx_blocks * NXPWIFI_SDIO_BLOCK_SIZE); + + ret = nxpwifi_sdio_card_to_host_mp_aggr(adapter, rx_len, + port); + if (ret) { + nxpwifi_dbg(adapter, ERROR, + "card_to_host_mpa failed: int status=%#x\n", + sdio_ireg); + goto term_cmd; + } + } + } + + return 0; + +term_cmd: + /* terminate cmd */ + if (nxpwifi_read_reg(adapter, CONFIGURATION_REG, &cr)) + nxpwifi_dbg(adapter, ERROR, "read CFG reg failed\n"); + else + nxpwifi_dbg(adapter, INFO, "info: CFG reg val = %d\n", cr); + + if (nxpwifi_write_reg(adapter, CONFIGURATION_REG, (cr | 0x04))) + nxpwifi_dbg(adapter, ERROR, "write CFG reg failed\n"); + else + nxpwifi_dbg(adapter, INFO, "info: write success\n"); + + if (nxpwifi_read_reg(adapter, CONFIGURATION_REG, &cr)) + nxpwifi_dbg(adapter, ERROR, "read CFG reg failed\n"); + else + nxpwifi_dbg(adapter, INFO, "info: CFG reg val =%x\n", cr); + + return ret; +} + +/* Transmit using SDIO multi-port aggregation. */ +static int nxpwifi_host_to_card_mp_aggr(struct nxpwifi_adapter *adapter, + u8 *payload, u32 pkt_len, u32 port, + u32 next_pkt_len) +{ + struct sdio_mmc_card *card = adapter->card; + int ret = 0; + s32 f_send_aggr_buf = 0; + s32 f_send_cur_buf = 0; + s32 f_precopy_cur_buf = 0; + s32 f_postcopy_cur_buf = 0; + u32 mport; + int index; + + if (!card->mpa_tx.enabled || port == CMD_PORT_SLCT) { + nxpwifi_dbg(adapter, WARN, "tx aggregation disabled\n"); + f_send_cur_buf = 1; + goto tx_curr_single; + } + + if (next_pkt_len) { + /* More pkt in TX queue */ + + if (MP_TX_AGGR_IN_PROGRESS(card)) { + if (MP_TX_AGGR_BUF_HAS_ROOM(card, pkt_len)) { + f_precopy_cur_buf = 1; + + if (!(card->mp_wr_bitmap & + (1 << card->curr_wr_port)) || + !MP_TX_AGGR_BUF_HAS_ROOM + (card, pkt_len + next_pkt_len)) + f_send_aggr_buf = 1; + } else { + /* No room in Aggr buf, send it */ + f_send_aggr_buf = 1; + + if (!(card->mp_wr_bitmap & + (1 << card->curr_wr_port))) + f_send_cur_buf = 1; + else + f_postcopy_cur_buf = 1; + } + } else { + if (MP_TX_AGGR_BUF_HAS_ROOM(card, pkt_len) && + (card->mp_wr_bitmap & (1 << card->curr_wr_port))) + f_precopy_cur_buf = 1; + else + f_send_cur_buf = 1; + } + } else { + /* Last pkt in TX queue */ + + if (MP_TX_AGGR_IN_PROGRESS(card)) { + /* some packs in Aggr buf already */ + f_send_aggr_buf = 1; + + if (MP_TX_AGGR_BUF_HAS_ROOM(card, pkt_len)) + f_precopy_cur_buf = 1; + else + /* No room in Aggr buf, send it */ + f_send_cur_buf = 1; + } else { + f_send_cur_buf = 1; + } + } + + if (f_precopy_cur_buf) { + MP_TX_AGGR_BUF_PUT(card, payload, pkt_len, port); + + if (MP_TX_AGGR_PKT_LIMIT_REACHED(card) || + mp_tx_aggr_port_limit_reached(card)) + /* No more pkts allowed in Aggr buf, send it */ + f_send_aggr_buf = 1; + } + + if (f_send_aggr_buf) { + u32 port_count; + int i; + + for (i = 0, port_count = 0; i < card->max_ports; i++) + if (card->mpa_tx.ports & BIT(i)) + port_count++; + + /* + * Writing data from "start_port + 0" to "start_port + + * port_count -1", so decrease the count by 1 + */ + port_count--; + mport = (adapter->ioport | SDIO_MPA_ADDR_BASE | + (port_count << 8)) + card->mpa_tx.start_port; + + if (card->mpa_tx.pkt_cnt == 1) + mport = adapter->ioport + card->mpa_tx.start_port; + + ret = nxpwifi_write_data_to_card(adapter, card->mpa_tx.buf, + card->mpa_tx.buf_len, mport); + + /* Save the last multi port tx aggregation info to debug log */ + index = adapter->dbg.last_sdio_mp_index; + index = (index + 1) % NXPWIFI_DBG_SDIO_MP_NUM; + adapter->dbg.last_sdio_mp_index = index; + adapter->dbg.last_mp_wr_ports[index] = mport; + adapter->dbg.last_mp_wr_bitmap[index] = card->mp_wr_bitmap; + adapter->dbg.last_mp_wr_len[index] = card->mpa_tx.buf_len; + adapter->dbg.last_mp_curr_wr_port[index] = card->curr_wr_port; + + MP_TX_AGGR_BUF_RESET(card); + } + +tx_curr_single: + if (f_send_cur_buf) + ret = nxpwifi_write_data_to_card(adapter, payload, pkt_len, + adapter->ioport + port); + + if (f_postcopy_cur_buf) + MP_TX_AGGR_BUF_PUT(card, payload, pkt_len, port); + + return ret; +} + +static int nxpwifi_sdio_host_to_card(struct nxpwifi_adapter *adapter, + u8 type, struct sk_buff *skb, + struct nxpwifi_tx_param *tx_param) +{ + struct sdio_mmc_card *card = adapter->card; + int ret; + u32 buf_block_len; + u32 blk_size; + u32 port; + u8 *payload = (u8 *)skb->data; + u32 pkt_len = skb->len; + + /* Allocate buffer and copy payload */ + blk_size = NXPWIFI_SDIO_BLOCK_SIZE; + buf_block_len = (pkt_len + blk_size - 1) / blk_size; + put_unaligned_le16((u16)pkt_len, payload + 0); + put_unaligned_le16((u16)type, payload + 2); + + /* + * This is SDIO specific header + * u16 length, + * u16 type (NXPWIFI_TYPE_DATA = 0, NXPWIFI_TYPE_CMD = 1, + * NXPWIFI_TYPE_EVENT = 3) + */ + if (type == NXPWIFI_TYPE_DATA) { + ret = nxpwifi_get_wr_port_data(adapter, &port); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "no wr_port available\n"); + return ret; + } + } else { + adapter->cmd_sent = true; + + if (pkt_len <= adapter->intf_hdr_len || + pkt_len > NXPWIFI_UPLD_SIZE) { + nxpwifi_dbg(adapter, ERROR, + "invalid upld pkt_len=%u (hdr_len=%u, max=%u)\n", + pkt_len, adapter->intf_hdr_len, NXPWIFI_UPLD_SIZE); + return -EINVAL; + } + + port = CMD_PORT_SLCT; + } + + /* Transfer data to card */ + pkt_len = buf_block_len * blk_size; + + if (tx_param) + ret = nxpwifi_host_to_card_mp_aggr(adapter, payload, pkt_len, + port, tx_param->next_pkt_len + ); + else + ret = nxpwifi_host_to_card_mp_aggr(adapter, payload, pkt_len, + port, 0); + + if (ret) { + if (type == NXPWIFI_TYPE_CMD || + type == NXPWIFI_TYPE_VDLL) + adapter->cmd_sent = false; + if (type == NXPWIFI_TYPE_DATA) { + adapter->data_sent = false; + /* restore curr_wr_port in error cases */ + card->curr_wr_port = port; + card->mp_wr_bitmap |= (u32)(1 << card->curr_wr_port); + } + } else { + if (type == NXPWIFI_TYPE_DATA) { + if (!(card->mp_wr_bitmap & (1 << card->curr_wr_port))) + adapter->data_sent = true; + else + adapter->data_sent = false; + } + } + + return ret; +} + +static int nxpwifi_alloc_sdio_mpa_buffers(struct nxpwifi_adapter *adapter, + u32 mpa_tx_buf_size, + u32 mpa_rx_buf_size) +{ + struct sdio_mmc_card *card = adapter->card; + u32 rx_buf_size; + int ret = 0; + + card->mpa_tx.buf = kzalloc(mpa_tx_buf_size, GFP_KERNEL); + if (!card->mpa_tx.buf) { + ret = -ENOMEM; + goto error; + } + + card->mpa_tx.buf_size = mpa_tx_buf_size; + + rx_buf_size = max_t(u32, mpa_rx_buf_size, + (u32)SDIO_MAX_AGGR_BUF_SIZE); + card->mpa_rx.buf = kzalloc(rx_buf_size, GFP_KERNEL); + if (!card->mpa_rx.buf) { + ret = -ENOMEM; + goto error; + } + + card->mpa_rx.buf_size = rx_buf_size; + +error: + if (ret) { + kfree(card->mpa_tx.buf); + kfree(card->mpa_rx.buf); + card->mpa_tx.buf_size = 0; + card->mpa_rx.buf_size = 0; + card->mpa_tx.buf = NULL; + card->mpa_rx.buf = NULL; + } + + return ret; +} + +static void +nxpwifi_unregister_dev(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + + if (adapter->card) { + card->adapter = NULL; + sdio_claim_host(card->func); + sdio_disable_func(card->func); + sdio_release_host(card->func); + } +} + +static int nxpwifi_register_dev(struct nxpwifi_adapter *adapter) +{ + int ret; + struct sdio_mmc_card *card = adapter->card; + struct sdio_func *func = card->func; + const char *firmware = card->firmware; + + /* save adapter pointer in card */ + card->adapter = adapter; + adapter->tx_buf_size = card->tx_buf_size; + + sdio_claim_host(func); + + /* Set block size */ + ret = sdio_set_block_size(card->func, NXPWIFI_SDIO_BLOCK_SIZE); + sdio_release_host(func); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "cannot set SDIO block size\n"); + return ret; + } + + /* + * Select correct firmware (sdsd or sdiouart) firmware based on the strapping + * option + */ + if (card->firmware_sdiouart) { + u8 val; + + nxpwifi_read_reg(adapter, card->reg->host_strap_reg, &val); + if ((val & card->reg->host_strap_mask) == card->reg->host_strap_value) + firmware = card->firmware_sdiouart; + } + strscpy(adapter->fw_name, firmware, sizeof(adapter->fw_name)); + + if (card->fw_dump_enh) { + adapter->mem_type_mapping_tbl = generic_mem_type_map; + adapter->num_mem_types = 1; + } else { + adapter->mem_type_mapping_tbl = mem_type_mapping_tbl; + adapter->num_mem_types = ARRAY_SIZE(mem_type_mapping_tbl); + } + + return 0; +} + +static int nxpwifi_init_sdio(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + const struct nxpwifi_sdio_card_reg *reg = card->reg; + int ret; + u8 sdio_ireg; + + sdio_set_drvdata(card->func, card); + + /* + * Read the host_int_status_reg for ACK the first interrupt got + * from the bootloader. If we don't do this we get a interrupt + * as soon as we register the irq. + */ + nxpwifi_read_reg(adapter, card->reg->host_int_status_reg, &sdio_ireg); + + /* Get SDIO ioport */ + if (nxpwifi_init_sdio_ioport(adapter)) + return -EIO; + + /* Initialize SDIO variables in card */ + card->mp_rd_bitmap = 0; + card->mp_wr_bitmap = 0; + card->curr_rd_port = reg->start_rd_port; + card->curr_wr_port = reg->start_wr_port; + + card->mp_data_port_mask = reg->data_port_mask; + + card->mpa_tx.buf_len = 0; + card->mpa_tx.pkt_cnt = 0; + card->mpa_tx.start_port = 0; + + card->mpa_tx.enabled = 1; + card->mpa_tx.pkt_aggr_limit = card->mp_agg_pkt_limit; + + card->mpa_rx.buf_len = 0; + card->mpa_rx.pkt_cnt = 0; + card->mpa_rx.start_port = 0; + + card->mpa_rx.enabled = 1; + card->mpa_rx.pkt_aggr_limit = card->mp_agg_pkt_limit; + + /* Allocate buffers for SDIO MP-A */ + card->mp_regs = devm_kzalloc(&card->func->dev, reg->max_mp_regs, GFP_KERNEL); + + if (!card->mp_regs) + return -ENOMEM; + + card->mpa_rx.len_arr = + devm_kcalloc(&card->func->dev, card->mp_agg_pkt_limit, + sizeof(*card->mpa_rx.len_arr), GFP_KERNEL); + + if (!card->mpa_rx.len_arr) + return -ENOMEM; + + ret = nxpwifi_alloc_sdio_mpa_buffers(adapter, + card->mp_tx_agg_buf_size, + card->mp_rx_agg_buf_size); + + /* Allocate 32k MPA Tx/Rx buffers if 64k memory allocation fails */ + if (ret && (card->mp_tx_agg_buf_size == NXPWIFI_MP_AGGR_BSIZE_MAX || + card->mp_rx_agg_buf_size == NXPWIFI_MP_AGGR_BSIZE_MAX)) { + /* Disable rx single port aggregation */ + adapter->host_disable_sdio_rx_aggr = true; + + ret = nxpwifi_alloc_sdio_mpa_buffers(adapter, + NXPWIFI_MP_AGGR_BSIZE_32K, + NXPWIFI_MP_AGGR_BSIZE_32K); + if (ret) { + /* Disable multi port aggregation */ + card->mpa_tx.enabled = 0; + card->mpa_rx.enabled = 0; + } + } + + adapter->ext_scan = card->can_ext_scan; + return ret; +} + +static void nxpwifi_cleanup_mpa_buf(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + + MP_TX_AGGR_BUF_RESET(card); + MP_RX_AGGR_BUF_RESET(card); +} + +static void nxpwifi_cleanup_sdio(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + + cancel_work_sync(&card->work); + + kfree(card->mpa_tx.buf); + kfree(card->mpa_rx.buf); +} + +static void +nxpwifi_update_mp_end_port(struct nxpwifi_adapter *adapter, u16 port) +{ + struct sdio_mmc_card *card = adapter->card; + const struct nxpwifi_sdio_card_reg *reg = card->reg; + int i; + + card->mp_end_port = port; + + card->mp_data_port_mask = reg->data_port_mask; + + if (reg->start_wr_port) { + for (i = 1; i <= card->max_ports - card->mp_end_port; i++) + card->mp_data_port_mask &= + ~(1 << (card->max_ports - i)); + } + + card->curr_wr_port = reg->start_wr_port; +} + +/* Perform an SDIO card reset in workqueue context. */ +static void nxpwifi_sdio_card_reset_work(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + struct sdio_func *func = card->func; + int ret; + + /* Prepare the adapter for the reset. */ + nxpwifi_shutdown_sw(adapter); + clear_bit(NXPWIFI_IFACE_WORK_DEVICE_DUMP, &card->work_flags); + clear_bit(NXPWIFI_IFACE_WORK_CARD_RESET, &card->work_flags); + + /* Run a HW reset of the SDIO interface. */ + sdio_claim_host(func); + ret = mmc_hw_reset(func->card); + sdio_release_host(func); + + switch (ret) { + case 1: + nxpwifi_dbg(adapter, MSG, "SDIO HW reset asynchronous\n"); + complete_all(adapter->fw_done); + break; + case 0: + ret = nxpwifi_reinit_sw(adapter); + if (ret) + dev_err(&func->dev, "reinit failed: %d\n", ret); + break; + default: + dev_err(&func->dev, "SDIO HW reset failed: %d\n", ret); + break; + } +} + +static enum +rdwr_status nxpwifi_sdio_rdwr_firmware(struct nxpwifi_adapter *adapter, + u8 doneflag) +{ + struct sdio_mmc_card *card = adapter->card; + int ret, tries; + u8 ctrl_data = 0; + + sdio_writeb(card->func, card->reg->fw_dump_host_ready, + card->reg->fw_dump_ctrl, &ret); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "SDIO Write ERR\n"); + return RDWR_STATUS_FAILURE; + } + for (tries = 0; tries < MAX_POLL_TRIES; tries++) { + ctrl_data = sdio_readb(card->func, card->reg->fw_dump_ctrl, + &ret); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "SDIO read err\n"); + return RDWR_STATUS_FAILURE; + } + if (ctrl_data == FW_DUMP_DONE) + break; + if (doneflag && ctrl_data == doneflag) + return RDWR_STATUS_DONE; + if (ctrl_data != card->reg->fw_dump_host_ready) { + nxpwifi_dbg(adapter, WARN, + "The ctrl reg was changed, re-try again\n"); + sdio_writeb(card->func, card->reg->fw_dump_host_ready, + card->reg->fw_dump_ctrl, &ret); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "SDIO write err\n"); + return RDWR_STATUS_FAILURE; + } + } + usleep_range(100, 200); + } + if (ctrl_data == card->reg->fw_dump_host_ready) { + nxpwifi_dbg(adapter, ERROR, "Fail to pull ctrl_data\n"); + return RDWR_STATUS_FAILURE; + } + + return RDWR_STATUS_SUCCESS; +} + +/* Dump firmware memories for post-mortem analysis. */ +static void nxpwifi_sdio_fw_dump(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + int ret = 0; + unsigned int reg, reg_start, reg_end; + u8 *dbg_ptr, *end_ptr, dump_num, idx, i, read_reg, doneflag = 0; + enum rdwr_status stat; + u32 memory_size; + + if (!card->can_dump_fw) + return; + + for (idx = 0; idx < ARRAY_SIZE(mem_type_mapping_tbl); idx++) { + struct memory_type_mapping *entry = &mem_type_mapping_tbl[idx]; + + if (entry->mem_ptr) { + vfree(entry->mem_ptr); + entry->mem_ptr = NULL; + } + entry->mem_size = 0; + } + + nxpwifi_pm_wakeup_card(adapter); + sdio_claim_host(card->func); + + nxpwifi_dbg(adapter, MSG, "== nxpwifi firmware dump start ==\n"); + + stat = nxpwifi_sdio_rdwr_firmware(adapter, doneflag); + if (stat == RDWR_STATUS_FAILURE) + goto done; + + reg = card->reg->fw_dump_start; + /* Read the number of the memories which will dump */ + dump_num = sdio_readb(card->func, reg, &ret); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "SDIO read memory length err\n"); + goto done; + } + + /* Read the length of every memory which will dump */ + for (idx = 0; idx < dump_num; idx++) { + struct memory_type_mapping *entry = &mem_type_mapping_tbl[idx]; + + stat = nxpwifi_sdio_rdwr_firmware(adapter, doneflag); + if (stat == RDWR_STATUS_FAILURE) + goto done; + + memory_size = 0; + reg = card->reg->fw_dump_start; + for (i = 0; i < 4; i++) { + read_reg = sdio_readb(card->func, reg, &ret); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "SDIO read err\n"); + goto done; + } + memory_size |= (read_reg << i * 8); + reg++; + } + + if (memory_size == 0) { + nxpwifi_dbg(adapter, DUMP, "Firmware dump Finished!\n"); + ret = nxpwifi_write_reg(adapter, + card->reg->fw_dump_ctrl, + FW_DUMP_READ_DONE); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "SDIO write err\n"); + return; + } + break; + } + + nxpwifi_dbg(adapter, DUMP, + "%s_SIZE=0x%x\n", entry->mem_name, memory_size); + entry->mem_ptr = vmalloc(memory_size + 1); + entry->mem_size = memory_size; + if (!entry->mem_ptr) + goto done; + dbg_ptr = entry->mem_ptr; + end_ptr = dbg_ptr + memory_size; + + doneflag = entry->done_flag; + nxpwifi_dbg(adapter, DUMP, "Start %s output, please wait...\n", + entry->mem_name); + + do { + stat = nxpwifi_sdio_rdwr_firmware(adapter, doneflag); + if (stat == RDWR_STATUS_FAILURE) + goto done; + + reg_start = card->reg->fw_dump_start; + reg_end = card->reg->fw_dump_end; + for (reg = reg_start; reg <= reg_end; reg++) { + *dbg_ptr = sdio_readb(card->func, reg, &ret); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "SDIO read err\n"); + goto done; + } + if (dbg_ptr < end_ptr) + dbg_ptr++; + else + nxpwifi_dbg(adapter, ERROR, "Allocated buf not enough\n"); + } + + if (stat != RDWR_STATUS_DONE) + continue; + + nxpwifi_dbg(adapter, DUMP, "%s done: size=0x%tx\n", + entry->mem_name, dbg_ptr - entry->mem_ptr); + break; + } while (1); + } + nxpwifi_dbg(adapter, MSG, "== nxpwifi firmware dump end ==\n"); + +done: + sdio_release_host(card->func); +} + +/* Generic firmware dump flow for enhanced devices. */ +static void nxpwifi_sdio_generic_fw_dump(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + struct memory_type_mapping *entry = &generic_mem_type_map[0]; + unsigned int reg, reg_start, reg_end; + u8 start_flag = 0, done_flag = 0; + u8 *dbg_ptr, *end_ptr; + enum rdwr_status stat; + int ret = -EPERM, tries; + + if (!card->fw_dump_enh) + return; + + if (entry->mem_ptr) { + vfree(entry->mem_ptr); + entry->mem_ptr = NULL; + } + entry->mem_size = 0; + + nxpwifi_pm_wakeup_card(adapter); + sdio_claim_host(card->func); + + nxpwifi_dbg(adapter, MSG, "== nxpwifi firmware dump start ==\n"); + + stat = nxpwifi_sdio_rdwr_firmware(adapter, done_flag); + if (stat == RDWR_STATUS_FAILURE) + goto done; + + reg_start = card->reg->fw_dump_start; + reg_end = card->reg->fw_dump_end; + for (reg = reg_start; reg <= reg_end; reg++) { + for (tries = 0; tries < MAX_POLL_TRIES; tries++) { + start_flag = sdio_readb(card->func, reg, &ret); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "SDIO read err\n"); + goto done; + } + if (start_flag == 0) + break; + if (tries == MAX_POLL_TRIES) { + nxpwifi_dbg(adapter, ERROR, "FW not ready to dump\n"); + ret = -EPERM; + goto done; + } + } + usleep_range(100, 200); + } + + entry->mem_ptr = vmalloc(0xf0000 + 1); + if (!entry->mem_ptr) { + ret = -ENOMEM; + goto done; + } + dbg_ptr = entry->mem_ptr; + entry->mem_size = 0xf0000; + end_ptr = dbg_ptr + entry->mem_size; + + done_flag = entry->done_flag; + nxpwifi_dbg(adapter, DUMP, + "Start %s output, please wait...\n", entry->mem_name); + + while (true) { + stat = nxpwifi_sdio_rdwr_firmware(adapter, done_flag); + if (stat == RDWR_STATUS_FAILURE) + goto done; + for (reg = reg_start; reg <= reg_end; reg++) { + *dbg_ptr = sdio_readb(card->func, reg, &ret); + if (ret) { + nxpwifi_dbg(adapter, ERROR, "SDIO read err\n"); + goto done; + } + dbg_ptr++; + if (dbg_ptr >= end_ptr) { + u8 *tmp_ptr; + + tmp_ptr = vmalloc(entry->mem_size + 0x4000 + 1); + if (!tmp_ptr) + goto done; + + memcpy(tmp_ptr, entry->mem_ptr, + entry->mem_size); + vfree(entry->mem_ptr); + entry->mem_ptr = tmp_ptr; + tmp_ptr = NULL; + dbg_ptr = entry->mem_ptr + entry->mem_size; + entry->mem_size += 0x4000; + end_ptr = entry->mem_ptr + entry->mem_size; + } + } + if (stat == RDWR_STATUS_DONE) { + entry->mem_size = dbg_ptr - entry->mem_ptr; + nxpwifi_dbg(adapter, DUMP, "dump %s done size=0x%x\n", + entry->mem_name, entry->mem_size); + ret = 0; + break; + } + } + nxpwifi_dbg(adapter, MSG, "== nxpwifi firmware dump end ==\n"); + +done: + if (ret) { + nxpwifi_dbg(adapter, ERROR, "firmware dump failed\n"); + if (entry->mem_ptr) { + vfree(entry->mem_ptr); + entry->mem_ptr = NULL; + } + entry->mem_size = 0; + } + sdio_release_host(card->func); +} + +/* Build and upload consolidated device dump. */ +static void nxpwifi_sdio_device_dump_work(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + + adapter->devdump_data = vzalloc(NXPWIFI_FW_DUMP_SIZE); + if (!adapter->devdump_data) + return; + + nxpwifi_drv_info_dump(adapter); + + /* Generic firmware dump flow for enhanced devices. */ + if (card->fw_dump_enh) + nxpwifi_sdio_generic_fw_dump(adapter); + /* Dump firmware memories for post-mortem analysis. */ + else + nxpwifi_sdio_fw_dump(adapter); + + nxpwifi_prepare_fw_dump_info(adapter); + nxpwifi_upload_device_dump(adapter); +} + +/* Process deferred SDIO work items. */ +static void nxpwifi_sdio_work(struct work_struct *work) +{ + struct sdio_mmc_card *card = + container_of(work, struct sdio_mmc_card, work); + + /* Build and upload consolidated device dump. */ + if (test_and_clear_bit(NXPWIFI_IFACE_WORK_DEVICE_DUMP, + &card->work_flags)) + nxpwifi_sdio_device_dump_work(card->adapter); + + /* Perform an SDIO card reset in workqueue context. */ + if (test_and_clear_bit(NXPWIFI_IFACE_WORK_CARD_RESET, + &card->work_flags)) + nxpwifi_sdio_card_reset_work(card->adapter); +} + +/* Schedule SDIO card reset. */ +static void nxpwifi_sdio_card_reset(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + + if (!test_and_set_bit(NXPWIFI_IFACE_WORK_CARD_RESET, &card->work_flags)) + nxpwifi_queue_work(adapter, &card->work); +} + +static void nxpwifi_sdio_device_dump(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + + if (!test_and_set_bit(NXPWIFI_IFACE_WORK_DEVICE_DUMP, + &card->work_flags)) + nxpwifi_queue_work(adapter, &card->work); +} + +/* Dump SDIO function and scratch registers into drv_buf. */ +static int +nxpwifi_sdio_reg_dump(struct nxpwifi_adapter *adapter, char *drv_buf) +{ + char *p = drv_buf; + struct sdio_mmc_card *cardp = adapter->card; + int ret = 0; + u8 count, func, data, index = 0, size = 0; + u8 reg, reg_start, reg_end; + char buf[256], *ptr; + + if (!p) + return 0; + + nxpwifi_dbg(adapter, MSG, "SDIO register dump start\n"); + + nxpwifi_pm_wakeup_card(adapter); + + sdio_claim_host(cardp->func); + + for (count = 0; count < 5; count++) { + memset(buf, 0, sizeof(buf)); + ptr = buf; + + switch (count) { + case 0: + /* Read the registers of SDIO function0 */ + func = count; + reg_start = 0; + reg_end = 9; + break; + case 1: + /* Read the registers of SDIO function1 */ + func = count; + reg_start = cardp->reg->func1_dump_reg_start; + reg_end = cardp->reg->func1_dump_reg_end; + break; + case 2: + index = 0; + func = 1; + reg_start = cardp->reg->func1_spec_reg_table[index++]; + size = cardp->reg->func1_spec_reg_num; + reg_end = cardp->reg->func1_spec_reg_table[size - 1]; + break; + default: + /* Read the scratch registers of SDIO function1 */ + if (count == 4) + msleep(100); + func = 1; + reg_start = cardp->reg->func1_scratch_reg; + reg_end = reg_start + NXPWIFI_SDIO_SCRATCH_SIZE; + } + + if (count != 2) + ptr += scnprintf(ptr, sizeof(buf) - (ptr - buf), + "SDIO Func%d (%#x-%#x): ", func, reg_start, + reg_end); + else + ptr += scnprintf(ptr, sizeof(buf) - (ptr - buf), + "SDIO Func%d: ", func); + + for (reg = reg_start; reg <= reg_end;) { + if (func == 0) + data = sdio_f0_readb(cardp->func, reg, &ret); + else + data = sdio_readb(cardp->func, reg, &ret); + + if (count == 2) + ptr += scnprintf(ptr, sizeof(buf) - (ptr - buf), "(%#x) ", reg); + if (!ret) { + ptr += scnprintf(ptr, sizeof(buf) - (ptr - buf), "%02x ", data); + } else { + ptr += scnprintf(ptr, sizeof(buf) - (ptr - buf), "ERR"); + break; + } + + if (count == 2 && reg < reg_end) + reg = cardp->reg->func1_spec_reg_table[index++]; + else + reg++; + } + + nxpwifi_dbg(adapter, MSG, "%s\n", buf); + p += sprintf(p, "%s\n", buf); + } + + sdio_release_host(cardp->func); + + nxpwifi_dbg(adapter, MSG, "SDIO register dump end\n"); + + return p - drv_buf; +} + +static void nxpwifi_sdio_up_dev(struct nxpwifi_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + u8 sdio_ireg; + int ret = 0; + + sdio_claim_host(card->func); + ret = sdio_enable_func(card->func); + + if (ret) + nxpwifi_dbg(adapter, ERROR, "sdio_enable_func failed: %d\n", ret); + + ret = sdio_set_block_size(card->func, NXPWIFI_SDIO_BLOCK_SIZE); + + if (ret) + nxpwifi_dbg(adapter, ERROR, "sdio_set_block_size failed: %d\n", ret); + + sdio_release_host(card->func); + + /* + * tx_buf_size might be changed to 3584 by firmware during + * data transfer, we will reset to default size. + */ + adapter->tx_buf_size = card->tx_buf_size; + + /* + * Read the host_int_status_reg for ACK the first interrupt got + * from the bootloader. If we don't do this we get a interrupt + * as soon as we register the irq. + */ + nxpwifi_read_reg(adapter, card->reg->host_int_status_reg, &sdio_ireg); + + if (nxpwifi_init_sdio_ioport(adapter)) + nxpwifi_dbg(adapter, ERROR, "error enabling SDIO port\n"); +} + +static struct nxpwifi_if_ops sdio_ops = { + .init_if = nxpwifi_init_sdio, + .cleanup_if = nxpwifi_cleanup_sdio, + .check_fw_status = nxpwifi_check_fw_status, + .check_winner_status = nxpwifi_check_winner_status, + .prog_fw = nxpwifi_prog_fw_w_helper, + .register_dev = nxpwifi_register_dev, + .unregister_dev = nxpwifi_unregister_dev, + .enable_int = nxpwifi_sdio_enable_host_int, + .disable_int = nxpwifi_sdio_disable_host_int, + .process_int_status = nxpwifi_process_int_status, + .host_to_card = nxpwifi_sdio_host_to_card, + .wakeup = nxpwifi_pm_wakeup_card, + .wakeup_complete = nxpwifi_pm_wakeup_card_complete, + + /* SDIO specific */ + .update_mp_end_port = nxpwifi_update_mp_end_port, + .cleanup_mpa_buf = nxpwifi_cleanup_mpa_buf, + .cmdrsp_complete = nxpwifi_sdio_cmdrsp_complete, + .event_complete = nxpwifi_sdio_event_complete, + .dnld_fw = nxpwifi_sdio_dnld_fw, + .card_reset = nxpwifi_sdio_card_reset, + .reg_dump = nxpwifi_sdio_reg_dump, + .device_dump = nxpwifi_sdio_device_dump, + .deaggr_pkt = nxpwifi_deaggr_sdio_pkt, + .up_dev = nxpwifi_sdio_up_dev, +}; + +module_sdio_driver(nxpwifi_sdio); + +MODULE_AUTHOR("NXP International Ltd."); +MODULE_DESCRIPTION("NXP WiFi SDIO Driver version " SDIO_VERSION); +MODULE_VERSION(SDIO_VERSION); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(IW61X_SDIO_FW_NAME); diff --git a/drivers/net/wireless/nxp/nxpwifi/sdio.h b/drivers/net/wireless/nxp/nxpwifi/sdio.h new file mode 100644 index 000000000000..de5c884a5b14 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/sdio.h @@ -0,0 +1,340 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * NXP Wireless LAN device driver: SDIO specific definitions + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_SDIO_H +#define _NXPWIFI_SDIO_H + +#include "main.h" + +#define IW61X_SDIO_FW_NAME "nxp/sd_w61x_v1.bin.se" + +#define BLOCK_MODE 1 +#define BYTE_MODE 0 + +#define NXPWIFI_SDIO_IO_PORT_MASK 0xfffff + +#define NXPWIFI_SDIO_BYTE_MODE_MASK 0x80000000 + +#define NXPWIFI_MAX_FUNC2_REG_NUM 13 +#define NXPWIFI_SDIO_SCRATCH_SIZE 10 + +#define SDIO_MPA_ADDR_BASE 0x1000 + +#define CMD_PORT_UPLD_INT_MASK (0x1U << 6) +#define CMD_PORT_DNLD_INT_MASK (0x1U << 7) +#define HOST_TERM_CMD53 (0x1U << 2) +#define REG_PORT 0 +#define MEM_PORT 0x10000 + +#define CMD53_NEW_MODE (0x1U << 0) +#define CMD_PORT_RD_LEN_EN (0x1U << 2) +#define CMD_PORT_AUTO_EN (0x1U << 0) +#define CMD_PORT_SLCT 0x8000 +#define UP_LD_CMD_PORT_HOST_INT_STATUS (0x40U) +#define DN_LD_CMD_PORT_HOST_INT_STATUS (0x80U) + +#define NXPWIFI_MP_AGGR_BSIZE_32K (32768) +/* we leave one block of 256 bytes for DMA alignment*/ +#define NXPWIFI_MP_AGGR_BSIZE_MAX (65280) + +/* Misc. Config Register : Auto Re-enable interrupts */ +#define AUTO_RE_ENABLE_INT BIT(4) + +/* Host Control Registers : Configuration */ +#define CONFIGURATION_REG 0x00 +/* Host Control Registers : Host power up */ +#define HOST_POWER_UP (0x1U << 1) + +/* Host Control Registers : Upload host interrupt mask */ +#define UP_LD_HOST_INT_MASK (0x1U) +/* Host Control Registers : Download host interrupt mask */ +#define DN_LD_HOST_INT_MASK (0x2U) + +/* Host Control Registers : Upload host interrupt status */ +#define UP_LD_HOST_INT_STATUS (0x1U) +/* Host Control Registers : Download host interrupt status */ +#define DN_LD_HOST_INT_STATUS (0x2U) + +/* Host Control Registers : Host interrupt status */ +#define CARD_INT_STATUS_REG 0x28 + +/* Card Control Registers : Card I/O ready */ +#define CARD_IO_READY (0x1U << 3) +/* Card Control Registers : Download card ready */ +#define DN_LD_CARD_RDY (0x1U << 0) + +/* Max retry number of CMD53 write */ +#define MAX_WRITE_IOMEM_RETRY 2 + +/* SDIO Tx aggregation in progress ? */ +#define MP_TX_AGGR_IN_PROGRESS(a) ((a)->mpa_tx.pkt_cnt > 0) + +/* SDIO Tx aggregation buffer room for next packet ? */ +#define MP_TX_AGGR_BUF_HAS_ROOM(a, len) ({ \ + typeof(a) (_a) = a; \ + (((_a)->mpa_tx.buf_len + (len)) <= (_a)->mpa_tx.buf_size); \ + }) + +/* Copy current packet (SDIO Tx aggregation buffer) to SDIO buffer */ +#define MP_TX_AGGR_BUF_PUT(a, payload, pkt_len, port) do { \ + typeof(a) (_a) = (a); \ + typeof(pkt_len) (_pkt_len) = pkt_len; \ + typeof(port) (_port) = port; \ + memmove(&(_a)->mpa_tx.buf[(_a)->mpa_tx.buf_len], \ + payload, (_pkt_len)); \ + (_a)->mpa_tx.buf_len += (_pkt_len); \ + if (!(_a)->mpa_tx.pkt_cnt) \ + (_a)->mpa_tx.start_port = (_port); \ + if ((_a)->mpa_tx.start_port <= (_port)) \ + (_a)->mpa_tx.ports |= (1 << ((_a)->mpa_tx.pkt_cnt)); \ + else \ + (_a)->mpa_tx.ports |= (1 << ((_a)->mpa_tx.pkt_cnt + 1 + \ + ((_a)->max_ports - \ + (_a)->mp_end_port))); \ + (_a)->mpa_tx.pkt_cnt++; \ +} while (0) + +/* SDIO Tx aggregation limit ? */ +#define MP_TX_AGGR_PKT_LIMIT_REACHED(a) ({ \ + typeof(a) (_a) = a; \ + ((_a)->mpa_tx.pkt_cnt == (_a)->mpa_tx.pkt_aggr_limit); \ + }) + +/* Reset SDIO Tx aggregation buffer parameters */ +#define MP_TX_AGGR_BUF_RESET(a) do { \ + typeof(a) (_a) = (a); \ + (_a)->mpa_tx.pkt_cnt = 0; \ + (_a)->mpa_tx.buf_len = 0; \ + (_a)->mpa_tx.ports = 0; \ + (_a)->mpa_tx.start_port = 0; \ +} while (0) + +/* SDIO Rx aggregation limit ? */ +#define MP_RX_AGGR_PKT_LIMIT_REACHED(a) ({ \ + typeof(a) (_a) = a; \ + ((_a)->mpa_rx.pkt_cnt == (_a)->mpa_rx.pkt_aggr_limit); \ + }) + +/* SDIO Rx aggregation in progress ? */ +#define MP_RX_AGGR_IN_PROGRESS(a) ((a)->mpa_rx.pkt_cnt > 0) + +/* SDIO Rx aggregation buffer room for next packet ? */ +#define MP_RX_AGGR_BUF_HAS_ROOM(a, rx_len) ({ \ + typeof(a) (_a) = a; \ + ((((_a)->mpa_rx.buf_len + (rx_len))) <= (_a)->mpa_rx.buf_size); \ + }) + +/* Reset SDIO Rx aggregation buffer parameters */ +#define MP_RX_AGGR_BUF_RESET(a) do { \ + typeof(a) (_a) = (a); \ + (_a)->mpa_rx.pkt_cnt = 0; \ + (_a)->mpa_rx.buf_len = 0; \ + (_a)->mpa_rx.ports = 0; \ + (_a)->mpa_rx.start_port = 0; \ +} while (0) + +/* data structure for SDIO MPA TX */ +struct nxpwifi_sdio_mpa_tx { + /* multiport tx aggregation buffer pointer */ + u8 *buf; + u32 buf_len; + u32 pkt_cnt; + u32 ports; + u16 start_port; + u8 enabled; + u32 buf_size; + u32 pkt_aggr_limit; +}; + +struct nxpwifi_sdio_mpa_rx { + u8 *buf; + u32 buf_len; + u32 pkt_cnt; + u32 ports; + u16 start_port; + u32 *len_arr; + u8 enabled; + u32 buf_size; + u32 pkt_aggr_limit; +}; + +int nxpwifi_bus_register(void); +void nxpwifi_bus_unregister(void); + +struct nxpwifi_sdio_card_reg { + u8 start_rd_port; + u8 start_wr_port; + u8 base_0_reg; + u8 base_1_reg; + u8 poll_reg; + u8 host_int_enable; + u8 host_int_rsr_reg; + u8 host_int_status_reg; + u8 host_int_mask_reg; + u8 host_strap_reg; + u8 host_strap_mask; + u8 host_strap_value; + u8 status_reg_0; + u8 status_reg_1; + u8 sdio_int_mask; + u32 data_port_mask; + u8 io_port_0_reg; + u8 io_port_1_reg; + u8 io_port_2_reg; + u8 max_mp_regs; + u8 rd_bitmap_l; + u8 rd_bitmap_u; + u8 rd_bitmap_1l; + u8 rd_bitmap_1u; + u8 wr_bitmap_l; + u8 wr_bitmap_u; + u8 wr_bitmap_1l; + u8 wr_bitmap_1u; + u8 rd_len_p0_l; + u8 rd_len_p0_u; + u8 card_misc_cfg_reg; + u8 card_cfg_2_1_reg; + u8 cmd_rd_len_0; + u8 cmd_rd_len_1; + u8 cmd_rd_len_2; + u8 cmd_rd_len_3; + u8 cmd_cfg_0; + u8 cmd_cfg_1; + u8 cmd_cfg_2; + u8 cmd_cfg_3; + u8 fw_dump_host_ready; + u8 fw_dump_ctrl; + u8 fw_dump_start; + u8 fw_dump_end; + u8 func1_dump_reg_start; + u8 func1_dump_reg_end; + u8 func1_scratch_reg; + u8 func1_spec_reg_num; + u8 func1_spec_reg_table[NXPWIFI_MAX_FUNC2_REG_NUM]; +}; + +struct sdio_mmc_card { + struct sdio_func *func; + struct nxpwifi_adapter *adapter; + + struct completion fw_done; + const char *firmware; + const char *firmware_sdiouart; + const struct nxpwifi_sdio_card_reg *reg; + u8 max_ports; + u8 mp_agg_pkt_limit; + u16 tx_buf_size; + u32 mp_tx_agg_buf_size; + u32 mp_rx_agg_buf_size; + + u32 mp_rd_bitmap; + u32 mp_wr_bitmap; + + u16 mp_end_port; + u32 mp_data_port_mask; + + u8 curr_rd_port; + u8 curr_wr_port; + + u8 *mp_regs; + bool can_dump_fw; + bool fw_dump_enh; + bool can_ext_scan; + + struct nxpwifi_sdio_mpa_tx mpa_tx; + struct nxpwifi_sdio_mpa_rx mpa_rx; + + struct work_struct work; + unsigned long work_flags; +}; + +struct nxpwifi_sdio_device { + const char *firmware; + const char *firmware_sdiouart; + const struct nxpwifi_sdio_card_reg *reg; + u8 max_ports; + u8 mp_agg_pkt_limit; + u16 tx_buf_size; + u32 mp_tx_agg_buf_size; + u32 mp_rx_agg_buf_size; + bool can_dump_fw; + bool fw_dump_enh; + bool can_ext_scan; +}; + +/* .cmdrsp_complete handler + */ +static inline int nxpwifi_sdio_cmdrsp_complete(struct nxpwifi_adapter *adapter, + struct sk_buff *skb) +{ + dev_kfree_skb_any(skb); + return 0; +} + +/* .event_complete handler + */ +static inline int nxpwifi_sdio_event_complete(struct nxpwifi_adapter *adapter, + struct sk_buff *skb) +{ + dev_kfree_skb_any(skb); + return 0; +} + +static inline bool +mp_rx_aggr_port_limit_reached(struct sdio_mmc_card *card) +{ + u8 tmp; + + if (card->curr_rd_port < card->mpa_rx.start_port) { + tmp = card->mp_end_port >> 1; + + if (((card->max_ports - card->mpa_rx.start_port) + + card->curr_rd_port) >= tmp) + return true; + } + + if ((card->curr_rd_port - card->mpa_rx.start_port) >= + (card->mp_end_port >> 1)) + return true; + + return false; +} + +static inline bool +mp_tx_aggr_port_limit_reached(struct sdio_mmc_card *card) +{ + u16 tmp; + + if (card->curr_wr_port < card->mpa_tx.start_port) { + tmp = card->mp_end_port >> 1; + + if (((card->max_ports - card->mpa_tx.start_port) + + card->curr_wr_port) >= tmp) + return true; + } + + if ((card->curr_wr_port - card->mpa_tx.start_port) >= + (card->mp_end_port >> 1)) + return true; + + return false; +} + +/* Prepare to copy current packet from card to SDIO Rx aggregation buffer */ +static inline void mp_rx_aggr_setup(struct sdio_mmc_card *card, + u16 rx_len, u8 port) +{ + card->mpa_rx.buf_len += rx_len; + + if (!card->mpa_rx.pkt_cnt) + card->mpa_rx.start_port = port; + + card->mpa_rx.ports |= (1 << port); + card->mpa_rx.len_arr[card->mpa_rx.pkt_cnt] = rx_len; + card->mpa_rx.pkt_cnt++; +} +#endif /* _NXPWIFI_SDIO_H */ diff --git a/drivers/net/wireless/nxp/nxpwifi/sta_cfg.c b/drivers/net/wireless/nxp/nxpwifi/sta_cfg.c new file mode 100644 index 000000000000..808c35c9d200 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/sta_cfg.c @@ -0,0 +1,1165 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: functions for station ioctl + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cmdevt.h" +#include "wmm.h" +#include "11n.h" +#include "cfg80211.h" + +static int disconnect_on_suspend; + +/* Copies the multicast address list from device to driver */ +int nxpwifi_copy_mcast_addr(struct nxpwifi_multicast_list *mlist, + struct net_device *dev) +{ + int i = 0; + struct netdev_hw_addr *ha; + + netdev_for_each_mc_addr(ha, dev) + memcpy(&mlist->mac_list[i++], ha->addr, ETH_ALEN); + + return i; +} + +/* Wait queue completion handler */ +int nxpwifi_wait_queue_complete(struct nxpwifi_adapter *adapter, + struct cmd_ctrl_node *cmd_queued) +{ + int status; + + /* Wait for completion */ + status = wait_event_interruptible_timeout(adapter->cmd_wait_q.wait, + *cmd_queued->condition, + (12 * HZ)); + if (status <= 0) { + if (status == 0) + status = -ETIMEDOUT; + nxpwifi_dbg(adapter, ERROR, "cmd_wait_q terminated: %d\n", + status); + nxpwifi_cancel_all_pending_cmd(adapter); + return status; + } + + status = adapter->cmd_wait_q.status; + adapter->cmd_wait_q.status = 0; + + return status; +} + +/* Set multicast list by issuing the proper firmware command */ +int +nxpwifi_request_set_multicast_list(struct nxpwifi_private *priv, + struct nxpwifi_multicast_list *mcast_list) +{ + int ret = 0; + u16 old_pkt_filter; + + old_pkt_filter = priv->curr_pkt_filter; + + if (mcast_list->mode == NXPWIFI_PROMISC_MODE) { + nxpwifi_dbg(priv->adapter, INFO, + "info: Enable Promiscuous mode\n"); + priv->curr_pkt_filter |= HOST_ACT_MAC_PROMISCUOUS_ENABLE; + priv->curr_pkt_filter &= + ~HOST_ACT_MAC_ALL_MULTICAST_ENABLE; + } else { + /* Multicast */ + priv->curr_pkt_filter &= ~HOST_ACT_MAC_PROMISCUOUS_ENABLE; + if (mcast_list->mode == NXPWIFI_ALL_MULTI_MODE) { + nxpwifi_dbg(priv->adapter, INFO, + "info: Enabling All Multicast!\n"); + priv->curr_pkt_filter |= + HOST_ACT_MAC_ALL_MULTICAST_ENABLE; + } else { + priv->curr_pkt_filter &= + ~HOST_ACT_MAC_ALL_MULTICAST_ENABLE; + nxpwifi_dbg(priv->adapter, INFO, + "info: Set multicast list=%d\n", + mcast_list->num_multicast_addr); + /* Send multicast addresses to firmware */ + ret = nxpwifi_send_cmd(priv, + HOST_CMD_MAC_MULTICAST_ADR, + HOST_ACT_GEN_SET, 0, + mcast_list, false); + } + } + nxpwifi_dbg(priv->adapter, INFO, + "info: old_pkt_filter=%#x, curr_pkt_filter=%#x\n", + old_pkt_filter, priv->curr_pkt_filter); + if (old_pkt_filter != priv->curr_pkt_filter) { + ret = nxpwifi_send_cmd(priv, HOST_CMD_MAC_CONTROL, + HOST_ACT_GEN_SET, + 0, &priv->curr_pkt_filter, false); + } + + return ret; +} + +/* Fill BSS descriptor from cfg80211_bss */ +int nxpwifi_fill_new_bss_desc(struct nxpwifi_private *priv, + struct cfg80211_bss *bss, + struct nxpwifi_bssdescriptor *bss_desc) +{ + u8 *beacon_ie; + size_t beacon_ie_len; + struct nxpwifi_bss_priv *bss_priv = (void *)bss->priv; + const struct cfg80211_bss_ies *ies; + + rcu_read_lock(); + ies = rcu_dereference(bss->ies); + beacon_ie = kmemdup(ies->data, ies->len, GFP_ATOMIC); + beacon_ie_len = ies->len; + bss_desc->timestamp = ies->tsf; + rcu_read_unlock(); + + if (!beacon_ie) { + nxpwifi_dbg(priv->adapter, ERROR, + " failed to alloc beacon_ie\n"); + return -ENOMEM; + } + + memcpy(bss_desc->mac_address, bss->bssid, ETH_ALEN); + bss_desc->rssi = bss->signal; + /* The caller of this function will free beacon_ie */ + bss_desc->beacon_buf = beacon_ie; + bss_desc->beacon_buf_size = beacon_ie_len; + bss_desc->beacon_period = bss->beacon_interval; + bss_desc->cap_info_bitmap = bss->capability; + bss_desc->bss_band = bss_priv->band; + bss_desc->fw_tsf = bss_priv->fw_tsf; + if (bss_desc->cap_info_bitmap & WLAN_CAPABILITY_PRIVACY) { + nxpwifi_dbg(priv->adapter, INFO, + "info: InterpretIE: AP WEP enabled\n"); + bss_desc->privacy = NXPWIFI_802_11_PRIV_FILTER_8021X_WEP; + } else { + bss_desc->privacy = NXPWIFI_802_11_PRIV_FILTER_ACCEPT_ALL; + } + bss_desc->bss_mode = NL80211_IFTYPE_STATION; + + /* Disable 11ac by default */ + bss_desc->disable_11ac = true; + /* Disable 11ax by default */ + bss_desc->disable_11ax = true; + + if (bss_desc->cap_info_bitmap & WLAN_CAPABILITY_SPECTRUM_MGMT) + bss_desc->sensed_11h = true; + + return nxpwifi_update_bss_desc_with_ie(priv->adapter, bss_desc); +} + +static int nxpwifi_process_country_ie(struct nxpwifi_private *priv, + struct cfg80211_bss *bss) +{ + const u8 *country_ie; + u8 country_ie_len; + struct nxpwifi_802_11d_domain_reg *domain_info = + &priv->adapter->domain_reg; + int ret; + + rcu_read_lock(); + country_ie = ieee80211_bss_get_ie(bss, WLAN_EID_COUNTRY); + if (!country_ie) { + rcu_read_unlock(); + return 0; + } + + country_ie_len = country_ie[1]; + if (country_ie_len < IEEE80211_COUNTRY_IE_MIN_LEN) { + rcu_read_unlock(); + return 0; + } + + if (!strncmp(priv->adapter->country_code, &country_ie[2], 2)) { + rcu_read_unlock(); + nxpwifi_dbg(priv->adapter, INFO, + "11D: skip setting domain info in FW\n"); + return 0; + } + + if (country_ie_len > + (IEEE80211_COUNTRY_STRING_LEN + NXPWIFI_MAX_TRIPLET_802_11D)) { + rcu_read_unlock(); + nxpwifi_dbg(priv->adapter, ERROR, + "11D: country_ie_len overflow!, deauth AP\n"); + return -EINVAL; + } + + memcpy(priv->adapter->country_code, &country_ie[2], 2); + + domain_info->country_code[0] = country_ie[2]; + domain_info->country_code[1] = country_ie[3]; + domain_info->country_code[2] = ' '; + + country_ie_len -= IEEE80211_COUNTRY_STRING_LEN; + + domain_info->no_of_triplet = + country_ie_len / sizeof(struct ieee80211_country_ie_triplet); + + memcpy((u8 *)domain_info->triplet, + &country_ie[2] + IEEE80211_COUNTRY_STRING_LEN, country_ie_len); + + rcu_read_unlock(); + + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11D_DOMAIN_INFO, + HOST_ACT_GEN_SET, 0, NULL, false); + if (ret) + nxpwifi_dbg(priv->adapter, ERROR, + "11D: setting domain info in FW fail\n"); + + return ret; +} + +/* In infra mode, an deauthentication is performed first */ +int nxpwifi_bss_start(struct nxpwifi_private *priv, struct cfg80211_bss *bss, + struct cfg80211_ssid *req_ssid) +{ + int ret; + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_bssdescriptor *bss_desc = NULL; + u16 config_bands; + + priv->scan_block = false; + + if (adapter->region_code == 0x00 && + nxpwifi_process_country_ie(priv, bss)) + return -EINVAL; + + /* Allocate and fill new bss descriptor */ + bss_desc = kzalloc_obj(*bss_desc, GFP_KERNEL); + if (!bss_desc) + return -ENOMEM; + + ret = nxpwifi_fill_new_bss_desc(priv, bss, bss_desc); + if (ret) + goto done; + + if (nxpwifi_band_to_radio_type(bss_desc->bss_band) == + HOST_SCAN_RADIO_TYPE_BG) { + config_bands = BAND_B | BAND_G | BAND_GN; + if (adapter->fw_bands & BAND_GAC) + config_bands |= BAND_GAC; + if (adapter->fw_bands & BAND_GAX) + config_bands |= BAND_GAX; + } else { + config_bands = BAND_A | BAND_AN; + if (adapter->fw_bands & BAND_AAC) + config_bands |= BAND_AAC; + if (adapter->fw_bands & BAND_AAX) + config_bands |= BAND_AAX; + } + + if (!((config_bands | adapter->fw_bands) & ~adapter->fw_bands)) + priv->config_bands = config_bands; + + ret = nxpwifi_check_network_compatibility(priv, bss_desc); + if (ret) + goto done; + + if (nxpwifi_11h_get_csa_closed_channel(priv) == (u8)bss_desc->channel) { + nxpwifi_dbg(adapter, ERROR, + "Attempt to reconnect on csa closed chan(%d)\n", + bss_desc->channel); + ret = -EINVAL; + goto done; + } + + nxpwifi_stop_net_dev_queue(priv->netdev, adapter); + netif_carrier_off(priv->netdev); + + /* Clear any past association response stored for application retrieval */ + priv->assoc_rsp_size = 0; + ret = nxpwifi_associate(priv, bss_desc); + + /* + * If auth type is auto and association fails using open mode, try to connect + * using shared mode + */ + if (ret == WLAN_STATUS_NOT_SUPPORTED_AUTH_ALG && + priv->sec_info.is_authtype_auto && + priv->sec_info.wep_enabled) { + priv->sec_info.authentication_mode = + NL80211_AUTHTYPE_SHARED_KEY; + ret = nxpwifi_associate(priv, bss_desc); + } + +done: + /* beacon_ie buffer was allocated in function nxpwifi_fill_new_bss_desc() */ + if (bss_desc) + kfree(bss_desc->beacon_buf); + kfree(bss_desc); + + if (ret < 0) + priv->attempted_bss_desc = NULL; + + return ret; +} + +/* IOCTL request handler to set host sleep configuration */ +int nxpwifi_set_hs_params(struct nxpwifi_private *priv, u16 action, + int cmd_type, struct nxpwifi_ds_hs_cfg *hs_cfg) + +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int status = 0; + u32 prev_cond = 0; + + if (!hs_cfg) + return -ENOMEM; + + switch (action) { + case HOST_ACT_GEN_SET: + if (adapter->pps_uapsd_mode) { + nxpwifi_dbg(adapter, INFO, + "info: Host Sleep IOCTL\t" + "is blocked in UAPSD/PPS mode\n"); + status = -EPERM; + break; + } + if (hs_cfg->is_invoke_hostcmd) { + if (hs_cfg->conditions == HS_CFG_CANCEL) { + if (!test_bit(NXPWIFI_IS_HS_CONFIGURED, + &adapter->work_flags)) + /* Already cancelled */ + break; + /* Save previous condition */ + prev_cond = le32_to_cpu(adapter->hs_cfg + .conditions); + adapter->hs_cfg.conditions = + cpu_to_le32(hs_cfg->conditions); + } else if (hs_cfg->conditions) { + adapter->hs_cfg.conditions = + cpu_to_le32(hs_cfg->conditions); + adapter->hs_cfg.gpio = (u8)hs_cfg->gpio; + if (hs_cfg->gap) + adapter->hs_cfg.gap = (u8)hs_cfg->gap; + } else if (adapter->hs_cfg.conditions == + cpu_to_le32(HS_CFG_CANCEL)) { + status = -EINVAL; + break; + } + + status = nxpwifi_send_cmd(priv, + HOST_CMD_802_11_HS_CFG_ENH, + HOST_ACT_GEN_SET, 0, + &adapter->hs_cfg, + cmd_type == NXPWIFI_SYNC_CMD); + + if (hs_cfg->conditions == HS_CFG_CANCEL) + /* Restore previous condition */ + adapter->hs_cfg.conditions = + cpu_to_le32(prev_cond); + } else { + adapter->hs_cfg.conditions = + cpu_to_le32(hs_cfg->conditions); + adapter->hs_cfg.gpio = (u8)hs_cfg->gpio; + adapter->hs_cfg.gap = (u8)hs_cfg->gap; + } + break; + case HOST_ACT_GEN_GET: + hs_cfg->conditions = le32_to_cpu(adapter->hs_cfg.conditions); + hs_cfg->gpio = adapter->hs_cfg.gpio; + hs_cfg->gap = adapter->hs_cfg.gap; + break; + default: + status = -EINVAL; + break; + } + + return status; +} + +/* Sends IOCTL request to cancel the existing Host Sleep configuration */ +int nxpwifi_cancel_hs(struct nxpwifi_private *priv, int cmd_type) +{ + struct nxpwifi_ds_hs_cfg hscfg; + + hscfg.conditions = HS_CFG_CANCEL; + hscfg.is_invoke_hostcmd = true; + + return nxpwifi_set_hs_params(priv, HOST_ACT_GEN_SET, + cmd_type, &hscfg); +} +EXPORT_SYMBOL_GPL(nxpwifi_cancel_hs); + +/* Sends IOCTL request to cancel the existing Host Sleep configuration */ +bool nxpwifi_enable_hs(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_ds_hs_cfg hscfg; + struct nxpwifi_private *priv; + int i; + + if (disconnect_on_suspend) { + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + nxpwifi_deauthenticate(priv, NULL); + } + } + + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_STA); + + if (priv && priv->sched_scanning) { +#ifdef CONFIG_PM + if (priv->wdev.wiphy->wowlan_config && + !priv->wdev.wiphy->wowlan_config->nd_config) { +#endif + nxpwifi_dbg(adapter, CMD, "aborting bgscan!\n"); + nxpwifi_stop_bg_scan(priv); + cfg80211_sched_scan_stopped(priv->wdev.wiphy, 0); +#ifdef CONFIG_PM + } +#endif + } + + if (adapter->hs_activated) { + nxpwifi_dbg(adapter, CMD, + "cmd: HS Already activated\n"); + return true; + } + + adapter->hs_activate_wait_q_woken = false; + + memset(&hscfg, 0, sizeof(hscfg)); + hscfg.is_invoke_hostcmd = true; + + set_bit(NXPWIFI_IS_HS_ENABLING, &adapter->work_flags); + nxpwifi_cancel_all_pending_cmd(adapter); + + if (nxpwifi_set_hs_params(nxpwifi_get_priv(adapter, + NXPWIFI_BSS_ROLE_STA), + HOST_ACT_GEN_SET, NXPWIFI_SYNC_CMD, + &hscfg)) { + nxpwifi_dbg(adapter, ERROR, + "IOCTL request HS enable failed\n"); + return false; + } + + if (wait_event_interruptible_timeout(adapter->hs_activate_wait_q, + adapter->hs_activate_wait_q_woken, + (10 * HZ)) <= 0) { + nxpwifi_dbg(adapter, ERROR, + "hs_activate_wait_q terminated\n"); + return false; + } + + return true; +} +EXPORT_SYMBOL_GPL(nxpwifi_enable_hs); + +/* IOCTL request handler to get BSS information */ +int nxpwifi_get_bss_info(struct nxpwifi_private *priv, + struct nxpwifi_bss_info *info) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_bssdescriptor *bss_desc; + + if (!info) + return -EINVAL; + + bss_desc = &priv->curr_bss_params.bss_descriptor; + + info->bss_mode = priv->bss_mode; + + memcpy(&info->ssid, &bss_desc->ssid, sizeof(struct cfg80211_ssid)); + + memcpy(&info->bssid, &bss_desc->mac_address, ETH_ALEN); + + info->bss_chan = bss_desc->channel; + + memcpy(info->country_code, adapter->country_code, + IEEE80211_COUNTRY_STRING_LEN); + + info->media_connected = priv->media_connected; + + info->max_power_level = priv->max_tx_power_level; + info->min_power_level = priv->min_tx_power_level; + + info->bcn_nf_last = priv->bcn_nf_last; + + if (priv->sec_info.wep_enabled) + info->wep_status = true; + else + info->wep_status = false; + + info->is_hs_configured = test_bit(NXPWIFI_IS_HS_CONFIGURED, + &adapter->work_flags); + info->is_deep_sleep = adapter->is_deep_sleep; + + return 0; +} + +/* The function disables auto deep sleep mode */ +int nxpwifi_disable_auto_ds(struct nxpwifi_private *priv) +{ + struct nxpwifi_ds_auto_ds auto_ds = { + .auto_ds = DEEP_SLEEP_OFF, + }; + + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_PS_MODE_ENH, + DIS_AUTO_PS, BITMAP_AUTO_DS, &auto_ds, true); +} +EXPORT_SYMBOL_GPL(nxpwifi_disable_auto_ds); + +/* Sends IOCTL request to get the data rate */ +int nxpwifi_drv_get_data_rate(struct nxpwifi_private *priv, u32 *rate) +{ + int ret; + + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_TX_RATE_QUERY, + HOST_ACT_GEN_GET, 0, NULL, true); + + if (!ret) { + if (priv->is_data_rate_auto) + *rate = nxpwifi_index_to_data_rate(priv, priv->tx_rate, + priv->tx_htinfo); + else + *rate = priv->data_rate; + } + + return ret; +} + +/* IOCTL request handler to set tx power configuration */ +int nxpwifi_set_tx_power(struct nxpwifi_private *priv, + struct nxpwifi_power_cfg *power_cfg) +{ + int ret; + struct host_cmd_ds_txpwr_cfg *txp_cfg; + struct nxpwifi_types_power_group *pg_tlv; + struct nxpwifi_power_group *pg; + u8 *buf; + u16 dbm = 0; + + if (!power_cfg->is_power_auto) { + dbm = (u16)power_cfg->power_level; + if (dbm < priv->min_tx_power_level || + dbm > priv->max_tx_power_level) { + nxpwifi_dbg(priv->adapter, ERROR, + "txpower value %d dBm\t" + "is out of range (%d dBm-%d dBm)\n", + dbm, priv->min_tx_power_level, + priv->max_tx_power_level); + return -EINVAL; + } + } + buf = kzalloc(NXPWIFI_SIZE_OF_CMD_BUFFER, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + txp_cfg = (struct host_cmd_ds_txpwr_cfg *)buf; + txp_cfg->action = cpu_to_le16(HOST_ACT_GEN_SET); + if (!power_cfg->is_power_auto) { + u16 dbm_min = power_cfg->is_power_fixed ? + dbm : priv->min_tx_power_level; + + txp_cfg->mode = cpu_to_le32(1); + pg_tlv = (struct nxpwifi_types_power_group *) + (buf + sizeof(struct host_cmd_ds_txpwr_cfg)); + pg_tlv->type = cpu_to_le16(TLV_TYPE_POWER_GROUP); + pg_tlv->length = + cpu_to_le16(4 * sizeof(struct nxpwifi_power_group)); + pg = (struct nxpwifi_power_group *) + (buf + sizeof(struct host_cmd_ds_txpwr_cfg) + + sizeof(struct nxpwifi_types_power_group)); + /* Power group for modulation class HR/DSSS */ + pg->first_rate_code = 0x00; + pg->last_rate_code = 0x03; + pg->modulation_class = MOD_CLASS_HR_DSSS; + pg->power_step = 0; + pg->power_min = (s8)dbm_min; + pg->power_max = (s8)dbm; + pg++; + /* Power group for modulation class OFDM */ + pg->first_rate_code = 0x00; + pg->last_rate_code = 0x07; + pg->modulation_class = MOD_CLASS_OFDM; + pg->power_step = 0; + pg->power_min = (s8)dbm_min; + pg->power_max = (s8)dbm; + pg++; + /* Power group for modulation class HTBW20 */ + pg->first_rate_code = 0x00; + pg->last_rate_code = 0x20; + pg->modulation_class = MOD_CLASS_HT; + pg->power_step = 0; + pg->power_min = (s8)dbm_min; + pg->power_max = (s8)dbm; + pg->ht_bandwidth = HT_BW_20; + pg++; + /* Power group for modulation class HTBW40 */ + pg->first_rate_code = 0x00; + pg->last_rate_code = 0x20; + pg->modulation_class = MOD_CLASS_HT; + pg->power_step = 0; + pg->power_min = (s8)dbm_min; + pg->power_max = (s8)dbm; + pg->ht_bandwidth = HT_BW_40; + } + ret = nxpwifi_send_cmd(priv, HOST_CMD_TXPWR_CFG, + HOST_ACT_GEN_SET, 0, buf, true); + + kfree(buf); + return ret; +} + +/* IOCTL request handler to get power save mode */ +int nxpwifi_drv_set_power(struct nxpwifi_private *priv, u32 *ps_mode) +{ + int ret; + struct nxpwifi_adapter *adapter = priv->adapter; + u16 sub_cmd; + + if (*ps_mode) + adapter->ps_mode = NXPWIFI_802_11_POWER_MODE_PSP; + else + adapter->ps_mode = NXPWIFI_802_11_POWER_MODE_CAM; + sub_cmd = (*ps_mode) ? EN_AUTO_PS : DIS_AUTO_PS; + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_PS_MODE_ENH, + sub_cmd, BITMAP_STA_PS, NULL, true); + if (!ret && sub_cmd == DIS_AUTO_PS) + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_PS_MODE_ENH, + GET_PS, 0, NULL, false); + + return ret; +} + +/* IOCTL request handler to set/reset WPA element */ +static int nxpwifi_set_wpa_ie(struct nxpwifi_private *priv, + u8 *ie_data_ptr, u16 ie_len) +{ + if (ie_len) { + if (ie_len > sizeof(priv->wpa_ie)) { + nxpwifi_dbg(priv->adapter, ERROR, + "failed to copy WPA element, too big\n"); + return -EINVAL; + } + memcpy(priv->wpa_ie, ie_data_ptr, ie_len); + priv->wpa_ie_len = ie_len; + nxpwifi_dbg(priv->adapter, CMD, + "cmd: Set WPA element len=%d element=%#x\n", + priv->wpa_ie_len, priv->wpa_ie[0]); + + if (priv->wpa_ie[0] == WLAN_EID_VENDOR_SPECIFIC) { + priv->sec_info.wpa_enabled = true; + } else if (priv->wpa_ie[0] == WLAN_EID_RSN) { + priv->sec_info.wpa2_enabled = true; + } else { + priv->sec_info.wpa_enabled = false; + priv->sec_info.wpa2_enabled = false; + } + } else { + memset(priv->wpa_ie, 0, sizeof(priv->wpa_ie)); + priv->wpa_ie_len = 0; + nxpwifi_dbg(priv->adapter, INFO, + "info: reset WPA element len=%d element=%#x\n", + priv->wpa_ie_len, priv->wpa_ie[0]); + priv->sec_info.wpa_enabled = false; + priv->sec_info.wpa2_enabled = false; + } + + return 0; +} + +/* IOCTL request handler to set/reset WPS element */ +static int nxpwifi_set_wps_ie(struct nxpwifi_private *priv, + u8 *ie_data_ptr, u16 ie_len) +{ + if (ie_len) { + if (ie_len > NXPWIFI_MAX_VSIE_LEN) { + nxpwifi_dbg(priv->adapter, ERROR, + "info: failed to copy WPS element, too big\n"); + return -EINVAL; + } + + priv->wps_ie = kzalloc(NXPWIFI_MAX_VSIE_LEN, GFP_KERNEL); + if (!priv->wps_ie) + return -ENOMEM; + + memcpy(priv->wps_ie, ie_data_ptr, ie_len); + priv->wps_ie_len = ie_len; + nxpwifi_dbg(priv->adapter, CMD, + "cmd: Set WPS element len=%d element=%#x\n", + priv->wps_ie_len, priv->wps_ie[0]); + } else { + kfree(priv->wps_ie); + priv->wps_ie_len = ie_len; + nxpwifi_dbg(priv->adapter, INFO, + "info: Reset WPS element len=%d\n", priv->wps_ie_len); + } + return 0; +} + +/* IOCTL request handler to set WEP network key */ +static int +nxpwifi_sec_ioctl_set_wep_key(struct nxpwifi_private *priv, + struct nxpwifi_ds_encrypt_key *encrypt_key) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int ret; + struct nxpwifi_wep_key *wep_key; + int index; + + if (priv->wep_key_curr_index >= NUM_WEP_KEYS) + priv->wep_key_curr_index = 0; + wep_key = &priv->wep_key[priv->wep_key_curr_index]; + index = encrypt_key->key_index; + if (encrypt_key->key_disable) { + priv->sec_info.wep_enabled = 0; + } else if (!encrypt_key->key_len) { + /* Copy the required key as the current key */ + wep_key = &priv->wep_key[index]; + if (!wep_key->key_length) { + nxpwifi_dbg(adapter, ERROR, + "key not set, so cannot enable it\n"); + return -EINVAL; + } + + memcpy(encrypt_key->key_material, + wep_key->key_material, wep_key->key_length); + encrypt_key->key_len = wep_key->key_length; + + priv->wep_key_curr_index = (u16)index; + priv->sec_info.wep_enabled = 1; + } else { + wep_key = &priv->wep_key[index]; + memset(wep_key, 0, sizeof(struct nxpwifi_wep_key)); + /* Copy the key in the driver */ + memcpy(wep_key->key_material, + encrypt_key->key_material, + encrypt_key->key_len); + wep_key->key_index = index; + wep_key->key_length = encrypt_key->key_len; + priv->sec_info.wep_enabled = 1; + } + if (wep_key->key_length) { + void *enc_key; + + if (encrypt_key->key_disable) { + memset(&priv->wep_key[index], 0, + sizeof(struct nxpwifi_wep_key)); + goto done; + } + + enc_key = encrypt_key; + + /* Send request to firmware */ + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_KEY_MATERIAL, + HOST_ACT_GEN_SET, 0, enc_key, false); + if (ret) + return ret; + } + +done: + if (priv->sec_info.wep_enabled) + priv->curr_pkt_filter |= HOST_ACT_MAC_WEP_ENABLE; + else + priv->curr_pkt_filter &= ~HOST_ACT_MAC_WEP_ENABLE; + + ret = nxpwifi_send_cmd(priv, HOST_CMD_MAC_CONTROL, + HOST_ACT_GEN_SET, 0, + &priv->curr_pkt_filter, true); + + return ret; +} + +/* IOCTL request handler to set WPA key */ +static int +nxpwifi_sec_ioctl_set_wpa_key(struct nxpwifi_private *priv, + struct nxpwifi_ds_encrypt_key *encrypt_key) +{ + int ret; + u8 remove_key = false; + + /* Current driver only supports key length of up to 32 bytes */ + if (encrypt_key->key_len > WLAN_MAX_KEY_LEN) { + nxpwifi_dbg(priv->adapter, ERROR, + "key length too long\n"); + return -EINVAL; + } + + if (!encrypt_key->key_index) + encrypt_key->key_index = NXPWIFI_KEY_INDEX_UNICAST; + + if (remove_key) + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_KEY_MATERIAL, + HOST_ACT_GEN_SET, + !KEY_INFO_ENABLED, encrypt_key, true); + else + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_KEY_MATERIAL, + HOST_ACT_GEN_SET, + KEY_INFO_ENABLED, encrypt_key, true); + + return ret; +} + +/* IOCTL request handler to set/get network keys */ +static int +nxpwifi_sec_ioctl_encrypt_key(struct nxpwifi_private *priv, + struct nxpwifi_ds_encrypt_key *encrypt_key) +{ + int status; + + if (encrypt_key->key_len > WLAN_KEY_LEN_WEP104) + status = nxpwifi_sec_ioctl_set_wpa_key(priv, encrypt_key); + else + status = nxpwifi_sec_ioctl_set_wep_key(priv, encrypt_key); + + return status; +} + +/* Return driver version string */ +int +nxpwifi_drv_get_driver_version(struct nxpwifi_adapter *adapter, char *version, + int max_len) +{ + union { + __le32 l; + u8 c[4]; + } ver; + char fw_ver[32]; + + ver.l = cpu_to_le32(adapter->fw_release_number); + sprintf(fw_ver, "%u.%u.%u.p%u.%u", ver.c[2], ver.c[1], + ver.c[0], ver.c[3], adapter->fw_hotfix_ver); + + snprintf(version, max_len, driver_version, fw_ver); + + nxpwifi_dbg(adapter, MSG, "info: NXPWIFI VERSION: %s\n", version); + + return 0; +} + +/* Sends IOCTL request to set encoding parameters */ +int nxpwifi_set_encode(struct nxpwifi_private *priv, struct key_params *kp, + const u8 *key, int key_len, u8 key_index, + const u8 *mac_addr, int disable) +{ + struct nxpwifi_ds_encrypt_key encrypt_key; + + memset(&encrypt_key, 0, sizeof(encrypt_key)); + encrypt_key.key_len = key_len; + encrypt_key.key_index = key_index; + + if (kp) { + encrypt_key.key_cipher = kp->cipher; + if (kp->cipher == WLAN_CIPHER_SUITE_AES_CMAC || + kp->cipher == WLAN_CIPHER_SUITE_BIP_GMAC_256) + encrypt_key.is_igtk_key = true; + } + + if (!disable) { + if (key_len) + memcpy(encrypt_key.key_material, key, key_len); + else + encrypt_key.is_current_wep_key = true; + + if (mac_addr) + memcpy(encrypt_key.mac_addr, mac_addr, ETH_ALEN); + if (kp && kp->seq && kp->seq_len) { + memcpy(encrypt_key.pn, kp->seq, kp->seq_len); + encrypt_key.pn_len = kp->seq_len; + encrypt_key.is_rx_seq_valid = true; + } + } else { + encrypt_key.key_disable = true; + if (mac_addr) + memcpy(encrypt_key.mac_addr, mac_addr, ETH_ALEN); + } + + return nxpwifi_sec_ioctl_encrypt_key(priv, &encrypt_key); +} + +/* Sends IOCTL request to get extended version */ +int +nxpwifi_get_ver_ext(struct nxpwifi_private *priv, u32 version_str_sel) +{ + struct nxpwifi_ver_ext ver_ext; + + memset(&ver_ext, 0, sizeof(ver_ext)); + ver_ext.version_str_sel = version_str_sel; + + return nxpwifi_send_cmd(priv, HOST_CMD_VERSION_EXT, + HOST_ACT_GEN_GET, 0, &ver_ext, true); +} + +int +nxpwifi_remain_on_chan_cfg(struct nxpwifi_private *priv, u16 action, + struct ieee80211_channel *chan, + unsigned int duration) +{ + struct host_cmd_ds_remain_on_chan roc_cfg; + u8 sc; + int ret; + + memset(&roc_cfg, 0, sizeof(roc_cfg)); + roc_cfg.action = cpu_to_le16(action); + if (action == HOST_ACT_GEN_SET) { + roc_cfg.band_cfg = chan->band; + sc = nxpwifi_chan_type_to_sec_chan_offset(NL80211_CHAN_NO_HT); + roc_cfg.band_cfg |= (sc << 2); + + roc_cfg.channel = + ieee80211_frequency_to_channel(chan->center_freq); + roc_cfg.duration = cpu_to_le32(duration); + } + ret = nxpwifi_send_cmd(priv, HOST_CMD_REMAIN_ON_CHAN, + action, 0, &roc_cfg, true); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "failed to remain on channel\n"); + return ret; + } + + return roc_cfg.status; +} + +/* Sends IOCTL request to get statistics information */ +int +nxpwifi_get_stats_info(struct nxpwifi_private *priv, + struct nxpwifi_ds_get_stats *log) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_GET_LOG, + HOST_ACT_GEN_GET, 0, log, true); +} + +/* IOCTL request handler to read/write register */ +static int nxpwifi_reg_mem_ioctl_reg_rw(struct nxpwifi_private *priv, + struct nxpwifi_ds_reg_rw *reg_rw, + u16 action) +{ + u16 cmd_no; + + switch (reg_rw->type) { + case NXPWIFI_REG_MAC: + cmd_no = HOST_CMD_MAC_REG_ACCESS; + break; + case NXPWIFI_REG_BBP: + cmd_no = HOST_CMD_BBP_REG_ACCESS; + break; + case NXPWIFI_REG_RF: + cmd_no = HOST_CMD_RF_REG_ACCESS; + break; + case NXPWIFI_REG_PMIC: + cmd_no = HOST_CMD_PMIC_REG_ACCESS; + break; + case NXPWIFI_REG_CAU: + cmd_no = HOST_CMD_CAU_REG_ACCESS; + break; + default: + return -EINVAL; + } + + return nxpwifi_send_cmd(priv, cmd_no, action, 0, reg_rw, true); +} + +/* Sends IOCTL request to write to a register */ +int +nxpwifi_reg_write(struct nxpwifi_private *priv, u32 reg_type, + u32 reg_offset, u32 reg_value) +{ + struct nxpwifi_ds_reg_rw reg_rw; + + reg_rw.type = reg_type; + reg_rw.offset = reg_offset; + reg_rw.value = reg_value; + + return nxpwifi_reg_mem_ioctl_reg_rw(priv, ®_rw, HOST_ACT_GEN_SET); +} + +/* Sends IOCTL request to read from a register */ +int +nxpwifi_reg_read(struct nxpwifi_private *priv, u32 reg_type, + u32 reg_offset, u32 *value) +{ + int ret; + struct nxpwifi_ds_reg_rw reg_rw; + + reg_rw.type = reg_type; + reg_rw.offset = reg_offset; + ret = nxpwifi_reg_mem_ioctl_reg_rw(priv, ®_rw, HOST_ACT_GEN_GET); + + if (!ret) + *value = reg_rw.value; + + return ret; +} + +/* Sends IOCTL request to read from EEPROM */ +int +nxpwifi_eeprom_read(struct nxpwifi_private *priv, u16 offset, u16 bytes, + u8 *value) +{ + int ret; + struct nxpwifi_ds_read_eeprom rd_eeprom; + + rd_eeprom.offset = offset; + rd_eeprom.byte_count = bytes; + + /* Send request to firmware */ + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_EEPROM_ACCESS, + HOST_ACT_GEN_GET, 0, &rd_eeprom, true); + + if (!ret) + memcpy(value, rd_eeprom.value, + min((u16)MAX_EEPROM_DATA, rd_eeprom.byte_count)); + return ret; +} + +/* Set generic IE(s); handle WPA/WPS specially */ +static int +nxpwifi_set_gen_ie_helper(struct nxpwifi_private *priv, u8 *ie_data_ptr, + u16 ie_len) +{ + struct ieee80211_vendor_ie *pvendor_ie; + static const u8 wpa_oui[] = { 0x00, 0x50, 0xf2, 0x01 }; + static const u8 wps_oui[] = { 0x00, 0x50, 0xf2, 0x04 }; + u16 unparsed_len = ie_len, cur_ie_len; + + /* If the passed length is zero, reset the buffer */ + if (!ie_len) { + priv->gen_ie_buf_len = 0; + priv->wps.session_enable = false; + return 0; + } else if (!ie_data_ptr || + ie_len <= sizeof(struct element)) { + return -EINVAL; + } + pvendor_ie = (struct ieee80211_vendor_ie *)ie_data_ptr; + + while (pvendor_ie) { + cur_ie_len = pvendor_ie->len + sizeof(struct element); + + if (pvendor_ie->element_id == WLAN_EID_RSN) { + /* element is a WPA/WPA2 element so call set_wpa function */ + nxpwifi_set_wpa_ie(priv, (u8 *)pvendor_ie, cur_ie_len); + priv->wps.session_enable = false; + goto next_ie; + } + + if (pvendor_ie->element_id == WLAN_EID_VENDOR_SPECIFIC) { + /* Test to see if it is a WPA element, if not, then it is a gen element */ + if (!memcmp(&pvendor_ie->oui, wpa_oui, + sizeof(wpa_oui))) { + /* element is a WPA/WPA2 element so call set_wpa function */ + nxpwifi_set_wpa_ie(priv, (u8 *)pvendor_ie, + cur_ie_len); + priv->wps.session_enable = false; + goto next_ie; + } + + if (!memcmp(&pvendor_ie->oui, wps_oui, + sizeof(wps_oui))) { + /* + * Test to see if it is a WPS element, if so, enable wps session + * flag + */ + priv->wps.session_enable = true; + nxpwifi_dbg(priv->adapter, MSG, + "WPS Session Enabled.\n"); + nxpwifi_set_wps_ie(priv, (u8 *)pvendor_ie, + cur_ie_len); + goto next_ie; + } + } + + /* + * Verify that the passed length is not larger than the available space + * remaining in the buffer + */ + if (cur_ie_len < + (sizeof(priv->gen_ie_buf) - priv->gen_ie_buf_len)) { + /* Append the passed data to the end of the genIeBuffer */ + memcpy(priv->gen_ie_buf + priv->gen_ie_buf_len, + (u8 *)pvendor_ie, cur_ie_len); + /* Increment the stored buffer length by the size passed */ + priv->gen_ie_buf_len += cur_ie_len; + } + +next_ie: + unparsed_len -= cur_ie_len; + + if (unparsed_len <= sizeof(struct element)) + pvendor_ie = NULL; + else + pvendor_ie = (struct ieee80211_vendor_ie *) + (((u8 *)pvendor_ie) + cur_ie_len); + } + + return 0; +} + +/* IOCTL request handler to set/get generic element */ +static int nxpwifi_misc_ioctl_gen_ie(struct nxpwifi_private *priv, + struct nxpwifi_ds_misc_gen_ie *gen_ie, + u16 action) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + switch (gen_ie->type) { + case NXPWIFI_IE_TYPE_GEN_IE: + if (action == HOST_ACT_GEN_GET) { + gen_ie->len = priv->wpa_ie_len; + memcpy(gen_ie->ie_data, priv->wpa_ie, gen_ie->len); + } else { + nxpwifi_set_gen_ie_helper(priv, gen_ie->ie_data, + (u16)gen_ie->len); + } + break; + case NXPWIFI_IE_TYPE_ARP_FILTER: + memset(adapter->arp_filter, 0, sizeof(adapter->arp_filter)); + if (gen_ie->len > ARP_FILTER_MAX_BUF_SIZE) { + adapter->arp_filter_size = 0; + nxpwifi_dbg(adapter, ERROR, + "invalid ARP filter size\n"); + return -EINVAL; + } + memcpy(adapter->arp_filter, gen_ie->ie_data, gen_ie->len); + adapter->arp_filter_size = gen_ie->len; + break; + default: + nxpwifi_dbg(adapter, ERROR, "invalid element type\n"); + return -EINVAL; + } + return 0; +} + +/* Sends IOCTL request to set a generic element */ +int +nxpwifi_set_gen_ie(struct nxpwifi_private *priv, const u8 *ie, int ie_len) +{ + struct nxpwifi_ds_misc_gen_ie gen_ie; + + if (ie_len > IEEE_MAX_IE_SIZE) + return -EFAULT; + + gen_ie.type = NXPWIFI_IE_TYPE_GEN_IE; + gen_ie.len = ie_len; + memcpy(gen_ie.ie_data, ie, ie_len); + + return nxpwifi_misc_ioctl_gen_ie(priv, &gen_ie, HOST_ACT_GEN_SET); +} + +/* Get Host Sleep wakeup reason */ +int nxpwifi_get_wakeup_reason(struct nxpwifi_private *priv, u16 action, + int cmd_type, + struct nxpwifi_ds_wakeup_reason *wakeup_reason) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_HS_WAKEUP_REASON, + HOST_ACT_GEN_GET, 0, wakeup_reason, + cmd_type == NXPWIFI_SYNC_CMD); +} + +int nxpwifi_get_chan_info(struct nxpwifi_private *priv, + struct nxpwifi_channel_band *channel_band) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_STA_CONFIGURE, + HOST_ACT_GEN_GET, 0, channel_band, + NXPWIFI_SYNC_CMD); +} diff --git a/drivers/net/wireless/nxp/nxpwifi/sta_cmd.c b/drivers/net/wireless/nxp/nxpwifi/sta_cmd.c new file mode 100644 index 000000000000..3dc4cf5704f5 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/sta_cmd.c @@ -0,0 +1,3387 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nxpwifi: station command handling + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cmdevt.h" +#include "wmm.h" +#include "11n.h" +#include "11ac.h" +#include "11ax.h" + +static bool disable_auto_ds; + +static int +nxpwifi_cmd_sta_get_hw_spec(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_get_hw_spec *hw_spec = &cmd->params.hw_spec; + + cmd->command = cpu_to_le16(HOST_CMD_GET_HW_SPEC); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_get_hw_spec) + + S_DS_GEN); + memcpy(hw_spec->permanent_addr, priv->curr_addr, ETH_ALEN); + + return 0; +} + +static int +nxpwifi_ret_sta_get_hw_spec(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_get_hw_spec *hw_spec = &resp->params.hw_spec; + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_ie_types_header *tlv; + struct hw_spec_api_rev *api_rev; + struct hw_spec_max_conn *max_conn; + struct hw_spec_extension *hw_he_cap; + struct hw_spec_fw_cap_info *fw_cap; + struct hw_spec_secure_boot_uuid *sb_uuid; + u16 resp_size, api_id; + int i, left_len, parsed_len = 0; + + adapter->fw_cap_info = le32_to_cpu(hw_spec->fw_cap_info); + + if (IS_SUPPORT_MULTI_BANDS(adapter)) + adapter->fw_bands = GET_FW_DEFAULT_BANDS(adapter); + else + adapter->fw_bands = BAND_B; + + if ((adapter->fw_bands & BAND_A) && (adapter->fw_bands & BAND_GN)) + adapter->fw_bands |= BAND_AN; + if (!(adapter->fw_bands & BAND_G) && (adapter->fw_bands & BAND_GN)) + adapter->fw_bands &= ~BAND_GN; + + adapter->fw_release_number = le32_to_cpu(hw_spec->fw_release_number); + adapter->fw_api_ver = (adapter->fw_release_number >> 16) & 0xff; + adapter->number_of_antenna = + le16_to_cpu(hw_spec->number_of_antenna) & 0xf; + + if (le32_to_cpu(hw_spec->dot_11ac_dev_cap)) { + adapter->is_hw_11ac_capable = true; + + /* Copy 11AC cap */ + adapter->hw_dot_11ac_dev_cap = + le32_to_cpu(hw_spec->dot_11ac_dev_cap); + adapter->usr_dot_11ac_dev_cap_bg = adapter->hw_dot_11ac_dev_cap + & ~NXPWIFI_DEF_11AC_CAP_BF_RESET_MASK; + adapter->usr_dot_11ac_dev_cap_a = adapter->hw_dot_11ac_dev_cap + & ~NXPWIFI_DEF_11AC_CAP_BF_RESET_MASK; + + /* Copy 11AC mcs */ + adapter->hw_dot_11ac_mcs_support = + le32_to_cpu(hw_spec->dot_11ac_mcs_support); + adapter->usr_dot_11ac_mcs_support = + adapter->hw_dot_11ac_mcs_support; + } else { + adapter->is_hw_11ac_capable = false; + } + + resp_size = le16_to_cpu(resp->size) - S_DS_GEN; + if (resp_size > sizeof(struct host_cmd_ds_get_hw_spec)) { + /* we have variable HW SPEC information */ + left_len = resp_size - sizeof(struct host_cmd_ds_get_hw_spec); + while (left_len > sizeof(struct nxpwifi_ie_types_header)) { + tlv = (void *)&hw_spec->tlv + parsed_len; + switch (le16_to_cpu(tlv->type)) { + case TLV_TYPE_API_REV: + api_rev = (struct hw_spec_api_rev *)tlv; + api_id = le16_to_cpu(api_rev->api_id); + switch (api_id) { + case KEY_API_VER_ID: + adapter->key_api_major_ver = + api_rev->major_ver; + adapter->key_api_minor_ver = + api_rev->minor_ver; + nxpwifi_dbg(adapter, INFO, + "key_api v%d.%d\n", + adapter->key_api_major_ver, + adapter->key_api_minor_ver); + break; + case FW_API_VER_ID: + adapter->fw_api_ver = + api_rev->major_ver; + nxpwifi_dbg(adapter, MSG, + "Firmware api version %d.%d\n", + adapter->fw_api_ver, + api_rev->minor_ver); + break; + case UAP_FW_API_VER_ID: + nxpwifi_dbg(adapter, INFO, + "uAP api version %d.%d\n", + api_rev->major_ver, + api_rev->minor_ver); + break; + case CHANRPT_API_VER_ID: + nxpwifi_dbg(adapter, INFO, + "channel report api version %d.%d\n", + api_rev->major_ver, + api_rev->minor_ver); + break; + case FW_HOTFIX_VER_ID: + adapter->fw_hotfix_ver = + api_rev->major_ver; + nxpwifi_dbg(adapter, INFO, + "Firmware hotfix version %d\n", + api_rev->major_ver); + break; + default: + nxpwifi_dbg(adapter, FATAL, + "Unknown api_id: %d\n", + api_id); + break; + } + break; + case TLV_TYPE_MAX_CONN: + max_conn = (struct hw_spec_max_conn *)tlv; + adapter->max_sta_conn = max_conn->max_sta_conn; + nxpwifi_dbg(adapter, INFO, + "max sta connections: %u\n", + adapter->max_sta_conn); + break; + case TLV_TYPE_EXTENSION_ID: + hw_he_cap = (struct hw_spec_extension *)tlv; + if (hw_he_cap->ext_id == + WLAN_EID_EXT_HE_CAPABILITY) + nxpwifi_update_11ax_cap(adapter, hw_he_cap); + break; + case TLV_TYPE_FW_CAP_INFO: + fw_cap = (struct hw_spec_fw_cap_info *)tlv; + adapter->fw_cap_info = + le32_to_cpu(fw_cap->fw_cap_info); + adapter->fw_cap_ext = + le32_to_cpu(fw_cap->fw_cap_ext); + nxpwifi_dbg(adapter, INFO, + "fw_cap_info:%#x fw_cap_ext:%#x\n", + adapter->fw_cap_info, + adapter->fw_cap_ext); + break; + case TLV_TYPE_SECURE_BOOT_UUID: + sb_uuid = (struct hw_spec_secure_boot_uuid *)tlv; + adapter->uuid_lo = + le64_to_cpu(sb_uuid->uuid_lo); + adapter->uuid_hi = + le64_to_cpu(sb_uuid->uuid_hi); + nxpwifi_dbg(adapter, INFO, + "uuid: %#llx%#llx\n", + adapter->uuid_lo, adapter->uuid_hi); + break; + default: + nxpwifi_dbg(adapter, FATAL, + "Unknown GET_HW_SPEC TLV type: %#x\n", + le16_to_cpu(tlv->type)); + break; + } + parsed_len += le16_to_cpu(tlv->len) + + sizeof(struct nxpwifi_ie_types_header); + left_len -= le16_to_cpu(tlv->len) + + sizeof(struct nxpwifi_ie_types_header); + } + } + + if (adapter->key_api_major_ver < KEY_API_VER_MAJOR_V2) + return -EOPNOTSUPP; + + nxpwifi_dbg(adapter, INFO, + "info: GET_HW_SPEC: fw_release_number- %#x\n", + adapter->fw_release_number); + nxpwifi_dbg(adapter, INFO, + "info: GET_HW_SPEC: permanent addr: %pM\n", + hw_spec->permanent_addr); + nxpwifi_dbg(adapter, INFO, + "info: GET_HW_SPEC: hw_if_version=%#x version=%#x\n", + le16_to_cpu(hw_spec->hw_if_version), + le16_to_cpu(hw_spec->version)); + + ether_addr_copy(priv->adapter->perm_addr, hw_spec->permanent_addr); + adapter->region_code = le16_to_cpu(hw_spec->region_code); + + for (i = 0; i < NXPWIFI_MAX_REGION_CODE; i++) + /* Use the region code to search for the index */ + if (adapter->region_code == region_code_index[i]) + break; + + /* If it's unidentified region code, use the default (world) */ + if (i >= NXPWIFI_MAX_REGION_CODE) { + adapter->region_code = 0x00; + nxpwifi_dbg(adapter, WARN, + "cmd: unknown region code, use default (USA)\n"); + } + + adapter->hw_dot_11n_dev_cap = le32_to_cpu(hw_spec->dot_11n_dev_cap); + adapter->hw_dev_mcs_support = hw_spec->dev_mcs_support; + adapter->hw_mpdu_density = GET_MPDU_DENSITY(le32_to_cpu(hw_spec->hw_dev_cap)); + adapter->user_dev_mcs_support = adapter->hw_dev_mcs_support; + adapter->user_htstream = adapter->hw_dev_mcs_support; + if (adapter->fw_bands & BAND_A) + adapter->user_htstream |= (adapter->user_htstream << 8); + + if (adapter->if_ops.update_mp_end_port) { + u16 mp_end_port; + + mp_end_port = le16_to_cpu(hw_spec->mp_end_port); + adapter->if_ops.update_mp_end_port(adapter, mp_end_port); + } + + if (adapter->fw_api_ver == NXPWIFI_FW_V15) + adapter->scan_chan_gap_enabled = true; + + for (i = 0; i < adapter->priv_num; i++) + adapter->priv[i]->config_bands = adapter->fw_bands; + + return 0; +} + +static int +nxpwifi_cmd_sta_802_11_scan(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_802_11_scan(cmd, data_buf); +} + +static int +nxpwifi_ret_sta_802_11_scan(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int ret; + + ret = nxpwifi_ret_802_11_scan(priv, resp); + adapter->curr_cmd->wait_q_enabled = false; + + return ret; +} + +static int +nxpwifi_cmd_sta_802_11_get_log(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(HOST_CMD_802_11_GET_LOG); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_802_11_get_log) + + S_DS_GEN); + + return 0; +} + +static int +nxpwifi_ret_sta_802_11_get_log(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_802_11_get_log *get_log = + &resp->params.get_log; + struct nxpwifi_ds_get_stats *stats = + (struct nxpwifi_ds_get_stats *)data_buf; + + if (stats) { + stats->mcast_tx_frame = le32_to_cpu(get_log->mcast_tx_frame); + stats->failed = le32_to_cpu(get_log->failed); + stats->retry = le32_to_cpu(get_log->retry); + stats->multi_retry = le32_to_cpu(get_log->multi_retry); + stats->frame_dup = le32_to_cpu(get_log->frame_dup); + stats->rts_success = le32_to_cpu(get_log->rts_success); + stats->rts_failure = le32_to_cpu(get_log->rts_failure); + stats->ack_failure = le32_to_cpu(get_log->ack_failure); + stats->rx_frag = le32_to_cpu(get_log->rx_frag); + stats->mcast_rx_frame = le32_to_cpu(get_log->mcast_rx_frame); + stats->fcs_error = le32_to_cpu(get_log->fcs_error); + stats->tx_frame = le32_to_cpu(get_log->tx_frame); + stats->wep_icv_error[0] = + le32_to_cpu(get_log->wep_icv_err_cnt[0]); + stats->wep_icv_error[1] = + le32_to_cpu(get_log->wep_icv_err_cnt[1]); + stats->wep_icv_error[2] = + le32_to_cpu(get_log->wep_icv_err_cnt[2]); + stats->wep_icv_error[3] = + le32_to_cpu(get_log->wep_icv_err_cnt[3]); + stats->bcn_rcv_cnt = le32_to_cpu(get_log->bcn_rcv_cnt); + stats->bcn_miss_cnt = le32_to_cpu(get_log->bcn_miss_cnt); + } + + return 0; +} + +static int +nxpwifi_cmd_sta_mac_multicast_adr(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_mac_multicast_adr *mcast_addr = &cmd->params.mc_addr; + struct nxpwifi_multicast_list *mcast_list = + (struct nxpwifi_multicast_list *)data_buf; + + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_mac_multicast_adr) + + S_DS_GEN); + cmd->command = cpu_to_le16(HOST_CMD_MAC_MULTICAST_ADR); + + mcast_addr->action = cpu_to_le16(cmd_action); + mcast_addr->num_of_adrs = + cpu_to_le16((u16)mcast_list->num_multicast_addr); + memcpy(mcast_addr->mac_list, mcast_list->mac_list, + mcast_list->num_multicast_addr * ETH_ALEN); + + return 0; +} + +static int +nxpwifi_cmd_sta_802_11_associate(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_802_11_associate(priv, cmd, data_buf); +} + +static int +nxpwifi_ret_sta_802_11_associate(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + return nxpwifi_ret_802_11_associate(priv, resp); +} + +static int +nxpwifi_cmd_sta_802_11_snmp_mib(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_802_11_snmp_mib *snmp_mib = &cmd->params.smib; + u16 *ul_temp = (u16 *)data_buf; + + nxpwifi_dbg(priv->adapter, CMD, + "cmd: SNMP_CMD: cmd_oid = 0x%x\n", cmd_type); + cmd->command = cpu_to_le16(HOST_CMD_802_11_SNMP_MIB); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_802_11_snmp_mib) + + S_DS_GEN); + + snmp_mib->oid = cpu_to_le16((u16)cmd_type); + if (cmd_action == HOST_ACT_GEN_GET) { + snmp_mib->query_type = cpu_to_le16(HOST_ACT_GEN_GET); + snmp_mib->buf_size = cpu_to_le16(MAX_SNMP_BUF_SIZE); + le16_unaligned_add_cpu(&cmd->size, MAX_SNMP_BUF_SIZE); + } else if (cmd_action == HOST_ACT_GEN_SET) { + snmp_mib->query_type = cpu_to_le16(HOST_ACT_GEN_SET); + snmp_mib->buf_size = cpu_to_le16(sizeof(u16)); + put_unaligned_le16(*ul_temp, snmp_mib->value); + le16_unaligned_add_cpu(&cmd->size, sizeof(u16)); + } + + nxpwifi_dbg(priv->adapter, CMD, + "cmd: SNMP_CMD: Action=0x%x, OID=0x%x,\t" + "OIDSize=0x%x, Value=0x%x\n", + cmd_action, cmd_type, le16_to_cpu(snmp_mib->buf_size), + get_unaligned_le16(snmp_mib->value)); + + return 0; +} + +static int +nxpwifi_ret_sta_802_11_snmp_mib(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_802_11_snmp_mib *smib = &resp->params.smib; + u16 oid = le16_to_cpu(smib->oid); + u16 query_type = le16_to_cpu(smib->query_type); + u32 ul_temp; + + nxpwifi_dbg(priv->adapter, INFO, + "info: SNMP_RESP: oid value = %#x,\t" + "query_type = %#x, buf size = %#x\n", + oid, query_type, le16_to_cpu(smib->buf_size)); + if (query_type == HOST_ACT_GEN_GET) { + ul_temp = get_unaligned_le16(smib->value); + if (data_buf) + *(u32 *)data_buf = ul_temp; + switch (oid) { + case FRAG_THRESH_I: + nxpwifi_dbg(priv->adapter, INFO, + "info: SNMP_RESP: FragThsd =%u\n", + ul_temp); + break; + case RTS_THRESH_I: + nxpwifi_dbg(priv->adapter, INFO, + "info: SNMP_RESP: RTSThsd =%u\n", + ul_temp); + break; + case SHORT_RETRY_LIM_I: + nxpwifi_dbg(priv->adapter, INFO, + "info: SNMP_RESP: TxRetryCount=%u\n", + ul_temp); + break; + case DTIM_PERIOD_I: + nxpwifi_dbg(priv->adapter, INFO, + "info: SNMP_RESP: DTIM period=%u\n", + ul_temp); + break; + default: + break; + } + } + + return 0; +} + +static int nxpwifi_cmd_sta_reg_access(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct nxpwifi_ds_reg_rw *reg_rw = data_buf; + + cmd->command = cpu_to_le16(cmd_no); + + switch (cmd_no) { + case HOST_CMD_MAC_REG_ACCESS: + { + struct host_cmd_ds_mac_reg_access *mac_reg; + + cmd->size = cpu_to_le16(sizeof(*mac_reg) + S_DS_GEN); + mac_reg = &cmd->params.mac_reg; + mac_reg->action = cpu_to_le16(cmd_action); + mac_reg->offset = cpu_to_le16((u16)reg_rw->offset); + mac_reg->value = cpu_to_le32(reg_rw->value); + break; + } + case HOST_CMD_BBP_REG_ACCESS: + { + struct host_cmd_ds_bbp_reg_access *bbp_reg; + + cmd->size = cpu_to_le16(sizeof(*bbp_reg) + S_DS_GEN); + bbp_reg = &cmd->params.bbp_reg; + bbp_reg->action = cpu_to_le16(cmd_action); + bbp_reg->offset = cpu_to_le16((u16)reg_rw->offset); + bbp_reg->value = (u8)reg_rw->value; + break; + } + case HOST_CMD_RF_REG_ACCESS: + { + struct host_cmd_ds_rf_reg_access *rf_reg; + + cmd->size = cpu_to_le16(sizeof(*rf_reg) + S_DS_GEN); + rf_reg = &cmd->params.rf_reg; + rf_reg->action = cpu_to_le16(cmd_action); + rf_reg->offset = cpu_to_le16((u16)reg_rw->offset); + rf_reg->value = (u8)reg_rw->value; + break; + } + case HOST_CMD_PMIC_REG_ACCESS: + { + struct host_cmd_ds_pmic_reg_access *pmic_reg; + + cmd->size = cpu_to_le16(sizeof(*pmic_reg) + S_DS_GEN); + pmic_reg = &cmd->params.pmic_reg; + pmic_reg->action = cpu_to_le16(cmd_action); + pmic_reg->offset = cpu_to_le16((u16)reg_rw->offset); + pmic_reg->value = (u8)reg_rw->value; + break; + } + case HOST_CMD_CAU_REG_ACCESS: + { + struct host_cmd_ds_rf_reg_access *cau_reg; + + cmd->size = cpu_to_le16(sizeof(*cau_reg) + S_DS_GEN); + cau_reg = &cmd->params.rf_reg; + cau_reg->action = cpu_to_le16(cmd_action); + cau_reg->offset = cpu_to_le16((u16)reg_rw->offset); + cau_reg->value = (u8)reg_rw->value; + break; + } + case HOST_CMD_802_11_EEPROM_ACCESS: + { + struct nxpwifi_ds_read_eeprom *rd_eeprom = data_buf; + struct host_cmd_ds_802_11_eeprom_access *cmd_eeprom = + &cmd->params.eeprom; + + cmd->size = cpu_to_le16(sizeof(*cmd_eeprom) + S_DS_GEN); + cmd_eeprom->action = cpu_to_le16(cmd_action); + cmd_eeprom->offset = cpu_to_le16(rd_eeprom->offset); + cmd_eeprom->byte_count = cpu_to_le16(rd_eeprom->byte_count); + cmd_eeprom->value = 0; + break; + } + default: + return -EINVAL; + } + + return 0; +} + +static int +nxpwifi_ret_sta_reg_access(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct nxpwifi_ds_reg_rw *reg_rw; + struct nxpwifi_ds_read_eeprom *eeprom; + union reg { + struct host_cmd_ds_mac_reg_access *mac; + struct host_cmd_ds_bbp_reg_access *bbp; + struct host_cmd_ds_rf_reg_access *rf; + struct host_cmd_ds_pmic_reg_access *pmic; + struct host_cmd_ds_802_11_eeprom_access *eeprom; + } r; + + if (!data_buf) + return 0; + + reg_rw = data_buf; + eeprom = data_buf; + switch (cmdresp_no) { + case HOST_CMD_MAC_REG_ACCESS: + r.mac = &resp->params.mac_reg; + reg_rw->offset = (u32)le16_to_cpu(r.mac->offset); + reg_rw->value = le32_to_cpu(r.mac->value); + break; + case HOST_CMD_BBP_REG_ACCESS: + r.bbp = &resp->params.bbp_reg; + reg_rw->offset = (u32)le16_to_cpu(r.bbp->offset); + reg_rw->value = (u32)r.bbp->value; + break; + + case HOST_CMD_RF_REG_ACCESS: + r.rf = &resp->params.rf_reg; + reg_rw->offset = (u32)le16_to_cpu(r.rf->offset); + reg_rw->value = (u32)r.bbp->value; + break; + case HOST_CMD_PMIC_REG_ACCESS: + r.pmic = &resp->params.pmic_reg; + reg_rw->offset = (u32)le16_to_cpu(r.pmic->offset); + reg_rw->value = (u32)r.pmic->value; + break; + case HOST_CMD_CAU_REG_ACCESS: + r.rf = &resp->params.rf_reg; + reg_rw->offset = (u32)le16_to_cpu(r.rf->offset); + reg_rw->value = (u32)r.rf->value; + break; + case HOST_CMD_802_11_EEPROM_ACCESS: + r.eeprom = &resp->params.eeprom; + pr_debug("info: EEPROM read len=%x\n", + le16_to_cpu(r.eeprom->byte_count)); + if (eeprom->byte_count < le16_to_cpu(r.eeprom->byte_count)) { + eeprom->byte_count = 0; + pr_debug("info: EEPROM read length is too big\n"); + return -ENOMEM; + } + eeprom->offset = le16_to_cpu(r.eeprom->offset); + eeprom->byte_count = le16_to_cpu(r.eeprom->byte_count); + if (eeprom->byte_count > 0) + memcpy(&eeprom->value, &r.eeprom->value, + min((u16)MAX_EEPROM_DATA, eeprom->byte_count)); + break; + default: + return -EINVAL; + } + return 0; +} + +static int +nxpwifi_cmd_sta_rf_tx_pwr(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_rf_tx_pwr *txp = &cmd->params.txp; + + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_rf_tx_pwr) + + S_DS_GEN); + cmd->command = cpu_to_le16(HOST_CMD_RF_TX_PWR); + txp->action = cpu_to_le16(cmd_action); + + return 0; +} + +static int +nxpwifi_ret_sta_rf_tx_pwr(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_rf_tx_pwr *txp = &resp->params.txp; + u16 action = le16_to_cpu(txp->action); + + priv->tx_power_level = le16_to_cpu(txp->cur_level); + + if (action == HOST_ACT_GEN_GET) { + priv->max_tx_power_level = txp->max_power; + priv->min_tx_power_level = txp->min_power; + } + + nxpwifi_dbg(priv->adapter, INFO, + "Current TxPower Level=%d, Max Power=%d, Min Power=%d\n", + priv->tx_power_level, priv->max_tx_power_level, + priv->min_tx_power_level); + + return 0; +} + +static int +nxpwifi_cmd_sta_rf_antenna(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_rf_ant_mimo *ant_mimo = &cmd->params.ant_mimo; + struct host_cmd_ds_rf_ant_siso *ant_siso = &cmd->params.ant_siso; + struct nxpwifi_ds_ant_cfg *ant_cfg = + (struct nxpwifi_ds_ant_cfg *)data_buf; + + cmd->command = cpu_to_le16(HOST_CMD_RF_ANTENNA); + + switch (cmd_action) { + case HOST_ACT_GEN_SET: + if (priv->adapter->hw_dev_mcs_support == HT_STREAM_2X2) { + cmd->size = cpu_to_le16(sizeof(struct + host_cmd_ds_rf_ant_mimo) + + S_DS_GEN); + ant_mimo->action_tx = cpu_to_le16(HOST_ACT_SET_TX); + ant_mimo->tx_ant_mode = + cpu_to_le16((u16)ant_cfg->tx_ant); + ant_mimo->action_rx = cpu_to_le16(HOST_ACT_SET_RX); + ant_mimo->rx_ant_mode = + cpu_to_le16((u16)ant_cfg->rx_ant); + } else { + cmd->size = cpu_to_le16(sizeof(struct + host_cmd_ds_rf_ant_siso) + + S_DS_GEN); + ant_siso->action = cpu_to_le16(HOST_ACT_SET_BOTH); + ant_siso->ant_mode = cpu_to_le16((u16)ant_cfg->tx_ant); + } + break; + case HOST_ACT_GEN_GET: + if (priv->adapter->hw_dev_mcs_support == HT_STREAM_2X2) { + cmd->size = cpu_to_le16(sizeof(struct + host_cmd_ds_rf_ant_mimo) + + S_DS_GEN); + ant_mimo->action_tx = cpu_to_le16(HOST_ACT_GET_TX); + ant_mimo->action_rx = cpu_to_le16(HOST_ACT_GET_RX); + } else { + cmd->size = cpu_to_le16(sizeof(struct + host_cmd_ds_rf_ant_siso) + + S_DS_GEN); + ant_siso->action = cpu_to_le16(HOST_ACT_GET_BOTH); + } + break; + } + return 0; +} + +static int +nxpwifi_ret_sta_rf_antenna(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_rf_ant_mimo *ant_mimo = &resp->params.ant_mimo; + struct host_cmd_ds_rf_ant_siso *ant_siso = &resp->params.ant_siso; + struct nxpwifi_adapter *adapter = priv->adapter; + + if (adapter->hw_dev_mcs_support == HT_STREAM_2X2) { + priv->tx_ant = le16_to_cpu(ant_mimo->tx_ant_mode); + priv->rx_ant = le16_to_cpu(ant_mimo->rx_ant_mode); + nxpwifi_dbg(adapter, INFO, + "RF_ANT_RESP: Tx action = 0x%x, Tx Mode = 0x%04x\t" + "Rx action = 0x%x, Rx Mode = 0x%04x\n", + le16_to_cpu(ant_mimo->action_tx), + le16_to_cpu(ant_mimo->tx_ant_mode), + le16_to_cpu(ant_mimo->action_rx), + le16_to_cpu(ant_mimo->rx_ant_mode)); + } else { + priv->tx_ant = le16_to_cpu(ant_siso->ant_mode); + priv->rx_ant = le16_to_cpu(ant_siso->ant_mode); + nxpwifi_dbg(adapter, INFO, + "RF_ANT_RESP: action = 0x%x, Mode = 0x%04x\n", + le16_to_cpu(ant_siso->action), + le16_to_cpu(ant_siso->ant_mode)); + } + return 0; +} + +static int +nxpwifi_cmd_sta_802_11_deauthenticate(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_802_11_deauthenticate *deauth = &cmd->params.deauth; + u8 *mac = (u8 *)data_buf; + + cmd->command = cpu_to_le16(HOST_CMD_802_11_DEAUTHENTICATE); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_802_11_deauthenticate) + + S_DS_GEN); + + /* Set AP MAC address */ + memcpy(deauth->mac_addr, mac, ETH_ALEN); + + nxpwifi_dbg(priv->adapter, CMD, "cmd: Deauth: %pM\n", deauth->mac_addr); + + deauth->reason_code = cpu_to_le16(WLAN_REASON_DEAUTH_LEAVING); + + return 0; +} + +static int +nxpwifi_ret_sta_802_11_deauthenticate(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + adapter->dbg.num_cmd_deauth++; + if (!memcmp(resp->params.deauth.mac_addr, + &priv->curr_bss_params.bss_descriptor.mac_address, + sizeof(resp->params.deauth.mac_addr))) + nxpwifi_reset_connect_state(priv, WLAN_REASON_DEAUTH_LEAVING, + false); + + return 0; +} + +static int +nxpwifi_cmd_sta_mac_control(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_mac_control *mac_ctrl = &cmd->params.mac_ctrl; + u32 *action = (u32 *)data_buf; + + if (cmd_action != HOST_ACT_GEN_SET) { + nxpwifi_dbg(priv->adapter, ERROR, + "mac_control: only support set cmd\n"); + return -EINVAL; + } + + cmd->command = cpu_to_le16(HOST_CMD_MAC_CONTROL); + cmd->size = + cpu_to_le16(sizeof(struct host_cmd_ds_mac_control) + S_DS_GEN); + mac_ctrl->action = cpu_to_le32(*action); + + return 0; +} + +static int +nxpwifi_cmd_sta_802_11_mac_address(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(HOST_CMD_802_11_MAC_ADDRESS); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_802_11_mac_address) + + S_DS_GEN); + cmd->result = 0; + + cmd->params.mac_addr.action = cpu_to_le16(cmd_action); + + if (cmd_action == HOST_ACT_GEN_SET) + memcpy(cmd->params.mac_addr.mac_addr, priv->curr_addr, + ETH_ALEN); + + return 0; +} + +static int +nxpwifi_ret_sta_802_11_mac_address(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_802_11_mac_address *cmd_mac_addr; + + cmd_mac_addr = &resp->params.mac_addr; + + memcpy(priv->curr_addr, cmd_mac_addr->mac_addr, ETH_ALEN); + + nxpwifi_dbg(priv->adapter, INFO, + "info: set mac address: %pM\n", priv->curr_addr); + + return 0; +} + +static int +nxpwifi_cmd_sta_802_11d_domain_info(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_ds_802_11d_domain_info *domain_info = + &cmd->params.domain_info; + struct nxpwifi_ietypes_domain_param_set *domain = + &domain_info->domain; + struct nxpwifi_ietypes_domain_code *domain_code; + u8 no_of_triplet = adapter->domain_reg.no_of_triplet; + int triplet_size; + + nxpwifi_dbg(adapter, INFO, + "info: 11D: no_of_triplet=0x%x\n", no_of_triplet); + + cmd->command = cpu_to_le16(HOST_CMD_802_11D_DOMAIN_INFO); + cmd->size = cpu_to_le16(S_DS_GEN); + domain_info->action = cpu_to_le16(cmd_action); + le16_unaligned_add_cpu(&cmd->size, sizeof(domain_info->action)); + + if (cmd_action == HOST_ACT_GEN_GET) + return 0; + + triplet_size = no_of_triplet * + sizeof(struct ieee80211_country_ie_triplet); + + domain->header.type = cpu_to_le16(WLAN_EID_COUNTRY); + domain->header.len = + cpu_to_le16(sizeof(domain->country_code) + triplet_size); + memcpy(domain->country_code, adapter->domain_reg.country_code, + sizeof(domain->country_code)); + if (no_of_triplet) + memcpy(domain->triplet, adapter->domain_reg.triplet, + triplet_size); + le16_unaligned_add_cpu(&cmd->size, sizeof(*domain) + triplet_size); + + domain_code = (struct nxpwifi_ietypes_domain_code *)((u8 *)cmd + + le16_to_cpu(cmd->size)); + domain_code->header.type = cpu_to_le16(TLV_TYPE_REGION_DOMAIN_CODE); + domain_code->header.len = + cpu_to_le16(sizeof(*domain_code) - + sizeof(struct nxpwifi_ie_types_header)); + le16_unaligned_add_cpu(&cmd->size, sizeof(*domain_code)); + + return 0; +} + +static int +nxpwifi_ret_sta_802_11d_domain_info(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_802_11d_domain_info_rsp *domain_info = + &resp->params.domain_info_resp; + struct nxpwifi_ietypes_domain_param_set *domain = &domain_info->domain; + u16 action = le16_to_cpu(domain_info->action); + u8 no_of_triplet; + + no_of_triplet = (u8)((le16_to_cpu(domain->header.len) + - IEEE80211_COUNTRY_STRING_LEN) + / sizeof(struct ieee80211_country_ie_triplet)); + + nxpwifi_dbg(priv->adapter, INFO, + "info: 11D Domain Info Resp: no_of_triplet=%d\n", + no_of_triplet); + + if (no_of_triplet > NXPWIFI_MAX_TRIPLET_802_11D) { + nxpwifi_dbg(priv->adapter, FATAL, + "11D: invalid number of triplets %d returned\n", + no_of_triplet); + return -EINVAL; + } + + switch (action) { + case HOST_ACT_GEN_SET: /* Proc Set Action */ + break; + case HOST_ACT_GEN_GET: + break; + default: + nxpwifi_dbg(priv->adapter, ERROR, + "11D: invalid action:%d\n", domain_info->action); + return -EINVAL; + } + + return 0; +} + +static int nxpwifi_set_aes_key(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + struct nxpwifi_ds_encrypt_key *enc_key, + struct host_cmd_ds_802_11_key_material *km) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u16 size, len = KEY_PARAMS_FIXED_LEN; + u8 key_type, key_type_igtk; + + if (enc_key->key_len == WLAN_KEY_LEN_CCMP) { + key_type = KEY_TYPE_ID_AES; + key_type_igtk = KEY_TYPE_ID_AES_CMAC; + } else { + key_type = KEY_TYPE_ID_GCMP_256; + key_type_igtk = KEY_TYPE_ID_BIP_GMAC_256; + } + + if (enc_key->is_igtk_key) { + km->key_param_set.key_info &= cpu_to_le16(~KEY_MCAST); + km->key_param_set.key_info |= cpu_to_le16(KEY_IGTK); + km->key_param_set.key_type = key_type_igtk; + if (enc_key->key_len == WLAN_KEY_LEN_CCMP) { + nxpwifi_dbg(adapter, INFO, + "%s: Set CMAC AES Key\n", __func__); + if (enc_key->is_rx_seq_valid) + memcpy(km->key_param_set.key_params.cmac_aes.ipn, + enc_key->pn, enc_key->pn_len); + km->key_param_set.key_params.cmac_aes.key_len = + cpu_to_le16(enc_key->key_len); + memcpy(km->key_param_set.key_params.cmac_aes.key, + enc_key->key_material, enc_key->key_len); + len += sizeof(struct nxpwifi_cmac_aes_param); + } else { + nxpwifi_dbg(adapter, INFO, + "%s: Set GMAC AES Key\n", __func__); + if (enc_key->is_rx_seq_valid) + memcpy(km->key_param_set.key_params.gmac_aes.ipn, + enc_key->pn, enc_key->pn_len); + km->key_param_set.key_params.gmac_aes.key_len = + cpu_to_le16(enc_key->key_len); + memcpy(km->key_param_set.key_params.gmac_aes.key, + enc_key->key_material, enc_key->key_len); + len += sizeof(struct nxpwifi_gmac_aes_param); + } + } else if (enc_key->is_igtk_def_key) { + nxpwifi_dbg(adapter, INFO, + "%s: Set CMAC default Key index\n", __func__); + km->key_param_set.key_type = key_type_igtk; + km->key_param_set.key_idx = enc_key->key_index & KEY_INDEX_MASK; + } else { + nxpwifi_dbg(adapter, INFO, + "%s: Set AES Key\n", __func__); + if (enc_key->is_rx_seq_valid) + memcpy(km->key_param_set.key_params.aes.pn, + enc_key->pn, enc_key->pn_len); + km->key_param_set.key_type = key_type; + km->key_param_set.key_params.aes.key_len = + cpu_to_le16(enc_key->key_len); + memcpy(km->key_param_set.key_params.aes.key, + enc_key->key_material, enc_key->key_len); + len += sizeof(struct nxpwifi_aes_param); + } + + km->key_param_set.len = cpu_to_le16(len); + size = len + sizeof(struct nxpwifi_ie_types_header) + + sizeof(km->action) + S_DS_GEN; + cmd->size = cpu_to_le16(size); + + return 0; +} + +static int +nxpwifi_cmd_sta_802_11_key_material(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_ds_encrypt_key *enc_key = + (struct nxpwifi_ds_encrypt_key *)data_buf; + u8 *mac = enc_key->mac_addr; + u16 key_info, len = KEY_PARAMS_FIXED_LEN; + struct host_cmd_ds_802_11_key_material *km = + &cmd->params.key_material; + + cmd->command = cpu_to_le16(HOST_CMD_802_11_KEY_MATERIAL); + km->action = cpu_to_le16(cmd_action); + + if (cmd_action == HOST_ACT_GEN_GET) { + nxpwifi_dbg(adapter, INFO, "%s: Get key\n", __func__); + km->key_param_set.key_idx = + enc_key->key_index & KEY_INDEX_MASK; + km->key_param_set.type = cpu_to_le16(TLV_TYPE_KEY_PARAM_V2); + km->key_param_set.len = cpu_to_le16(KEY_PARAMS_FIXED_LEN); + ether_addr_copy(km->key_param_set.mac_addr, mac); + + if (enc_key->key_index & NXPWIFI_KEY_INDEX_UNICAST) + key_info = KEY_UNICAST; + else + key_info = KEY_MCAST; + + if (enc_key->is_igtk_key) + key_info |= KEY_IGTK; + + km->key_param_set.key_info = cpu_to_le16(key_info); + + cmd->size = cpu_to_le16(sizeof(struct nxpwifi_ie_types_header) + + S_DS_GEN + KEY_PARAMS_FIXED_LEN + + sizeof(km->action)); + return 0; + } + + memset(&km->key_param_set, 0, + sizeof(struct nxpwifi_ie_type_key_param_set)); + + if (enc_key->key_disable) { + nxpwifi_dbg(adapter, INFO, "%s: Remove key\n", __func__); + km->action = cpu_to_le16(HOST_ACT_GEN_REMOVE); + km->key_param_set.type = cpu_to_le16(TLV_TYPE_KEY_PARAM_V2); + km->key_param_set.len = cpu_to_le16(KEY_PARAMS_FIXED_LEN); + km->key_param_set.key_idx = enc_key->key_index & KEY_INDEX_MASK; + key_info = KEY_MCAST | KEY_UNICAST; + km->key_param_set.key_info = cpu_to_le16(key_info); + ether_addr_copy(km->key_param_set.mac_addr, mac); + cmd->size = cpu_to_le16(sizeof(struct nxpwifi_ie_types_header) + + S_DS_GEN + KEY_PARAMS_FIXED_LEN + + sizeof(km->action)); + return 0; + } + + km->action = cpu_to_le16(HOST_ACT_GEN_SET); + km->key_param_set.key_idx = enc_key->key_index & KEY_INDEX_MASK; + km->key_param_set.type = cpu_to_le16(TLV_TYPE_KEY_PARAM_V2); + key_info = KEY_ENABLED; + ether_addr_copy(km->key_param_set.mac_addr, mac); + + if (enc_key->key_len <= WLAN_KEY_LEN_WEP104) { + nxpwifi_dbg(adapter, INFO, "%s: Set WEP Key\n", __func__); + len += sizeof(struct nxpwifi_wep_param); + km->key_param_set.len = cpu_to_le16(len); + km->key_param_set.key_type = KEY_TYPE_ID_WEP; + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + key_info |= KEY_MCAST | KEY_UNICAST; + } else { + if (enc_key->is_current_wep_key) { + key_info |= KEY_MCAST | KEY_UNICAST; + if (km->key_param_set.key_idx == + (priv->wep_key_curr_index & KEY_INDEX_MASK)) + key_info |= KEY_DEFAULT; + } else { + if (is_broadcast_ether_addr(mac)) + key_info |= KEY_MCAST; + else + key_info |= KEY_UNICAST | KEY_DEFAULT; + } + } + km->key_param_set.key_info = cpu_to_le16(key_info); + + km->key_param_set.key_params.wep.key_len = + cpu_to_le16(enc_key->key_len); + memcpy(km->key_param_set.key_params.wep.key, + enc_key->key_material, enc_key->key_len); + + cmd->size = cpu_to_le16(sizeof(struct nxpwifi_ie_types_header) + + len + sizeof(km->action) + S_DS_GEN); + return 0; + } + + if (is_broadcast_ether_addr(mac)) + key_info |= KEY_MCAST | KEY_RX_KEY; + else + key_info |= KEY_UNICAST | KEY_TX_KEY | KEY_RX_KEY; + + /* Enable default key for WPA/WPA2 */ + if (!priv->wpa_is_gtk_set) + key_info |= KEY_DEFAULT; + + km->key_param_set.key_info = cpu_to_le16(key_info); + + if (enc_key->key_cipher != WLAN_CIPHER_SUITE_TKIP && + enc_key->key_len >= WLAN_KEY_LEN_CCMP) + return nxpwifi_set_aes_key(priv, cmd, enc_key, km); + + if (enc_key->key_len == WLAN_KEY_LEN_TKIP) { + nxpwifi_dbg(adapter, INFO, + "%s: Set TKIP Key\n", __func__); + if (enc_key->is_rx_seq_valid) + memcpy(km->key_param_set.key_params.tkip.pn, + enc_key->pn, enc_key->pn_len); + km->key_param_set.key_type = KEY_TYPE_ID_TKIP; + km->key_param_set.key_params.tkip.key_len = + cpu_to_le16(enc_key->key_len); + memcpy(km->key_param_set.key_params.tkip.key, + enc_key->key_material, enc_key->key_len); + + len += sizeof(struct nxpwifi_tkip_param); + km->key_param_set.len = cpu_to_le16(len); + cmd->size = cpu_to_le16(sizeof(struct nxpwifi_ie_types_header) + + len + sizeof(km->action) + S_DS_GEN); + } + + return 0; +} + +static int +nxpwifi_ret_sta_802_11_key_material(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_802_11_key_material *key; + int len; + + key = &resp->params.key_material; + + len = le16_to_cpu(key->key_param_set.key_params.aes.key_len); + if (len > sizeof(key->key_param_set.key_params.aes.key)) + return -EINVAL; + + if (le16_to_cpu(key->action) == HOST_ACT_GEN_SET) { + if ((le16_to_cpu(key->key_param_set.key_info) & KEY_MCAST)) { + nxpwifi_dbg(priv->adapter, INFO, + "info: key: GTK is set\n"); + priv->wpa_is_gtk_set = true; + priv->scan_block = false; + priv->port_open = true; + } + } + + if (key->key_param_set.key_type != KEY_TYPE_ID_AES && + key->key_param_set.key_type != KEY_TYPE_ID_GCMP_256) + return 0; + + memset(priv->aes_key.key_param_set.key_params.aes.key, 0, + sizeof(key->key_param_set.key_params.aes.key)); + priv->aes_key.key_param_set.key_params.aes.key_len = cpu_to_le16(len); + memcpy(priv->aes_key.key_param_set.key_params.aes.key, + key->key_param_set.key_params.aes.key, len); + + return 0; +} + +static int +nxpwifi_cmd_sta_802_11_bg_scan_config(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_802_11_bg_scan_config(priv, cmd, data_buf); +} + +static int +nxpwifi_cmd_sta_802_11_bg_scan_query(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_802_11_bg_scan_query(cmd); +} + +static int +nxpwifi_ret_sta_802_11_bg_scan_query(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int ret; + + ret = nxpwifi_ret_802_11_scan(priv, resp); + cfg80211_sched_scan_results(priv->wdev.wiphy, 0); + nxpwifi_dbg(adapter, CMD, + "info: CMD_RESP: BG_SCAN result is ready!\n"); + + return ret; +} + +static int +nxpwifi_cmd_sta_wmm_get_status(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(HOST_CMD_WMM_GET_STATUS); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_wmm_get_status) + + S_DS_GEN); + + return 0; +} + +static int +nxpwifi_ret_sta_wmm_get_status(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + return nxpwifi_ret_wmm_get_status(priv, resp); +} + +static int +nxpwifi_cmd_sta_802_11_subsc_evt(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_802_11_subsc_evt *subsc_evt = &cmd->params.subsc_evt; + struct nxpwifi_ds_misc_subsc_evt *subsc_evt_cfg = + (struct nxpwifi_ds_misc_subsc_evt *)data_buf; + struct nxpwifi_ie_types_rssi_threshold *rssi_tlv; + u16 event_bitmap; + u8 *pos; + + cmd->command = cpu_to_le16(HOST_CMD_802_11_SUBSCRIBE_EVENT); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_802_11_subsc_evt) + + S_DS_GEN); + + subsc_evt->action = cpu_to_le16(subsc_evt_cfg->action); + nxpwifi_dbg(priv->adapter, CMD, + "cmd: action: %d\n", subsc_evt_cfg->action); + + /* For query requests, no configuration TLV structures are to be added. */ + if (subsc_evt_cfg->action == HOST_ACT_GEN_GET) + return 0; + + subsc_evt->events = cpu_to_le16(subsc_evt_cfg->events); + + event_bitmap = subsc_evt_cfg->events; + nxpwifi_dbg(priv->adapter, CMD, "cmd: event bitmap : %16x\n", + event_bitmap); + + if ((subsc_evt_cfg->action == HOST_ACT_BITWISE_CLR || + subsc_evt_cfg->action == HOST_ACT_BITWISE_SET) && + event_bitmap == 0) { + nxpwifi_dbg(priv->adapter, ERROR, + "Error: No event specified\t" + "for bitwise action type\n"); + return -EINVAL; + } + + /* + * Append TLV structures for each of the specified events for + * subscribing or re-configuring. This is not required for + * bitwise unsubscribing request. + */ + if (subsc_evt_cfg->action == HOST_ACT_BITWISE_CLR) + return 0; + + pos = ((u8 *)subsc_evt) + + sizeof(struct host_cmd_ds_802_11_subsc_evt); + + if (event_bitmap & BITMASK_BCN_RSSI_LOW) { + rssi_tlv = (struct nxpwifi_ie_types_rssi_threshold *)pos; + + rssi_tlv->header.type = cpu_to_le16(TLV_TYPE_RSSI_LOW); + rssi_tlv->header.len = + cpu_to_le16(sizeof(struct nxpwifi_ie_types_rssi_threshold) - + sizeof(struct nxpwifi_ie_types_header)); + rssi_tlv->abs_value = subsc_evt_cfg->bcn_l_rssi_cfg.abs_value; + rssi_tlv->evt_freq = subsc_evt_cfg->bcn_l_rssi_cfg.evt_freq; + + nxpwifi_dbg(priv->adapter, EVENT, + "Cfg Beacon Low Rssi event,\t" + "RSSI:-%d dBm, Freq:%d\n", + subsc_evt_cfg->bcn_l_rssi_cfg.abs_value, + subsc_evt_cfg->bcn_l_rssi_cfg.evt_freq); + + pos += sizeof(struct nxpwifi_ie_types_rssi_threshold); + le16_unaligned_add_cpu + (&cmd->size, + sizeof(struct nxpwifi_ie_types_rssi_threshold)); + } + + if (event_bitmap & BITMASK_BCN_RSSI_HIGH) { + rssi_tlv = (struct nxpwifi_ie_types_rssi_threshold *)pos; + + rssi_tlv->header.type = cpu_to_le16(TLV_TYPE_RSSI_HIGH); + rssi_tlv->header.len = + cpu_to_le16(sizeof(struct nxpwifi_ie_types_rssi_threshold) - + sizeof(struct nxpwifi_ie_types_header)); + rssi_tlv->abs_value = subsc_evt_cfg->bcn_h_rssi_cfg.abs_value; + rssi_tlv->evt_freq = subsc_evt_cfg->bcn_h_rssi_cfg.evt_freq; + + nxpwifi_dbg(priv->adapter, EVENT, + "Cfg Beacon High Rssi event,\t" + "RSSI:-%d dBm, Freq:%d\n", + subsc_evt_cfg->bcn_h_rssi_cfg.abs_value, + subsc_evt_cfg->bcn_h_rssi_cfg.evt_freq); + + pos += sizeof(struct nxpwifi_ie_types_rssi_threshold); + le16_unaligned_add_cpu + (&cmd->size, + sizeof(struct nxpwifi_ie_types_rssi_threshold)); + } + + return 0; +} + +static int +nxpwifi_ret_sta_subsc_evt(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_802_11_subsc_evt *cmd_sub_event = + &resp->params.subsc_evt; + + /* + * For every subscribe event command (Get/Set/Clear), FW reports the current + * set of subscribed events + */ + nxpwifi_dbg(priv->adapter, EVENT, + "Bitmap of currently subscribed events: %16x\n", + le16_to_cpu(cmd_sub_event->events)); + + return 0; +} + +static int +nxpwifi_cmd_sta_802_11_tx_rate_query(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(HOST_CMD_802_11_TX_RATE_QUERY); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_tx_rate_query) + + S_DS_GEN); + priv->tx_rate = 0; + + return 0; +} + +static int +nxpwifi_ret_sta_802_11_tx_rate_query(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + priv->tx_rate = resp->params.tx_rate.tx_rate; + priv->tx_htinfo = resp->params.tx_rate.ht_info; + if (!priv->is_data_rate_auto) + priv->data_rate = + nxpwifi_index_to_data_rate(priv, priv->tx_rate, + priv->tx_htinfo); + + return 0; +} + +static int +nxpwifi_cmd_sta_mem_access(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct nxpwifi_ds_mem_rw *mem_rw = + (struct nxpwifi_ds_mem_rw *)data_buf; + struct host_cmd_ds_mem_access *mem_access = (void *)&cmd->params.mem; + + cmd->command = cpu_to_le16(HOST_CMD_MEM_ACCESS); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_mem_access) + + S_DS_GEN); + + mem_access->action = cpu_to_le16(cmd_action); + mem_access->addr = cpu_to_le32(mem_rw->addr); + mem_access->value = cpu_to_le32(mem_rw->value); + + return 0; +} + +static int +nxpwifi_ret_sta_mem_access(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_mem_access *mem = (void *)&resp->params.mem; + + priv->mem_rw.addr = le32_to_cpu(mem->addr); + priv->mem_rw.value = le32_to_cpu(mem->value); + + return 0; +} + +static u32 nxpwifi_parse_cal_cfg(u8 *src, size_t len, u8 *dst) +{ + u8 *s = src, *d = dst; + + while (s - src < len) { + if (*s && (isspace(*s) || *s == '\t')) { + s++; + continue; + } + if (isxdigit(*s)) { + if (kstrtou8(s, 16, d)) + return 0; + d++; + s += 2; + } else { + s++; + } + } + + return d - dst; +} + +static int +nxpwifi_cmd_sta_cfg_data(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u32 len; + u8 *data = (u8 *)cmd + S_DS_GEN; + + if (adapter->cal_data->data && adapter->cal_data->size > 0) { + len = nxpwifi_parse_cal_cfg((u8 *)adapter->cal_data->data, + adapter->cal_data->size, data); + nxpwifi_dbg(adapter, INFO, + "download cfg_data from config file\n"); + } else { + return -EINVAL; + } + + cmd->command = cpu_to_le16(HOST_CMD_CFG_DATA); + cmd->size = cpu_to_le16(S_DS_GEN + len); + + return 0; +} + +static int +nxpwifi_ret_sta_cfg_data(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + if (resp->result != HOST_RESULT_OK) { + nxpwifi_dbg(priv->adapter, ERROR, "Cal data cmd resp failed\n"); + return -EINVAL; + } + + return 0; +} + +static int +nxpwifi_cmd_sta_ver_ext(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(cmd_no); + cmd->params.verext.version_str_sel = + (u8)(get_unaligned((u32 *)data_buf)); + memcpy(&cmd->params, data_buf, sizeof(struct host_cmd_ds_version_ext)); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_version_ext) + + S_DS_GEN); + + return 0; +} + +static int +nxpwifi_ret_sta_ver_ext(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_version_ext *ver_ext = &resp->params.verext; + struct host_cmd_ds_version_ext *version_ext = + (struct host_cmd_ds_version_ext *)data_buf; + + if (test_and_clear_bit(NXPWIFI_IS_REQUESTING_FW_VEREXT, &priv->adapter->work_flags)) { + if (strncmp(ver_ext->version_str, "ChipRev:20, BB:9b(10.00), RF:40(21)", + NXPWIFI_VERSION_STR_LENGTH) == 0) { + struct nxpwifi_ds_auto_ds auto_ds = { + .auto_ds = DEEP_SLEEP_OFF, + }; + + nxpwifi_dbg(priv->adapter, MSG, + "Bad HW revision detected, disabling deep sleep\n"); + + if (nxpwifi_send_cmd(priv, HOST_CMD_802_11_PS_MODE_ENH, + DIS_AUTO_PS, BITMAP_AUTO_DS, &auto_ds, false)) { + nxpwifi_dbg(priv->adapter, MSG, + "Disabling deep sleep failed.\n"); + } + } + + return 0; + } + + if (version_ext) { + version_ext->version_str_sel = ver_ext->version_str_sel; + memcpy(version_ext->version_str, ver_ext->version_str, + NXPWIFI_VERSION_STR_LENGTH); + memcpy(priv->version_str, ver_ext->version_str, + NXPWIFI_VERSION_STR_LENGTH); + + /* Ensure the version string from the firmware is 0-terminated */ + priv->version_str[NXPWIFI_VERSION_STR_LENGTH - 1] = '\0'; + } + return 0; +} + +static int +nxpwifi_cmd_append_rpn_expression(struct nxpwifi_private *priv, + struct nxpwifi_mef_entry *mef_entry, + u8 **buffer) +{ + struct nxpwifi_mef_filter *filter = mef_entry->filter; + int i, byte_len; + u8 *stack_ptr = *buffer; + + for (i = 0; i < NXPWIFI_MEF_MAX_FILTERS; i++) { + filter = &mef_entry->filter[i]; + if (!filter->filt_type) + break; + put_unaligned_le32((u32)filter->repeat, stack_ptr); + stack_ptr += 4; + *stack_ptr = TYPE_DNUM; + stack_ptr += 1; + + byte_len = filter->byte_seq[NXPWIFI_MEF_MAX_BYTESEQ]; + memcpy(stack_ptr, filter->byte_seq, byte_len); + stack_ptr += byte_len; + *stack_ptr = byte_len; + stack_ptr += 1; + *stack_ptr = TYPE_BYTESEQ; + stack_ptr += 1; + put_unaligned_le32((u32)filter->offset, stack_ptr); + stack_ptr += 4; + *stack_ptr = TYPE_DNUM; + stack_ptr += 1; + + *stack_ptr = filter->filt_type; + stack_ptr += 1; + + if (filter->filt_action) { + *stack_ptr = filter->filt_action; + stack_ptr += 1; + } + + if (stack_ptr - *buffer > STACK_NBYTES) + return -ENOMEM; + } + + *buffer = stack_ptr; + return 0; +} + +static int +nxpwifi_cmd_sta_mef_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_mef_cfg *mef_cfg = &cmd->params.mef_cfg; + struct nxpwifi_ds_mef_cfg *mef = + (struct nxpwifi_ds_mef_cfg *)data_buf; + struct nxpwifi_fw_mef_entry *mef_entry = NULL; + u8 *pos = (u8 *)mef_cfg; + u16 i; + int ret = 0; + + cmd->command = cpu_to_le16(HOST_CMD_MEF_CFG); + + mef_cfg->criteria = cpu_to_le32(mef->criteria); + mef_cfg->num_entries = cpu_to_le16(mef->num_entries); + pos += sizeof(*mef_cfg); + + for (i = 0; i < mef->num_entries; i++) { + mef_entry = (struct nxpwifi_fw_mef_entry *)pos; + mef_entry->mode = mef->mef_entry[i].mode; + mef_entry->action = mef->mef_entry[i].action; + pos += sizeof(*mef_entry); + + ret = nxpwifi_cmd_append_rpn_expression(priv, + &mef->mef_entry[i], + &pos); + if (ret) + return ret; + + mef_entry->exprsize = + cpu_to_le16(pos - mef_entry->expr); + } + cmd->size = cpu_to_le16((u16)(pos - (u8 *)mef_cfg) + S_DS_GEN); + + return ret; +} + +static int +nxpwifi_cmd_sta_802_11_rssi_info(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(HOST_CMD_RSSI_INFO); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_802_11_rssi_info) + + S_DS_GEN); + cmd->params.rssi_info.action = cpu_to_le16(cmd_action); + cmd->params.rssi_info.ndata = cpu_to_le16(priv->data_avg_factor); + cmd->params.rssi_info.nbcn = cpu_to_le16(priv->bcn_avg_factor); + + /* Reset SNR/NF/RSSI values in private structure */ + priv->data_rssi_last = 0; + priv->data_nf_last = 0; + priv->data_rssi_avg = 0; + priv->data_nf_avg = 0; + priv->bcn_rssi_last = 0; + priv->bcn_nf_last = 0; + priv->bcn_rssi_avg = 0; + priv->bcn_nf_avg = 0; + + return 0; +} + +static int +nxpwifi_ret_sta_802_11_rssi_info(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_802_11_rssi_info_rsp *rssi_info_rsp = + &resp->params.rssi_info_rsp; + struct nxpwifi_ds_misc_subsc_evt *subsc_evt = + &priv->async_subsc_evt_storage; + + priv->data_rssi_last = le16_to_cpu(rssi_info_rsp->data_rssi_last); + priv->data_nf_last = le16_to_cpu(rssi_info_rsp->data_nf_last); + + priv->data_rssi_avg = le16_to_cpu(rssi_info_rsp->data_rssi_avg); + priv->data_nf_avg = le16_to_cpu(rssi_info_rsp->data_nf_avg); + + priv->bcn_rssi_last = le16_to_cpu(rssi_info_rsp->bcn_rssi_last); + priv->bcn_nf_last = le16_to_cpu(rssi_info_rsp->bcn_nf_last); + + priv->bcn_rssi_avg = le16_to_cpu(rssi_info_rsp->bcn_rssi_avg); + priv->bcn_nf_avg = le16_to_cpu(rssi_info_rsp->bcn_nf_avg); + + if (priv->subsc_evt_rssi_state == EVENT_HANDLED) + return 0; + + memset(subsc_evt, 0x00, sizeof(struct nxpwifi_ds_misc_subsc_evt)); + + /* Resubscribe low and high rssi events with new thresholds */ + subsc_evt->events = BITMASK_BCN_RSSI_LOW | BITMASK_BCN_RSSI_HIGH; + subsc_evt->action = HOST_ACT_BITWISE_SET; + if (priv->subsc_evt_rssi_state == RSSI_LOW_RECVD) { + subsc_evt->bcn_l_rssi_cfg.abs_value = abs(priv->bcn_rssi_avg - + priv->cqm_rssi_hyst); + subsc_evt->bcn_h_rssi_cfg.abs_value = abs(priv->cqm_rssi_thold); + } else if (priv->subsc_evt_rssi_state == RSSI_HIGH_RECVD) { + subsc_evt->bcn_l_rssi_cfg.abs_value = abs(priv->cqm_rssi_thold); + subsc_evt->bcn_h_rssi_cfg.abs_value = abs(priv->bcn_rssi_avg + + priv->cqm_rssi_hyst); + } + subsc_evt->bcn_l_rssi_cfg.evt_freq = 1; + subsc_evt->bcn_h_rssi_cfg.evt_freq = 1; + + priv->subsc_evt_rssi_state = EVENT_HANDLED; + + nxpwifi_send_cmd(priv, HOST_CMD_802_11_SUBSCRIBE_EVENT, + 0, 0, subsc_evt, false); + + return 0; +} + +static int +nxpwifi_cmd_sta_func_init(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + if (priv->adapter->hw_status == NXPWIFI_HW_STATUS_RESET) + priv->adapter->hw_status = NXPWIFI_HW_STATUS_READY; + cmd->command = cpu_to_le16(cmd_no); + cmd->size = cpu_to_le16(S_DS_GEN); + + return 0; +} + +static int +nxpwifi_cmd_sta_func_shutdown(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + priv->adapter->hw_status = NXPWIFI_HW_STATUS_RESET; + cmd->command = cpu_to_le16(cmd_no); + cmd->size = cpu_to_le16(S_DS_GEN); + + return 0; +} + +static int +nxpwifi_cmd_sta_11n_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_11n_cfg(priv, cmd, cmd_action, data_buf); +} + +static int +nxpwifi_cmd_sta_11n_addba_req(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_11n_addba_req(cmd, data_buf); +} + +static int +nxpwifi_ret_sta_11n_addba_req(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + return nxpwifi_ret_11n_addba_req(priv, resp); +} + +static int +nxpwifi_cmd_sta_11n_addba_rsp(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_11n_addba_rsp_gen(priv, cmd, data_buf); +} + +static int +nxpwifi_ret_sta_11n_addba_rsp(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + return nxpwifi_ret_11n_addba_resp(priv, resp); +} + +static int +nxpwifi_cmd_sta_11n_delba(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_11n_delba(cmd, data_buf); +} + +static int +nxpwifi_ret_sta_11n_delba(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + return nxpwifi_ret_11n_delba(priv, resp); +} + +static int +nxpwifi_cmd_sta_tx_power_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct nxpwifi_types_power_group *pg_tlv; + struct host_cmd_ds_txpwr_cfg *cmd_txp_cfg = &cmd->params.txp_cfg; + struct host_cmd_ds_txpwr_cfg *txp = + (struct host_cmd_ds_txpwr_cfg *)data_buf; + + cmd->command = cpu_to_le16(HOST_CMD_TXPWR_CFG); + cmd->size = + cpu_to_le16(S_DS_GEN + sizeof(struct host_cmd_ds_txpwr_cfg)); + switch (cmd_action) { + case HOST_ACT_GEN_SET: + if (txp->mode) { + pg_tlv = (struct nxpwifi_types_power_group + *)((unsigned long)txp + + sizeof(struct host_cmd_ds_txpwr_cfg)); + memmove(cmd_txp_cfg, txp, + sizeof(struct host_cmd_ds_txpwr_cfg) + + sizeof(struct nxpwifi_types_power_group) + + le16_to_cpu(pg_tlv->length)); + + pg_tlv = (struct nxpwifi_types_power_group *)((u8 *) + cmd_txp_cfg + + sizeof(struct host_cmd_ds_txpwr_cfg)); + cmd->size = cpu_to_le16(le16_to_cpu(cmd->size) + + sizeof(struct nxpwifi_types_power_group) + + le16_to_cpu(pg_tlv->length)); + } else { + memmove(cmd_txp_cfg, txp, sizeof(*txp)); + } + cmd_txp_cfg->action = cpu_to_le16(cmd_action); + break; + case HOST_ACT_GEN_GET: + cmd_txp_cfg->action = cpu_to_le16(cmd_action); + break; + } + + return 0; +} + +static int nxpwifi_get_power_level(struct nxpwifi_private *priv, void *data_buf) +{ + int length, max_power = -1, min_power = -1; + struct nxpwifi_types_power_group *pg_tlv_hdr; + struct nxpwifi_power_group *pg; + + if (!data_buf) + return -ENOMEM; + + pg_tlv_hdr = (struct nxpwifi_types_power_group *)((u8 *)data_buf); + pg = (struct nxpwifi_power_group *) + ((u8 *)pg_tlv_hdr + sizeof(struct nxpwifi_types_power_group)); + length = le16_to_cpu(pg_tlv_hdr->length); + + /* At least one structure required to update power */ + if (length < sizeof(struct nxpwifi_power_group)) + return 0; + + max_power = pg->power_max; + min_power = pg->power_min; + length -= sizeof(struct nxpwifi_power_group); + + while (length >= sizeof(struct nxpwifi_power_group)) { + pg++; + if (max_power < pg->power_max) + max_power = pg->power_max; + + if (min_power > pg->power_min) + min_power = pg->power_min; + + length -= sizeof(struct nxpwifi_power_group); + } + priv->min_tx_power_level = (u8)min_power; + priv->max_tx_power_level = (u8)max_power; + + return 0; +} + +static int +nxpwifi_ret_sta_tx_power_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_ds_txpwr_cfg *txp_cfg = &resp->params.txp_cfg; + struct nxpwifi_types_power_group *pg_tlv_hdr; + struct nxpwifi_power_group *pg; + u16 action = le16_to_cpu(txp_cfg->action); + u16 tlv_buf_left; + + pg_tlv_hdr = (struct nxpwifi_types_power_group *) + ((u8 *)txp_cfg + + sizeof(struct host_cmd_ds_txpwr_cfg)); + + pg = (struct nxpwifi_power_group *) + ((u8 *)pg_tlv_hdr + + sizeof(struct nxpwifi_types_power_group)); + + tlv_buf_left = le16_to_cpu(resp->size) - S_DS_GEN - sizeof(*txp_cfg); + if (tlv_buf_left < + le16_to_cpu(pg_tlv_hdr->length) + sizeof(*pg_tlv_hdr)) + return 0; + + switch (action) { + case HOST_ACT_GEN_GET: + if (adapter->hw_status == NXPWIFI_HW_STATUS_INITIALIZING) + nxpwifi_get_power_level(priv, pg_tlv_hdr); + + priv->tx_power_level = (u16)pg->power_min; + break; + + case HOST_ACT_GEN_SET: + if (!le32_to_cpu(txp_cfg->mode)) + break; + + if (pg->power_max == pg->power_min) + priv->tx_power_level = (u16)pg->power_min; + break; + default: + nxpwifi_dbg(adapter, ERROR, + "CMD_RESP: unknown cmd action %d\n", + action); + return 0; + } + nxpwifi_dbg(adapter, INFO, + "info: Current TxPower Level = %d, Max Power=%d, Min Power=%d\n", + priv->tx_power_level, priv->max_tx_power_level, + priv->min_tx_power_level); + + return 0; +} + +static int +nxpwifi_cmd_sta_tx_rate_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_tx_rate_cfg *rate_cfg = &cmd->params.tx_rate_cfg; + u16 *pbitmap_rates = (u16 *)data_buf; + struct nxpwifi_rate_scope *rate_scope; + struct nxpwifi_rate_drop_pattern *rate_drop; + u32 i; + + cmd->command = cpu_to_le16(HOST_CMD_TX_RATE_CFG); + + rate_cfg->action = cpu_to_le16(cmd_action); + rate_cfg->cfg_index = 0; + + rate_scope = (struct nxpwifi_rate_scope *)((u8 *)rate_cfg + + sizeof(struct host_cmd_ds_tx_rate_cfg)); + rate_scope->type = cpu_to_le16(TLV_TYPE_RATE_SCOPE); + rate_scope->length = cpu_to_le16 + (sizeof(*rate_scope) - sizeof(struct nxpwifi_ie_types_header)); + if (pbitmap_rates) { + rate_scope->hr_dsss_rate_bitmap = cpu_to_le16(pbitmap_rates[0]); + rate_scope->ofdm_rate_bitmap = cpu_to_le16(pbitmap_rates[1]); + for (i = 0; i < ARRAY_SIZE(rate_scope->ht_mcs_rate_bitmap); i++) + rate_scope->ht_mcs_rate_bitmap[i] = + cpu_to_le16(pbitmap_rates[2 + i]); + if (priv->adapter->fw_api_ver == NXPWIFI_FW_V15) { + for (i = 0; + i < ARRAY_SIZE(rate_scope->vht_mcs_rate_bitmap); + i++) + rate_scope->vht_mcs_rate_bitmap[i] = + cpu_to_le16(pbitmap_rates[10 + i]); + } + } else { + rate_scope->hr_dsss_rate_bitmap = + cpu_to_le16(priv->bitmap_rates[0]); + rate_scope->ofdm_rate_bitmap = + cpu_to_le16(priv->bitmap_rates[1]); + for (i = 0; i < ARRAY_SIZE(rate_scope->ht_mcs_rate_bitmap); i++) + rate_scope->ht_mcs_rate_bitmap[i] = + cpu_to_le16(priv->bitmap_rates[2 + i]); + if (priv->adapter->fw_api_ver == NXPWIFI_FW_V15) { + for (i = 0; + i < ARRAY_SIZE(rate_scope->vht_mcs_rate_bitmap); + i++) + rate_scope->vht_mcs_rate_bitmap[i] = + cpu_to_le16(priv->bitmap_rates[10 + i]); + } + } + + rate_drop = (struct nxpwifi_rate_drop_pattern *)((u8 *)rate_scope + + sizeof(struct nxpwifi_rate_scope)); + rate_drop->type = cpu_to_le16(TLV_TYPE_RATE_DROP_CONTROL); + rate_drop->length = cpu_to_le16(sizeof(rate_drop->rate_drop_mode)); + rate_drop->rate_drop_mode = 0; + + cmd->size = + cpu_to_le16(S_DS_GEN + sizeof(struct host_cmd_ds_tx_rate_cfg) + + sizeof(struct nxpwifi_rate_scope) + + sizeof(struct nxpwifi_rate_drop_pattern)); + + return 0; +} + +static void nxpwifi_ret_rate_scope(struct nxpwifi_private *priv, u8 *tlv_buf) +{ + struct nxpwifi_rate_scope *rate_scope; + int i; + + rate_scope = (struct nxpwifi_rate_scope *)tlv_buf; + priv->bitmap_rates[0] = + le16_to_cpu(rate_scope->hr_dsss_rate_bitmap); + priv->bitmap_rates[1] = + le16_to_cpu(rate_scope->ofdm_rate_bitmap); + for (i = 0; i < ARRAY_SIZE(rate_scope->ht_mcs_rate_bitmap); i++) + priv->bitmap_rates[2 + i] = + le16_to_cpu(rate_scope->ht_mcs_rate_bitmap[i]); + + if (priv->adapter->fw_api_ver == NXPWIFI_FW_V15) { + for (i = 0; i < ARRAY_SIZE(rate_scope->vht_mcs_rate_bitmap); + i++) + priv->bitmap_rates[10 + i] = + le16_to_cpu(rate_scope->vht_mcs_rate_bitmap[i]); + } +} + +static int +nxpwifi_ret_sta_tx_rate_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_tx_rate_cfg *rate_cfg = &resp->params.tx_rate_cfg; + struct nxpwifi_ie_types_header *head; + u16 tlv, tlv_buf_len, tlv_buf_left; + u8 *tlv_buf; + + tlv_buf = ((u8 *)rate_cfg) + sizeof(struct host_cmd_ds_tx_rate_cfg); + tlv_buf_left = le16_to_cpu(resp->size) - S_DS_GEN - sizeof(*rate_cfg); + + while (tlv_buf_left >= sizeof(*head)) { + head = (struct nxpwifi_ie_types_header *)tlv_buf; + tlv = le16_to_cpu(head->type); + tlv_buf_len = le16_to_cpu(head->len); + + if (tlv_buf_left < (sizeof(*head) + tlv_buf_len)) + break; + + switch (tlv) { + case TLV_TYPE_RATE_SCOPE: + nxpwifi_ret_rate_scope(priv, tlv_buf); + break; + /* Add RATE_DROP tlv here */ + } + + tlv_buf += (sizeof(*head) + tlv_buf_len); + tlv_buf_left -= (sizeof(*head) + tlv_buf_len); + } + + priv->is_data_rate_auto = nxpwifi_is_rate_auto(priv); + + if (priv->is_data_rate_auto) + priv->data_rate = 0; + else + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_TX_RATE_QUERY, + HOST_ACT_GEN_GET, 0, NULL, false); + + return 0; +} + +static int +nxpwifi_cmd_sta_reconfigure_rx_buff(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_recfg_tx_buf(priv, cmd, cmd_action, data_buf); +} + +static int +nxpwifi_ret_sta_reconfigure_rx_buff(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + if (0xffff != (u16)le16_to_cpu(resp->params.tx_buf.buff_size)) { + adapter->tx_buf_size = + (u16)le16_to_cpu(resp->params.tx_buf.buff_size); + adapter->tx_buf_size = + (adapter->tx_buf_size / NXPWIFI_SDIO_BLOCK_SIZE) * + NXPWIFI_SDIO_BLOCK_SIZE; + adapter->curr_tx_buf_size = adapter->tx_buf_size; + nxpwifi_dbg(adapter, CMD, "cmd: curr_tx_buf_size=%d\n", + adapter->curr_tx_buf_size); + + if (adapter->if_ops.update_mp_end_port) { + u16 mp_end_port; + + mp_end_port = + le16_to_cpu(resp->params.tx_buf.mp_end_port); + adapter->if_ops.update_mp_end_port(adapter, + mp_end_port); + } + } + + return 0; +} + +static int +nxpwifi_cmd_sta_chan_report_request(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_issue_chan_report_request(priv, cmd, data_buf); +} + +static int +nxpwifi_cmd_sta_amsdu_aggr_ctrl(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_amsdu_aggr_ctrl(cmd, cmd_action, data_buf); +} + +static int +nxpwifi_cmd_sta_robust_coex(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_robust_coex *coex = &cmd->params.coex; + bool *is_timeshare = (bool *)data_buf; + struct nxpwifi_ie_types_robust_coex *coex_tlv; + + cmd->command = cpu_to_le16(HOST_CMD_ROBUST_COEX); + cmd->size = cpu_to_le16(sizeof(*coex) + sizeof(*coex_tlv) + S_DS_GEN); + + coex->action = cpu_to_le16(cmd_action); + coex_tlv = (struct nxpwifi_ie_types_robust_coex *) + ((u8 *)coex + sizeof(*coex)); + coex_tlv->header.type = cpu_to_le16(TLV_TYPE_ROBUST_COEX); + coex_tlv->header.len = cpu_to_le16(sizeof(coex_tlv->mode)); + + if (coex->action == HOST_ACT_GEN_GET) + return 0; + + if (*is_timeshare) + coex_tlv->mode = cpu_to_le32(NXPWIFI_COEX_MODE_TIMESHARE); + else + coex_tlv->mode = cpu_to_le32(NXPWIFI_COEX_MODE_SPATIAL); + + return 0; +} + +static int +nxpwifi_ret_sta_robust_coex(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_robust_coex *coex = &resp->params.coex; + bool *is_timeshare = (bool *)data_buf; + struct nxpwifi_ie_types_robust_coex *coex_tlv; + u16 action = le16_to_cpu(coex->action); + u32 mode; + + coex_tlv = (struct nxpwifi_ie_types_robust_coex + *)((u8 *)coex + sizeof(struct host_cmd_ds_robust_coex)); + if (action == HOST_ACT_GEN_GET) { + mode = le32_to_cpu(coex_tlv->mode); + if (mode == NXPWIFI_COEX_MODE_TIMESHARE) + *is_timeshare = true; + else + *is_timeshare = false; + } + + return 0; +} + +static int +nxpwifi_cmd_sta_enh_power_mode(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_802_11_ps_mode_enh *psmode_enh = + &cmd->params.psmode_enh; + u16 ps_bitmap = (u16)cmd_type; + struct nxpwifi_ds_auto_ds *auto_ds = + (struct nxpwifi_ds_auto_ds *)data_buf; + u8 *tlv; + u16 cmd_size = 0; + + cmd->command = cpu_to_le16(HOST_CMD_802_11_PS_MODE_ENH); + if (cmd_action == DIS_AUTO_PS) { + psmode_enh->action = cpu_to_le16(DIS_AUTO_PS); + psmode_enh->params.ps_bitmap = cpu_to_le16(ps_bitmap); + cmd->size = cpu_to_le16(S_DS_GEN + sizeof(psmode_enh->action) + + sizeof(psmode_enh->params.ps_bitmap)); + } else if (cmd_action == GET_PS) { + psmode_enh->action = cpu_to_le16(GET_PS); + psmode_enh->params.ps_bitmap = cpu_to_le16(ps_bitmap); + cmd->size = cpu_to_le16(S_DS_GEN + sizeof(psmode_enh->action) + + sizeof(psmode_enh->params.ps_bitmap)); + } else if (cmd_action == EN_AUTO_PS) { + psmode_enh->action = cpu_to_le16(EN_AUTO_PS); + psmode_enh->params.ps_bitmap = cpu_to_le16(ps_bitmap); + cmd_size = S_DS_GEN + sizeof(psmode_enh->action) + + sizeof(psmode_enh->params.ps_bitmap); + tlv = (u8 *)cmd + cmd_size; + if (ps_bitmap & BITMAP_STA_PS) { + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_ie_types_ps_param *ps_tlv = + (struct nxpwifi_ie_types_ps_param *)tlv; + struct nxpwifi_ps_param *ps_mode = &ps_tlv->param; + + ps_tlv->header.type = cpu_to_le16(TLV_TYPE_PS_PARAM); + ps_tlv->header.len = cpu_to_le16(sizeof(*ps_tlv) - + sizeof(struct nxpwifi_ie_types_header)); + cmd_size += sizeof(*ps_tlv); + tlv += sizeof(*ps_tlv); + nxpwifi_dbg(priv->adapter, CMD, + "cmd: PS Command: Enter PS\n"); + ps_mode->null_pkt_interval = + cpu_to_le16(adapter->null_pkt_interval); + ps_mode->multiple_dtims = + cpu_to_le16(adapter->multiple_dtim); + ps_mode->bcn_miss_timeout = + cpu_to_le16(adapter->bcn_miss_time_out); + ps_mode->local_listen_interval = + cpu_to_le16(adapter->local_listen_interval); + ps_mode->delay_to_ps = + cpu_to_le16(adapter->delay_to_ps); + ps_mode->mode = cpu_to_le16(adapter->enhanced_ps_mode); + } + if (ps_bitmap & BITMAP_AUTO_DS) { + struct nxpwifi_ie_types_auto_ds_param *auto_ds_tlv = + (struct nxpwifi_ie_types_auto_ds_param *)tlv; + u16 idletime = 0; + + auto_ds_tlv->header.type = + cpu_to_le16(TLV_TYPE_AUTO_DS_PARAM); + auto_ds_tlv->header.len = + cpu_to_le16(sizeof(*auto_ds_tlv) - + sizeof(struct nxpwifi_ie_types_header)); + cmd_size += sizeof(*auto_ds_tlv); + tlv += sizeof(*auto_ds_tlv); + if (auto_ds) + idletime = auto_ds->idle_time; + nxpwifi_dbg(priv->adapter, CMD, + "cmd: PS Command: Enter Auto Deep Sleep\n"); + auto_ds_tlv->deep_sleep_timeout = cpu_to_le16(idletime); + } + cmd->size = cpu_to_le16(cmd_size); + } + return 0; +} + +static int +nxpwifi_ret_sta_enh_power_mode(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_ds_802_11_ps_mode_enh *ps_mode = + &resp->params.psmode_enh; + struct nxpwifi_ds_pm_cfg *pm_cfg = + (struct nxpwifi_ds_pm_cfg *)data_buf; + u16 action = le16_to_cpu(ps_mode->action); + u16 ps_bitmap = le16_to_cpu(ps_mode->params.ps_bitmap); + u16 auto_ps_bitmap = + le16_to_cpu(ps_mode->params.ps_bitmap); + + nxpwifi_dbg(adapter, INFO, + "info: %s: PS_MODE cmd reply result=%#x action=%#X\n", + __func__, resp->result, action); + if (action == EN_AUTO_PS) { + if (auto_ps_bitmap & BITMAP_AUTO_DS) { + nxpwifi_dbg(adapter, CMD, + "cmd: Enabled auto deep sleep\n"); + priv->adapter->is_deep_sleep = true; + } + if (auto_ps_bitmap & BITMAP_STA_PS) { + nxpwifi_dbg(adapter, CMD, + "cmd: Enabled STA power save\n"); + if (adapter->sleep_period.period) + nxpwifi_dbg(adapter, CMD, + "cmd: set to uapsd/pps mode\n"); + } + } else if (action == DIS_AUTO_PS) { + if (ps_bitmap & BITMAP_AUTO_DS) { + priv->adapter->is_deep_sleep = false; + nxpwifi_dbg(adapter, CMD, + "cmd: Disabled auto deep sleep\n"); + } + if (ps_bitmap & BITMAP_STA_PS) { + nxpwifi_dbg(adapter, CMD, + "cmd: Disabled STA power save\n"); + if (adapter->sleep_period.period) { + adapter->delay_null_pkt = false; + adapter->tx_lock_flag = false; + adapter->pps_uapsd_mode = false; + } + } + } else if (action == GET_PS) { + if (ps_bitmap & BITMAP_STA_PS) + adapter->ps_mode = NXPWIFI_802_11_POWER_MODE_PSP; + else + adapter->ps_mode = NXPWIFI_802_11_POWER_MODE_CAM; + + nxpwifi_dbg(adapter, CMD, + "cmd: ps_bitmap=%#x\n", ps_bitmap); + + if (pm_cfg) { + /* This section is for get power save mode */ + if (ps_bitmap & BITMAP_STA_PS) + pm_cfg->param.ps_mode = 1; + else + pm_cfg->param.ps_mode = 0; + } + } + return 0; +} + +static int +nxpwifi_cmd_sta_802_11_hs_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_ds_802_11_hs_cfg_enh *hs_cfg = &cmd->params.opt_hs_cfg; + struct nxpwifi_hs_config_param *hscfg_param = + (struct nxpwifi_hs_config_param *)data_buf; + u8 *tlv = (u8 *)hs_cfg + sizeof(struct host_cmd_ds_802_11_hs_cfg_enh); + struct nxpwifi_ps_param_in_hs *psparam_tlv = NULL; + bool hs_activate = false; + u16 size; + + if (!hscfg_param) + /* New Activate command */ + hs_activate = true; + cmd->command = cpu_to_le16(HOST_CMD_802_11_HS_CFG_ENH); + + if (!hs_activate && + hscfg_param->conditions != cpu_to_le32(HS_CFG_CANCEL) && + (adapter->arp_filter_size > 0 && + adapter->arp_filter_size <= ARP_FILTER_MAX_BUF_SIZE)) { + nxpwifi_dbg(adapter, CMD, + "cmd: Attach %d bytes ArpFilter to HSCfg cmd\n", + adapter->arp_filter_size); + memcpy(((u8 *)hs_cfg) + + sizeof(struct host_cmd_ds_802_11_hs_cfg_enh), + adapter->arp_filter, adapter->arp_filter_size); + size = adapter->arp_filter_size + + sizeof(struct host_cmd_ds_802_11_hs_cfg_enh) + + S_DS_GEN; + tlv = (u8 *)hs_cfg + + sizeof(struct host_cmd_ds_802_11_hs_cfg_enh) + + adapter->arp_filter_size; + } else { + size = S_DS_GEN + sizeof(struct host_cmd_ds_802_11_hs_cfg_enh); + } + if (hs_activate) { + hs_cfg->action = cpu_to_le16(HS_ACTIVATE); + hs_cfg->params.hs_activate.resp_ctrl = cpu_to_le16(RESP_NEEDED); + + adapter->hs_activated_manually = true; + nxpwifi_dbg(priv->adapter, CMD, + "cmd: Activating host sleep manually\n"); + } else { + hs_cfg->action = cpu_to_le16(HS_CONFIGURE); + hs_cfg->params.hs_config.conditions = hscfg_param->conditions; + hs_cfg->params.hs_config.gpio = hscfg_param->gpio; + hs_cfg->params.hs_config.gap = hscfg_param->gap; + + size += sizeof(struct nxpwifi_ps_param_in_hs); + psparam_tlv = (struct nxpwifi_ps_param_in_hs *)tlv; + psparam_tlv->header.type = + cpu_to_le16(TLV_TYPE_PS_PARAMS_IN_HS); + psparam_tlv->header.len = + cpu_to_le16(sizeof(struct nxpwifi_ps_param_in_hs) + - sizeof(struct nxpwifi_ie_types_header)); + psparam_tlv->hs_wake_int = cpu_to_le32(HS_DEF_WAKE_INTERVAL); + psparam_tlv->hs_inact_timeout = + cpu_to_le32(HS_DEF_INACTIVITY_TIMEOUT); + + nxpwifi_dbg(adapter, CMD, + "cmd: HS_CFG_CMD: condition:0x%x gpio:0x%x gap:0x%x\n", + hs_cfg->params.hs_config.conditions, + hs_cfg->params.hs_config.gpio, + hs_cfg->params.hs_config.gap); + } + cmd->size = cpu_to_le16(size); + + return 0; +} + +static int +nxpwifi_ret_sta_802_11_hs_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + return nxpwifi_ret_802_11_hs_cfg(priv, resp); +} + +static int +nxpwifi_cmd_sta_set_bss_mode(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(cmd_no); + if (priv->bss_mode == NL80211_IFTYPE_STATION) + cmd->params.bss_mode.con_type = CONNECTION_TYPE_INFRA; + else if (priv->bss_mode == NL80211_IFTYPE_AP) + cmd->params.bss_mode.con_type = CONNECTION_TYPE_AP; + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_set_bss_mode) + + S_DS_GEN); + + return 0; +} + +static int +nxpwifi_cmd_sta_802_11_net_monitor(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct nxpwifi_802_11_net_monitor *net_mon; + struct host_cmd_ds_802_11_net_monitor *cmd_net_mon = + &cmd->params.net_mon; + struct chan_band_param *chan_band = NULL; + u8 sec_chan_offset = 0; + u32 bw_offset = 0; + + net_mon = (struct nxpwifi_802_11_net_monitor *)data_buf; + + cmd->size = cpu_to_le16(S_DS_GEN + + sizeof(struct host_cmd_ds_802_11_net_monitor) + + sizeof(struct chan_band_param)); + cmd->command = cpu_to_le16(cmd_no); + cmd_net_mon->action = cpu_to_le16(cmd_action); + + if (cmd_action == HOST_ACT_GEN_SET) { + if (net_mon->enable_net_mon) { + cmd_net_mon->enable_net_mon = cpu_to_le16(0x1); + cmd_net_mon->filter_flag = cpu_to_le16((u16) + net_mon->filter_flag); + } + + if (net_mon->enable_net_mon && net_mon->channel) { + chan_band = &cmd_net_mon->monitor_chan.chan_band_param[0]; + cmd_net_mon->monitor_chan.header.type = + cpu_to_le16(TLV_TYPE_CHANNELBANDLIST); + cmd_net_mon->monitor_chan.header.len = + cpu_to_le16(sizeof(struct chan_band_param)); + chan_band->chan_number = (u8)net_mon->channel; + chan_band->band_cfg.chan_band = + nxpwifi_band_to_radio_type((u16)net_mon->band); + + if (net_mon->band & BAND_GN || + net_mon->band & BAND_AN || + net_mon->band & BAND_GAC || + net_mon->band & BAND_AAC) { + bw_offset = net_mon->chan_bandwidth; + if (bw_offset == CHANNEL_BW_40MHZ_ABOVE) { + chan_band->band_cfg.chan_2O_ffset = + NXPWIFI_SEC_CHAN_ABOVE; + chan_band->band_cfg.chan_width = + CHAN_BW_40MHZ; + } else if (bw_offset == CHANNEL_BW_40MHZ_BELOW) { + chan_band->band_cfg.chan_2O_ffset = + NXPWIFI_SEC_CHAN_BELOW; + chan_band->band_cfg.chan_width = + CHAN_BW_40MHZ; + } else if (bw_offset == CHANNEL_BW_80MHZ) { + sec_chan_offset = + nxpwifi_get_sec_chan_offset(net_mon->channel); + if (sec_chan_offset == NXPWIFI_SEC_CHAN_ABOVE) + chan_band->band_cfg.chan_2O_ffset = + NXPWIFI_SEC_CHAN_ABOVE; + else if (sec_chan_offset == NXPWIFI_SEC_CHAN_BELOW) + chan_band->band_cfg.chan_2O_ffset = + NXPWIFI_SEC_CHAN_BELOW; + chan_band->band_cfg.chan_width = CHAN_BW_80MHZ; + } + } + } + } + return 0; +} + +static int +nxpwifi_ret_sta_802_11_net_monitor(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_802_11_net_monitor *cmd_net_mon = &resp->params.net_mon; + + nxpwifi_dbg(priv->adapter, CMD, + "cmd: NET_MONITOR_CMD: action: %d, enable: %d, flag: %d ch: %d band: %d bw: %d offset: %d\n", + le16_to_cpu(cmd_net_mon->action), + le16_to_cpu(cmd_net_mon->enable_net_mon), + le16_to_cpu(cmd_net_mon->filter_flag), + cmd_net_mon->monitor_chan.chan_band_param[0].chan_number, + cmd_net_mon->monitor_chan.chan_band_param[0].band_cfg.chan_band, + cmd_net_mon->monitor_chan.chan_band_param[0].band_cfg.chan_width, + cmd_net_mon->monitor_chan.chan_band_param[0].band_cfg.chan_2O_ffset); + priv->adapter->enable_net_mon = le16_to_cpu(cmd_net_mon->enable_net_mon); + return 0; +} + +static int +nxpwifi_cmd_sta_802_11_scan_ext(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_802_11_scan_ext(priv, cmd, data_buf); +} + +static int +nxpwifi_ret_sta_802_11_scan_ext(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int ret; + + ret = nxpwifi_ret_802_11_scan_ext(priv, resp); + adapter->curr_cmd->wait_q_enabled = false; + + return ret; +} + +static int +nxpwifi_cmd_sta_coalesce_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_coalesce_cfg *coalesce_cfg = + &cmd->params.coalesce_cfg; + struct nxpwifi_ds_coalesce_cfg *cfg = + (struct nxpwifi_ds_coalesce_cfg *)data_buf; + struct coalesce_filt_field_param *param; + u16 cnt, idx, length; + struct coalesce_receive_filt_rule *rule; + + cmd->command = cpu_to_le16(HOST_CMD_COALESCE_CFG); + cmd->size = cpu_to_le16(S_DS_GEN); + + coalesce_cfg->action = cpu_to_le16(cmd_action); + coalesce_cfg->num_of_rules = cpu_to_le16(cfg->num_of_rules); + rule = (void *)coalesce_cfg->rule_data; + + for (cnt = 0; cnt < cfg->num_of_rules; cnt++) { + rule->header.type = cpu_to_le16(TLV_TYPE_COALESCE_RULE); + rule->max_coalescing_delay = + cpu_to_le16(cfg->rule[cnt].max_coalescing_delay); + rule->pkt_type = cfg->rule[cnt].pkt_type; + rule->num_of_fields = cfg->rule[cnt].num_of_fields; + + length = 0; + + param = rule->params; + for (idx = 0; idx < cfg->rule[cnt].num_of_fields; idx++) { + param->operation = cfg->rule[cnt].params[idx].operation; + param->operand_len = + cfg->rule[cnt].params[idx].operand_len; + param->offset = + cpu_to_le16(cfg->rule[cnt].params[idx].offset); + memcpy(param->operand_byte_stream, + cfg->rule[cnt].params[idx].operand_byte_stream, + param->operand_len); + + length += sizeof(struct coalesce_filt_field_param); + + param++; + } + + /* + * Total rule length is sizeof max_coalescing_delay(u16), + * num_of_fields(u8), pkt_type(u8) and total length of the all + * params + */ + rule->header.len = cpu_to_le16(length + sizeof(u16) + + sizeof(u8) + sizeof(u8)); + + /* Add the rule length to the command size */ + le16_unaligned_add_cpu(&cmd->size, + le16_to_cpu(rule->header.len) + + sizeof(struct nxpwifi_ie_types_header)); + + rule = (void *)((u8 *)rule->params + length); + } + + /* Add sizeof action, num_of_rules to total command length */ + le16_unaligned_add_cpu(&cmd->size, sizeof(u16) + sizeof(u16)); + + return 0; +} + +static int +nxpwifi_cmd_sta_mgmt_frame_reg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(cmd_no); + cmd->params.reg_mask.action = cpu_to_le16(cmd_action); + cmd->params.reg_mask.mask = + cpu_to_le32(get_unaligned((u32 *)data_buf)); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_mgmt_frame_reg) + + S_DS_GEN); + + return 0; +} + +static int +nxpwifi_cmd_sta_remain_on_chan(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(cmd_no); + memcpy(&cmd->params, data_buf, + sizeof(struct host_cmd_ds_remain_on_chan)); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_remain_on_chan) + + S_DS_GEN); + + return 0; +} + +static int +nxpwifi_ret_sta_remain_on_chan(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_remain_on_chan *resp_cfg = &resp->params.roc_cfg; + struct host_cmd_ds_remain_on_chan *roc_cfg = + (struct host_cmd_ds_remain_on_chan *)data_buf; + + if (roc_cfg) + memcpy(roc_cfg, resp_cfg, sizeof(*roc_cfg)); + + return 0; +} + +static int +nxpwifi_cmd_sta_gtk_rekey_offload(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_gtk_rekey_params *rekey = &cmd->params.rekey; + struct cfg80211_gtk_rekey_data *data = + (struct cfg80211_gtk_rekey_data *)data_buf; + u64 rekey_ctr; + + cmd->command = cpu_to_le16(HOST_CMD_GTK_REKEY_OFFLOAD_CFG); + cmd->size = cpu_to_le16(sizeof(*rekey) + S_DS_GEN); + + rekey->action = cpu_to_le16(cmd_action); + if (cmd_action == HOST_ACT_GEN_SET) { + memcpy(rekey->kek, data->kek, NL80211_KEK_LEN); + memcpy(rekey->kck, data->kck, NL80211_KCK_LEN); + rekey_ctr = be64_to_cpup((__be64 *)data->replay_ctr); + rekey->replay_ctr_low = cpu_to_le32((u32)rekey_ctr); + rekey->replay_ctr_high = + cpu_to_le32((u32)((u64)rekey_ctr >> 32)); + } + + return 0; +} + +static int +nxpwifi_cmd_sta_11ac_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_11ac_cfg(priv, cmd, cmd_action, data_buf); +} + +static int +nxpwifi_cmd_sta_hs_wakeup_reason(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(HOST_CMD_HS_WAKEUP_REASON); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_wakeup_reason) + + S_DS_GEN); + + return 0; +} + +static int +nxpwifi_ret_sta_hs_wakeup_reason(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_wakeup_reason *wakeup_reason = + (struct host_cmd_ds_wakeup_reason *)data_buf; + wakeup_reason->wakeup_reason = + resp->params.hs_wakeup_reason.wakeup_reason; + + return 0; +} + +static int +nxpwifi_cmd_sta_mc_policy(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_multi_chan_policy *mc_pol = &cmd->params.mc_policy; + const u16 *drcs_info = data_buf; + + mc_pol->action = cpu_to_le16(cmd_action); + mc_pol->policy = cpu_to_le16(*drcs_info); + cmd->command = cpu_to_le16(HOST_CMD_MC_POLICY); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_multi_chan_policy) + + S_DS_GEN); + return 0; +} + +static int +nxpwifi_cmd_sta_sdio_rx_aggr_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_sdio_sp_rx_aggr_cfg *cfg = + &cmd->params.sdio_rx_aggr_cfg; + + cmd->command = cpu_to_le16(HOST_CMD_SDIO_SP_RX_AGGR_CFG); + cmd->size = + cpu_to_le16(sizeof(struct host_cmd_sdio_sp_rx_aggr_cfg) + + S_DS_GEN); + cfg->action = cmd_action; + if (cmd_action == HOST_ACT_GEN_SET) + cfg->enable = *(u8 *)data_buf; + + return 0; +} + +static int +nxpwifi_ret_sta_sdio_rx_aggr_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_sdio_sp_rx_aggr_cfg *cfg = + &resp->params.sdio_rx_aggr_cfg; + + adapter->sdio_rx_aggr_enable = cfg->enable; + adapter->sdio_rx_block_size = le16_to_cpu(cfg->block_size); + + return 0; +} + +static int +nxpwifi_cmd_sta_get_chan_info(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_sta_configure *sta_cfg_cmd = &cmd->params.sta_cfg; + struct host_cmd_tlv_channel_band *tlv_band_channel = + (struct host_cmd_tlv_channel_band *)sta_cfg_cmd->tlv_buffer; + + cmd->command = cpu_to_le16(HOST_CMD_STA_CONFIGURE); + cmd->size = cpu_to_le16(sizeof(*sta_cfg_cmd) + + sizeof(*tlv_band_channel) + S_DS_GEN); + sta_cfg_cmd->action = cpu_to_le16(cmd_action); + memset(tlv_band_channel, 0, sizeof(*tlv_band_channel)); + tlv_band_channel->header.type = cpu_to_le16(TLV_TYPE_CHANNELBANDLIST); + tlv_band_channel->header.len = cpu_to_le16(sizeof(*tlv_band_channel) - + sizeof(struct nxpwifi_ie_types_header)); + + return 0; +} + +static int +nxpwifi_ret_sta_get_chan_info(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_sta_configure *sta_cfg_cmd = &resp->params.sta_cfg; + struct nxpwifi_channel_band *channel_band = + (struct nxpwifi_channel_band *)data_buf; + struct host_cmd_tlv_channel_band *tlv_band_channel; + + tlv_band_channel = + (struct host_cmd_tlv_channel_band *)sta_cfg_cmd->tlv_buffer; + memcpy(&channel_band->band_config, &tlv_band_channel->band_config, + sizeof(struct nxpwifi_band_config)); + channel_band->channel = tlv_band_channel->channel; + + return 0; +} + +static int +nxpwifi_cmd_sta_chan_region_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_chan_region_cfg *reg = &cmd->params.reg_cfg; + + cmd->command = cpu_to_le16(HOST_CMD_CHAN_REGION_CFG); + cmd->size = cpu_to_le16(sizeof(*reg) + S_DS_GEN); + + if (cmd_action == HOST_ACT_GEN_GET) + reg->action = cpu_to_le16(cmd_action); + + return 0; +} + +static struct ieee80211_regdomain * +nxpwifi_create_custom_regdomain(struct nxpwifi_private *priv, + u8 *buf, u16 buf_len) +{ + u16 num_chan = buf_len / 2; + struct ieee80211_regdomain *regd; + struct ieee80211_reg_rule *rule; + bool new_rule; + int idx, freq, prev_freq = 0; + u32 bw, prev_bw = 0; + u8 chflags, prev_chflags = 0, valid_rules = 0; + + if (WARN_ON_ONCE(num_chan > NL80211_MAX_SUPP_REG_RULES)) + return ERR_PTR(-EINVAL); + + regd = kzalloc_flex(*regd, reg_rules, num_chan, GFP_KERNEL); + if (!regd) + return ERR_PTR(-ENOMEM); + + for (idx = 0; idx < num_chan; idx++) { + u8 chan; + enum nl80211_band band; + + chan = *buf++; + if (!chan) { + kfree(regd); + return NULL; + } + chflags = *buf++; + band = (chan <= 14) ? NL80211_BAND_2GHZ : NL80211_BAND_5GHZ; + freq = ieee80211_channel_to_frequency(chan, band); + new_rule = false; + + if (chflags & NXPWIFI_CHANNEL_DISABLED) + continue; + + if (band == NL80211_BAND_5GHZ) { + if (!(chflags & NXPWIFI_CHANNEL_NOHT80)) + bw = MHZ_TO_KHZ(80); + else if (!(chflags & NXPWIFI_CHANNEL_NOHT40)) + bw = MHZ_TO_KHZ(40); + else + bw = MHZ_TO_KHZ(20); + } else { + if (!(chflags & NXPWIFI_CHANNEL_NOHT40)) + bw = MHZ_TO_KHZ(40); + else + bw = MHZ_TO_KHZ(20); + } + + if (idx == 0 || prev_chflags != chflags || prev_bw != bw || + freq - prev_freq > 20) { + valid_rules++; + new_rule = true; + } + + rule = ®d->reg_rules[valid_rules - 1]; + + rule->freq_range.end_freq_khz = MHZ_TO_KHZ(freq + 10); + + prev_chflags = chflags; + prev_freq = freq; + prev_bw = bw; + + if (!new_rule) + continue; + + rule->freq_range.start_freq_khz = MHZ_TO_KHZ(freq - 10); + rule->power_rule.max_eirp = DBM_TO_MBM(19); + + if (chflags & NXPWIFI_CHANNEL_PASSIVE) + rule->flags = NL80211_RRF_NO_IR; + + if (chflags & NXPWIFI_CHANNEL_DFS) + rule->flags = NL80211_RRF_DFS; + + rule->freq_range.max_bandwidth_khz = bw; + } + + regd->n_reg_rules = valid_rules; + regd->alpha2[0] = '9'; + regd->alpha2[1] = '9'; + + return regd; +} + +static int +nxpwifi_ret_sta_chan_region_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_chan_region_cfg *reg = &resp->params.reg_cfg; + u16 action = le16_to_cpu(reg->action); + u16 tlv, tlv_buf_len, tlv_buf_left; + struct nxpwifi_ie_types_header *head; + struct ieee80211_regdomain *regd; + u8 *tlv_buf; + + if (action != HOST_ACT_GEN_GET) + return 0; + + tlv_buf = (u8 *)reg + sizeof(*reg); + tlv_buf_left = le16_to_cpu(resp->size) - S_DS_GEN - sizeof(*reg); + + while (tlv_buf_left >= sizeof(*head)) { + head = (struct nxpwifi_ie_types_header *)tlv_buf; + tlv = le16_to_cpu(head->type); + tlv_buf_len = le16_to_cpu(head->len); + + if (tlv_buf_left < (sizeof(*head) + tlv_buf_len)) + break; + + switch (tlv) { + case TLV_TYPE_CHAN_ATTR_CFG: + nxpwifi_dbg_dump(priv->adapter, CMD_D, "CHAN:", + (u8 *)head + sizeof(*head), + tlv_buf_len); + regd = nxpwifi_create_custom_regdomain(priv, (u8 *)head + + sizeof(*head), + tlv_buf_len); + if (!IS_ERR(regd)) + priv->adapter->regd = regd; + break; + } + + tlv_buf += (sizeof(*head) + tlv_buf_len); + tlv_buf_left -= (sizeof(*head) + tlv_buf_len); + } + + return 0; +} + +static int +nxpwifi_cmd_sta_pkt_aggr_ctrl(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + cmd->command = cpu_to_le16(cmd_no); + cmd->params.pkt_aggr_ctrl.action = cpu_to_le16(cmd_action); + cmd->params.pkt_aggr_ctrl.enable = cpu_to_le16(*(u16 *)data_buf); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_pkt_aggr_ctrl) + + S_DS_GEN); + + return 0; +} + +static int +nxpwifi_ret_sta_pkt_aggr_ctrl(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct host_cmd_ds_pkt_aggr_ctrl *pkt_aggr_ctrl = + &resp->params.pkt_aggr_ctrl; + struct nxpwifi_adapter *adapter = priv->adapter; + + adapter->bus_aggr.enable = le16_to_cpu(pkt_aggr_ctrl->enable); + if (adapter->bus_aggr.enable) + adapter->intf_hdr_len = INTF_HEADER_LEN; + adapter->bus_aggr.mode = NXPWIFI_BUS_AGGR_MODE_LEN_V2; + adapter->bus_aggr.tx_aggr_max_size = + le16_to_cpu(pkt_aggr_ctrl->tx_aggr_max_size); + adapter->bus_aggr.tx_aggr_max_num = + le16_to_cpu(pkt_aggr_ctrl->tx_aggr_max_num); + adapter->bus_aggr.tx_aggr_align = + le16_to_cpu(pkt_aggr_ctrl->tx_aggr_align); + + return 0; +} + +static int +nxpwifi_cmd_sta_11ax_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_11ax_cfg(priv, cmd, cmd_action, data_buf); +} + +static int +nxpwifi_ret_sta_11ax_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + return nxpwifi_ret_11ax_cfg(priv, resp, data_buf); +} + +static int +nxpwifi_cmd_sta_11ax_cmd(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_11ax_cmd(priv, cmd, cmd_action, data_buf); +} + +static int +nxpwifi_ret_sta_11ax_cmd(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + return nxpwifi_ret_11ax_cmd(priv, resp, data_buf); +} + +static int +nxpwifi_cmd_sta_twt_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_twt_cfg(priv, cmd, cmd_action, data_buf); +} + +static int +nxpwifi_ret_sta_twt_cfg(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + return nxpwifi_ret_twt_cfg(priv, resp, data_buf); +} + +static const struct nxpwifi_cmd_entry cmd_table_sta[] = { + {.cmd_no = HOST_CMD_GET_HW_SPEC, + .prepare_cmd = nxpwifi_cmd_sta_get_hw_spec, + .cmd_resp = nxpwifi_ret_sta_get_hw_spec}, + {.cmd_no = HOST_CMD_802_11_SCAN, + .prepare_cmd = nxpwifi_cmd_sta_802_11_scan, + .cmd_resp = nxpwifi_ret_sta_802_11_scan}, + {.cmd_no = HOST_CMD_802_11_GET_LOG, + .prepare_cmd = nxpwifi_cmd_sta_802_11_get_log, + .cmd_resp = nxpwifi_ret_sta_802_11_get_log}, + {.cmd_no = HOST_CMD_MAC_MULTICAST_ADR, + .prepare_cmd = nxpwifi_cmd_sta_mac_multicast_adr, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_802_11_ASSOCIATE, + .prepare_cmd = nxpwifi_cmd_sta_802_11_associate, + .cmd_resp = nxpwifi_ret_sta_802_11_associate}, + {.cmd_no = HOST_CMD_802_11_SNMP_MIB, + .prepare_cmd = nxpwifi_cmd_sta_802_11_snmp_mib, + .cmd_resp = nxpwifi_ret_sta_802_11_snmp_mib}, + {.cmd_no = HOST_CMD_MAC_REG_ACCESS, + .prepare_cmd = nxpwifi_cmd_sta_reg_access, + .cmd_resp = nxpwifi_ret_sta_reg_access}, + {.cmd_no = HOST_CMD_BBP_REG_ACCESS, + .prepare_cmd = nxpwifi_cmd_sta_reg_access, + .cmd_resp = nxpwifi_ret_sta_reg_access}, + {.cmd_no = HOST_CMD_RF_REG_ACCESS, + .prepare_cmd = nxpwifi_cmd_sta_reg_access, + .cmd_resp = nxpwifi_ret_sta_reg_access}, + {.cmd_no = HOST_CMD_RF_TX_PWR, + .prepare_cmd = nxpwifi_cmd_sta_rf_tx_pwr, + .cmd_resp = nxpwifi_ret_sta_rf_tx_pwr}, + {.cmd_no = HOST_CMD_RF_ANTENNA, + .prepare_cmd = nxpwifi_cmd_sta_rf_antenna, + .cmd_resp = nxpwifi_ret_sta_rf_antenna}, + {.cmd_no = HOST_CMD_802_11_DEAUTHENTICATE, + .prepare_cmd = nxpwifi_cmd_sta_802_11_deauthenticate, + .cmd_resp = nxpwifi_ret_sta_802_11_deauthenticate}, + {.cmd_no = HOST_CMD_MAC_CONTROL, + .prepare_cmd = nxpwifi_cmd_sta_mac_control, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_802_11_MAC_ADDRESS, + .prepare_cmd = nxpwifi_cmd_sta_802_11_mac_address, + .cmd_resp = nxpwifi_ret_sta_802_11_mac_address}, + {.cmd_no = HOST_CMD_802_11_EEPROM_ACCESS, + .prepare_cmd = nxpwifi_cmd_sta_reg_access, + .cmd_resp = nxpwifi_ret_sta_reg_access}, + {.cmd_no = HOST_CMD_802_11D_DOMAIN_INFO, + .prepare_cmd = nxpwifi_cmd_sta_802_11d_domain_info, + .cmd_resp = nxpwifi_ret_sta_802_11d_domain_info}, + {.cmd_no = HOST_CMD_802_11_KEY_MATERIAL, + .prepare_cmd = nxpwifi_cmd_sta_802_11_key_material, + .cmd_resp = nxpwifi_ret_sta_802_11_key_material}, + {.cmd_no = HOST_CMD_802_11_BG_SCAN_CONFIG, + .prepare_cmd = nxpwifi_cmd_sta_802_11_bg_scan_config, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_802_11_BG_SCAN_QUERY, + .prepare_cmd = nxpwifi_cmd_sta_802_11_bg_scan_query, + .cmd_resp = nxpwifi_ret_sta_802_11_bg_scan_query}, + {.cmd_no = HOST_CMD_WMM_GET_STATUS, + .prepare_cmd = nxpwifi_cmd_sta_wmm_get_status, + .cmd_resp = nxpwifi_ret_sta_wmm_get_status}, + {.cmd_no = HOST_CMD_802_11_SUBSCRIBE_EVENT, + .prepare_cmd = nxpwifi_cmd_sta_802_11_subsc_evt, + .cmd_resp = nxpwifi_ret_sta_subsc_evt}, + {.cmd_no = HOST_CMD_802_11_TX_RATE_QUERY, + .prepare_cmd = nxpwifi_cmd_sta_802_11_tx_rate_query, + .cmd_resp = nxpwifi_ret_sta_802_11_tx_rate_query}, + {.cmd_no = HOST_CMD_MEM_ACCESS, + .prepare_cmd = nxpwifi_cmd_sta_mem_access, + .cmd_resp = nxpwifi_ret_sta_mem_access}, + {.cmd_no = HOST_CMD_CFG_DATA, + .prepare_cmd = nxpwifi_cmd_sta_cfg_data, + .cmd_resp = nxpwifi_ret_sta_cfg_data}, + {.cmd_no = HOST_CMD_VERSION_EXT, + .prepare_cmd = nxpwifi_cmd_sta_ver_ext, + .cmd_resp = nxpwifi_ret_sta_ver_ext}, + {.cmd_no = HOST_CMD_MEF_CFG, + .prepare_cmd = nxpwifi_cmd_sta_mef_cfg, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_RSSI_INFO, + .prepare_cmd = nxpwifi_cmd_sta_802_11_rssi_info, + .cmd_resp = nxpwifi_ret_sta_802_11_rssi_info}, + {.cmd_no = HOST_CMD_FUNC_INIT, + .prepare_cmd = nxpwifi_cmd_sta_func_init, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_FUNC_SHUTDOWN, + .prepare_cmd = nxpwifi_cmd_sta_func_shutdown, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_PMIC_REG_ACCESS, + .prepare_cmd = nxpwifi_cmd_sta_reg_access, + .cmd_resp = nxpwifi_ret_sta_reg_access}, + {.cmd_no = HOST_CMD_11N_CFG, + .prepare_cmd = nxpwifi_cmd_sta_11n_cfg, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_11N_ADDBA_REQ, + .prepare_cmd = nxpwifi_cmd_sta_11n_addba_req, + .cmd_resp = nxpwifi_ret_sta_11n_addba_req}, + {.cmd_no = HOST_CMD_11N_ADDBA_RSP, + .prepare_cmd = nxpwifi_cmd_sta_11n_addba_rsp, + .cmd_resp = nxpwifi_ret_sta_11n_addba_rsp}, + {.cmd_no = HOST_CMD_11N_DELBA, + .prepare_cmd = nxpwifi_cmd_sta_11n_delba, + .cmd_resp = nxpwifi_ret_sta_11n_delba}, + {.cmd_no = HOST_CMD_TXPWR_CFG, + .prepare_cmd = nxpwifi_cmd_sta_tx_power_cfg, + .cmd_resp = nxpwifi_ret_sta_tx_power_cfg}, + {.cmd_no = HOST_CMD_TX_RATE_CFG, + .prepare_cmd = nxpwifi_cmd_sta_tx_rate_cfg, + .cmd_resp = nxpwifi_ret_sta_tx_rate_cfg}, + {.cmd_no = HOST_CMD_RECONFIGURE_TX_BUFF, + .prepare_cmd = nxpwifi_cmd_sta_reconfigure_rx_buff, + .cmd_resp = nxpwifi_ret_sta_reconfigure_rx_buff}, + {.cmd_no = HOST_CMD_CHAN_REPORT_REQUEST, + .prepare_cmd = nxpwifi_cmd_sta_chan_report_request, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_AMSDU_AGGR_CTRL, + .prepare_cmd = nxpwifi_cmd_sta_amsdu_aggr_ctrl, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_ROBUST_COEX, + .prepare_cmd = nxpwifi_cmd_sta_robust_coex, + .cmd_resp = nxpwifi_ret_sta_robust_coex}, + {.cmd_no = HOST_CMD_802_11_PS_MODE_ENH, + .prepare_cmd = nxpwifi_cmd_sta_enh_power_mode, + .cmd_resp = nxpwifi_ret_sta_enh_power_mode}, + {.cmd_no = HOST_CMD_802_11_HS_CFG_ENH, + .prepare_cmd = nxpwifi_cmd_sta_802_11_hs_cfg, + .cmd_resp = nxpwifi_ret_sta_802_11_hs_cfg}, + {.cmd_no = HOST_CMD_CAU_REG_ACCESS, + .prepare_cmd = nxpwifi_cmd_sta_reg_access, + .cmd_resp = nxpwifi_ret_sta_reg_access}, + {.cmd_no = HOST_CMD_SET_BSS_MODE, + .prepare_cmd = nxpwifi_cmd_sta_set_bss_mode, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_802_11_NET_MONITOR, + .prepare_cmd = nxpwifi_cmd_sta_802_11_net_monitor, + .cmd_resp = nxpwifi_ret_sta_802_11_net_monitor}, + {.cmd_no = HOST_CMD_802_11_SCAN_EXT, + .prepare_cmd = nxpwifi_cmd_sta_802_11_scan_ext, + .cmd_resp = nxpwifi_ret_sta_802_11_scan_ext}, + {.cmd_no = HOST_CMD_COALESCE_CFG, + .prepare_cmd = nxpwifi_cmd_sta_coalesce_cfg, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_MGMT_FRAME_REG, + .prepare_cmd = nxpwifi_cmd_sta_mgmt_frame_reg, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_REMAIN_ON_CHAN, + .prepare_cmd = nxpwifi_cmd_sta_remain_on_chan, + .cmd_resp = nxpwifi_ret_sta_remain_on_chan}, + {.cmd_no = HOST_CMD_GTK_REKEY_OFFLOAD_CFG, + .prepare_cmd = nxpwifi_cmd_sta_gtk_rekey_offload, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_11AC_CFG, + .prepare_cmd = nxpwifi_cmd_sta_11ac_cfg, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_HS_WAKEUP_REASON, + .prepare_cmd = nxpwifi_cmd_sta_hs_wakeup_reason, + .cmd_resp = nxpwifi_ret_sta_hs_wakeup_reason}, + {.cmd_no = HOST_CMD_MC_POLICY, + .prepare_cmd = nxpwifi_cmd_sta_mc_policy, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_FW_DUMP_EVENT, + .prepare_cmd = nxpwifi_cmd_fill_head_only, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_SDIO_SP_RX_AGGR_CFG, + .prepare_cmd = nxpwifi_cmd_sta_sdio_rx_aggr_cfg, + .cmd_resp = nxpwifi_ret_sta_sdio_rx_aggr_cfg}, + {.cmd_no = HOST_CMD_STA_CONFIGURE, + .prepare_cmd = nxpwifi_cmd_sta_get_chan_info, + .cmd_resp = nxpwifi_ret_sta_get_chan_info}, + {.cmd_no = HOST_CMD_CHAN_REGION_CFG, + .prepare_cmd = nxpwifi_cmd_sta_chan_region_cfg, + .cmd_resp = nxpwifi_ret_sta_chan_region_cfg}, + {.cmd_no = HOST_CMD_PACKET_AGGR_CTRL, + .prepare_cmd = nxpwifi_cmd_sta_pkt_aggr_ctrl, + .cmd_resp = nxpwifi_ret_sta_pkt_aggr_ctrl}, + {.cmd_no = HOST_CMD_11AX_CFG, + .prepare_cmd = nxpwifi_cmd_sta_11ax_cfg, + .cmd_resp = nxpwifi_ret_sta_11ax_cfg}, + {.cmd_no = HOST_CMD_11AX_CMD, + .prepare_cmd = nxpwifi_cmd_sta_11ax_cmd, + .cmd_resp = nxpwifi_ret_sta_11ax_cmd}, + {.cmd_no = HOST_CMD_TWT_CFG, + .prepare_cmd = nxpwifi_cmd_sta_twt_cfg, + .cmd_resp = nxpwifi_ret_sta_twt_cfg}, +}; + +/* + * Prepare a command before sending it to firmware by invoking the + * appropriate handler based on the command ID. + */ +int nxpwifi_sta_prepare_cmd(struct nxpwifi_private *priv, + struct cmd_ctrl_node *cmd_node, + u16 cmd_action, u32 cmd_oid) + +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u16 cmd_no = cmd_node->cmd_no; + struct host_cmd_ds_command *cmd = + (struct host_cmd_ds_command *)cmd_node->skb->data; + void *data_buf = cmd_node->data_buf; + int i, ret = -EINVAL; + + for (i = 0; i < ARRAY_SIZE(cmd_table_sta); i++) { + if (cmd_no == cmd_table_sta[i].cmd_no) { + if (cmd_table_sta[i].prepare_cmd) + ret = cmd_table_sta[i].prepare_cmd(priv, cmd, + cmd_no, + data_buf, + cmd_action, + cmd_oid); + cmd_node->cmd_resp = cmd_table_sta[i].cmd_resp; + break; + } + } + + if (i == ARRAY_SIZE(cmd_table_sta)) + nxpwifi_dbg(adapter, ERROR, + "%s: unknown command: %#x\n", + __func__, cmd_no); + else + nxpwifi_dbg(adapter, CMD, + "%s: command: %#x\n", + __func__, cmd_no); + + return ret; +} + +/* + * Initialize firmware after download or during virtual interface + * reinitialization to bring the device to a working state. + */ +int nxpwifi_sta_init_cmd(struct nxpwifi_private *priv, u8 first_sta, bool init) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int ret; + struct nxpwifi_ds_11n_amsdu_aggr_ctrl amsdu_aggr_ctrl; + struct nxpwifi_ds_auto_ds auto_ds; + enum state_11d_t state_11d; + struct nxpwifi_ds_11n_tx_cfg tx_cfg; + u8 sdio_sp_rx_aggr_enable; + + if (first_sta) { + ret = nxpwifi_send_cmd(priv, HOST_CMD_FUNC_INIT, + HOST_ACT_GEN_SET, 0, NULL, true); + if (ret) + return ret; + + if (adapter->cal_data) + nxpwifi_send_cmd(priv, HOST_CMD_CFG_DATA, + HOST_ACT_GEN_SET, 0, NULL, true); + + /* Read MAC address from HW */ + ret = nxpwifi_send_cmd(priv, HOST_CMD_GET_HW_SPEC, + HOST_ACT_GEN_GET, 0, NULL, true); + if (ret) + return ret; + + /* * Set SDIO Single Port RX Aggr Info */ + if (priv->adapter->iface_type == NXPWIFI_SDIO && + ISSUPP_SDIO_SPA_ENABLED(priv->adapter->fw_cap_info) && + !priv->adapter->host_disable_sdio_rx_aggr) { + sdio_sp_rx_aggr_enable = true; + ret = nxpwifi_send_cmd(priv, + HOST_CMD_SDIO_SP_RX_AGGR_CFG, + HOST_ACT_GEN_SET, 0, + &sdio_sp_rx_aggr_enable, + true); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "error while enabling SP aggregation..disable it"); + adapter->sdio_rx_aggr_enable = false; + } + } + + /* Reconfigure tx buf size */ + ret = nxpwifi_send_cmd(priv, HOST_CMD_RECONFIGURE_TX_BUFF, + HOST_ACT_GEN_SET, 0, + &priv->adapter->tx_buf_size, true); + if (ret) + return ret; + + if (priv->bss_type != NXPWIFI_BSS_TYPE_UAP) { + /* Enable IEEE PS by default */ + priv->adapter->ps_mode = NXPWIFI_802_11_POWER_MODE_PSP; + ret = nxpwifi_send_cmd(priv, + HOST_CMD_802_11_PS_MODE_ENH, + EN_AUTO_PS, BITMAP_STA_PS, NULL, + true); + if (ret) + return ret; + } + + nxpwifi_send_cmd(priv, HOST_CMD_CHAN_REGION_CFG, + HOST_ACT_GEN_GET, 0, NULL, true); + } + + /* get tx rate */ + ret = nxpwifi_send_cmd(priv, HOST_CMD_TX_RATE_CFG, + HOST_ACT_GEN_GET, 0, NULL, true); + if (ret) + return ret; + priv->data_rate = 0; + + /* get tx power */ + ret = nxpwifi_send_cmd(priv, HOST_CMD_RF_TX_PWR, + HOST_ACT_GEN_GET, 0, NULL, true); + if (ret) + return ret; + + memset(&amsdu_aggr_ctrl, 0, sizeof(amsdu_aggr_ctrl)); + amsdu_aggr_ctrl.enable = true; + /* Send request to firmware */ + ret = nxpwifi_send_cmd(priv, HOST_CMD_AMSDU_AGGR_CTRL, + HOST_ACT_GEN_SET, 0, + &amsdu_aggr_ctrl, true); + if (ret) + return ret; + /* MAC Control must be the last command in init_fw */ + /* set MAC Control */ + ret = nxpwifi_send_cmd(priv, HOST_CMD_MAC_CONTROL, + HOST_ACT_GEN_SET, 0, + &priv->curr_pkt_filter, true); + if (ret) + return ret; + + if (!disable_auto_ds && first_sta && + priv->bss_type != NXPWIFI_BSS_TYPE_UAP) { + /* Enable auto deep sleep */ + auto_ds.auto_ds = DEEP_SLEEP_ON; + auto_ds.idle_time = DEEP_SLEEP_IDLE_TIME; + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_PS_MODE_ENH, + EN_AUTO_PS, BITMAP_AUTO_DS, + &auto_ds, true); + if (ret) + return ret; + } + + if (priv->bss_type != NXPWIFI_BSS_TYPE_UAP) { + /* Send cmd to FW to enable/disable 11D function */ + state_11d = ENABLE_11D; + ret = nxpwifi_send_cmd(priv, HOST_CMD_802_11_SNMP_MIB, + HOST_ACT_GEN_SET, DOT11D_I, + &state_11d, true); + if (ret) + nxpwifi_dbg(priv->adapter, ERROR, + "11D: failed to enable 11D\n"); + } + + /* + * Send cmd to FW to configure 11n specific configuration + * (Short GI, Channel BW, Green field support etc.) for transmit + */ + tx_cfg.tx_htcap = NXPWIFI_FW_DEF_HTTXCFG; + ret = nxpwifi_send_cmd(priv, HOST_CMD_11N_CFG, + HOST_ACT_GEN_SET, 0, &tx_cfg, true); + + return ret; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/sta_event.c b/drivers/net/wireless/nxp/nxpwifi/sta_event.c new file mode 100644 index 000000000000..355064b1d8f7 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/sta_event.c @@ -0,0 +1,862 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP Wireless LAN device driver: station event handling + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cmdevt.h" +#include "wmm.h" +#include "11n.h" + +static int +nxpwifi_sta_event_link_lost(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + adapter->dbg.num_event_link_lost++; + if (priv->media_connected) { + adapter->priv_link_lost = priv; + adapter->host_mlme_link_lost = true; + nxpwifi_queue_wiphy_work(adapter, + &adapter->host_mlme_work); + } + + return 0; +} + +static int +nxpwifi_sta_event_link_sensed(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + netif_carrier_on(priv->netdev); + nxpwifi_wake_up_net_dev_queue(priv->netdev, adapter); + + return 0; +} + +static int +nxpwifi_sta_event_deauthenticated(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + if (priv->wps.session_enable) { + nxpwifi_dbg(adapter, INFO, + "info: receive deauth event in wps session\n"); + } else { + adapter->dbg.num_event_deauth++; + if (priv->media_connected) { + priv->last_deauth_reason = + get_unaligned_le16(priv->adapter->event_body); + nxpwifi_queue_wiphy_work(priv->adapter, + &priv->reset_conn_state_work); + } + } + + return 0; +} + +static int +nxpwifi_sta_event_disassociated(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + if (priv->wps.session_enable) { + nxpwifi_dbg(adapter, INFO, + "info: receive disassoc event in wps session\n"); + } else { + adapter->dbg.num_event_disassoc++; + if (priv->media_connected) { + priv->last_deauth_reason = + get_unaligned_le16(priv->adapter->event_body); + nxpwifi_queue_wiphy_work(priv->adapter, + &priv->reset_conn_state_work); + } + } + + return 0; +} + +static int +nxpwifi_sta_event_ps_awake(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + if (!adapter->pps_uapsd_mode && + priv->port_open && + priv->media_connected && adapter->sleep_period.period) { + adapter->pps_uapsd_mode = true; + nxpwifi_dbg(adapter, EVENT, + "event: PPS/UAPSD mode activated\n"); + } + adapter->tx_lock_flag = false; + if (adapter->pps_uapsd_mode && adapter->gen_null_pkt) { + if (nxpwifi_check_last_packet_indication(priv)) { + if (adapter->data_sent) { + adapter->ps_state = PS_STATE_AWAKE; + adapter->pm_wakeup_card_req = false; + adapter->pm_wakeup_fw_try = false; + timer_delete(&adapter->wakeup_timer); + } else { + if (!nxpwifi_send_null_packet + (priv, + NXPWIFI_TxPD_POWER_MGMT_NULL_PACKET | + NXPWIFI_TxPD_POWER_MGMT_LAST_PACKET)) + adapter->ps_state = PS_STATE_SLEEP; + } + + return 0; + } + } + + adapter->ps_state = PS_STATE_AWAKE; + adapter->pm_wakeup_card_req = false; + adapter->pm_wakeup_fw_try = false; + timer_delete(&adapter->wakeup_timer); + + return 0; +} + +static int +nxpwifi_sta_event_ps_sleep(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + adapter->ps_state = PS_STATE_PRE_SLEEP; + nxpwifi_check_ps_cond(adapter); + + return 0; +} + +static int +nxpwifi_sta_event_mic_err_multicast(struct nxpwifi_private *priv) +{ + cfg80211_michael_mic_failure(priv->netdev, priv->cfg_bssid, + NL80211_KEYTYPE_GROUP, + -1, NULL, GFP_KERNEL); + + return 0; +} + +static int +nxpwifi_sta_event_mic_err_unicast(struct nxpwifi_private *priv) +{ + cfg80211_michael_mic_failure(priv->netdev, priv->cfg_bssid, + NL80211_KEYTYPE_PAIRWISE, + -1, NULL, GFP_KERNEL); + + return 0; +} + +static int +nxpwifi_sta_event_deep_sleep_awake(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + adapter->if_ops.wakeup_complete(adapter); + if (adapter->is_deep_sleep) + adapter->is_deep_sleep = false; + + return 0; +} + +static int +nxpwifi_sta_event_wmm_status_change(struct nxpwifi_private *priv) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_WMM_GET_STATUS, + 0, 0, NULL, false); +} + +static int +nxpwifi_sta_event_bs_scan_report(struct nxpwifi_private *priv) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_BG_SCAN_QUERY, + HOST_ACT_GEN_GET, 0, NULL, false); +} + +static int +nxpwifi_sta_event_rssi_low(struct nxpwifi_private *priv) +{ + cfg80211_cqm_rssi_notify(priv->netdev, + NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW, + 0, GFP_KERNEL); + priv->subsc_evt_rssi_state = RSSI_LOW_RECVD; + + return nxpwifi_send_cmd(priv, HOST_CMD_RSSI_INFO, + HOST_ACT_GEN_GET, 0, NULL, false); +} + +static int +nxpwifi_sta_event_rssi_high(struct nxpwifi_private *priv) +{ + cfg80211_cqm_rssi_notify(priv->netdev, + NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH, + 0, GFP_KERNEL); + priv->subsc_evt_rssi_state = RSSI_HIGH_RECVD; + + return nxpwifi_send_cmd(priv, HOST_CMD_RSSI_INFO, + HOST_ACT_GEN_GET, 0, NULL, false); +} + +static int +nxpwifi_sta_event_port_release(struct nxpwifi_private *priv) +{ + priv->port_open = true; + + return 0; +} + +static int +nxpwifi_sta_event_addba(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + return nxpwifi_send_cmd(priv, HOST_CMD_11N_ADDBA_RSP, + HOST_ACT_GEN_SET, 0, + adapter->event_body, false); +} + +static int +nxpwifi_sta_event_delba(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + nxpwifi_11n_delete_ba_stream(priv, adapter->event_body); + + return 0; +} + +static int +nxpwifi_sta_event_bs_stream_timeout(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_ds_11n_batimeout *event = + (struct host_cmd_ds_11n_batimeout *)adapter->event_body; + + nxpwifi_11n_ba_stream_timeout(priv, event); + + return 0; +} + +static int +nxpwifi_sta_event_amsdu_aggr_ctrl(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u16 ctrl; + + ctrl = get_unaligned_le16(adapter->event_body); + adapter->tx_buf_size = min_t(u16, adapter->curr_tx_buf_size, ctrl); + + return 0; +} + +static int +nxpwifi_sta_event_hs_act_req(struct nxpwifi_private *priv) +{ + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_HS_CFG_ENH, + 0, 0, NULL, false); +} + +static int +nxpwifi_sta_event_channel_switch_ann(struct nxpwifi_private *priv) +{ + struct nxpwifi_bssdescriptor *bss_desc; + + bss_desc = &priv->curr_bss_params.bss_descriptor; + priv->csa_expire_time = jiffies + msecs_to_jiffies(DFS_CHAN_MOVE_TIME); + priv->csa_chan = bss_desc->channel; + return nxpwifi_send_cmd(priv, HOST_CMD_802_11_DEAUTHENTICATE, + HOST_ACT_GEN_SET, 0, + bss_desc->mac_address, false); +} + +static int +nxpwifi_sta_event_radar_detected(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + return nxpwifi_11h_handle_radar_detected(priv, adapter->event_skb); +} + +static int +nxpwifi_sta_event_channel_report_rdy(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + return nxpwifi_11h_handle_chanrpt_ready(priv, adapter->event_skb); +} + +static int +nxpwifi_sta_event_tx_data_pause(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + nxpwifi_process_tx_pause_event(priv, adapter->event_skb); + + return 0; +} + +static int +nxpwifi_sta_event_ext_scan_report(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + void *buf = adapter->event_skb->data; + int ret = 0; + + /* + * We intend to skip this event during suspend, but handle + * it in interface disabled case + */ + if (adapter->ext_scan && (!priv->scan_aborting || + !netif_running(priv->netdev))) + ret = nxpwifi_handle_event_ext_scan_report(priv, buf); + + return ret; +} + +static int +nxpwifi_sta_event_rxba_sync(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + nxpwifi_11n_rxba_sync_event(priv, adapter->event_body, + adapter->event_skb->len - + sizeof(adapter->event_cause)); + + return 0; +} + +static int +nxpwifi_sta_event_remain_on_chan_expired(struct nxpwifi_private *priv) +{ + if (priv->auth_flag & HOST_MLME_AUTH_PENDING) { + priv->auth_flag = 0; + priv->auth_alg = WLAN_AUTH_NONE; + } else { + cfg80211_remain_on_channel_expired(&priv->wdev, + priv->roc_cfg.cookie, + &priv->roc_cfg.chan, + GFP_ATOMIC); + } + + memset(&priv->roc_cfg, 0x00, sizeof(struct nxpwifi_roc_cfg)); + + return 0; +} + +static int +nxpwifi_sta_event_bg_scan_stopped(struct nxpwifi_private *priv) +{ + cfg80211_sched_scan_stopped(priv->wdev.wiphy, 0); + if (priv->sched_scanning) + priv->sched_scanning = false; + + return 0; +} + +static int +nxpwifi_sta_event_multi_chan_info(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + nxpwifi_process_multi_chan_event(priv, adapter->event_skb); + + return 0; +} + +static int +nxpwifi_sta_event_tx_status_report(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + nxpwifi_parse_tx_status_event(priv, adapter->event_body); + + return 0; +} + +static int +nxpwifi_sta_event_bt_coex_wlan_para_change(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + if (!adapter->ignore_btcoex_events) + nxpwifi_bt_coex_wlan_param_update_event(priv, + adapter->event_skb); + + return 0; +} + +static int +nxpwifi_sta_event_vdll_ind(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + return nxpwifi_process_vdll_event(priv, adapter->event_skb); +} + +static const struct nxpwifi_evt_entry evt_table_sta[] = { + {.event_cause = EVENT_LINK_LOST, + .event_handler = nxpwifi_sta_event_link_lost}, + {.event_cause = EVENT_LINK_SENSED, + .event_handler = nxpwifi_sta_event_link_sensed}, + {.event_cause = EVENT_DEAUTHENTICATED, + .event_handler = nxpwifi_sta_event_deauthenticated}, + {.event_cause = EVENT_DISASSOCIATED, + .event_handler = nxpwifi_sta_event_disassociated}, + {.event_cause = EVENT_PS_AWAKE, + .event_handler = nxpwifi_sta_event_ps_awake}, + {.event_cause = EVENT_PS_SLEEP, + .event_handler = nxpwifi_sta_event_ps_sleep}, + {.event_cause = EVENT_MIC_ERR_MULTICAST, + .event_handler = nxpwifi_sta_event_mic_err_multicast}, + {.event_cause = EVENT_MIC_ERR_UNICAST, + .event_handler = nxpwifi_sta_event_mic_err_unicast}, + {.event_cause = EVENT_DEEP_SLEEP_AWAKE, + .event_handler = nxpwifi_sta_event_deep_sleep_awake}, + {.event_cause = EVENT_WMM_STATUS_CHANGE, + .event_handler = nxpwifi_sta_event_wmm_status_change}, + {.event_cause = EVENT_BG_SCAN_REPORT, + .event_handler = nxpwifi_sta_event_bs_scan_report}, + {.event_cause = EVENT_RSSI_LOW, + .event_handler = nxpwifi_sta_event_rssi_low}, + {.event_cause = EVENT_RSSI_HIGH, + .event_handler = nxpwifi_sta_event_rssi_high}, + {.event_cause = EVENT_PORT_RELEASE, + .event_handler = nxpwifi_sta_event_port_release}, + {.event_cause = EVENT_ADDBA, + .event_handler = nxpwifi_sta_event_addba}, + {.event_cause = EVENT_DELBA, + .event_handler = nxpwifi_sta_event_delba}, + {.event_cause = EVENT_BA_STREAM_TIEMOUT, + .event_handler = nxpwifi_sta_event_bs_stream_timeout}, + {.event_cause = EVENT_AMSDU_AGGR_CTRL, + .event_handler = nxpwifi_sta_event_amsdu_aggr_ctrl}, + {.event_cause = EVENT_HS_ACT_REQ, + .event_handler = nxpwifi_sta_event_hs_act_req}, + {.event_cause = EVENT_CHANNEL_SWITCH_ANN, + .event_handler = nxpwifi_sta_event_channel_switch_ann}, + {.event_cause = EVENT_RADAR_DETECTED, + .event_handler = nxpwifi_sta_event_radar_detected}, + {.event_cause = EVENT_CHANNEL_REPORT_RDY, + .event_handler = nxpwifi_sta_event_channel_report_rdy}, + {.event_cause = EVENT_TX_DATA_PAUSE, + .event_handler = nxpwifi_sta_event_tx_data_pause}, + {.event_cause = EVENT_EXT_SCAN_REPORT, + .event_handler = nxpwifi_sta_event_ext_scan_report}, + {.event_cause = EVENT_RXBA_SYNC, + .event_handler = nxpwifi_sta_event_rxba_sync}, + {.event_cause = EVENT_REMAIN_ON_CHAN_EXPIRED, + .event_handler = nxpwifi_sta_event_remain_on_chan_expired}, + {.event_cause = EVENT_BG_SCAN_STOPPED, + .event_handler = nxpwifi_sta_event_bg_scan_stopped}, + {.event_cause = EVENT_MULTI_CHAN_INFO, + .event_handler = nxpwifi_sta_event_multi_chan_info}, + {.event_cause = EVENT_TX_STATUS_REPORT, + .event_handler = nxpwifi_sta_event_tx_status_report}, + {.event_cause = EVENT_BT_COEX_WLAN_PARA_CHANGE, + .event_handler = nxpwifi_sta_event_bt_coex_wlan_para_change}, + {.event_cause = EVENT_VDLL_IND, + .event_handler = nxpwifi_sta_event_vdll_ind}, + {.event_cause = EVENT_DUMMY_HOST_WAKEUP_SIGNAL, + .event_handler = NULL}, + {.event_cause = EVENT_MIB_CHANGED, + .event_handler = NULL}, + {.event_cause = EVENT_INIT_DONE, + .event_handler = NULL}, + {.event_cause = EVENT_SNR_LOW, + .event_handler = NULL}, + {.event_cause = EVENT_MAX_FAIL, + .event_handler = NULL}, + {.event_cause = EVENT_SNR_HIGH, + .event_handler = NULL}, + {.event_cause = EVENT_DATA_RSSI_LOW, + .event_handler = NULL}, + {.event_cause = EVENT_DATA_SNR_LOW, + .event_handler = NULL}, + {.event_cause = EVENT_DATA_RSSI_HIGH, + .event_handler = NULL}, + {.event_cause = EVENT_DATA_SNR_HIGH, + .event_handler = NULL}, + {.event_cause = EVENT_LINK_QUALITY, + .event_handler = NULL}, + {.event_cause = EVENT_PRE_BEACON_LOST, + .event_handler = NULL}, + {.event_cause = EVENT_WEP_ICV_ERR, + .event_handler = NULL}, + {.event_cause = EVENT_BW_CHANGE, + .event_handler = NULL}, + {.event_cause = EVENT_HOSTWAKE_STAIE, + .event_handler = NULL}, + {.event_cause = EVENT_UNKNOWN_DEBUG, + .event_handler = NULL}, +}; + +static void nxpwifi_process_uap_tx_pause(struct nxpwifi_private *priv, + struct nxpwifi_ie_types_header *tlv) +{ + struct nxpwifi_tx_pause_tlv *tp; + struct nxpwifi_sta_node *sta_ptr; + + tp = (void *)tlv; + nxpwifi_dbg(priv->adapter, EVENT, + "uap tx_pause: %pM pause=%d, pkts=%d\n", + tp->peermac, tp->tx_pause, + tp->pkt_cnt); + + if (ether_addr_equal(tp->peermac, priv->netdev->dev_addr)) { + if (tp->tx_pause) + priv->port_open = false; + else + priv->port_open = true; + } else if (is_multicast_ether_addr(tp->peermac)) { + nxpwifi_update_ralist_tx_pause(priv, tp->peermac, tp->tx_pause); + } else { + rcu_read_lock(); + sta_ptr = nxpwifi_get_sta_entry(priv, tp->peermac); + if (sta_ptr && sta_ptr->tx_pause != tp->tx_pause) { + sta_ptr->tx_pause = tp->tx_pause; + nxpwifi_update_ralist_tx_pause(priv, tp->peermac, + tp->tx_pause); + } + rcu_read_unlock(); + } +} + +static void nxpwifi_process_sta_tx_pause(struct nxpwifi_private *priv, + struct nxpwifi_ie_types_header *tlv) +{ + struct nxpwifi_tx_pause_tlv *tp; + + tp = (void *)tlv; + nxpwifi_dbg(priv->adapter, EVENT, + "sta tx_pause: %pM pause=%d, pkts=%d\n", + tp->peermac, tp->tx_pause, + tp->pkt_cnt); + + if (ether_addr_equal(tp->peermac, priv->cfg_bssid)) { + if (tp->tx_pause) + priv->port_open = false; + else + priv->port_open = true; + } +} + +/* + * Reset connection state after a firmware-triggered disconnect. + * Clears link state, queues, RSSI/SNR and security settings, + * saves previous SSID/BSSID for possible reassociation, + * and notifies cfg80211. + */ +void nxpwifi_reset_connect_state(struct nxpwifi_private *priv, u16 reason_code, + bool from_ap) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + if (!priv->media_connected) + return; + + nxpwifi_dbg(adapter, INFO, + "info: handles disconnect event\n"); + + priv->media_connected = false; + + priv->auth_flag = 0; + priv->auth_alg = WLAN_AUTH_NONE; + + priv->scan_block = false; + priv->port_open = false; + + /* Free Tx and Rx packets, report disconnect to upper layer */ + nxpwifi_clean_txrx(priv); + + /* Reset SNR/NF/RSSI values */ + priv->data_rssi_last = 0; + priv->data_nf_last = 0; + priv->data_rssi_avg = 0; + priv->data_nf_avg = 0; + priv->bcn_rssi_last = 0; + priv->bcn_nf_last = 0; + priv->bcn_rssi_avg = 0; + priv->bcn_nf_avg = 0; + priv->rxpd_rate = 0; + priv->rxpd_htinfo = 0; + priv->sec_info.wpa_enabled = false; + priv->sec_info.wpa2_enabled = false; + priv->wpa_ie_len = 0; + + priv->sec_info.encryption_mode = 0; + + /* Enable auto data rate */ + priv->is_data_rate_auto = true; + priv->data_rate = 0; + + priv->assoc_resp_ht_param = 0; + priv->ht_param_present = false; + + if ((GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA || + GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) && priv->hist_data) + nxpwifi_hist_data_reset(priv); + + /* + * Memorize the previous SSID and BSSID so + * it could be used for re-assoc + */ + + nxpwifi_dbg(adapter, INFO, + "info: previous SSID=%s, SSID len=%u\n", + priv->prev_ssid.ssid, priv->prev_ssid.ssid_len); + + nxpwifi_dbg(adapter, INFO, + "info: current SSID=%s, SSID len=%u\n", + priv->curr_bss_params.bss_descriptor.ssid.ssid, + priv->curr_bss_params.bss_descriptor.ssid.ssid_len); + + memcpy(&priv->prev_ssid, + &priv->curr_bss_params.bss_descriptor.ssid, + sizeof(struct cfg80211_ssid)); + + memcpy(priv->prev_bssid, + priv->curr_bss_params.bss_descriptor.mac_address, ETH_ALEN); + + /* Need to erase the current SSID and BSSID info */ + memset(&priv->curr_bss_params, 0x00, sizeof(priv->curr_bss_params)); + + adapter->tx_lock_flag = false; + adapter->pps_uapsd_mode = false; + + if (test_bit(NXPWIFI_IS_CMD_TIMEDOUT, &adapter->work_flags) && + adapter->curr_cmd) + return; + + priv->media_connected = false; + nxpwifi_dbg(adapter, MSG, + "info: successfully disconnected from %pM: reason code %d\n", + priv->cfg_bssid, reason_code); + + if (priv->bss_mode == NL80211_IFTYPE_STATION) { + if (adapter->host_mlme_link_lost) + nxpwifi_host_mlme_disconnect(adapter->priv_link_lost, + reason_code, NULL); + else + cfg80211_disconnected(priv->netdev, reason_code, NULL, + 0, !from_ap, GFP_KERNEL); + } + eth_zero_addr(priv->cfg_bssid); + + nxpwifi_stop_net_dev_queue(priv->netdev, adapter); + netif_carrier_off(priv->netdev); + + if (!ISSUPP_FIRMWARE_SUPPLICANT(priv->adapter->fw_cap_info)) + return; + + nxpwifi_send_cmd(priv, HOST_CMD_GTK_REKEY_OFFLOAD_CFG, + HOST_ACT_GEN_REMOVE, 0, NULL, false); +} + +void nxpwifi_reset_conn_state_work(struct wiphy *wiphy, struct wiphy_work *work) +{ + struct nxpwifi_private *priv = container_of(work, + struct nxpwifi_private, + reset_conn_state_work); + + nxpwifi_reset_connect_state(priv, priv->last_deauth_reason, true); +} + +void nxpwifi_process_multi_chan_event(struct nxpwifi_private *priv, + struct sk_buff *event_skb) +{ + struct nxpwifi_ie_types_multi_chan_info *chan_info; + struct nxpwifi_ie_types_mc_group_info *grp_info; + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_ie_types_header *tlv; + u16 tlv_buf_left, tlv_type, tlv_len; + int intf_num, bss_type, bss_num, i; + struct nxpwifi_private *intf_priv; + + tlv_buf_left = event_skb->len - sizeof(u32); + chan_info = (void *)event_skb->data + sizeof(u32); + + if (le16_to_cpu(chan_info->header.type) != TLV_TYPE_MULTI_CHAN_INFO || + tlv_buf_left < sizeof(struct nxpwifi_ie_types_multi_chan_info)) { + nxpwifi_dbg(adapter, ERROR, + "unknown TLV in chan_info event\n"); + return; + } + + adapter->usb_mc_status = le16_to_cpu(chan_info->status); + nxpwifi_dbg(adapter, EVENT, "multi chan operation %s\n", + adapter->usb_mc_status ? "started" : "over"); + + tlv_buf_left -= sizeof(struct nxpwifi_ie_types_multi_chan_info); + tlv = (struct nxpwifi_ie_types_header *)chan_info->tlv_buffer; + + while (tlv_buf_left >= (int)sizeof(struct nxpwifi_ie_types_header)) { + tlv_type = le16_to_cpu(tlv->type); + tlv_len = le16_to_cpu(tlv->len); + if ((sizeof(struct nxpwifi_ie_types_header) + tlv_len) > + tlv_buf_left) { + nxpwifi_dbg(adapter, ERROR, "wrong tlv: tlvLen=%d,\t" + "tlvBufLeft=%d\n", tlv_len, tlv_buf_left); + break; + } + if (tlv_type != TLV_TYPE_MC_GROUP_INFO) { + nxpwifi_dbg(adapter, ERROR, "wrong tlv type: 0x%x\n", + tlv_type); + break; + } + + grp_info = (struct nxpwifi_ie_types_mc_group_info *)tlv; + intf_num = grp_info->intf_num; + for (i = 0; i < intf_num; i++) { + bss_type = grp_info->bss_type_numlist[i] >> 4; + bss_num = grp_info->bss_type_numlist[i] & BSS_NUM_MASK; + intf_priv = nxpwifi_get_priv_by_id(adapter, bss_num, + bss_type); + if (!intf_priv) { + nxpwifi_dbg(adapter, ERROR, + "Invalid bss_type bss_num\t" + "in multi channel event\n"); + continue; + } + } + + tlv_buf_left -= sizeof(struct nxpwifi_ie_types_header) + + tlv_len; + tlv = (void *)((u8 *)tlv + tlv_len + + sizeof(struct nxpwifi_ie_types_header)); + } +} + +void nxpwifi_process_tx_pause_event(struct nxpwifi_private *priv, + struct sk_buff *event_skb) +{ + struct nxpwifi_ie_types_header *tlv; + u16 tlv_type, tlv_len; + int tlv_buf_left; + + if (!priv->media_connected) { + nxpwifi_dbg(priv->adapter, ERROR, + "tx_pause event while disconnected; bss_role=%d\n", + priv->bss_role); + return; + } + + tlv_buf_left = event_skb->len - sizeof(u32); + tlv = (void *)event_skb->data + sizeof(u32); + + while (tlv_buf_left >= (int)sizeof(struct nxpwifi_ie_types_header)) { + tlv_type = le16_to_cpu(tlv->type); + tlv_len = le16_to_cpu(tlv->len); + if ((sizeof(struct nxpwifi_ie_types_header) + tlv_len) > + tlv_buf_left) { + nxpwifi_dbg(priv->adapter, ERROR, + "wrong tlv: tlvLen=%d, tlvBufLeft=%d\n", + tlv_len, tlv_buf_left); + break; + } + if (tlv_type == TLV_TYPE_TX_PAUSE) { + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) + nxpwifi_process_sta_tx_pause(priv, tlv); + else + nxpwifi_process_uap_tx_pause(priv, tlv); + } + + tlv_buf_left -= sizeof(struct nxpwifi_ie_types_header) + + tlv_len; + tlv = (void *)((u8 *)tlv + tlv_len + + sizeof(struct nxpwifi_ie_types_header)); + } +} + +/* + * Handle BT coexistence event. Parse TLVs and update + * coexistence aggregation window and scan timing parameters. + */ +void nxpwifi_bt_coex_wlan_param_update_event(struct nxpwifi_private *priv, + struct sk_buff *event_skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_ie_types_header *tlv; + struct nxpwifi_ie_types_btcoex_aggr_win_size *winsizetlv; + struct nxpwifi_ie_types_btcoex_scan_time *scantlv; + s32 len = event_skb->len - sizeof(u32); + u8 *cur_ptr = event_skb->data + sizeof(u32); + u16 tlv_type, tlv_len; + + while (len >= sizeof(struct nxpwifi_ie_types_header)) { + tlv = (struct nxpwifi_ie_types_header *)cur_ptr; + tlv_len = le16_to_cpu(tlv->len); + tlv_type = le16_to_cpu(tlv->type); + + if ((tlv_len + sizeof(struct nxpwifi_ie_types_header)) > len) + break; + switch (tlv_type) { + case TLV_BTCOEX_WL_AGGR_WINSIZE: + winsizetlv = + (struct nxpwifi_ie_types_btcoex_aggr_win_size *)tlv; + adapter->coex_win_size = winsizetlv->coex_win_size; + adapter->coex_tx_win_size = + winsizetlv->tx_win_size; + adapter->coex_rx_win_size = + winsizetlv->rx_win_size; + nxpwifi_coex_ampdu_rxwinsize(adapter); + nxpwifi_update_ampdu_txwinsize(adapter); + break; + + case TLV_BTCOEX_WL_SCANTIME: + scantlv = + (struct nxpwifi_ie_types_btcoex_scan_time *)tlv; + adapter->coex_scan = scantlv->coex_scan; + adapter->coex_min_scan_time = le16_to_cpu(scantlv->min_scan_time); + adapter->coex_max_scan_time = le16_to_cpu(scantlv->max_scan_time); + break; + + default: + break; + } + + len -= tlv_len + sizeof(struct nxpwifi_ie_types_header); + cur_ptr += tlv_len + + sizeof(struct nxpwifi_ie_types_header); + } + + nxpwifi_dbg(adapter, INFO, "coex_scan=%d min_scan=%d coex_win=%d, tx_win=%d rx_win=%d\n", + adapter->coex_scan, adapter->coex_min_scan_time, + adapter->coex_win_size, adapter->coex_tx_win_size, + adapter->coex_rx_win_size); +} + +/* + * Dispatch station firmware event based on event_cause. + * Looks up the handler in the station event table and invokes it. + */ +int nxpwifi_process_sta_event(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u32 eventcause = adapter->event_cause; + int evt, ret = 0; + + for (evt = 0; evt < ARRAY_SIZE(evt_table_sta); evt++) { + if (eventcause == evt_table_sta[evt].event_cause) { + if (evt_table_sta[evt].event_handler) + ret = evt_table_sta[evt].event_handler(priv); + break; + } + } + + if (evt == ARRAY_SIZE(evt_table_sta)) + nxpwifi_dbg(adapter, EVENT, + "%s: unknown event id: %#x\n", + __func__, eventcause); + else + nxpwifi_dbg(adapter, EVENT, + "%s: event id: %#x\n", + __func__, eventcause); + + return ret; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/sta_rx.c b/drivers/net/wireless/nxp/nxpwifi/sta_rx.c new file mode 100644 index 000000000000..d951d21eb41c --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/sta_rx.c @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP Wireless LAN device driver: station RX data handling + * + * Copyright 2011-2024 NXP + */ + +#include +#include +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "11n_aggr.h" +#include "11n_rxreorder.h" + +/* + * Drop gratuitous IPv4 ARP and IPv6 neighbour advertisements when + * source and destination addresses are identical. + */ +static bool +nxpwifi_discard_gratuitous_arp(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + const struct nxpwifi_arp_eth_header *arp; + struct ethhdr *eth; + struct ipv6hdr *ipv6; + struct icmp6hdr *icmpv6; + + eth = (struct ethhdr *)skb->data; + switch (ntohs(eth->h_proto)) { + case ETH_P_ARP: + arp = (void *)(skb->data + sizeof(struct ethhdr)); + if (arp->hdr.ar_op == htons(ARPOP_REPLY) || + arp->hdr.ar_op == htons(ARPOP_REQUEST)) { + if (!memcmp(arp->ar_sip, arp->ar_tip, 4)) + return true; + } + break; + case ETH_P_IPV6: + ipv6 = (void *)(skb->data + sizeof(struct ethhdr)); + icmpv6 = (void *)(skb->data + sizeof(struct ethhdr) + + sizeof(struct ipv6hdr)); + if (icmpv6->icmp6_type == NDISC_NEIGHBOUR_ADVERTISEMENT) { + if (!memcmp(&ipv6->saddr, &ipv6->daddr, + sizeof(struct in6_addr))) + return true; + } + break; + default: + break; + } + + return false; +} + +/* + * Process a received data packet. + * Convert 802.2/LLC/SNAP to Ethernet II when appropriate, trim the + * rxpd and extra headers, optionally drop gratuitous ARP/NA, cache + * RX rate for unicast, then deliver to the stack. + */ +int nxpwifi_process_rx_packet(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + int ret; + struct rx_packet_hdr *rx_pkt_hdr; + struct rxpd *local_rx_pd; + int hdr_chop; + struct ethhdr *eth; + u16 rx_pkt_off; + u8 adj_rx_rate = 0; + + local_rx_pd = (struct rxpd *)(skb->data); + + rx_pkt_off = le16_to_cpu(local_rx_pd->rx_pkt_offset); + rx_pkt_hdr = (void *)local_rx_pd + rx_pkt_off; + + if (sizeof(rx_pkt_hdr->eth803_hdr) + sizeof(rfc1042_header) + + rx_pkt_off > skb->len) { + priv->stats.rx_dropped++; + dev_kfree_skb_any(skb); + return -EINVAL; + } + + if (sizeof(*rx_pkt_hdr) + rx_pkt_off <= skb->len && + ((!memcmp(&rx_pkt_hdr->rfc1042_hdr, bridge_tunnel_header, + sizeof(bridge_tunnel_header))) || + (!memcmp(&rx_pkt_hdr->rfc1042_hdr, rfc1042_header, + sizeof(rfc1042_header)) && + rx_pkt_hdr->rfc1042_hdr.snap_type != htons(ETH_P_AARP) && + rx_pkt_hdr->rfc1042_hdr.snap_type != htons(ETH_P_IPX)))) { + /* + * Replace the 803 header and rfc1042 header (llc/snap) with an + * EthernetII header, keep the src/dst and snap_type + * (ethertype). + * The firmware only passes up SNAP frames converting + * all RX Data from 802.11 to 802.2/LLC/SNAP frames. + * To create the Ethernet II, just move the src, dst address + * right before the snap_type. + */ + eth = (struct ethhdr *) + ((u8 *)&rx_pkt_hdr->eth803_hdr + + sizeof(rx_pkt_hdr->eth803_hdr) + + sizeof(rx_pkt_hdr->rfc1042_hdr) + - sizeof(rx_pkt_hdr->eth803_hdr.h_dest) + - sizeof(rx_pkt_hdr->eth803_hdr.h_source) + - sizeof(rx_pkt_hdr->rfc1042_hdr.snap_type)); + + memcpy(eth->h_source, rx_pkt_hdr->eth803_hdr.h_source, + sizeof(eth->h_source)); + memcpy(eth->h_dest, rx_pkt_hdr->eth803_hdr.h_dest, + sizeof(eth->h_dest)); + + /* + * Chop off the rxpd + the excess memory from the 802.2/llc/snap + * header that was removed. + */ + hdr_chop = (u8 *)eth - (u8 *)local_rx_pd; + } else { + /* Chop off the rxpd */ + hdr_chop = (u8 *)&rx_pkt_hdr->eth803_hdr - (u8 *)local_rx_pd; + } + + /* + * Chop off the leading header bytes so the it points to the start of + * either the reconstructed EthII frame or the 802.2/llc/snap frame + */ + skb_pull(skb, hdr_chop); + + if (priv->hs2_enabled && + nxpwifi_discard_gratuitous_arp(priv, skb)) { + nxpwifi_dbg(priv->adapter, INFO, "Bypassed Gratuitous ARP\n"); + dev_kfree_skb_any(skb); + return 0; + } + + /* Only stash RX bitrate for unicast packets. */ + if (likely(!is_multicast_ether_addr(rx_pkt_hdr->eth803_hdr.h_dest))) { + priv->rxpd_rate = local_rx_pd->rx_rate; + priv->rxpd_htinfo = local_rx_pd->rate_info; + } + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA || + GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + adj_rx_rate = nxpwifi_adjust_data_rate(priv, + local_rx_pd->rx_rate, + local_rx_pd->rate_info); + nxpwifi_hist_data_add(priv, adj_rx_rate, local_rx_pd->snr, + local_rx_pd->nf); + } + + ret = nxpwifi_recv_packet(priv, skb); + if (ret) + nxpwifi_dbg(priv->adapter, ERROR, + "recv packet failed\n"); + + return ret; +} + +/* + * Process a received buffer on the STA path. + * Validate RxPD and lengths, handle monitor/mgmt frames, fast-path + * non-unicast frames, and feed unicast data into 11n reorder/BA logic. + */ +int nxpwifi_process_sta_rx_packet(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int ret = 0; + struct rxpd *local_rx_pd; + struct rx_packet_hdr *rx_pkt_hdr; + u8 ta[ETH_ALEN]; + u16 rx_pkt_type, rx_pkt_offset, rx_pkt_length, seq_num; + + local_rx_pd = (struct rxpd *)(skb->data); + rx_pkt_type = le16_to_cpu(local_rx_pd->rx_pkt_type); + rx_pkt_offset = le16_to_cpu(local_rx_pd->rx_pkt_offset); + rx_pkt_length = le16_to_cpu(local_rx_pd->rx_pkt_length); + seq_num = le16_to_cpu(local_rx_pd->seq_num); + + rx_pkt_hdr = (void *)local_rx_pd + rx_pkt_offset; + + if ((rx_pkt_offset + rx_pkt_length) > skb->len || + sizeof(rx_pkt_hdr->eth803_hdr) + rx_pkt_offset > skb->len) { + nxpwifi_dbg(adapter, ERROR, + "wrong rx packet: len=%d, rx_pkt_offset=%d, rx_pkt_length=%d\n", + skb->len, rx_pkt_offset, rx_pkt_length); + priv->stats.rx_dropped++; + dev_kfree_skb_any(skb); + return ret; + } + + if (priv->adapter->enable_net_mon && rx_pkt_type == PKT_TYPE_802DOT11) { + ret = nxpwifi_recv_packet_to_monif(priv, skb); + if (ret) + dev_kfree_skb_any(skb); + return ret; + } + + if (rx_pkt_type == PKT_TYPE_MGMT) { + ret = nxpwifi_process_mgmt_packet(priv, skb); + if (ret && (ret != -EINPROGRESS)) + nxpwifi_dbg(adapter, DATA, "Rx of mgmt packet failed"); + if (ret != -EINPROGRESS) + dev_kfree_skb_any(skb); + return ret; + } + + /* + * If the packet is not an unicast packet then send the packet + * directly to os. Don't pass thru rx reordering + */ + if (!IS_11N_ENABLED(priv) || + !ether_addr_equal_unaligned(priv->curr_addr, + rx_pkt_hdr->eth803_hdr.h_dest)) { + nxpwifi_process_rx_packet(priv, skb); + return ret; + } + + if (nxpwifi_queuing_ra_based(priv)) { + memcpy(ta, rx_pkt_hdr->eth803_hdr.h_source, ETH_ALEN); + } else { + if (rx_pkt_type != PKT_TYPE_BAR && + local_rx_pd->priority < MAX_NUM_TID) + priv->rx_seq[local_rx_pd->priority] = seq_num; + memcpy(ta, priv->curr_bss_params.bss_descriptor.mac_address, + ETH_ALEN); + } + + /* Reorder and send to OS */ + ret = nxpwifi_11n_rx_reorder_pkt(priv, seq_num, local_rx_pd->priority, + ta, (u8)rx_pkt_type, skb); + + if (ret || rx_pkt_type == PKT_TYPE_BAR) + dev_kfree_skb_any(skb); + + if (ret) + priv->stats.rx_dropped++; + + return ret; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/sta_tx.c b/drivers/net/wireless/nxp/nxpwifi/sta_tx.c new file mode 100644 index 000000000000..5ad578223f1c --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/sta_tx.c @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP Wireless LAN device driver: station TX data handling + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cmdevt.h" +#include "wmm.h" + +/* + * Fill TxPD for TX packets by inserting it before payload and setting required fields. + */ +void nxpwifi_process_sta_txpd(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct txpd *local_tx_pd; + struct nxpwifi_txinfo *tx_info = NXPWIFI_SKB_TXCB(skb); + unsigned int pad; + u16 pkt_type, pkt_length, pkt_offset; + int hroom = adapter->intf_hdr_len; + u32 tx_control; + + pkt_type = nxpwifi_is_skb_mgmt_frame(skb) ? PKT_TYPE_MGMT : 0; + + pad = ((uintptr_t)skb->data - (sizeof(*local_tx_pd) + hroom)) & + (NXPWIFI_DMA_ALIGN_SZ - 1); + skb_push(skb, sizeof(*local_tx_pd) + pad); + + local_tx_pd = (struct txpd *)skb->data; + memset(local_tx_pd, 0, sizeof(struct txpd)); + local_tx_pd->bss_num = priv->bss_num; + local_tx_pd->bss_type = priv->bss_type; + + pkt_length = (u16)(skb->len - (sizeof(struct txpd) + pad)); + if (pkt_type == PKT_TYPE_MGMT) + pkt_length -= NXPWIFI_MGMT_FRAME_HEADER_SIZE; + local_tx_pd->tx_pkt_length = cpu_to_le16(pkt_length); + + local_tx_pd->priority = (u8)skb->priority; + local_tx_pd->pkt_delay_2ms = + nxpwifi_wmm_compute_drv_pkt_delay(priv, skb); + + if (tx_info->flags & NXPWIFI_BUF_FLAG_EAPOL_TX_STATUS || + tx_info->flags & NXPWIFI_BUF_FLAG_ACTION_TX_STATUS) { + local_tx_pd->tx_token_id = tx_info->ack_frame_id; + local_tx_pd->flags |= NXPWIFI_TXPD_FLAGS_REQ_TX_STATUS; + } + + if (local_tx_pd->priority < + ARRAY_SIZE(priv->wmm.user_pri_pkt_tx_ctrl)) { + /* + * Set the priority specific tx_control field, setting of 0 will + * cause the default value to be used later in this function + */ + tx_control = + priv->wmm.user_pri_pkt_tx_ctrl[local_tx_pd->priority]; + local_tx_pd->tx_control = cpu_to_le32(tx_control); + } + + if (adapter->pps_uapsd_mode) { + if (nxpwifi_check_last_packet_indication(priv)) { + adapter->tx_lock_flag = true; + local_tx_pd->flags = + NXPWIFI_TxPD_POWER_MGMT_LAST_PACKET; + } + } + + /* Offset of actual data */ + pkt_offset = sizeof(struct txpd) + pad; + if (pkt_type == PKT_TYPE_MGMT) { + /* Set the packet type and add header for management frame */ + local_tx_pd->tx_pkt_type = cpu_to_le16(pkt_type); + pkt_offset += NXPWIFI_MGMT_FRAME_HEADER_SIZE; + } + + local_tx_pd->tx_pkt_offset = cpu_to_le16(pkt_offset); + + /* make space for adapter->intf_hdr_len */ + skb_push(skb, hroom); + + if (!local_tx_pd->tx_control) + /* TxCtrl set by user or default */ + local_tx_pd->tx_control = cpu_to_le32(priv->pkt_tx_ctrl); +} + +/* Send a NULL-data frame with TxPD at highest priority. */ +int nxpwifi_send_null_packet(struct nxpwifi_private *priv, u8 flags) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct txpd *local_tx_pd; + struct nxpwifi_tx_param tx_param; +/* sizeof(struct txpd) + Interface specific header */ +#define NULL_PACKET_HDR 64 + u32 data_len = NULL_PACKET_HDR; + struct sk_buff *skb; + int ret; + struct nxpwifi_txinfo *tx_info = NULL; + + if (test_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags)) + return -EPERM; + + if (!priv->media_connected) + return -EPERM; + + if (adapter->data_sent) + return -EBUSY; + + skb = dev_alloc_skb(data_len); + if (!skb) + return -ENOMEM; + + tx_info = NXPWIFI_SKB_TXCB(skb); + memset(tx_info, 0, sizeof(*tx_info)); + tx_info->bss_num = priv->bss_num; + tx_info->bss_type = priv->bss_type; + tx_info->pkt_len = data_len - + (sizeof(struct txpd) + adapter->intf_hdr_len); + skb_reserve(skb, sizeof(struct txpd) + adapter->intf_hdr_len); + skb_push(skb, sizeof(struct txpd)); + + local_tx_pd = (struct txpd *)skb->data; + local_tx_pd->tx_control = cpu_to_le32(priv->pkt_tx_ctrl); + local_tx_pd->flags = flags; + local_tx_pd->priority = WMM_HIGHEST_PRIORITY; + local_tx_pd->tx_pkt_offset = cpu_to_le16(sizeof(struct txpd)); + local_tx_pd->bss_num = priv->bss_num; + local_tx_pd->bss_type = priv->bss_type; + + skb_push(skb, adapter->intf_hdr_len); + tx_param.next_pkt_len = 0; + ret = adapter->if_ops.host_to_card(adapter, NXPWIFI_TYPE_DATA, + skb, &tx_param); + + switch (ret) { + case -EBUSY: + dev_kfree_skb_any(skb); + nxpwifi_dbg(adapter, ERROR, + "%s: host_to_card failed: ret=%d\n", + __func__, ret); + adapter->dbg.num_tx_host_to_card_failure++; + break; + case 0: + dev_kfree_skb_any(skb); + nxpwifi_dbg(adapter, DATA, + "data: %s: host_to_card succeeded\n", + __func__); + adapter->tx_lock_flag = true; + break; + case -EINPROGRESS: + adapter->tx_lock_flag = true; + break; + default: + dev_kfree_skb_any(skb); + nxpwifi_dbg(adapter, ERROR, + "%s: host_to_card failed: ret=%d\n", + __func__, ret); + adapter->dbg.num_tx_host_to_card_failure++; + break; + } + + return ret; +} + +/* Check whether a last‑packet indication needs to be sent. */ +u8 nxpwifi_check_last_packet_indication(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u8 ret = false; + + if (!adapter->sleep_period.period) + return ret; + if (nxpwifi_wmm_lists_empty(adapter)) + ret = true; + + if (ret && !adapter->cmd_sent && !adapter->curr_cmd && + !is_command_pending(adapter)) { + adapter->delay_null_pkt = false; + ret = true; + } else { + ret = false; + adapter->delay_null_pkt = true; + } + return ret; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/txrx.c b/drivers/net/wireless/nxp/nxpwifi/txrx.c new file mode 100644 index 000000000000..6e8b49138e57 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/txrx.c @@ -0,0 +1,352 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP Wireless LAN device driver: generic TX/RX data handling + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "wmm.h" + +/* + * Parse the RxPD, select the target interface, and dispatch the packet for + * handling. + */ +int nxpwifi_handle_rx_packet(struct nxpwifi_adapter *adapter, + struct sk_buff *skb) +{ + struct nxpwifi_private *priv = + nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + struct rxpd *local_rx_pd; + struct nxpwifi_rxinfo *rx_info = NXPWIFI_SKB_RXCB(skb); + int ret; + + local_rx_pd = (struct rxpd *)(skb->data); + /* Get the BSS number from rxpd, get corresponding priv */ + priv = nxpwifi_get_priv_by_id(adapter, local_rx_pd->bss_num & + BSS_NUM_MASK, local_rx_pd->bss_type); + if (!priv) + priv = nxpwifi_get_priv(adapter, NXPWIFI_BSS_ROLE_ANY); + + if (!priv) { + nxpwifi_dbg(adapter, ERROR, + "data: priv not found. Drop RX packet\n"); + dev_kfree_skb_any(skb); + return -EINVAL; + } + + nxpwifi_dbg_dump(adapter, DAT_D, "rx pkt:", skb->data, + min_t(size_t, skb->len, DEBUG_DUMP_DATA_MAX_LEN)); + + memset(rx_info, 0, sizeof(*rx_info)); + rx_info->bss_num = priv->bss_num; + rx_info->bss_type = priv->bss_type; + + if (priv->bss_role == NXPWIFI_BSS_ROLE_UAP) + ret = nxpwifi_process_uap_rx_packet(priv, skb); + else + ret = nxpwifi_process_sta_rx_packet(priv, skb); + + return ret; +} +EXPORT_SYMBOL_GPL(nxpwifi_handle_rx_packet); + +/* + * Add TxPD, validate, send the packet to firmware, then run completion + * callback. + */ +int nxpwifi_process_tx(struct nxpwifi_private *priv, struct sk_buff *skb, + struct nxpwifi_tx_param *tx_param) +{ + int hroom, ret; + struct nxpwifi_adapter *adapter = priv->adapter; + struct txpd *local_tx_pd = NULL; + struct nxpwifi_sta_node *dest_node; + struct ethhdr *hdr = (void *)skb->data; + + if (unlikely(!skb->len || + skb_headroom(skb) < NXPWIFI_MIN_DATA_HEADER_LEN)) { + ret = -EINVAL; + goto out; + } + + hroom = adapter->intf_hdr_len; + + if (priv->bss_role == NXPWIFI_BSS_ROLE_UAP) { + rcu_read_lock(); + dest_node = nxpwifi_get_sta_entry(priv, hdr->h_dest); + if (dest_node) { + dest_node->stats.tx_bytes += skb->len; + dest_node->stats.tx_packets++; + } + rcu_read_unlock(); + nxpwifi_process_uap_txpd(priv, skb); + } else { + nxpwifi_process_sta_txpd(priv, skb); + } + + if ((adapter->data_sent || adapter->tx_lock_flag)) { + skb_queue_tail(&adapter->tx_data_q, skb); + atomic_inc(&adapter->tx_queued); + return 0; + } + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) + local_tx_pd = (struct txpd *)(skb->data + hroom); + ret = adapter->if_ops.host_to_card(adapter, + NXPWIFI_TYPE_DATA, + skb, tx_param); + nxpwifi_dbg_dump(adapter, DAT_D, "tx pkt:", skb->data, + min_t(size_t, skb->len, DEBUG_DUMP_DATA_MAX_LEN)); + +out: + switch (ret) { + case -ENOSR: + nxpwifi_dbg(adapter, DATA, "data: -ENOSR is returned\n"); + break; + case -EBUSY: + if ((GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) && + adapter->pps_uapsd_mode && adapter->tx_lock_flag) { + priv->adapter->tx_lock_flag = false; + if (local_tx_pd) + local_tx_pd->flags = 0; + } + nxpwifi_dbg(adapter, ERROR, "data: -EBUSY is returned\n"); + break; + case -EINPROGRESS: + break; + case -EINVAL: + nxpwifi_dbg(adapter, ERROR, + "malformed skb (length: %u, headroom: %u)\n", + skb->len, skb_headroom(skb)); + fallthrough; + case 0: + nxpwifi_write_data_complete(adapter, skb, 0, ret); + break; + default: + nxpwifi_dbg(adapter, ERROR, + "nxpwifi_write_data_async failed: 0x%X\n", + ret); + adapter->dbg.num_tx_host_to_card_failure++; + nxpwifi_write_data_complete(adapter, skb, 0, ret); + break; + } + + return ret; +} + +static int nxpwifi_host_to_card(struct nxpwifi_adapter *adapter, + struct sk_buff *skb, + struct nxpwifi_tx_param *tx_param) +{ + struct txpd *local_tx_pd = NULL; + u8 *head_ptr = skb->data; + int ret = 0; + struct nxpwifi_private *priv; + struct nxpwifi_txinfo *tx_info; + + tx_info = NXPWIFI_SKB_TXCB(skb); + priv = nxpwifi_get_priv_by_id(adapter, tx_info->bss_num, + tx_info->bss_type); + if (!priv) { + nxpwifi_dbg(adapter, ERROR, + "data: priv not found. Drop TX packet\n"); + adapter->dbg.num_tx_host_to_card_failure++; + nxpwifi_write_data_complete(adapter, skb, 0, 0); + return ret; + } + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) + local_tx_pd = (struct txpd *)(head_ptr + adapter->intf_hdr_len); + + ret = adapter->if_ops.host_to_card(adapter, + NXPWIFI_TYPE_DATA, + skb, tx_param); + + switch (ret) { + case -ENOSR: + nxpwifi_dbg(adapter, ERROR, "data: -ENOSR is returned\n"); + break; + case -EBUSY: + if ((GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) && + adapter->pps_uapsd_mode && + adapter->tx_lock_flag) { + priv->adapter->tx_lock_flag = false; + if (local_tx_pd) + local_tx_pd->flags = 0; + } + skb_queue_head(&adapter->tx_data_q, skb); + if (tx_info->flags & NXPWIFI_BUF_FLAG_AGGR_PKT) + atomic_add(tx_info->aggr_num, &adapter->tx_queued); + else + atomic_inc(&adapter->tx_queued); + nxpwifi_dbg(adapter, ERROR, "data: -EBUSY is returned\n"); + break; + case -EINPROGRESS: + break; + case 0: + nxpwifi_write_data_complete(adapter, skb, 0, ret); + break; + default: + nxpwifi_dbg(adapter, ERROR, + "nxpwifi_write_data_async failed: 0x%X\n", ret); + adapter->dbg.num_tx_host_to_card_failure++; + nxpwifi_write_data_complete(adapter, skb, 0, ret); + break; + } + return ret; +} + +static int +nxpwifi_dequeue_tx_queue(struct nxpwifi_adapter *adapter) +{ + struct sk_buff *skb, *skb_next; + struct nxpwifi_txinfo *tx_info; + struct nxpwifi_tx_param tx_param; + + skb = skb_dequeue(&adapter->tx_data_q); + if (!skb) + return -ENOMEM; + + tx_info = NXPWIFI_SKB_TXCB(skb); + if (tx_info->flags & NXPWIFI_BUF_FLAG_AGGR_PKT) + atomic_sub(tx_info->aggr_num, &adapter->tx_queued); + else + atomic_dec(&adapter->tx_queued); + + if (!skb_queue_empty(&adapter->tx_data_q)) + skb_next = skb_peek(&adapter->tx_data_q); + else + skb_next = NULL; + tx_param.next_pkt_len = ((skb_next) ? skb_next->len : 0); + if (!tx_param.next_pkt_len) { + if (!nxpwifi_wmm_lists_empty(adapter)) + tx_param.next_pkt_len = 1; + } + return nxpwifi_host_to_card(adapter, skb, &tx_param); +} + +void +nxpwifi_process_tx_queue(struct nxpwifi_adapter *adapter) +{ + do { + if (adapter->data_sent || adapter->tx_lock_flag) + break; + if (nxpwifi_dequeue_tx_queue(adapter)) + break; + } while (!skb_queue_empty(&adapter->tx_data_q)); +} + +/* + * Packet send completion callback handler. + * + * It either frees the buffer directly or forwards it to another + * completion callback which checks conditions, updates statistics, + * wakes up stalled traffic queue if required, and then frees the buffer. + */ +int nxpwifi_write_data_complete(struct nxpwifi_adapter *adapter, + struct sk_buff *skb, int aggr, int status) +{ + struct nxpwifi_private *priv; + struct nxpwifi_txinfo *tx_info; + struct netdev_queue *txq; + int index; + + if (!skb) + return 0; + + tx_info = NXPWIFI_SKB_TXCB(skb); + priv = nxpwifi_get_priv_by_id(adapter, tx_info->bss_num, + tx_info->bss_type); + if (!priv) + goto done; + + nxpwifi_set_trans_start(priv->netdev); + + if (tx_info->flags & NXPWIFI_BUF_FLAG_BRIDGED_PKT) + atomic_dec_return(&adapter->pending_bridged_pkts); + + if (tx_info->flags & NXPWIFI_BUF_FLAG_AGGR_PKT) + goto done; + + if (!status) { + priv->stats.tx_packets++; + priv->stats.tx_bytes += tx_info->pkt_len; + if (priv->tx_timeout_cnt) + priv->tx_timeout_cnt = 0; + } else { + priv->stats.tx_errors++; + } + + if (aggr) + /* For skb_aggr, do not wake up tx queue */ + goto done; + + atomic_dec(&adapter->tx_pending); + + index = nxpwifi_1d_to_wmm_queue[skb->priority]; + if (atomic_dec_return(&priv->wmm_tx_pending[index]) < LOW_TX_PENDING) { + txq = netdev_get_tx_queue(priv->netdev, index); + if (netif_tx_queue_stopped(txq)) { + netif_tx_wake_queue(txq); + nxpwifi_dbg(adapter, DATA, "wake queue: %d\n", index); + } + } +done: + dev_kfree_skb_any(skb); + + return 0; +} +EXPORT_SYMBOL_GPL(nxpwifi_write_data_complete); + +void nxpwifi_parse_tx_status_event(struct nxpwifi_private *priv, + void *event_body) +{ + struct tx_status_event *tx_status = (void *)priv->adapter->event_body; + struct sk_buff *ack_skb; + struct nxpwifi_txinfo *tx_info; + + if (!tx_status->tx_token_id) + return; + + spin_lock_bh(&priv->ack_status_lock); + ack_skb = xa_erase(&priv->ack_status_frames, tx_status->tx_token_id); + spin_unlock_bh(&priv->ack_status_lock); + + if (ack_skb) { + tx_info = NXPWIFI_SKB_TXCB(ack_skb); + + if (tx_info->flags & NXPWIFI_BUF_FLAG_EAPOL_TX_STATUS) { + /* consumes ack_skb */ + skb_complete_wifi_ack(ack_skb, !tx_status->status); + } else { + /* Remove broadcast address which was added by driver */ + memmove(ack_skb->data + + sizeof(struct ieee80211_hdr_3addr) + + NXPWIFI_MGMT_FRAME_HEADER_SIZE + sizeof(u16), + ack_skb->data + + sizeof(struct ieee80211_hdr_3addr) + + NXPWIFI_MGMT_FRAME_HEADER_SIZE + sizeof(u16) + + ETH_ALEN, ack_skb->len - + (sizeof(struct ieee80211_hdr_3addr) + + NXPWIFI_MGMT_FRAME_HEADER_SIZE + sizeof(u16) + + ETH_ALEN)); + ack_skb->len = ack_skb->len - ETH_ALEN; + /* + * Remove driver's proprietary header including 2 bytes + * of packet length and pass actual management frame buffer + * to cfg80211. + */ + cfg80211_mgmt_tx_status(&priv->wdev, tx_info->cookie, + ack_skb->data + + NXPWIFI_MGMT_FRAME_HEADER_SIZE + + sizeof(u16), ack_skb->len - + (NXPWIFI_MGMT_FRAME_HEADER_SIZE + + sizeof(u16)), + !tx_status->status, GFP_ATOMIC); + dev_kfree_skb_any(ack_skb); + } + } +} diff --git a/drivers/net/wireless/nxp/nxpwifi/uap_cmd.c b/drivers/net/wireless/nxp/nxpwifi/uap_cmd.c new file mode 100644 index 000000000000..04551847643f --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/uap_cmd.c @@ -0,0 +1,1256 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP Wireless LAN device driver: AP specific command handling + * + * Copyright 2011-2024 NXP + */ + +#include "main.h" +#include "cmdevt.h" +#include "11n.h" +#include "11ac.h" +#include "11ax.h" + +/* Parse BSS params and append WPA/WPA2 TLVs to the command buffer. */ +static void +nxpwifi_uap_bss_wpa(u8 **tlv_buf, void *cmd_buf, u16 *param_size) +{ + struct host_cmd_tlv_pwk_cipher *pwk_cipher; + struct host_cmd_tlv_gwk_cipher *gwk_cipher; + struct host_cmd_tlv_passphrase *passphrase; + struct host_cmd_tlv_akmp *tlv_akmp; + struct nxpwifi_uap_bss_param *bss_cfg = cmd_buf; + u16 cmd_size = *param_size; + u8 *tlv = *tlv_buf; + + tlv_akmp = (struct host_cmd_tlv_akmp *)tlv; + tlv_akmp->header.type = cpu_to_le16(TLV_TYPE_UAP_AKMP); + tlv_akmp->header.len = cpu_to_le16(sizeof(struct host_cmd_tlv_akmp) - + sizeof(struct nxpwifi_ie_types_header)); + tlv_akmp->key_mgmt_operation = cpu_to_le16(bss_cfg->key_mgmt_operation); + tlv_akmp->key_mgmt = cpu_to_le16(bss_cfg->key_mgmt); + cmd_size += sizeof(struct host_cmd_tlv_akmp); + tlv += sizeof(struct host_cmd_tlv_akmp); + + if (bss_cfg->wpa_cfg.pairwise_cipher_wpa & VALID_CIPHER_BITMAP) { + pwk_cipher = (struct host_cmd_tlv_pwk_cipher *)tlv; + pwk_cipher->header.type = cpu_to_le16(TLV_TYPE_PWK_CIPHER); + pwk_cipher->header.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_pwk_cipher) - + sizeof(struct nxpwifi_ie_types_header)); + pwk_cipher->proto = cpu_to_le16(PROTOCOL_WPA); + pwk_cipher->cipher = bss_cfg->wpa_cfg.pairwise_cipher_wpa; + cmd_size += sizeof(struct host_cmd_tlv_pwk_cipher); + tlv += sizeof(struct host_cmd_tlv_pwk_cipher); + } + + if (bss_cfg->wpa_cfg.pairwise_cipher_wpa2 & VALID_CIPHER_BITMAP) { + pwk_cipher = (struct host_cmd_tlv_pwk_cipher *)tlv; + pwk_cipher->header.type = cpu_to_le16(TLV_TYPE_PWK_CIPHER); + pwk_cipher->header.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_pwk_cipher) - + sizeof(struct nxpwifi_ie_types_header)); + pwk_cipher->proto = cpu_to_le16(PROTOCOL_WPA2); + pwk_cipher->cipher = bss_cfg->wpa_cfg.pairwise_cipher_wpa2; + cmd_size += sizeof(struct host_cmd_tlv_pwk_cipher); + tlv += sizeof(struct host_cmd_tlv_pwk_cipher); + } + + if (bss_cfg->wpa_cfg.group_cipher & VALID_CIPHER_BITMAP) { + gwk_cipher = (struct host_cmd_tlv_gwk_cipher *)tlv; + gwk_cipher->header.type = cpu_to_le16(TLV_TYPE_GWK_CIPHER); + gwk_cipher->header.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_gwk_cipher) - + sizeof(struct nxpwifi_ie_types_header)); + gwk_cipher->cipher = bss_cfg->wpa_cfg.group_cipher; + cmd_size += sizeof(struct host_cmd_tlv_gwk_cipher); + tlv += sizeof(struct host_cmd_tlv_gwk_cipher); + } + + if (bss_cfg->wpa_cfg.length) { + passphrase = (struct host_cmd_tlv_passphrase *)tlv; + passphrase->header.type = + cpu_to_le16(TLV_TYPE_UAP_WPA_PASSPHRASE); + passphrase->header.len = cpu_to_le16(bss_cfg->wpa_cfg.length); + memcpy(passphrase->passphrase, bss_cfg->wpa_cfg.passphrase, + bss_cfg->wpa_cfg.length); + cmd_size += sizeof(struct nxpwifi_ie_types_header) + + bss_cfg->wpa_cfg.length; + tlv += sizeof(struct nxpwifi_ie_types_header) + + bss_cfg->wpa_cfg.length; + } + + *param_size = cmd_size; + *tlv_buf = tlv; +} + +/* Parse BSS params and append WEP TLVs to the command buffer. */ +static void +nxpwifi_uap_bss_wep(u8 **tlv_buf, void *cmd_buf, u16 *param_size) +{ + struct host_cmd_tlv_wep_key *wep_key; + u16 cmd_size = *param_size; + int i; + u8 *tlv = *tlv_buf; + struct nxpwifi_uap_bss_param *bss_cfg = cmd_buf; + + for (i = 0; i < NUM_WEP_KEYS; i++) { + if (bss_cfg->wep_cfg[i].length && + (bss_cfg->wep_cfg[i].length == WLAN_KEY_LEN_WEP40 || + bss_cfg->wep_cfg[i].length == WLAN_KEY_LEN_WEP104)) { + wep_key = (struct host_cmd_tlv_wep_key *)tlv; + wep_key->header.type = + cpu_to_le16(TLV_TYPE_UAP_WEP_KEY); + wep_key->header.len = + cpu_to_le16(bss_cfg->wep_cfg[i].length + 2); + wep_key->key_index = bss_cfg->wep_cfg[i].key_index; + wep_key->is_default = bss_cfg->wep_cfg[i].is_default; + memcpy(wep_key->key, bss_cfg->wep_cfg[i].key, + bss_cfg->wep_cfg[i].length); + cmd_size += sizeof(struct nxpwifi_ie_types_header) + 2 + + bss_cfg->wep_cfg[i].length; + tlv += sizeof(struct nxpwifi_ie_types_header) + 2 + + bss_cfg->wep_cfg[i].length; + } + } + + *param_size = cmd_size; + *tlv_buf = tlv; +} + +/* Parse BSS params and append TLVs to the command buffer. */ +static int nxpwifi_uap_bss_param_prepare(struct nxpwifi_private *priv, u8 *tlv, + void *cmd_buf, u16 *param_size) +{ + struct host_cmd_tlv_mac_addr *mac_tlv; + struct host_cmd_tlv_dtim_period *dtim_period; + struct host_cmd_tlv_beacon_period *beacon_period; + struct host_cmd_tlv_ssid *ssid; + struct host_cmd_tlv_bcast_ssid *bcast_ssid; + struct host_cmd_tlv_channel_band *chan_band; + struct host_cmd_tlv_frag_threshold *frag_threshold; + struct host_cmd_tlv_rts_threshold *rts_threshold; + struct host_cmd_tlv_retry_limit *retry_limit; + struct host_cmd_tlv_encrypt_protocol *encrypt_protocol; + struct host_cmd_tlv_auth_type *auth_type; + struct host_cmd_tlv_rates *tlv_rates; + struct host_cmd_tlv_ageout_timer *ao_timer, *ps_ao_timer; + struct host_cmd_tlv_power_constraint *pwr_ct; + struct nxpwifi_ie_types_htcap *htcap; + struct nxpwifi_uap_bss_param *bss_cfg = cmd_buf; + int i; + u16 cmd_size = *param_size; + + mac_tlv = (struct host_cmd_tlv_mac_addr *)tlv; + mac_tlv->header.type = cpu_to_le16(TLV_TYPE_UAP_MAC_ADDRESS); + mac_tlv->header.len = cpu_to_le16(ETH_ALEN); + memcpy(mac_tlv->mac_addr, bss_cfg->mac_addr, ETH_ALEN); + cmd_size += sizeof(struct host_cmd_tlv_mac_addr); + tlv += sizeof(struct host_cmd_tlv_mac_addr); + + if (bss_cfg->ssid.ssid_len) { + ssid = (struct host_cmd_tlv_ssid *)tlv; + ssid->header.type = cpu_to_le16(TLV_TYPE_UAP_SSID); + ssid->header.len = cpu_to_le16((u16)bss_cfg->ssid.ssid_len); + memcpy(ssid->ssid, bss_cfg->ssid.ssid, bss_cfg->ssid.ssid_len); + cmd_size += sizeof(struct nxpwifi_ie_types_header) + + bss_cfg->ssid.ssid_len; + tlv += sizeof(struct nxpwifi_ie_types_header) + + bss_cfg->ssid.ssid_len; + + bcast_ssid = (struct host_cmd_tlv_bcast_ssid *)tlv; + bcast_ssid->header.type = cpu_to_le16(TLV_TYPE_UAP_BCAST_SSID); + bcast_ssid->header.len = + cpu_to_le16(sizeof(bcast_ssid->bcast_ctl)); + bcast_ssid->bcast_ctl = bss_cfg->bcast_ssid_ctl; + cmd_size += sizeof(struct host_cmd_tlv_bcast_ssid); + tlv += sizeof(struct host_cmd_tlv_bcast_ssid); + } + if (bss_cfg->rates[0]) { + tlv_rates = (struct host_cmd_tlv_rates *)tlv; + tlv_rates->header.type = cpu_to_le16(TLV_TYPE_UAP_RATES); + + for (i = 0; i < NXPWIFI_SUPPORTED_RATES && bss_cfg->rates[i]; + i++) + tlv_rates->rates[i] = bss_cfg->rates[i]; + + tlv_rates->header.len = cpu_to_le16(i); + cmd_size += sizeof(struct host_cmd_tlv_rates) + i; + tlv += sizeof(struct host_cmd_tlv_rates) + i; + } + if (bss_cfg->channel && + (((bss_cfg->band_cfg & BIT(0)) == BAND_CONFIG_BG && + bss_cfg->channel <= MAX_CHANNEL_BAND_BG) || + ((bss_cfg->band_cfg & BIT(0)) == BAND_CONFIG_A && + bss_cfg->channel <= MAX_CHANNEL_BAND_A))) { + chan_band = (struct host_cmd_tlv_channel_band *)tlv; + chan_band->header.type = cpu_to_le16(TLV_TYPE_CHANNELBANDLIST); + chan_band->header.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_channel_band) - + sizeof(struct nxpwifi_ie_types_header)); + chan_band->band_config = bss_cfg->band_cfg; + chan_band->channel = bss_cfg->channel; + cmd_size += sizeof(struct host_cmd_tlv_channel_band); + tlv += sizeof(struct host_cmd_tlv_channel_band); + } + if (bss_cfg->beacon_period >= NXPWIFI_BEACON_PERIOD_MIN && + bss_cfg->beacon_period <= NXPWIFI_BEACON_PERIOD_MAX) { + beacon_period = (struct host_cmd_tlv_beacon_period *)tlv; + beacon_period->header.type = + cpu_to_le16(TLV_TYPE_UAP_BEACON_PERIOD); + beacon_period->header.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_beacon_period) - + sizeof(struct nxpwifi_ie_types_header)); + beacon_period->period = cpu_to_le16(bss_cfg->beacon_period); + cmd_size += sizeof(struct host_cmd_tlv_beacon_period); + tlv += sizeof(struct host_cmd_tlv_beacon_period); + } + if (bss_cfg->dtim_period >= NXPWIFI_MIN_DTIM_PERIOD && + bss_cfg->dtim_period <= NXPWIFI_MAX_DTIM_PERIOD) { + dtim_period = (struct host_cmd_tlv_dtim_period *)tlv; + dtim_period->header.type = + cpu_to_le16(TLV_TYPE_UAP_DTIM_PERIOD); + dtim_period->header.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_dtim_period) - + sizeof(struct nxpwifi_ie_types_header)); + dtim_period->period = bss_cfg->dtim_period; + cmd_size += sizeof(struct host_cmd_tlv_dtim_period); + tlv += sizeof(struct host_cmd_tlv_dtim_period); + } + if (bss_cfg->rts_threshold <= NXPWIFI_RTS_THRESHOLD_MAX) { + rts_threshold = (struct host_cmd_tlv_rts_threshold *)tlv; + rts_threshold->header.type = + cpu_to_le16(TLV_TYPE_UAP_RTS_THRESHOLD); + rts_threshold->header.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_rts_threshold) - + sizeof(struct nxpwifi_ie_types_header)); + rts_threshold->rts_thr = cpu_to_le16(bss_cfg->rts_threshold); + cmd_size += sizeof(struct host_cmd_tlv_frag_threshold); + tlv += sizeof(struct host_cmd_tlv_frag_threshold); + } + if (bss_cfg->frag_threshold >= NXPWIFI_FRAG_THRESHOLD_MIN && + bss_cfg->frag_threshold <= NXPWIFI_FRAG_THRESHOLD_MAX) { + frag_threshold = (struct host_cmd_tlv_frag_threshold *)tlv; + frag_threshold->header.type = + cpu_to_le16(TLV_TYPE_UAP_FRAG_THRESHOLD); + frag_threshold->header.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_frag_threshold) - + sizeof(struct nxpwifi_ie_types_header)); + frag_threshold->frag_thr = cpu_to_le16(bss_cfg->frag_threshold); + cmd_size += sizeof(struct host_cmd_tlv_frag_threshold); + tlv += sizeof(struct host_cmd_tlv_frag_threshold); + } + if (bss_cfg->retry_limit <= NXPWIFI_RETRY_LIMIT_MAX) { + retry_limit = (struct host_cmd_tlv_retry_limit *)tlv; + retry_limit->header.type = + cpu_to_le16(TLV_TYPE_UAP_RETRY_LIMIT); + retry_limit->header.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_retry_limit) - + sizeof(struct nxpwifi_ie_types_header)); + retry_limit->limit = (u8)bss_cfg->retry_limit; + cmd_size += sizeof(struct host_cmd_tlv_retry_limit); + tlv += sizeof(struct host_cmd_tlv_retry_limit); + } + if ((bss_cfg->protocol & PROTOCOL_WPA) || + (bss_cfg->protocol & PROTOCOL_WPA2) || + (bss_cfg->protocol & PROTOCOL_EAP)) + nxpwifi_uap_bss_wpa(&tlv, cmd_buf, &cmd_size); + else + nxpwifi_uap_bss_wep(&tlv, cmd_buf, &cmd_size); + + if (bss_cfg->auth_mode <= WLAN_AUTH_SHARED_KEY || + bss_cfg->auth_mode == NXPWIFI_AUTH_MODE_AUTO) { + auth_type = (struct host_cmd_tlv_auth_type *)tlv; + auth_type->header.type = cpu_to_le16(TLV_TYPE_AUTH_TYPE); + auth_type->header.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_auth_type) - + sizeof(struct nxpwifi_ie_types_header)); + auth_type->auth_type = (u8)bss_cfg->auth_mode; + auth_type->pwe_derivation = 0; + auth_type->transition_disable = 0; + cmd_size += sizeof(struct host_cmd_tlv_auth_type); + tlv += sizeof(struct host_cmd_tlv_auth_type); + } + if (bss_cfg->protocol) { + encrypt_protocol = (struct host_cmd_tlv_encrypt_protocol *)tlv; + encrypt_protocol->header.type = + cpu_to_le16(TLV_TYPE_UAP_ENCRY_PROTOCOL); + encrypt_protocol->header.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_encrypt_protocol) + - sizeof(struct nxpwifi_ie_types_header)); + encrypt_protocol->proto = cpu_to_le16(bss_cfg->protocol); + cmd_size += sizeof(struct host_cmd_tlv_encrypt_protocol); + tlv += sizeof(struct host_cmd_tlv_encrypt_protocol); + } + + if (bss_cfg->ht_cap.cap_info) { + htcap = (struct nxpwifi_ie_types_htcap *)tlv; + htcap->header.type = cpu_to_le16(WLAN_EID_HT_CAPABILITY); + htcap->header.len = + cpu_to_le16(sizeof(struct ieee80211_ht_cap)); + htcap->ht_cap.cap_info = bss_cfg->ht_cap.cap_info; + htcap->ht_cap.ampdu_params_info = + bss_cfg->ht_cap.ampdu_params_info; + memcpy(&htcap->ht_cap.mcs, &bss_cfg->ht_cap.mcs, + sizeof(struct ieee80211_mcs_info)); + htcap->ht_cap.extended_ht_cap_info = + bss_cfg->ht_cap.extended_ht_cap_info; + htcap->ht_cap.tx_BF_cap_info = bss_cfg->ht_cap.tx_BF_cap_info; + htcap->ht_cap.antenna_selection_info = + bss_cfg->ht_cap.antenna_selection_info; + cmd_size += sizeof(struct nxpwifi_ie_types_htcap); + tlv += sizeof(struct nxpwifi_ie_types_htcap); + } + + if (priv->wmm_enabled) { + struct nxpwifi_ie_types_wmmcap *wmm_cap; + struct nxpwifi_types_wmm_info *fw_wmm; + const struct ieee80211_wmm_param_ie *ie; + + wmm_cap = (struct nxpwifi_ie_types_wmmcap *)tlv; + fw_wmm = &wmm_cap->wmm_info; + ie = &bss_cfg->wmm_element; + + wmm_cap->header.type = cpu_to_le16(WLAN_EID_VENDOR_SPECIFIC); + wmm_cap->header.len = + cpu_to_le16(sizeof(struct nxpwifi_types_wmm_info)); + + /* Map 802.11 WMM IE fields to FW WMM TLV payload */ + fw_wmm->oui[0] = ie->oui[0]; + fw_wmm->oui[1] = ie->oui[1]; + fw_wmm->oui[2] = ie->oui[2]; + fw_wmm->oui[3] = ie->oui_type; + + fw_wmm->subtype = ie->oui_subtype; + fw_wmm->version = ie->version; + fw_wmm->qos_info = ie->qos_info; + fw_wmm->reserved = ie->reserved; + + memcpy(fw_wmm->ac, ie->ac, sizeof(fw_wmm->ac)); + + cmd_size += sizeof(*wmm_cap); + tlv += sizeof(*wmm_cap); + } + + if (bss_cfg->sta_ao_timer) { + ao_timer = (struct host_cmd_tlv_ageout_timer *)tlv; + ao_timer->header.type = cpu_to_le16(TLV_TYPE_UAP_AO_TIMER); + ao_timer->header.len = cpu_to_le16(sizeof(*ao_timer) - + sizeof(struct nxpwifi_ie_types_header)); + ao_timer->sta_ao_timer = cpu_to_le32(bss_cfg->sta_ao_timer); + cmd_size += sizeof(*ao_timer); + tlv += sizeof(*ao_timer); + } + + if (bss_cfg->power_constraint) { + pwr_ct = (void *)tlv; + pwr_ct->header.type = cpu_to_le16(TLV_TYPE_PWR_CONSTRAINT); + pwr_ct->header.len = cpu_to_le16(sizeof(u8)); + pwr_ct->constraint = bss_cfg->power_constraint; + cmd_size += sizeof(*pwr_ct); + tlv += sizeof(*pwr_ct); + } + + if (bss_cfg->ps_sta_ao_timer) { + ps_ao_timer = (struct host_cmd_tlv_ageout_timer *)tlv; + ps_ao_timer->header.type = + cpu_to_le16(TLV_TYPE_UAP_PS_AO_TIMER); + ps_ao_timer->header.len = cpu_to_le16(sizeof(*ps_ao_timer) - + sizeof(struct nxpwifi_ie_types_header)); + ps_ao_timer->sta_ao_timer = + cpu_to_le32(bss_cfg->ps_sta_ao_timer); + cmd_size += sizeof(*ps_ao_timer); + tlv += sizeof(*ps_ao_timer); + } + + *param_size = cmd_size; + + return 0; +} + +/* Parse custom IEs and write them to the command buffer. */ +static int nxpwifi_uap_custom_ie_prepare(u8 *tlv, void *cmd_buf, u16 *ie_size) +{ + struct nxpwifi_ie_list *ap_ie = cmd_buf; + struct nxpwifi_ie_types_header *tlv_ie = (void *)tlv; + + if (!ap_ie || !ap_ie->len) + return -EINVAL; + + *ie_size += le16_to_cpu(ap_ie->len) + + sizeof(struct nxpwifi_ie_types_header); + + tlv_ie->type = cpu_to_le16(TLV_TYPE_MGMT_IE); + tlv_ie->len = ap_ie->len; + tlv += sizeof(struct nxpwifi_ie_types_header); + + memcpy(tlv, ap_ie->ie_list, le16_to_cpu(ap_ie->len)); + + return 0; +} + +static int +nxpwifi_cmd_uap_sys_config(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + u8 *tlv; + u16 cmd_size, param_size, ie_size; + struct host_cmd_ds_sys_config *sys_cfg; + int ret = 0; + + cmd->command = cpu_to_le16(HOST_CMD_UAP_SYS_CONFIG); + cmd_size = (u16)(sizeof(struct host_cmd_ds_sys_config) + S_DS_GEN); + sys_cfg = &cmd->params.uap_sys_config; + sys_cfg->action = cpu_to_le16(cmd_action); + tlv = sys_cfg->tlv; + + switch (cmd_type) { + case UAP_BSS_PARAMS_I: + param_size = cmd_size; + ret = nxpwifi_uap_bss_param_prepare(priv, tlv, data_buf, ¶m_size); + if (ret) + return ret; + cmd->size = cpu_to_le16(param_size); + break; + case UAP_CUSTOM_IE_I: + ie_size = cmd_size; + ret = nxpwifi_uap_custom_ie_prepare(tlv, data_buf, &ie_size); + if (ret) + return ret; + cmd->size = cpu_to_le16(ie_size); + break; + default: + return -EINVAL; + } + + return ret; +} + +static int +nxpwifi_cmd_uap_bss_start(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct nxpwifi_ie_types_host_mlme *tlv; + int size; + + cmd->command = cpu_to_le16(HOST_CMD_UAP_BSS_START); + size = S_DS_GEN; + + tlv = (struct nxpwifi_ie_types_host_mlme *)((u8 *)cmd + size); + tlv->header.type = cpu_to_le16(TLV_TYPE_HOST_MLME); + tlv->header.len = cpu_to_le16(sizeof(tlv->host_mlme)); + tlv->host_mlme = 1; + size += sizeof(struct nxpwifi_ie_types_host_mlme); + + cmd->size = cpu_to_le16(size); + + return 0; +} + +static int +nxpwifi_ret_uap_bss_start(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + adapter->tx_lock_flag = false; + adapter->pps_uapsd_mode = false; + adapter->delay_null_pkt = false; + priv->bss_started = 1; + + return 0; +} + +static int +nxpwifi_ret_uap_bss_stop(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, + void *data_buf) +{ + priv->bss_started = 0; + + return 0; +} + +static int nxpwifi_ret_apcmd_sta_list(struct nxpwifi_private *priv, + struct host_cmd_ds_command *resp, + u16 cmdresp_no, void *data_buf) +{ + struct host_cmd_ds_sta_list *sta_list = &resp->params.sta_list; + struct nxpwifi_ie_types_sta_info *sta_info; + struct nxpwifi_sta_node *sta_node; + u16 sta_count; + u32 resp_size; + u32 base; + u32 required_size; + int i; + + resp_size = le16_to_cpu(resp->size); + sta_count = le16_to_cpu(sta_list->sta_count); + + /* End of fixed fields before sta_list.tlv[] */ + base = offsetofend(struct host_cmd_ds_command, params.sta_list.sta_count); + + /* At least fixed fields must be present */ + if (resp_size < base) + return -EINVAL; + + required_size = base + sta_count * sizeof(*sta_info); + + /* Verify firmware did not claim more entries than the buffer holds */ + if (resp_size < required_size) + return -EINVAL; + + sta_info = (void *)sta_list->tlv; + + rcu_read_lock(); + for (i = 0; i < sta_count; i++) { + sta_node = nxpwifi_get_sta_entry(priv, sta_info->mac); + if (unlikely(!sta_node)) { + sta_info++; + continue; + } + + sta_node->stats.rssi = sta_info->rssi; + sta_info++; + } + rcu_read_unlock(); + + return 0; +} + +/* Build AP deauth command for the given MAC address. */ +static int nxpwifi_cmd_uap_sta_deauth(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_sta_deauth *sta_deauth = &cmd->params.sta_deauth; + u8 *mac = (u8 *)data_buf; + + cmd->command = cpu_to_le16(HOST_CMD_UAP_STA_DEAUTH); + memcpy(sta_deauth->mac, mac, ETH_ALEN); + sta_deauth->reason = cpu_to_le16(WLAN_REASON_DEAUTH_LEAVING); + + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_sta_deauth) + + S_DS_GEN); + return 0; +} + +static int +nxpwifi_cmd_uap_chan_report_request(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + return nxpwifi_cmd_issue_chan_report_request(priv, cmd, data_buf); +} + +/* Build AP add-station command. */ +static int +nxpwifi_cmd_uap_add_new_station(struct nxpwifi_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_no, void *data_buf, + u16 cmd_action, u32 cmd_type) +{ + struct host_cmd_ds_add_station *new_sta = &cmd->params.sta_info; + struct nxpwifi_sta_info *add_sta = (struct nxpwifi_sta_info *)data_buf; + struct station_parameters *params = add_sta->params; + struct nxpwifi_sta_node *sta_ptr; + u16 cmd_size; + u8 *pos, *cmd_end; + u16 tlv_len; + struct nxpwifi_ie_types_sta_flag *sta_flag; + int i; + + cmd->command = cpu_to_le16(HOST_CMD_ADD_NEW_STATION); + new_sta->action = cpu_to_le16(cmd_action); + cmd_size = sizeof(struct host_cmd_ds_add_station) + S_DS_GEN; + + if (cmd_action == HOST_ACT_ADD_STA) + sta_ptr = nxpwifi_add_sta_entry(priv, add_sta->peer_mac); + else + sta_ptr = nxpwifi_get_sta_entry_rcu(priv, add_sta->peer_mac); + + if (!sta_ptr) + return -EINVAL; + + memcpy(new_sta->peer_mac, add_sta->peer_mac, ETH_ALEN); + + if (cmd_action == HOST_ACT_REMOVE_STA) { + cmd->size = cpu_to_le16(cmd_size); + return 0; + } + + new_sta->aid = cpu_to_le16(params->aid); + new_sta->listen_interval = cpu_to_le32(params->listen_interval); + new_sta->cap_info = cpu_to_le16(params->capability); + + pos = new_sta->tlv; + cmd_end = (u8 *)cmd; + cmd_end += (NXPWIFI_SIZE_OF_CMD_BUFFER - 1); + + if (params->sta_flags_set & NL80211_STA_FLAG_WME) + sta_ptr->is_wmm_enabled = 1; + sta_flag = (struct nxpwifi_ie_types_sta_flag *)pos; + sta_flag->header.type = cpu_to_le16(TLV_TYPE_UAP_STA_FLAGS); + sta_flag->header.len = cpu_to_le16(sizeof(__le32)); + sta_flag->sta_flags = cpu_to_le32(params->sta_flags_set); + pos += sizeof(struct nxpwifi_ie_types_sta_flag); + cmd_size += sizeof(struct nxpwifi_ie_types_sta_flag); + + if (params->ext_capab_len) { + u8 *data = (u8 *)params->ext_capab; + u16 len = params->ext_capab_len; + + tlv_len = nxpwifi_append_data_tlv(WLAN_EID_EXT_CAPABILITY, + data, len, pos, cmd_end); + if (!tlv_len) + return -EINVAL; + pos += tlv_len; + cmd_size += tlv_len; + } + + if (params->link_sta_params.supported_rates_len) { + u8 *data = (u8 *)params->link_sta_params.supported_rates; + u16 len = params->link_sta_params.supported_rates_len; + + tlv_len = nxpwifi_append_data_tlv(WLAN_EID_SUPP_RATES, + data, len, pos, cmd_end); + if (!tlv_len) + return -EINVAL; + pos += tlv_len; + cmd_size += tlv_len; + } + + if (params->uapsd_queues || params->max_sp) { + u8 qos_capability = params->uapsd_queues | (params->max_sp << 5); + u8 *data = &qos_capability; + u16 len = sizeof(u8); + + tlv_len = nxpwifi_append_data_tlv(WLAN_EID_QOS_CAPA, + data, len, pos, cmd_end); + if (!tlv_len) + return -EINVAL; + pos += tlv_len; + cmd_size += tlv_len; + sta_ptr->is_wmm_enabled = 1; + } + + if (params->link_sta_params.ht_capa) { + u8 *data = (u8 *)params->link_sta_params.ht_capa; + u16 len = sizeof(struct ieee80211_ht_cap); + + tlv_len = nxpwifi_append_data_tlv(WLAN_EID_HT_CAPABILITY, + data, len, pos, cmd_end); + if (!tlv_len) + return -EINVAL; + pos += tlv_len; + cmd_size += tlv_len; + sta_ptr->is_11n_enabled = 1; + sta_ptr->max_amsdu = + le16_to_cpu(params->link_sta_params.ht_capa->cap_info) & + IEEE80211_HT_CAP_MAX_AMSDU ? + NXPWIFI_TX_DATA_BUF_SIZE_8K : + NXPWIFI_TX_DATA_BUF_SIZE_4K; + } + + if (params->link_sta_params.vht_capa) { + u8 *data = (u8 *)params->link_sta_params.vht_capa; + u16 len = sizeof(struct ieee80211_vht_cap); + + tlv_len = nxpwifi_append_data_tlv(WLAN_EID_VHT_CAPABILITY, + data, len, pos, cmd_end); + if (!tlv_len) + return -EINVAL; + pos += tlv_len; + cmd_size += tlv_len; + sta_ptr->is_11ac_enabled = 1; + } + + if (params->link_sta_params.opmode_notif_used) { + u8 *data = ¶ms->link_sta_params.opmode_notif; + u16 len = sizeof(u8); + + tlv_len = nxpwifi_append_data_tlv(WLAN_EID_OPMODE_NOTIF, + data, len, pos, cmd_end); + if (!tlv_len) + return -EINVAL; + pos += tlv_len; + cmd_size += tlv_len; + } + + if (params->link_sta_params.he_capa_len) { + u8 *data = (u8 *)params->link_sta_params.he_capa; + u16 len = params->link_sta_params.he_capa_len; + + tlv_len = nxpwifi_append_data_tlv(WLAN_EID_EXT_HE_CAPABILITY, + data, len, pos, cmd_end); + if (!tlv_len) + return -EINVAL; + pos += tlv_len; + cmd_size += tlv_len; + sta_ptr->is_11ax_enabled = 1; + } + + for (i = 0; i < MAX_NUM_TID; i++) { + if (sta_ptr->is_11n_enabled || sta_ptr->is_11ax_enabled) + sta_ptr->ampdu_sta[i] = + priv->aggr_prio_tbl[i].ampdu_user; + else + sta_ptr->ampdu_sta[i] = BA_STREAM_NOT_ALLOWED; + } + + memset(sta_ptr->rx_seq, 0xff, sizeof(sta_ptr->rx_seq)); + + cmd->size = cpu_to_le16(cmd_size); + + return 0; +} + +static const struct nxpwifi_cmd_entry cmd_table_uap[] = { + {.cmd_no = HOST_CMD_APCMD_SYS_RESET, + .prepare_cmd = nxpwifi_cmd_fill_head_only, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_UAP_SYS_CONFIG, + .prepare_cmd = nxpwifi_cmd_uap_sys_config, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_UAP_BSS_START, + .prepare_cmd = nxpwifi_cmd_uap_bss_start, + .cmd_resp = nxpwifi_ret_uap_bss_start}, + {.cmd_no = HOST_CMD_UAP_BSS_STOP, + .prepare_cmd = nxpwifi_cmd_fill_head_only, + .cmd_resp = nxpwifi_ret_uap_bss_stop}, + {.cmd_no = HOST_CMD_APCMD_STA_LIST, + .prepare_cmd = nxpwifi_cmd_fill_head_only, + .cmd_resp = nxpwifi_ret_apcmd_sta_list}, + {.cmd_no = HOST_CMD_UAP_STA_DEAUTH, + .prepare_cmd = nxpwifi_cmd_uap_sta_deauth, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_CHAN_REPORT_REQUEST, + .prepare_cmd = nxpwifi_cmd_uap_chan_report_request, + .cmd_resp = NULL}, + {.cmd_no = HOST_CMD_ADD_NEW_STATION, + .prepare_cmd = nxpwifi_cmd_uap_add_new_station, + .cmd_resp = NULL}, +}; + +/* Prepare AP commands and dispatch to per-cmd builders before sending to firmware. */ +int nxpwifi_uap_prepare_cmd(struct nxpwifi_private *priv, + struct cmd_ctrl_node *cmd_node, + u16 cmd_action, u32 type) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u16 cmd_no = cmd_node->cmd_no; + struct host_cmd_ds_command *cmd = + (struct host_cmd_ds_command *)cmd_node->skb->data; + void *data_buf = cmd_node->data_buf; + int i, ret = -EINVAL; + + for (i = 0; i < ARRAY_SIZE(cmd_table_uap); i++) { + if (cmd_no == cmd_table_uap[i].cmd_no) { + if (cmd_table_uap[i].prepare_cmd) + ret = cmd_table_uap[i].prepare_cmd(priv, cmd, + cmd_no, + data_buf, + cmd_action, + type); + cmd_node->cmd_resp = cmd_table_uap[i].cmd_resp; + break; + } + } + + if (i == ARRAY_SIZE(cmd_table_uap)) + nxpwifi_dbg(adapter, ERROR, + "%s: unknown command: %#x\n", + __func__, cmd_no); + else + nxpwifi_dbg(adapter, CMD, + "%s: command: %#x\n", + __func__, cmd_no); + + return ret; +} + +/* Translate cfg80211_ap_settings security into bss_config for firmware. */ +int nxpwifi_set_secure_params(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_config, + struct cfg80211_ap_settings *params) +{ + int i; + struct nxpwifi_wep_key wep_key; + + if (!params->privacy) { + bss_config->protocol = PROTOCOL_NO_SECURITY; + bss_config->key_mgmt = KEY_MGMT_NONE; + bss_config->wpa_cfg.length = 0; + priv->sec_info.wep_enabled = 0; + priv->sec_info.wpa_enabled = 0; + priv->sec_info.wpa2_enabled = 0; + + return 0; + } + + switch (params->auth_type) { + case NL80211_AUTHTYPE_OPEN_SYSTEM: + bss_config->auth_mode = WLAN_AUTH_OPEN; + break; + case NL80211_AUTHTYPE_SHARED_KEY: + bss_config->auth_mode = WLAN_AUTH_SHARED_KEY; + break; + case NL80211_AUTHTYPE_NETWORK_EAP: + bss_config->auth_mode = WLAN_AUTH_LEAP; + break; + default: + bss_config->auth_mode = NXPWIFI_AUTH_MODE_AUTO; + break; + } + + bss_config->key_mgmt_operation |= KEY_MGMT_ON_HOST; + + bss_config->protocol = 0; + if (params->crypto.wpa_versions & NL80211_WPA_VERSION_1) + bss_config->protocol |= PROTOCOL_WPA; + if (params->crypto.wpa_versions & NL80211_WPA_VERSION_2) + bss_config->protocol |= PROTOCOL_WPA2; + + bss_config->key_mgmt = 0; + for (i = 0; i < params->crypto.n_akm_suites; i++) { + switch (params->crypto.akm_suites[i]) { + case WLAN_AKM_SUITE_8021X: + bss_config->key_mgmt |= KEY_MGMT_EAP; + break; + case WLAN_AKM_SUITE_PSK: + bss_config->key_mgmt |= KEY_MGMT_PSK; + break; + case WLAN_AKM_SUITE_PSK_SHA256: + bss_config->key_mgmt |= KEY_MGMT_PSK_SHA256; + break; + case WLAN_AKM_SUITE_OWE: + bss_config->key_mgmt |= KEY_MGMT_OWE; + break; + case WLAN_AKM_SUITE_SAE: + bss_config->key_mgmt |= KEY_MGMT_SAE; + break; + default: + break; + } + } + + for (i = 0; i < params->crypto.n_ciphers_pairwise; i++) { + switch (params->crypto.ciphers_pairwise[i]) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + break; + case WLAN_CIPHER_SUITE_TKIP: + if (params->crypto.wpa_versions & NL80211_WPA_VERSION_1) + bss_config->wpa_cfg.pairwise_cipher_wpa |= + CIPHER_TKIP; + if (params->crypto.wpa_versions & NL80211_WPA_VERSION_2) + bss_config->wpa_cfg.pairwise_cipher_wpa2 |= + CIPHER_TKIP; + break; + case WLAN_CIPHER_SUITE_CCMP: + if (params->crypto.wpa_versions & NL80211_WPA_VERSION_1) + bss_config->wpa_cfg.pairwise_cipher_wpa |= + CIPHER_AES_CCMP; + if (params->crypto.wpa_versions & NL80211_WPA_VERSION_2) + bss_config->wpa_cfg.pairwise_cipher_wpa2 |= + CIPHER_AES_CCMP; + break; + default: + break; + } + } + + switch (params->crypto.cipher_group) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + if (priv->sec_info.wep_enabled) { + bss_config->protocol = PROTOCOL_STATIC_WEP; + bss_config->key_mgmt = KEY_MGMT_NONE; + bss_config->wpa_cfg.length = 0; + + for (i = 0; i < NUM_WEP_KEYS; i++) { + wep_key = priv->wep_key[i]; + bss_config->wep_cfg[i].key_index = i; + + if (priv->wep_key_curr_index == i) + bss_config->wep_cfg[i].is_default = 1; + else + bss_config->wep_cfg[i].is_default = 0; + + bss_config->wep_cfg[i].length = + wep_key.key_length; + memcpy(&bss_config->wep_cfg[i].key, + &wep_key.key_material, + wep_key.key_length); + } + } + break; + case WLAN_CIPHER_SUITE_TKIP: + bss_config->wpa_cfg.group_cipher = CIPHER_TKIP; + break; + case WLAN_CIPHER_SUITE_CCMP: + bss_config->wpa_cfg.group_cipher = CIPHER_AES_CCMP; + break; + default: + break; + } + + return 0; +} + +/* Update 11n HT params from beacon and fill bss_config. */ +void +nxpwifi_set_ht_params(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params) +{ + const u8 *ht_ie; + + if (!ISSUPP_11NENABLED(priv->adapter->fw_cap_info)) + return; + + ht_ie = cfg80211_find_ie(WLAN_EID_HT_CAPABILITY, params->beacon.tail, + params->beacon.tail_len); + if (ht_ie) { + memcpy(&bss_cfg->ht_cap, ht_ie + 2, + sizeof(struct ieee80211_ht_cap)); + if (ISSUPP_BEAMFORMING(priv->adapter->hw_dot_11n_dev_cap)) + bss_cfg->ht_cap.tx_BF_cap_info = + cpu_to_le32(NXPWIFI_DEF_11N_TX_BF_CAP); + priv->ap_11n_enabled = 1; + } else { + memset(&bss_cfg->ht_cap, 0, sizeof(struct ieee80211_ht_cap)); + bss_cfg->ht_cap.cap_info = cpu_to_le16(NXPWIFI_DEF_HT_CAP); + bss_cfg->ht_cap.ampdu_params_info = NXPWIFI_DEF_AMPDU; + } +} + +/* Update 11ac VHT params from beacon and fill bss_config. */ +void nxpwifi_set_vht_params(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params) +{ + const u8 *vht_ie; + + vht_ie = cfg80211_find_ie(WLAN_EID_VHT_CAPABILITY, params->beacon.tail, + params->beacon.tail_len); + if (vht_ie) { + memcpy(&bss_cfg->vht_cap, vht_ie + 2, + sizeof(struct ieee80211_vht_cap)); + priv->ap_11ac_enabled = 1; + } else { + priv->ap_11ac_enabled = 0; + } +} + +/* Extract TPC request from beacon and set power_constraint. */ +void nxpwifi_set_tpc_params(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params) +{ + const u8 *tpc_ie; + + tpc_ie = cfg80211_find_ie(WLAN_EID_TPC_REQUEST, params->beacon.tail, + params->beacon.tail_len); + if (tpc_ie) + bss_cfg->power_constraint = *(tpc_ie + 2); + else + bss_cfg->power_constraint = 0; +} + +/* Enable VHT only when VHT IE is present; otherwise disable VHT. */ +void nxpwifi_set_vht_width(struct nxpwifi_private *priv, + enum nl80211_chan_width width, + bool ap_11ac_enable) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_11ac_vht_cfg vht_cfg; + + vht_cfg.band_config = VHT_CFG_5GHZ; + vht_cfg.cap_info = adapter->hw_dot_11ac_dev_cap; + + if (!ap_11ac_enable) { + vht_cfg.mcs_tx_set = DISABLE_VHT_MCS_SET; + vht_cfg.mcs_rx_set = DISABLE_VHT_MCS_SET; + } else { + vht_cfg.mcs_tx_set = DEFAULT_VHT_MCS_SET; + vht_cfg.mcs_rx_set = DEFAULT_VHT_MCS_SET; + } + + vht_cfg.misc_config = VHT_CAP_UAP_ONLY; + + if (ap_11ac_enable && width >= NL80211_CHAN_WIDTH_80) + vht_cfg.misc_config |= VHT_BW_80_160_80P80; + + nxpwifi_send_cmd(priv, HOST_CMD_11AC_CFG, + HOST_ACT_GEN_SET, 0, &vht_cfg, true); +} + +bool nxpwifi_check_11ax_capability(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u8 band = bss_cfg->band_cfg & BAND_CFG_CHAN_BAND_MASK; + + if (band == BAND_2GHZ && + !(adapter->fw_bands & BAND_GAX)) + return false; + + if (band == BAND_5GHZ && + !(adapter->fw_bands & BAND_AAX)) + return false; + + if (params->he_cap) + return true; + else + return false; +} + +int nxpwifi_set_11ax_status(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params) +{ + struct nxpwifi_11ax_he_cfg ax_cfg; + u8 band = bss_cfg->band_cfg & BAND_CFG_CHAN_BAND_MASK; + const struct element *he_cap; + int ret; + + if (band == BAND_2GHZ) + ax_cfg.band = BIT(0); + else if (band == BAND_5GHZ) + ax_cfg.band = BIT(1); + else + return -EINVAL; + + ret = nxpwifi_send_cmd(priv, HOST_CMD_11AX_CFG, + HOST_ACT_GEN_GET, 0, &ax_cfg, true); + if (ret) + return ret; + + he_cap = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_CAPABILITY, + params->beacon.tail, + params->beacon.tail_len); + + if (he_cap) { + ax_cfg.he_cap_cfg.id = he_cap->id; + ax_cfg.he_cap_cfg.len = he_cap->datalen; + if (params->twt_responder == 0) { + struct nxpwifi_11ax_he_cap_cfg *he_cap_cfg = + (struct nxpwifi_11ax_he_cap_cfg *)he_cap; + + he_cap_cfg->cap_elem.mac_cap_info[0] &= + ~HE_MAC_CAP_TWT_RESP_SUPPORT; + } + memcpy(ax_cfg.data + 4, + he_cap->data, + he_cap->datalen); + } else { + /* disable */ + if (ax_cfg.he_cap_cfg.len && + ax_cfg.he_cap_cfg.ext_id == WLAN_EID_EXT_HE_CAPABILITY) { + memset(ax_cfg.he_cap_cfg.he_txrx_mcs_support, 0xff, + sizeof(ax_cfg.he_cap_cfg.he_txrx_mcs_support)); + } + } + + return nxpwifi_send_cmd(priv, HOST_CMD_11AX_CFG, + HOST_ACT_GEN_SET, 0, &ax_cfg, true); +} + +/* Copy supported rates from beacon into bss_config. */ +void +nxpwifi_set_uap_rates(struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params) +{ + struct element *rate_ie; + int var_offset = offsetof(struct ieee80211_mgmt, u.beacon.variable); + const u8 *var_pos = params->beacon.head + var_offset; + int len = params->beacon.head_len - var_offset; + u8 rate_len = 0; + + rate_ie = (void *)cfg80211_find_ie(WLAN_EID_SUPP_RATES, var_pos, len); + if (rate_ie) { + if (rate_ie->datalen > NXPWIFI_SUPPORTED_RATES) + return; + memcpy(bss_cfg->rates, rate_ie + 1, rate_ie->datalen); + rate_len = rate_ie->datalen; + } + + rate_ie = (void *)cfg80211_find_ie(WLAN_EID_EXT_SUPP_RATES, + params->beacon.tail, + params->beacon.tail_len); + if (rate_ie) { + if (rate_ie->datalen > NXPWIFI_SUPPORTED_RATES - rate_len) + return; + memcpy(bss_cfg->rates + rate_len, + rate_ie + 1, rate_ie->datalen); + } +} + +/* + * Initialize bss_config fields to sentinel values. + * Fields left with sentinel values are treated as unset and will not be + * included in the corresponding firmware command. + */ +void nxpwifi_set_sys_config_invalid_data(struct nxpwifi_uap_bss_param *config) +{ + config->radio_ctl = __NXPWIFI_RADIO_CTL_MAX; + config->dtim_period = NXPWIFI_INVALID_DTIM_PERIOD; + config->beacon_period = NXPWIFI_INVALID_BEACON_PERIOD; + config->auth_mode = NXPWIFI_AUTH_MODE_AUTO; + config->rts_threshold = NXPWIFI_INVALID_RTS; + config->frag_threshold = NXPWIFI_INVALID_FRAG; + config->retry_limit = NXPWIFI_INVALID_RETRY_LIMI; +} + +/* Parse WMM params from cfg80211_ap_settings and update bss_config. */ +void +nxpwifi_set_wmm_params(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params) +{ + const u8 *vendor_ie; + const struct ieee80211_wmm_param_ie *wmm; + u8 ie_len; + + vendor_ie = cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT, + WLAN_OUI_TYPE_MICROSOFT_WMM, + params->beacon.tail, + params->beacon.tail_len); + if (!vendor_ie) + goto no_wmm; + + ie_len = vendor_ie[1]; + + if (ie_len < sizeof(struct ieee80211_wmm_param_ie) - 2) + goto no_wmm; + + wmm = (const struct ieee80211_wmm_param_ie *)vendor_ie; + + if (memcmp(wmm->oui, "\x00\x50\xf2", 3)) + goto no_wmm; + + if (wmm->oui_type != WLAN_OUI_TYPE_MICROSOFT_WMM) + goto no_wmm; + + if (wmm->oui_subtype != 1) + goto no_wmm; + + /* Only WMM version 1 is supported */ + if (wmm->version != 1) + goto no_wmm; + + memcpy(&bss_cfg->wmm_element, wmm, + sizeof(struct ieee80211_wmm_param_ie)); + + priv->wmm_enabled = true; + return; + +no_wmm: + memset(&bss_cfg->wmm_element, 0, sizeof(bss_cfg->wmm_element)); + priv->wmm_enabled = false; +} + +/* Enable 11d when country IE is present. */ +void nxpwifi_config_uap_11d(struct nxpwifi_private *priv, + struct cfg80211_beacon_data *beacon_data) +{ + enum state_11d_t state_11d; + const u8 *country_ie; + + country_ie = cfg80211_find_ie(WLAN_EID_COUNTRY, beacon_data->tail, + beacon_data->tail_len); + if (country_ie) { + /* Send cmd to FW to enable 11D function */ + state_11d = ENABLE_11D; + if (nxpwifi_send_cmd(priv, HOST_CMD_802_11_SNMP_MIB, + HOST_ACT_GEN_SET, DOT11D_I, + &state_11d, true)) { + nxpwifi_dbg(priv->adapter, ERROR, + "11D: failed to enable 11D\n"); + } + } +} + +void nxpwifi_uap_set_channel(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg, + struct cfg80211_chan_def chandef) +{ + u8 config_bands = 0, old_bands = priv->config_bands; + + priv->bss_chandef = chandef; + + bss_cfg->channel = + ieee80211_frequency_to_channel(chandef.chan->center_freq); + + nxpwifi_convert_chan_to_band_cfg(priv, &bss_cfg->band_cfg, &chandef); + + /* Set appropriate bands */ + if (chandef.chan->band == NL80211_BAND_2GHZ) { + config_bands = BAND_B | BAND_G; + if (chandef.width > NL80211_CHAN_WIDTH_20_NOHT) + config_bands |= BAND_GN | BAND_GAX; + } else { + config_bands = BAND_A; + if (chandef.width > NL80211_CHAN_WIDTH_20_NOHT) + config_bands |= BAND_AN; + if (chandef.width > NL80211_CHAN_WIDTH_40) + config_bands |= BAND_AAC | BAND_AAX; + } + + priv->config_bands = config_bands; + + if (old_bands != config_bands) { + if (nxpwifi_band_to_radio_type(priv->config_bands) == + HOST_SCAN_RADIO_TYPE_BG) + nxpwifi_send_domain_info_cmd_fw(priv->adapter->wiphy, + NL80211_BAND_2GHZ); + else + nxpwifi_send_domain_info_cmd_fw(priv->adapter->wiphy, + NL80211_BAND_5GHZ); + } +} + +int nxpwifi_config_start_uap(struct nxpwifi_private *priv, + struct nxpwifi_uap_bss_param *bss_cfg) +{ + int ret; + + ret = nxpwifi_send_cmd(priv, HOST_CMD_UAP_SYS_CONFIG, + HOST_ACT_GEN_SET, + UAP_BSS_PARAMS_I, bss_cfg, true); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "Failed to set AP configuration\n"); + return ret; + } + + ret = nxpwifi_send_cmd(priv, HOST_CMD_UAP_BSS_START, + HOST_ACT_GEN_SET, 0, NULL, true); + if (ret) { + nxpwifi_dbg(priv->adapter, ERROR, + "Failed to start the BSS\n"); + return ret; + } + + if (priv->sec_info.wep_enabled) + priv->curr_pkt_filter |= HOST_ACT_MAC_WEP_ENABLE; + else + priv->curr_pkt_filter &= ~HOST_ACT_MAC_WEP_ENABLE; + + ret = nxpwifi_send_cmd(priv, HOST_CMD_MAC_CONTROL, + HOST_ACT_GEN_SET, 0, + &priv->curr_pkt_filter, true); + + return ret; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/uap_event.c b/drivers/net/wireless/nxp/nxpwifi/uap_event.c new file mode 100644 index 000000000000..ed8e24ae9c0a --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/uap_event.c @@ -0,0 +1,488 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP Wireless LAN device driver: AP event handling + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "main.h" +#include "cmdevt.h" +#include "11n.h" + +#define NXPWIFI_BSS_START_EVT_FIX_SIZE 12 + +static int +nxpwifi_uap_event_ps_awake(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + if (!adapter->pps_uapsd_mode && + priv->media_connected && adapter->sleep_period.period) { + adapter->pps_uapsd_mode = true; + nxpwifi_dbg(adapter, EVENT, + "event: PPS/UAPSD mode activated\n"); + } + adapter->tx_lock_flag = false; + if (adapter->pps_uapsd_mode && adapter->gen_null_pkt) { + if (nxpwifi_check_last_packet_indication(priv)) { + if (adapter->data_sent) { + adapter->ps_state = PS_STATE_AWAKE; + adapter->pm_wakeup_card_req = false; + adapter->pm_wakeup_fw_try = false; + } else { + if (!nxpwifi_send_null_packet + (priv, + NXPWIFI_TxPD_POWER_MGMT_NULL_PACKET | + NXPWIFI_TxPD_POWER_MGMT_LAST_PACKET)) + adapter->ps_state = PS_STATE_SLEEP; + } + + return 0; + } + } + + adapter->ps_state = PS_STATE_AWAKE; + adapter->pm_wakeup_card_req = false; + adapter->pm_wakeup_fw_try = false; + + return 0; +} + +static int +nxpwifi_uap_event_ps_sleep(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + adapter->ps_state = PS_STATE_PRE_SLEEP; + nxpwifi_check_ps_cond(adapter); + + return 0; +} + +static int +nxpwifi_uap_event_sta_deauth(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u8 *deauth_mac; + + deauth_mac = adapter->event_body + + NXPWIFI_UAP_EVENT_EXTRA_HEADER; + cfg80211_del_sta(priv->netdev->ieee80211_ptr, deauth_mac, GFP_KERNEL); + + if (priv->ap_11n_enabled) { + nxpwifi_11n_del_rx_reorder_tbl_by_ta(priv, deauth_mac); + nxpwifi_del_tx_ba_stream_tbl_by_ra(priv, deauth_mac); + } + nxpwifi_wmm_del_peer_ra_list(priv, deauth_mac); + + return 0; +} + +static int +nxpwifi_uap_event_sta_assoc(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct station_info *sinfo; + struct nxpwifi_assoc_event *event; + struct nxpwifi_sta_node *node; + int len, i; + + sinfo = kzalloc_obj(*sinfo, GFP_KERNEL); + if (!sinfo) + return -ENOMEM; + + event = (struct nxpwifi_assoc_event *) + (adapter->event_body + NXPWIFI_UAP_EVENT_EXTRA_HEADER); + if (le16_to_cpu(event->type) == TLV_TYPE_UAP_MGMT_FRAME) { + len = -1; + + if (ieee80211_is_assoc_req(event->frame_control)) + len = 0; + else if (ieee80211_is_reassoc_req(event->frame_control)) + /* + * There will be ETH_ALEN bytes of + * current_ap_addr before the re-assoc ies. + */ + len = ETH_ALEN; + + if (len != -1) { + sinfo->assoc_req_ies = &event->data[len]; + len = (u8 *)sinfo->assoc_req_ies - + (u8 *)&event->frame_control; + sinfo->assoc_req_ies_len = + le16_to_cpu(event->len) - (u16)len; + } + } + cfg80211_new_sta(priv->netdev->ieee80211_ptr, event->sta_addr, sinfo, + GFP_KERNEL); + + node = nxpwifi_add_sta_entry(priv, event->sta_addr); + if (!node) { + nxpwifi_dbg(adapter, ERROR, + "could not create station entry!\n"); + kfree(sinfo); + return -ENOENT; + } + + if (!priv->ap_11n_enabled) { + kfree(sinfo); + return 0; + } + + nxpwifi_set_sta_ht_cap(priv, sinfo->assoc_req_ies, + sinfo->assoc_req_ies_len, node); + + for (i = 0; i < MAX_NUM_TID; i++) { + if (node->is_11n_enabled || node->is_11ax_enabled) + node->ampdu_sta[i] = + priv->aggr_prio_tbl[i].ampdu_user; + else + node->ampdu_sta[i] = BA_STREAM_NOT_ALLOWED; + } + memset(node->rx_seq, 0xff, sizeof(node->rx_seq)); + kfree(sinfo); + + return 0; +} + +static int +nxpwifi_check_uap_capabilities(struct nxpwifi_private *priv, + struct sk_buff *event) +{ + int evt_len; + u8 *curr; + u16 tlv_len; + struct nxpwifi_ie_types_data *tlv_hdr; + struct ieee80211_wmm_param_ie *wmm_param_ie = NULL; + int mask = IEEE80211_WMM_IE_AP_QOSINFO_PARAM_SET_CNT_MASK; + + priv->wmm_enabled = false; + skb_pull(event, NXPWIFI_BSS_START_EVT_FIX_SIZE); + evt_len = event->len; + curr = event->data; + + nxpwifi_dbg_dump(priv->adapter, EVT_D, "uap capabilities:", + event->data, event->len); + + skb_push(event, NXPWIFI_BSS_START_EVT_FIX_SIZE); + + while ((evt_len >= sizeof(tlv_hdr->header))) { + tlv_hdr = (struct nxpwifi_ie_types_data *)curr; + tlv_len = le16_to_cpu(tlv_hdr->header.len); + + if (evt_len < tlv_len + sizeof(tlv_hdr->header)) + break; + + switch (le16_to_cpu(tlv_hdr->header.type)) { + case WLAN_EID_HT_CAPABILITY: + priv->ap_11n_enabled = true; + break; + + case WLAN_EID_VHT_CAPABILITY: + priv->ap_11ac_enabled = true; + break; + + case WLAN_EID_VENDOR_SPECIFIC: + /* + * Point the regular IEEE element 2 bytes into the NXP element + * and setup the IEEE element type and length byte fields + */ + wmm_param_ie = (void *)(curr + 2); + wmm_param_ie->len = (u8)tlv_len; + wmm_param_ie->element_id = + WLAN_EID_VENDOR_SPECIFIC; + nxpwifi_dbg(priv->adapter, EVENT, + "info: check uap capabilities:\t" + "wmm parameter set count: %d\n", + wmm_param_ie->qos_info & mask); + + nxpwifi_wmm_setup_ac_downgrade(priv); + priv->wmm_enabled = true; + nxpwifi_wmm_setup_queue_priorities(priv, wmm_param_ie); + break; + + default: + break; + } + + curr += (tlv_len + sizeof(tlv_hdr->header)); + evt_len -= (tlv_len + sizeof(tlv_hdr->header)); + } + + return 0; +} + +static int +nxpwifi_uap_event_bss_start(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + priv->port_open = false; + eth_hw_addr_set(priv->netdev, adapter->event_body + 2); + if (priv->hist_data) + nxpwifi_hist_data_reset(priv); + return nxpwifi_check_uap_capabilities(priv, adapter->event_skb); +} + +static int +nxpwifi_uap_event_addba(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + if (priv->media_connected) + nxpwifi_send_cmd(priv, HOST_CMD_11N_ADDBA_RSP, + HOST_ACT_GEN_SET, 0, + adapter->event_body, false); + + return 0; +} + +static int +nxpwifi_uap_event_delba(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + if (priv->media_connected) + nxpwifi_11n_delete_ba_stream(priv, adapter->event_body); + + return 0; +} + +static int +nxpwifi_uap_event_ba_stream_timeout(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct host_cmd_ds_11n_batimeout *ba_timeout; + + if (priv->media_connected) { + ba_timeout = (void *)adapter->event_body; + nxpwifi_11n_ba_stream_timeout(priv, ba_timeout); + } + + return 0; +} + +static int +nxpwifi_uap_event_amsdu_aggr_ctrl(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u16 ctrl; + + ctrl = get_unaligned_le16(adapter->event_body); + nxpwifi_dbg(adapter, EVENT, + "event: AMSDU_AGGR_CTRL %d\n", ctrl); + + if (priv->media_connected) { + adapter->tx_buf_size = + min_t(u16, adapter->curr_tx_buf_size, ctrl); + nxpwifi_dbg(adapter, EVENT, + "event: tx_buf_size %d\n", + adapter->tx_buf_size); + } + + return 0; +} + +static int +nxpwifi_uap_event_bss_idle(struct nxpwifi_private *priv) +{ + priv->media_connected = false; + priv->port_open = false; + nxpwifi_clean_txrx(priv); + nxpwifi_del_all_sta_list(priv); + + return 0; +} + +static int +nxpwifi_uap_event_bss_active(struct nxpwifi_private *priv) +{ + priv->media_connected = true; + priv->port_open = true; + + return 0; +} + +static int +nxpwifi_uap_event_mic_countermeasures(struct nxpwifi_private *priv) +{ + /* For future development */ + + return 0; +} + +static int +nxpwifi_uap_event_radar_detected(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + return nxpwifi_11h_handle_radar_detected(priv, adapter->event_skb); +} + +static int +nxpwifi_uap_event_channel_report_rdy(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + return nxpwifi_11h_handle_chanrpt_ready(priv, adapter->event_skb); +} + +static int +nxpwifi_uap_event_tx_data_pause(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + nxpwifi_process_tx_pause_event(priv, adapter->event_skb); + + return 0; +} + +static int +nxpwifi_uap_event_ext_scan_report(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + void *buf = adapter->event_skb->data; + int ret = 0; + + if (adapter->ext_scan) + ret = nxpwifi_handle_event_ext_scan_report(priv, buf); + + return ret; +} + +static int +nxpwifi_uap_event_rxba_sync(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + nxpwifi_11n_rxba_sync_event(priv, adapter->event_body, + adapter->event_skb->len - + sizeof(adapter->event_cause)); + + return 0; +} + +static int +nxpwifi_uap_event_remain_on_chan_expired(struct nxpwifi_private *priv) +{ + cfg80211_remain_on_channel_expired(&priv->wdev, + priv->roc_cfg.cookie, + &priv->roc_cfg.chan, + GFP_ATOMIC); + memset(&priv->roc_cfg, 0x00, sizeof(struct nxpwifi_roc_cfg)); + + return 0; +} + +static int +nxpwifi_uap_event_multi_chan_info(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + nxpwifi_process_multi_chan_event(priv, adapter->event_skb); + + return 0; +} + +static int +nxpwifi_uap_event_tx_status_report(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + nxpwifi_parse_tx_status_event(priv, adapter->event_body); + + return 0; +} + +static int +nxpwifi_uap_event_bt_coex_wlan_para_change(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + nxpwifi_bt_coex_wlan_param_update_event(priv, adapter->event_skb); + + return 0; +} + +static int +nxpwifi_uap_event_vdll_ind(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + return nxpwifi_process_vdll_event(priv, adapter->event_skb); +} + +static const struct nxpwifi_evt_entry evt_table_uap[] = { + {.event_cause = EVENT_PS_AWAKE, + .event_handler = nxpwifi_uap_event_ps_awake}, + {.event_cause = EVENT_PS_SLEEP, + .event_handler = nxpwifi_uap_event_ps_sleep}, + {.event_cause = EVENT_UAP_STA_DEAUTH, + .event_handler = nxpwifi_uap_event_sta_deauth}, + {.event_cause = EVENT_UAP_STA_ASSOC, + .event_handler = nxpwifi_uap_event_sta_assoc}, + {.event_cause = EVENT_UAP_BSS_START, + .event_handler = nxpwifi_uap_event_bss_start}, + {.event_cause = EVENT_ADDBA, + .event_handler = nxpwifi_uap_event_addba}, + {.event_cause = EVENT_DELBA, + .event_handler = nxpwifi_uap_event_delba}, + {.event_cause = EVENT_BA_STREAM_TIEMOUT, + .event_handler = nxpwifi_uap_event_ba_stream_timeout}, + {.event_cause = EVENT_AMSDU_AGGR_CTRL, + .event_handler = nxpwifi_uap_event_amsdu_aggr_ctrl}, + {.event_cause = EVENT_UAP_BSS_IDLE, + .event_handler = nxpwifi_uap_event_bss_idle}, + {.event_cause = EVENT_UAP_BSS_ACTIVE, + .event_handler = nxpwifi_uap_event_bss_active}, + {.event_cause = EVENT_UAP_MIC_COUNTERMEASURES, + .event_handler = nxpwifi_uap_event_mic_countermeasures}, + {.event_cause = EVENT_RADAR_DETECTED, + .event_handler = nxpwifi_uap_event_radar_detected}, + {.event_cause = EVENT_CHANNEL_REPORT_RDY, + .event_handler = nxpwifi_uap_event_channel_report_rdy}, + {.event_cause = EVENT_TX_DATA_PAUSE, + .event_handler = nxpwifi_uap_event_tx_data_pause}, + {.event_cause = EVENT_EXT_SCAN_REPORT, + .event_handler = nxpwifi_uap_event_ext_scan_report}, + {.event_cause = EVENT_RXBA_SYNC, + .event_handler = nxpwifi_uap_event_rxba_sync}, + {.event_cause = EVENT_REMAIN_ON_CHAN_EXPIRED, + .event_handler = nxpwifi_uap_event_remain_on_chan_expired}, + {.event_cause = EVENT_MULTI_CHAN_INFO, + .event_handler = nxpwifi_uap_event_multi_chan_info}, + {.event_cause = EVENT_TX_STATUS_REPORT, + .event_handler = nxpwifi_uap_event_tx_status_report}, + {.event_cause = EVENT_BT_COEX_WLAN_PARA_CHANGE, + .event_handler = nxpwifi_uap_event_bt_coex_wlan_para_change}, + {.event_cause = EVENT_VDLL_IND, + .event_handler = nxpwifi_uap_event_vdll_ind}, +}; + +/* Handle AP‑interface events by dispatching them to event‑specific routines. */ +int nxpwifi_process_uap_event(struct nxpwifi_private *priv) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u32 eventcause = adapter->event_cause; + int evt, ret = 0; + + for (evt = 0; evt < ARRAY_SIZE(evt_table_uap); evt++) { + if (eventcause == evt_table_uap[evt].event_cause) { + if (evt_table_uap[evt].event_handler) + ret = evt_table_uap[evt].event_handler(priv); + break; + } + } + + if (evt == ARRAY_SIZE(evt_table_uap)) + nxpwifi_dbg(adapter, EVENT, + "%s: unknown event id: %#x\n", + __func__, eventcause); + else + nxpwifi_dbg(adapter, EVENT, + "%s: event id: %#x\n", + __func__, eventcause); + + return ret; +} diff --git a/drivers/net/wireless/nxp/nxpwifi/uap_txrx.c b/drivers/net/wireless/nxp/nxpwifi/uap_txrx.c new file mode 100644 index 000000000000..f3d24bf861ca --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/uap_txrx.c @@ -0,0 +1,478 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP Wireless LAN device driver: AP TX and RX data handling + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "main.h" +#include "wmm.h" +#include "11n_aggr.h" +#include "11n_rxreorder.h" + +/* + * Drop bridged pkts from RA list until pending <= low threshold; return true if + * any. + */ +static bool +nxpwifi_uap_del_tx_pkts_in_ralist(struct nxpwifi_private *priv, + struct list_head *ra_list_head, + int tid) +{ + struct nxpwifi_ra_list_tbl *ra_list; + struct sk_buff *skb, *tmp; + bool pkt_deleted = false; + struct nxpwifi_txinfo *tx_info; + struct nxpwifi_adapter *adapter = priv->adapter; + + list_for_each_entry(ra_list, ra_list_head, list) { + if (skb_queue_empty(&ra_list->skb_head)) + continue; + + skb_queue_walk_safe(&ra_list->skb_head, skb, tmp) { + tx_info = NXPWIFI_SKB_TXCB(skb); + if (tx_info->flags & NXPWIFI_BUF_FLAG_BRIDGED_PKT) { + __skb_unlink(skb, &ra_list->skb_head); + nxpwifi_write_data_complete(adapter, skb, 0, + -1); + if (ra_list->tx_paused) + priv->wmm.pkts_paused[tid]--; + else + atomic_dec(&priv->wmm.tx_pkts_queued); + pkt_deleted = true; + } + if ((atomic_read(&adapter->pending_bridged_pkts) <= + NXPWIFI_BRIDGED_PKTS_THR_LOW)) + break; + } + } + + return pkt_deleted; +} + +/* Delete bridged pkts from one RA list; rotate index to keep fairness. */ +static void nxpwifi_uap_cleanup_tx_queues(struct nxpwifi_private *priv) +{ + struct list_head *ra_list; + int i; + + spin_lock_bh(&priv->wmm.ra_list_spinlock); + + for (i = 0; i < MAX_NUM_TID; i++, priv->del_list_idx++) { + if (priv->del_list_idx == MAX_NUM_TID) + priv->del_list_idx = 0; + ra_list = &priv->wmm.tid_tbl_ptr[priv->del_list_idx].ra_list; + if (nxpwifi_uap_del_tx_pkts_in_ralist(priv, ra_list, i)) { + priv->del_list_idx++; + break; + } + } + + spin_unlock_bh(&priv->wmm.ra_list_spinlock); +} + +static void +nxpwifi_uap_queue_bridged_pkt(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct uap_rxpd *uap_rx_pd; + struct rx_packet_hdr *rx_pkt_hdr; + struct sk_buff *new_skb; + struct nxpwifi_txinfo *tx_info; + int hdr_chop; + struct ethhdr *p_ethhdr; + struct nxpwifi_sta_node *src_node; + int index; + + uap_rx_pd = (struct uap_rxpd *)(skb->data); + rx_pkt_hdr = (void *)uap_rx_pd + le16_to_cpu(uap_rx_pd->rx_pkt_offset); + + if ((atomic_read(&adapter->pending_bridged_pkts) >= + NXPWIFI_BRIDGED_PKTS_THR_HIGH)) { + nxpwifi_dbg(adapter, ERROR, + "Tx: Bridge packet limit reached. Drop packet!\n"); + kfree_skb(skb); + nxpwifi_uap_cleanup_tx_queues(priv); + return; + } + + if (sizeof(*rx_pkt_hdr) + + le16_to_cpu(uap_rx_pd->rx_pkt_offset) > skb->len) { + priv->stats.rx_dropped++; + dev_kfree_skb_any(skb); + return; + } + + if ((!memcmp(&rx_pkt_hdr->rfc1042_hdr, bridge_tunnel_header, + sizeof(bridge_tunnel_header))) || + (!memcmp(&rx_pkt_hdr->rfc1042_hdr, rfc1042_header, + sizeof(rfc1042_header)) && + rx_pkt_hdr->rfc1042_hdr.snap_type != htons(ETH_P_AARP) && + rx_pkt_hdr->rfc1042_hdr.snap_type != htons(ETH_P_IPX))) { + /* + * Replace the 803 header and rfc1042 header (llc/snap) with + * an Ethernet II header, keep the src/dst and snap_type + * (ethertype). + * + * The firmware only passes up SNAP frames converting all RX + * data from 802.11 to 802.2/LLC/SNAP frames. + * + * To create the Ethernet II, just move the src, dst address + * right before the snap_type. + */ + p_ethhdr = (struct ethhdr *) + ((u8 *)(&rx_pkt_hdr->eth803_hdr) + + sizeof(rx_pkt_hdr->eth803_hdr) + + sizeof(rx_pkt_hdr->rfc1042_hdr) + - sizeof(rx_pkt_hdr->eth803_hdr.h_dest) + - sizeof(rx_pkt_hdr->eth803_hdr.h_source) + - sizeof(rx_pkt_hdr->rfc1042_hdr.snap_type)); + memcpy(p_ethhdr->h_source, rx_pkt_hdr->eth803_hdr.h_source, + sizeof(p_ethhdr->h_source)); + memcpy(p_ethhdr->h_dest, rx_pkt_hdr->eth803_hdr.h_dest, + sizeof(p_ethhdr->h_dest)); + /* + * Chop off the rxpd + the excess memory from + * 802.2/llc/snap header that was removed. + */ + hdr_chop = (u8 *)p_ethhdr - (u8 *)uap_rx_pd; + } else { + /* Chop off the rxpd */ + hdr_chop = (u8 *)&rx_pkt_hdr->eth803_hdr - (u8 *)uap_rx_pd; + } + + /* + * Chop off the leading header bytes so that it points + * to the start of either the reconstructed EthII frame + * or the 802.2/llc/snap frame. + */ + skb_pull(skb, hdr_chop); + + if (skb_headroom(skb) < NXPWIFI_MIN_DATA_HEADER_LEN) { + nxpwifi_dbg(adapter, ERROR, + "data: Tx: insufficient skb headroom %d\n", + skb_headroom(skb)); + /* Insufficient skb headroom - allocate a new skb */ + new_skb = + skb_realloc_headroom(skb, NXPWIFI_MIN_DATA_HEADER_LEN); + if (unlikely(!new_skb)) { + nxpwifi_dbg(adapter, ERROR, + "Tx: cannot allocate new_skb\n"); + kfree_skb(skb); + priv->stats.tx_dropped++; + return; + } + + kfree_skb(skb); + skb = new_skb; + nxpwifi_dbg(adapter, INFO, + "info: new skb headroom %d\n", + skb_headroom(skb)); + } + + tx_info = NXPWIFI_SKB_TXCB(skb); + memset(tx_info, 0, sizeof(*tx_info)); + tx_info->bss_num = priv->bss_num; + tx_info->bss_type = priv->bss_type; + tx_info->flags |= NXPWIFI_BUF_FLAG_BRIDGED_PKT; + + rcu_read_lock(); + src_node = nxpwifi_get_sta_entry(priv, rx_pkt_hdr->eth803_hdr.h_source); + if (src_node) { + src_node->stats.last_rx = jiffies; + src_node->stats.rx_bytes += skb->len; + src_node->stats.rx_packets++; + src_node->stats.last_tx_rate = uap_rx_pd->rx_rate; + src_node->stats.last_tx_htinfo = uap_rx_pd->ht_info; + } + rcu_read_unlock(); + + if (is_unicast_ether_addr(rx_pkt_hdr->eth803_hdr.h_dest)) { + /* + * Update bridge packet statistics as the + * packet is not going to kernel/upper layer. + */ + priv->stats.rx_bytes += skb->len; + priv->stats.rx_packets++; + + /* + * Sending bridge packet to TX queue, so save the packet + * length in TXCB to update statistics in TX complete. + */ + tx_info->pkt_len = skb->len; + } + + __net_timestamp(skb); + + index = nxpwifi_1d_to_wmm_queue[skb->priority]; + atomic_inc(&priv->wmm_tx_pending[index]); + nxpwifi_wmm_add_buf_txqueue(priv, skb); + atomic_inc(&adapter->tx_pending); + atomic_inc(&adapter->pending_bridged_pkts); + + nxpwifi_queue_work(adapter, &adapter->main_work); +} + +/* AP fwd: mcast/bcast -> up + bridge; unicast -> bridge if RA assoc, else up. */ +int nxpwifi_handle_uap_rx_forward(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct uap_rxpd *uap_rx_pd; + struct rx_packet_hdr *rx_pkt_hdr; + u8 ra[ETH_ALEN]; + struct sk_buff *skb_uap; + struct nxpwifi_sta_node *node; + + uap_rx_pd = (struct uap_rxpd *)(skb->data); + rx_pkt_hdr = (void *)uap_rx_pd + le16_to_cpu(uap_rx_pd->rx_pkt_offset); + + /* don't do packet forwarding in disconnected state */ + if (!priv->media_connected) { + nxpwifi_dbg(adapter, ERROR, + "drop packet in disconnected state.\n"); + dev_kfree_skb_any(skb); + return 0; + } + + memcpy(ra, rx_pkt_hdr->eth803_hdr.h_dest, ETH_ALEN); + + if (is_multicast_ether_addr(ra)) { + skb_uap = skb_copy(skb, GFP_ATOMIC); + if (likely(skb_uap)) { + nxpwifi_uap_queue_bridged_pkt(priv, skb_uap); + } else { + nxpwifi_dbg(adapter, ERROR, + "failed to copy skb for uAP\n"); + priv->stats.rx_dropped++; + dev_kfree_skb_any(skb); + return -ENOMEM; + } + } else { + node = nxpwifi_get_sta_entry_rcu(priv, ra); + if (node) { + /* Requeue Intra-BSS packet */ + nxpwifi_uap_queue_bridged_pkt(priv, skb); + return 0; + } + } + + /* Forward unicat/Inter-BSS packets to kernel. */ + return nxpwifi_process_rx_packet(priv, skb); +} + +int nxpwifi_uap_recv_packet(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_sta_node *src_node, *dst_node; + struct ethhdr *p_ethhdr; + struct sk_buff *skb_uap; + struct nxpwifi_txinfo *tx_info; + + if (!skb) + return -ENOMEM; + + p_ethhdr = (void *)skb->data; + rcu_read_lock(); + src_node = nxpwifi_get_sta_entry(priv, p_ethhdr->h_source); + if (src_node) { + src_node->stats.last_rx = jiffies; + src_node->stats.rx_bytes += skb->len; + src_node->stats.rx_packets++; + } + dst_node = nxpwifi_get_sta_entry(priv, p_ethhdr->h_dest); + rcu_read_unlock(); + + if (is_multicast_ether_addr(p_ethhdr->h_dest) || dst_node) { + if (skb_headroom(skb) < NXPWIFI_MIN_DATA_HEADER_LEN) + skb_uap = + skb_realloc_headroom(skb, NXPWIFI_MIN_DATA_HEADER_LEN); + else + skb_uap = skb_copy(skb, GFP_ATOMIC); + + if (likely(skb_uap)) { + tx_info = NXPWIFI_SKB_TXCB(skb_uap); + memset(tx_info, 0, sizeof(*tx_info)); + tx_info->bss_num = priv->bss_num; + tx_info->bss_type = priv->bss_type; + tx_info->flags |= NXPWIFI_BUF_FLAG_BRIDGED_PKT; + __net_timestamp(skb_uap); + nxpwifi_wmm_add_buf_txqueue(priv, skb_uap); + atomic_inc(&adapter->tx_pending); + atomic_inc(&adapter->pending_bridged_pkts); + if ((atomic_read(&adapter->pending_bridged_pkts) >= + NXPWIFI_BRIDGED_PKTS_THR_HIGH)) { + nxpwifi_dbg(adapter, ERROR, + "Tx: Bridge packet limit reached. Drop packet!\n"); + nxpwifi_uap_cleanup_tx_queues(priv); + } + + } else { + nxpwifi_dbg(adapter, ERROR, "failed to allocate skb_uap"); + } + + nxpwifi_queue_work(adapter, &adapter->main_work); + /* Don't forward Intra-BSS unicast packet to upper layer*/ + + if (dst_node) + return 0; + } + + skb->dev = priv->netdev; + skb->protocol = eth_type_trans(skb, priv->netdev); + skb->ip_summed = CHECKSUM_NONE; + + /* Forward multicast/broadcast packet to upper layer*/ + netif_rx(skb); + return 0; +} + +/* Process AP RX: check RxPD/len, handle mgmt or 11n reorder/AMSDU, then forward. */ +int nxpwifi_process_uap_rx_packet(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int ret; + struct uap_rxpd *uap_rx_pd; + struct rx_packet_hdr *rx_pkt_hdr; + u16 rx_pkt_type; + u8 ta[ETH_ALEN], pkt_type; + struct nxpwifi_sta_node *node; + + uap_rx_pd = (struct uap_rxpd *)(skb->data); + rx_pkt_type = le16_to_cpu(uap_rx_pd->rx_pkt_type); + rx_pkt_hdr = (void *)uap_rx_pd + le16_to_cpu(uap_rx_pd->rx_pkt_offset); + + if (le16_to_cpu(uap_rx_pd->rx_pkt_offset) + + sizeof(rx_pkt_hdr->eth803_hdr) > skb->len) { + nxpwifi_dbg(adapter, ERROR, + "wrong rx packet for struct ethhdr: len=%d, offset=%d\n", + skb->len, le16_to_cpu(uap_rx_pd->rx_pkt_offset)); + priv->stats.rx_dropped++; + dev_kfree_skb_any(skb); + return 0; + } + + ether_addr_copy(ta, rx_pkt_hdr->eth803_hdr.h_source); + + if ((le16_to_cpu(uap_rx_pd->rx_pkt_offset) + + le16_to_cpu(uap_rx_pd->rx_pkt_length)) > (u16)skb->len) { + nxpwifi_dbg(adapter, ERROR, + "wrong rx packet: len=%d, offset=%d, length=%d\n", + skb->len, le16_to_cpu(uap_rx_pd->rx_pkt_offset), + le16_to_cpu(uap_rx_pd->rx_pkt_length)); + priv->stats.rx_dropped++; + rcu_read_lock(); + node = nxpwifi_get_sta_entry(priv, ta); + if (node) + node->stats.tx_failed++; + rcu_read_unlock(); + + dev_kfree_skb_any(skb); + return 0; + } + + if (rx_pkt_type == PKT_TYPE_MGMT) { + ret = nxpwifi_process_mgmt_packet(priv, skb); + if (ret && (ret != -EINPROGRESS)) + nxpwifi_dbg(adapter, DATA, "Rx of mgmt packet failed"); + if (ret != -EINPROGRESS) + dev_kfree_skb_any(skb); + return ret; + } + + if (rx_pkt_type != PKT_TYPE_BAR && uap_rx_pd->priority < MAX_NUM_TID) { + rcu_read_lock(); + node = nxpwifi_get_sta_entry(priv, ta); + if (node) + node->rx_seq[uap_rx_pd->priority] = + le16_to_cpu(uap_rx_pd->seq_num); + rcu_read_unlock(); + } + + if (!priv->ap_11n_enabled || + (!nxpwifi_11n_get_rx_reorder_tbl(priv, uap_rx_pd->priority, ta) && + (le16_to_cpu(uap_rx_pd->rx_pkt_type) != PKT_TYPE_AMSDU))) { + ret = nxpwifi_handle_uap_rx_forward(priv, skb); + return ret; + } + + /* Reorder and send to kernel */ + pkt_type = (u8)le16_to_cpu(uap_rx_pd->rx_pkt_type); + ret = nxpwifi_11n_rx_reorder_pkt(priv, le16_to_cpu(uap_rx_pd->seq_num), + uap_rx_pd->priority, ta, pkt_type, skb); + + if (ret || rx_pkt_type == PKT_TYPE_BAR) + dev_kfree_skb_any(skb); + + if (ret) + priv->stats.rx_dropped++; + + return ret; +} + +/* + * Build TxPD for AP TX: push aligned TxPD; set bss, len/off, prio, delay, txctl, + * flags. + */ +void nxpwifi_process_uap_txpd(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct uap_txpd *txpd; + struct nxpwifi_txinfo *tx_info = NXPWIFI_SKB_TXCB(skb); + int pad; + u16 pkt_type, pkt_offset; + int hroom = adapter->intf_hdr_len; + + pkt_type = nxpwifi_is_skb_mgmt_frame(skb) ? PKT_TYPE_MGMT : 0; + + pad = ((uintptr_t)skb->data - (sizeof(*txpd) + hroom)) & + (NXPWIFI_DMA_ALIGN_SZ - 1); + + skb_push(skb, sizeof(*txpd) + pad); + + txpd = (struct uap_txpd *)skb->data; + memset(txpd, 0, sizeof(*txpd)); + txpd->bss_num = priv->bss_num; + txpd->bss_type = priv->bss_type; + txpd->tx_pkt_length = cpu_to_le16((u16)(skb->len - (sizeof(*txpd) + + pad))); + txpd->priority = (u8)skb->priority; + + txpd->pkt_delay_2ms = nxpwifi_wmm_compute_drv_pkt_delay(priv, skb); + + if (tx_info->flags & NXPWIFI_BUF_FLAG_EAPOL_TX_STATUS || + tx_info->flags & NXPWIFI_BUF_FLAG_ACTION_TX_STATUS) { + txpd->tx_token_id = tx_info->ack_frame_id; + txpd->flags |= NXPWIFI_TXPD_FLAGS_REQ_TX_STATUS; + } + + if (txpd->priority < ARRAY_SIZE(priv->wmm.user_pri_pkt_tx_ctrl)) + /* + * Set the priority specific tx_control field, setting of 0 will + * cause the default value to be used later in this function. + */ + txpd->tx_control = + cpu_to_le32(priv->wmm.user_pri_pkt_tx_ctrl[txpd->priority]); + + /* Offset of actual data */ + pkt_offset = sizeof(*txpd) + pad; + if (pkt_type == PKT_TYPE_MGMT) { + /* Set the packet type and add header for management frame */ + txpd->tx_pkt_type = cpu_to_le16(pkt_type); + pkt_offset += NXPWIFI_MGMT_FRAME_HEADER_SIZE; + } + + txpd->tx_pkt_offset = cpu_to_le16(pkt_offset); + + /* make space for adapter->intf_hdr_len */ + skb_push(skb, hroom); + + if (!txpd->tx_control) + /* TxCtrl set by user or default */ + txpd->tx_control = cpu_to_le32(priv->pkt_tx_ctrl); +} diff --git a/drivers/net/wireless/nxp/nxpwifi/util.c b/drivers/net/wireless/nxp/nxpwifi/util.c new file mode 100644 index 000000000000..29ef031f8ec9 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/util.c @@ -0,0 +1,1381 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP Wireless LAN device driver: utility functions + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cmdevt.h" +#include "wmm.h" +#include "11n.h" +#include +#include +#include +#include +#include + +#define RX_RATE_FORMAT_MASK GENMASK(1, 0) +#define RX_RATE_BW_MASK GENMASK(3, 2) +#define RX_RATE_GI_MASK BIT(4) +#define RX_RATE_STBC_MASK BIT(5) +#define RX_RATE_LDPC_MASK BIT(6) + +static struct nxpwifi_debug_data items[] = { + {"debug_mask", item_size(debug_mask), + item_addr(debug_mask), 1}, + {"int_counter", item_size(int_counter), + item_addr(int_counter), 1}, + {"wmm_ac_vo", item_size(packets_out[WMM_AC_VO]), + item_addr(packets_out[WMM_AC_VO]), 1}, + {"wmm_ac_vi", item_size(packets_out[WMM_AC_VI]), + item_addr(packets_out[WMM_AC_VI]), 1}, + {"wmm_ac_be", item_size(packets_out[WMM_AC_BE]), + item_addr(packets_out[WMM_AC_BE]), 1}, + {"wmm_ac_bk", item_size(packets_out[WMM_AC_BK]), + item_addr(packets_out[WMM_AC_BK]), 1}, + {"tx_buf_size", item_size(tx_buf_size), + item_addr(tx_buf_size), 1}, + {"curr_tx_buf_size", item_size(curr_tx_buf_size), + item_addr(curr_tx_buf_size), 1}, + {"ps_mode", item_size(ps_mode), + item_addr(ps_mode), 1}, + {"ps_state", item_size(ps_state), + item_addr(ps_state), 1}, + {"is_deep_sleep", item_size(is_deep_sleep), + item_addr(is_deep_sleep), 1}, + {"wakeup_dev_req", item_size(pm_wakeup_card_req), + item_addr(pm_wakeup_card_req), 1}, + {"wakeup_tries", item_size(pm_wakeup_fw_try), + item_addr(pm_wakeup_fw_try), 1}, + {"hs_configured", item_size(is_hs_configured), + item_addr(is_hs_configured), 1}, + {"hs_activated", item_size(hs_activated), + item_addr(hs_activated), 1}, + {"num_tx_timeout", item_size(num_tx_timeout), + item_addr(num_tx_timeout), 1}, + {"is_cmd_timedout", item_size(is_cmd_timedout), + item_addr(is_cmd_timedout), 1}, + {"timeout_cmd_id", item_size(timeout_cmd_id), + item_addr(timeout_cmd_id), 1}, + {"timeout_cmd_act", item_size(timeout_cmd_act), + item_addr(timeout_cmd_act), 1}, + {"last_cmd_id", item_size(last_cmd_id), + item_addr(last_cmd_id), DBG_CMD_NUM}, + {"last_cmd_act", item_size(last_cmd_act), + item_addr(last_cmd_act), DBG_CMD_NUM}, + {"last_cmd_index", item_size(last_cmd_index), + item_addr(last_cmd_index), 1}, + {"last_cmd_resp_id", item_size(last_cmd_resp_id), + item_addr(last_cmd_resp_id), DBG_CMD_NUM}, + {"last_cmd_resp_index", item_size(last_cmd_resp_index), + item_addr(last_cmd_resp_index), 1}, + {"last_event", item_size(last_event), + item_addr(last_event), DBG_CMD_NUM}, + {"last_event_index", item_size(last_event_index), + item_addr(last_event_index), 1}, + {"last_mp_wr_bitmap", item_size(last_mp_wr_bitmap), + item_addr(last_mp_wr_bitmap), NXPWIFI_DBG_SDIO_MP_NUM}, + {"last_mp_wr_ports", item_size(last_mp_wr_ports), + item_addr(last_mp_wr_ports), NXPWIFI_DBG_SDIO_MP_NUM}, + {"last_mp_wr_len", item_size(last_mp_wr_len), + item_addr(last_mp_wr_len), NXPWIFI_DBG_SDIO_MP_NUM}, + {"last_mp_curr_wr_port", item_size(last_mp_curr_wr_port), + item_addr(last_mp_curr_wr_port), NXPWIFI_DBG_SDIO_MP_NUM}, + {"last_sdio_mp_index", item_size(last_sdio_mp_index), + item_addr(last_sdio_mp_index), 1}, + {"num_cmd_h2c_fail", item_size(num_cmd_host_to_card_failure), + item_addr(num_cmd_host_to_card_failure), 1}, + {"num_cmd_sleep_cfm_fail", + item_size(num_cmd_sleep_cfm_host_to_card_failure), + item_addr(num_cmd_sleep_cfm_host_to_card_failure), 1}, + {"num_tx_h2c_fail", item_size(num_tx_host_to_card_failure), + item_addr(num_tx_host_to_card_failure), 1}, + {"num_evt_deauth", item_size(num_event_deauth), + item_addr(num_event_deauth), 1}, + {"num_evt_disassoc", item_size(num_event_disassoc), + item_addr(num_event_disassoc), 1}, + {"num_evt_link_lost", item_size(num_event_link_lost), + item_addr(num_event_link_lost), 1}, + {"num_cmd_deauth", item_size(num_cmd_deauth), + item_addr(num_cmd_deauth), 1}, + {"num_cmd_assoc_ok", item_size(num_cmd_assoc_success), + item_addr(num_cmd_assoc_success), 1}, + {"num_cmd_assoc_fail", item_size(num_cmd_assoc_failure), + item_addr(num_cmd_assoc_failure), 1}, + {"cmd_sent", item_size(cmd_sent), + item_addr(cmd_sent), 1}, + {"data_sent", item_size(data_sent), + item_addr(data_sent), 1}, + {"cmd_resp_received", item_size(cmd_resp_received), + item_addr(cmd_resp_received), 1}, + {"event_received", item_size(event_received), + item_addr(event_received), 1}, + + /* variables defined in struct nxpwifi_adapter */ + {"cmd_pending", adapter_item_size(cmd_pending), + adapter_item_addr(cmd_pending), 1}, + {"tx_pending", adapter_item_size(tx_pending), + adapter_item_addr(tx_pending), 1}, + {"rx_pending", adapter_item_size(rx_pending), + adapter_item_addr(rx_pending), 1}, +}; + +static int num_of_items = ARRAY_SIZE(items); + +/* Send init or shutdown command to firmware. */ +int nxpwifi_init_shutdown_fw(struct nxpwifi_private *priv, + u32 func_init_shutdown) +{ + u16 cmd; + + if (func_init_shutdown == NXPWIFI_FUNC_INIT) { + cmd = HOST_CMD_FUNC_INIT; + } else if (func_init_shutdown == NXPWIFI_FUNC_SHUTDOWN) { + cmd = HOST_CMD_FUNC_SHUTDOWN; + } else { + nxpwifi_dbg(priv->adapter, ERROR, + "unsupported parameter\n"); + return -EINVAL; + } + + return nxpwifi_send_cmd(priv, cmd, HOST_ACT_GEN_SET, 0, NULL, true); +} +EXPORT_SYMBOL_GPL(nxpwifi_init_shutdown_fw); + +/* Handle IOCTL get/set of debug info across driver structures. */ +int nxpwifi_get_debug_info(struct nxpwifi_private *priv, + struct nxpwifi_debug_info *info) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + + if (info) { + info->debug_mask = adapter->debug_mask; + memcpy(info->packets_out, + priv->wmm.packets_out, + sizeof(priv->wmm.packets_out)); + info->curr_tx_buf_size = (u32)adapter->curr_tx_buf_size; + info->tx_buf_size = (u32)adapter->tx_buf_size; + info->rx_tbl_num = nxpwifi_get_rx_reorder_tbl(priv, + info->rx_tbl); + info->tx_tbl_num = nxpwifi_get_tx_ba_stream_tbl(priv, + info->tx_tbl); + info->ps_mode = adapter->ps_mode; + info->ps_state = adapter->ps_state; + info->is_deep_sleep = adapter->is_deep_sleep; + info->pm_wakeup_card_req = adapter->pm_wakeup_card_req; + info->pm_wakeup_fw_try = adapter->pm_wakeup_fw_try; + info->is_hs_configured = test_bit(NXPWIFI_IS_HS_CONFIGURED, + &adapter->work_flags); + info->hs_activated = adapter->hs_activated; + info->is_cmd_timedout = test_bit(NXPWIFI_IS_CMD_TIMEDOUT, + &adapter->work_flags); + info->num_cmd_host_to_card_failure = + adapter->dbg.num_cmd_host_to_card_failure; + info->num_cmd_sleep_cfm_host_to_card_failure = + adapter->dbg.num_cmd_sleep_cfm_host_to_card_failure; + info->num_tx_host_to_card_failure = + adapter->dbg.num_tx_host_to_card_failure; + info->num_event_deauth = adapter->dbg.num_event_deauth; + info->num_event_disassoc = adapter->dbg.num_event_disassoc; + info->num_event_link_lost = adapter->dbg.num_event_link_lost; + info->num_cmd_deauth = adapter->dbg.num_cmd_deauth; + info->num_cmd_assoc_success = + adapter->dbg.num_cmd_assoc_success; + info->num_cmd_assoc_failure = + adapter->dbg.num_cmd_assoc_failure; + info->num_tx_timeout = adapter->dbg.num_tx_timeout; + info->timeout_cmd_id = adapter->dbg.timeout_cmd_id; + info->timeout_cmd_act = adapter->dbg.timeout_cmd_act; + memcpy(info->last_cmd_id, adapter->dbg.last_cmd_id, + sizeof(adapter->dbg.last_cmd_id)); + memcpy(info->last_cmd_act, adapter->dbg.last_cmd_act, + sizeof(adapter->dbg.last_cmd_act)); + info->last_cmd_index = adapter->dbg.last_cmd_index; + memcpy(info->last_cmd_resp_id, adapter->dbg.last_cmd_resp_id, + sizeof(adapter->dbg.last_cmd_resp_id)); + info->last_cmd_resp_index = adapter->dbg.last_cmd_resp_index; + memcpy(info->last_event, adapter->dbg.last_event, + sizeof(adapter->dbg.last_event)); + info->last_event_index = adapter->dbg.last_event_index; + memcpy(info->last_mp_wr_bitmap, adapter->dbg.last_mp_wr_bitmap, + sizeof(adapter->dbg.last_mp_wr_bitmap)); + memcpy(info->last_mp_wr_ports, adapter->dbg.last_mp_wr_ports, + sizeof(adapter->dbg.last_mp_wr_ports)); + memcpy(info->last_mp_curr_wr_port, + adapter->dbg.last_mp_curr_wr_port, + sizeof(adapter->dbg.last_mp_curr_wr_port)); + memcpy(info->last_mp_wr_len, adapter->dbg.last_mp_wr_len, + sizeof(adapter->dbg.last_mp_wr_len)); + info->last_sdio_mp_index = adapter->dbg.last_sdio_mp_index; + info->data_sent = adapter->data_sent; + info->cmd_sent = adapter->cmd_sent; + info->cmd_resp_received = adapter->cmd_resp_received; + } + + return 0; +} + +int nxpwifi_debug_info_to_buffer(struct nxpwifi_private *priv, char *buf, + struct nxpwifi_debug_info *info) +{ + char *p = buf; + struct nxpwifi_debug_data *d = &items[0]; + size_t size, addr; + long val; + int i, j; + + if (!info) + return 0; + + for (i = 0; i < num_of_items; i++) { + p += sprintf(p, "%s=", d[i].name); + + size = d[i].size / d[i].num; + + if (i < (num_of_items - 3)) + addr = d[i].addr + (size_t)info; + else /* The last 3 items are struct nxpwifi_adapter variables */ + addr = d[i].addr + (size_t)priv->adapter; + + for (j = 0; j < d[i].num; j++) { + switch (size) { + case 1: + val = *((u8 *)addr); + break; + case 2: + val = get_unaligned((u16 *)addr); + break; + case 4: + val = get_unaligned((u32 *)addr); + break; + case 8: + val = get_unaligned((long long *)addr); + break; + default: + val = -1; + break; + } + + p += sprintf(p, "%#lx ", val); + addr += size; + } + + p += sprintf(p, "\n"); + } + + if (info->tx_tbl_num) { + p += sprintf(p, "Tx BA stream table:\n"); + for (i = 0; i < info->tx_tbl_num; i++) + p += sprintf(p, "tid = %d, ra = %pM\n", + info->tx_tbl[i].tid, info->tx_tbl[i].ra); + } + + if (info->rx_tbl_num) { + p += sprintf(p, "Rx reorder table:\n"); + for (i = 0; i < info->rx_tbl_num; i++) { + p += sprintf(p, "tid = %d, ta = %pM, ", + info->rx_tbl[i].tid, + info->rx_tbl[i].ta); + p += sprintf(p, "start_win = %d, ", + info->rx_tbl[i].start_win); + p += sprintf(p, "win_size = %d, buffer: ", + info->rx_tbl[i].win_size); + + for (j = 0; j < info->rx_tbl[i].win_size; j++) + p += sprintf(p, "%c ", + info->rx_tbl[i].buffer[j] ? + '1' : '0'); + + p += sprintf(p, "\n"); + } + } + + return p - buf; +} + +bool nxpwifi_is_channel_setting_allowable(struct nxpwifi_private *priv, + struct ieee80211_channel *check_chan) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + int i; + struct nxpwifi_private *tmp_priv; + u8 bss_role = GET_BSS_ROLE(priv); + struct ieee80211_channel *set_chan; + + for (i = 0; i < adapter->priv_num; i++) { + tmp_priv = adapter->priv[i]; + if (tmp_priv == priv) + continue; + + set_chan = NULL; + if (bss_role == NXPWIFI_BSS_ROLE_STA) { + if (GET_BSS_ROLE(tmp_priv) == NXPWIFI_BSS_ROLE_UAP && + netif_carrier_ok(tmp_priv->netdev) && + cfg80211_chandef_valid(&tmp_priv->bss_chandef)) + set_chan = tmp_priv->bss_chandef.chan; + } else if (bss_role == NXPWIFI_BSS_ROLE_UAP) { + struct nxpwifi_current_bss_params *bss_params = + &tmp_priv->curr_bss_params; + int channel = bss_params->bss_descriptor.channel; + enum nl80211_band band = + nxpwifi_band_to_radio_type(bss_params->band); + int freq = + ieee80211_channel_to_frequency(channel, band); + + if (GET_BSS_ROLE(tmp_priv) == NXPWIFI_BSS_ROLE_STA && + tmp_priv->media_connected) + set_chan = ieee80211_get_channel(adapter->wiphy, freq); + } + + if (set_chan && !ieee80211_channel_equal(check_chan, set_chan)) { + nxpwifi_dbg(adapter, ERROR, + "AP/STA must run on the same channel\n"); + return false; + } + } + + return true; +} + +void nxpwifi_convert_chan_to_band_cfg(struct nxpwifi_private *priv, + u8 *band_cfg, + struct cfg80211_chan_def *chan_def) +{ + u8 chan_band = 0, chan_width = 0, chan2_offset = 0; + + switch (chan_def->chan->band) { + case NL80211_BAND_2GHZ: + chan_band = BAND_2GHZ; + break; + case NL80211_BAND_5GHZ: + chan_band = BAND_5GHZ; + break; + default: + break; + } + + switch (chan_def->width) { + case NL80211_CHAN_WIDTH_20_NOHT: + case NL80211_CHAN_WIDTH_20: + chan_width = CHAN_BW_20MHZ; + break; + case NL80211_CHAN_WIDTH_40: + chan_width = CHAN_BW_40MHZ; + if (chan_def->center_freq1 > chan_def->chan->center_freq) + chan2_offset = IEEE80211_HT_PARAM_CHA_SEC_ABOVE; + else + chan2_offset = IEEE80211_HT_PARAM_CHA_SEC_BELOW; + break; + case NL80211_CHAN_WIDTH_80: + chan2_offset = + nxpwifi_get_sec_chan_offset(chan_def->chan->hw_value); + chan_width = CHAN_BW_80MHZ; + break; + case NL80211_CHAN_WIDTH_80P80: + case NL80211_CHAN_WIDTH_160: + default: + nxpwifi_dbg(priv->adapter, + WARN, "Unknown channel width: %d\n", + chan_def->width); + break; + } + + *band_cfg = ((chan2_offset << BAND_CFG_CHAN2_SHIFT_BIT) & + BAND_CFG_CHAN2_OFFSET_MASK) | + ((chan_width << BAND_CFG_CHAN_WIDTH_SHIFT_BIT) & + BAND_CFG_CHAN_WIDTH_MASK) | + ((chan_band << BAND_CFG_CHAN_BAND_SHIFT_BIT) & + BAND_CFG_CHAN_BAND_MASK); +} + +static int +nxpwifi_parse_mgmt_packet(struct nxpwifi_private *priv, u8 *payload, u16 len, + struct rxpd *rx_pd) +{ + u16 stype; + u8 category; + struct ieee80211_hdr *ieee_hdr = (void *)payload; + + stype = (le16_to_cpu(ieee_hdr->frame_control) & IEEE80211_FCTL_STYPE); + + switch (stype) { + case IEEE80211_STYPE_ACTION: + category = *(payload + sizeof(struct ieee80211_hdr)); + switch (category) { + case WLAN_CATEGORY_BACK: + /*we dont indicate BACK action frames to cfg80211*/ + nxpwifi_dbg(priv->adapter, INFO, + "drop BACK action frames"); + return -EINVAL; + default: + nxpwifi_dbg(priv->adapter, INFO, + "unknown public action frame category %d\n", + category); + } + break; + default: + nxpwifi_dbg(priv->adapter, INFO, + "unknown mgmt frame subtype %#x\n", stype); + return 0; + } + + return 0; +} + +/* Send deauth frame to cfg80211. */ +void nxpwifi_host_mlme_disconnect(struct nxpwifi_private *priv, + u16 reason_code, u8 *sa) +{ + u8 frame_buf[100]; + struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)frame_buf; + + memset(frame_buf, 0, sizeof(frame_buf)); + mgmt->frame_control = cpu_to_le16(IEEE80211_STYPE_DEAUTH); + mgmt->duration = 0; + mgmt->seq_ctrl = 0; + mgmt->u.deauth.reason_code = cpu_to_le16(reason_code); + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA) { + eth_broadcast_addr(mgmt->da); + memcpy(mgmt->sa, + priv->curr_bss_params.bss_descriptor.mac_address, + ETH_ALEN); + memcpy(mgmt->bssid, priv->cfg_bssid, ETH_ALEN); + priv->auth_flag = 0; + priv->auth_alg = WLAN_AUTH_NONE; + } else { + memcpy(mgmt->da, priv->curr_addr, ETH_ALEN); + memcpy(mgmt->sa, sa, ETH_ALEN); + memcpy(mgmt->bssid, priv->curr_addr, ETH_ALEN); + } + + if (GET_BSS_ROLE(priv) != NXPWIFI_BSS_ROLE_UAP) { + cfg80211_rx_mlme_mgmt(priv->netdev, frame_buf, 26); + } else { + cfg80211_rx_mgmt(&priv->wdev, + priv->bss_chandef.chan->center_freq, + 0, frame_buf, 26, 0); + } +} + +/* Parse and forward received management packet to cfg80211. */ +int +nxpwifi_process_mgmt_packet(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct rxpd *rx_pd; + u16 pkt_len; + struct ieee80211_hdr *ieee_hdr; + int ret; + + if (!skb) + return -ENOMEM; + + if (!priv->mgmt_frame_mask || + priv->wdev.iftype == NL80211_IFTYPE_UNSPECIFIED) { + nxpwifi_dbg(adapter, ERROR, + "do not receive mgmt frames on uninitialized intf"); + return -EINVAL; + } + + rx_pd = (struct rxpd *)skb->data; + pkt_len = le16_to_cpu(rx_pd->rx_pkt_length); + if (pkt_len < sizeof(struct ieee80211_hdr) + sizeof(pkt_len)) { + nxpwifi_dbg(adapter, ERROR, "invalid rx_pkt_length"); + return -EINVAL; + } + + skb_pull(skb, le16_to_cpu(rx_pd->rx_pkt_offset)); + skb_pull(skb, sizeof(pkt_len)); + pkt_len -= sizeof(pkt_len); + + ieee_hdr = (void *)skb->data; + if (ieee80211_is_mgmt(ieee_hdr->frame_control)) { + ret = nxpwifi_parse_mgmt_packet(priv, (u8 *)ieee_hdr, + pkt_len, rx_pd); + if (ret) + return ret; + } + /* Remove address4 */ + memmove(skb->data + sizeof(struct ieee80211_hdr_3addr), + skb->data + sizeof(struct ieee80211_hdr), + pkt_len - sizeof(struct ieee80211_hdr)); + + pkt_len -= ETH_ALEN; + rx_pd->rx_pkt_length = cpu_to_le16(pkt_len); + + if (priv->host_mlme_reg && + (GET_BSS_ROLE(priv) != NXPWIFI_BSS_ROLE_UAP) && + (ieee80211_is_auth(ieee_hdr->frame_control) || + ieee80211_is_deauth(ieee_hdr->frame_control) || + ieee80211_is_disassoc(ieee_hdr->frame_control))) { + struct nxpwifi_rxinfo *rx_info; + + if (ieee80211_is_auth(ieee_hdr->frame_control)) { + if (priv->auth_flag & HOST_MLME_AUTH_PENDING) { + if (priv->auth_alg != WLAN_AUTH_SAE) { + priv->auth_flag &= + ~HOST_MLME_AUTH_PENDING; + priv->auth_flag |= + HOST_MLME_AUTH_DONE; + } + } else { + return 0; + } + + nxpwifi_dbg(adapter, MSG, + "auth: receive authentication from %pM\n", + ieee_hdr->addr3); + } else { + if (!priv->wdev.connected) + return 0; + + if (ieee80211_is_deauth(ieee_hdr->frame_control)) { + nxpwifi_dbg(adapter, MSG, + "auth: receive deauth from %pM\n", + ieee_hdr->addr3); + priv->auth_flag = 0; + priv->auth_alg = WLAN_AUTH_NONE; + } else { + nxpwifi_dbg(adapter, MSG, + "assoc: receive disassoc from %pM\n", + ieee_hdr->addr3); + } + } + + rx_info = NXPWIFI_SKB_RXCB(skb); + rx_info->pkt_len = pkt_len; + skb_queue_tail(&adapter->rx_mlme_q, skb); + nxpwifi_queue_wiphy_work(adapter, &adapter->host_mlme_work); + return -EINPROGRESS; + } + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + if (ieee80211_is_auth(ieee_hdr->frame_control)) + nxpwifi_dbg(adapter, MSG, + "auth: receive auth from %pM\n", + ieee_hdr->addr2); + if (ieee80211_is_deauth(ieee_hdr->frame_control)) + nxpwifi_dbg(adapter, MSG, + "auth: receive deauth from %pM\n", + ieee_hdr->addr2); + if (ieee80211_is_disassoc(ieee_hdr->frame_control)) + nxpwifi_dbg(adapter, MSG, + "assoc: receive disassoc from %pM\n", + ieee_hdr->addr2); + if (ieee80211_is_assoc_req(ieee_hdr->frame_control)) + nxpwifi_dbg(adapter, MSG, + "assoc: receive assoc req from %pM\n", + ieee_hdr->addr2); + if (ieee80211_is_reassoc_req(ieee_hdr->frame_control)) + nxpwifi_dbg(adapter, MSG, + "assoc: receive reassoc req from %pM\n", + ieee_hdr->addr2); + } + + cfg80211_rx_mgmt(&priv->wdev, priv->roc_cfg.chan.center_freq, + CAL_RSSI(rx_pd->snr, rx_pd->nf), skb->data, pkt_len, + 0); + + return 0; +} + +#define RTAP_MAX_LEN 128 + +int nxpwifi_recv_packet_to_monif(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct rxpd *rxpd; + struct rxpd_extra_info ext; + struct ieee80211_hdr *dot11; + struct ieee80211_radiotap_header *hdr; + int freq, band; + bool has_ext = false, has_ts = false, use_tsft = false; + u16 rx_flags = 0, off, chan_flags, rx_pkt_offset; + __le16 freq_le, flags_le; + u8 rthdr[RTAP_MAX_LEN] = {0}; + s8 signal, noise; + u8 flags = 0, chan, ant, format, bw, gi, ldpc, mcs, nss, stbc; + + if (!skb) + return -EINVAL; + + rxpd = (struct rxpd *)skb->data; + rx_pkt_offset = le16_to_cpu(rxpd->rx_pkt_offset); + + if (rx_pkt_offset > skb->len || rx_pkt_offset < sizeof(struct rxpd)) + return -EINVAL; + + if (skb->len - rx_pkt_offset < sizeof(struct ieee80211_hdr)) + return -EINVAL; + + has_ext = rxpd->flags & RXPD_FLAG_EXTRA_HEADER; + + if (has_ext) + memcpy((void *)&ext, (void *)(rxpd + 1), sizeof(ext)); + + dot11 = (void *)skb->data + rx_pkt_offset; + memset(rthdr, 0, sizeof(rthdr)); + hdr = (void *)rthdr; + hdr->it_version = 0; + hdr->it_pad = 0; + hdr->it_present = 0; + off = sizeof(*hdr); + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_FLAGS)); + + if (has_ext) { + has_ts = true; + use_tsft = (ext.timestamp.position == 0); + + if (has_ts) { + if (use_tsft) + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_TSFT)); + else + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_TIMESTAMP)); + } + + flags = ext.flags & ~IEEE80211_RADIOTAP_F_BADFCS; + /* reverse fail fcs, 1 means pass FCS in FW, + * but means fail FCS in radiotap + */ + flags &= ~((~ext.flags) & IEEE80211_RADIOTAP_F_BADFCS); + + if (ext.plcp_crc_failed) + rx_flags |= IEEE80211_RADIOTAP_F_RX_BADPLCP; + } + + if (has_ts && use_tsft) { + off = ALIGN(off, 8); + memcpy(rthdr + off, &ext.timestamp.device_timestamp, 8); + off += 8; + } + + if (ieee80211_has_morefrags(dot11->frame_control)) + flags |= IEEE80211_RADIOTAP_F_FRAG; + + if (ieee80211_has_protected(dot11->frame_control)) + flags |= IEEE80211_RADIOTAP_F_WEP; + + rthdr[off++] = flags; + off = ALIGN(off, 2); + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_CHANNEL)); + chan = (le32_to_cpu(rxpd->rx_info) >> 5) & 0xff; + band = (chan <= 14) ? NL80211_BAND_2GHZ : NL80211_BAND_5GHZ; + freq = ieee80211_channel_to_frequency(chan, band); + + if (has_ext) + chan_flags = ext.channel_flags; + else if (band == NL80211_BAND_2GHZ) + chan_flags = IEEE80211_CHAN_2GHZ; + else + chan_flags = IEEE80211_CHAN_5GHZ; + + freq_le = cpu_to_le16(freq); + flags_le = cpu_to_le16(chan_flags); + memcpy(rthdr + off, &freq_le, 2); + off += 2; + memcpy(rthdr + off, &flags_le, 2); + off += 2; + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_DBM_ANTSIGNAL)); + signal = -(rxpd->nf - rxpd->snr); + rthdr[off++] = signal; + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_DBM_ANTNOISE)); + noise = -rxpd->nf; + rthdr[off++] = noise; + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_ANTENNA)); + ant = rxpd->antenna >> 1; + rthdr[off++] = ant; + + if (rx_flags) { + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_RX_FLAGS)); + off = ALIGN(off, 2); + memcpy(rthdr + off, &rx_flags, 2); + off += 2; + } + + format = FIELD_GET(RX_RATE_FORMAT_MASK, rxpd->rate_info); + bw = FIELD_GET(RX_RATE_BW_MASK, rxpd->rate_info); + ldpc = FIELD_GET(RX_RATE_LDPC_MASK, rxpd->rate_info) ? 1 : 0; + stbc = FIELD_GET(RX_RATE_STBC_MASK, rxpd->rate_info) ? 1 : 0; + + if (format == NXPWIFI_RATE_FORMAT_HE) { + gi = ((rxpd->rate_info >> 7) & 0x1) << 1 | + ((rxpd->rate_info >> 4) & 0x1); + } else { + gi = FIELD_GET(RX_RATE_GI_MASK, rxpd->rate_info); + } + + mcs = rxpd->rx_rate & 0xf; + nss = ((rxpd->rx_rate >> 4) & 0xf) + 1; + + if (format == NXPWIFI_RATE_FORMAT_HT) { + u8 mcs_flags = 0; + + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_MCS)); + rthdr[off++] = IEEE80211_RADIOTAP_MCS_HAVE_MCS | + IEEE80211_RADIOTAP_MCS_HAVE_BW | + IEEE80211_RADIOTAP_MCS_HAVE_GI; + + if (bw == 1) + mcs_flags |= IEEE80211_RADIOTAP_MCS_BW_40; + + if (gi) + mcs_flags |= IEEE80211_RADIOTAP_MCS_SGI; + + if (ldpc) + mcs_flags |= IEEE80211_RADIOTAP_MCS_FEC_LDPC; + + if (stbc) + mcs_flags |= (1 << IEEE80211_RADIOTAP_MCS_STBC_SHIFT); + + rthdr[off++] = mcs_flags; + rthdr[off++] = mcs; + } + + if (format == NXPWIFI_RATE_FORMAT_VHT && has_ext) { + struct ieee80211_radiotap_vht vht = {0}; + u32 vht_sig1 = 0, vht_sig2 = 0; + __le16 partial_aid = 0; + u8 bw_field = 0; + + vht_sig1 = ext.vht_he_sig1; + vht_sig2 = ext.vht_he_sig2; + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_VHT)); + vht.known = cpu_to_le16(IEEE80211_RADIOTAP_VHT_KNOWN_GI | + IEEE80211_RADIOTAP_VHT_KNOWN_BANDWIDTH | + IEEE80211_RADIOTAP_VHT_KNOWN_STBC | + IEEE80211_RADIOTAP_VHT_KNOWN_BEAMFORMED | + IEEE80211_RADIOTAP_VHT_KNOWN_GROUP_ID); + + if (stbc) + vht.flags |= IEEE80211_RADIOTAP_VHT_FLAG_STBC; + + if (gi) + vht.flags |= IEEE80211_RADIOTAP_VHT_FLAG_SGI; + + if (vht_sig2 & BIT(1)) + vht.flags |= IEEE80211_RADIOTAP_VHT_FLAG_SGI_NSYM_M10_9; + + if (vht_sig2 & BIT(8)) + vht.flags |= IEEE80211_RADIOTAP_VHT_FLAG_BEAMFORMED; + + switch (bw) { + case 1: + bw_field = 1; /* 40 MHz */ + break; + case 2: + bw_field = 4; /* 80 MHz */ + break; + case 3: + bw_field = 11; /* 160 MHz */ + break; + default: + bw_field = 0; /* 20 MHz */ + } + + vht.bandwidth = bw_field; + vht.mcs_nss[0] = (nss & 0xf) | (mcs << 4); + + if (vht_sig2 & BIT(2)) + vht.coding |= IEEE80211_RADIOTAP_CODING_LDPC_USER0; + + vht.group_id = (vht_sig1 >> 4) & 0x3f; + memcpy(&vht.partial_aid, &partial_aid, 2); + off = ALIGN(off, 2); + memcpy(rthdr + off, &vht, sizeof(vht)); + off += sizeof(vht); + } + + /* TIMESTAMP */ + if (has_ts && !use_tsft) { + u64 ts; + __le64 ts_le; + u16 accuracy = 0; + __le16 acc_le; + u8 flags = 0; + + if (ext.timestamp.position <= 15) { + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_TIMESTAMP)); + off = ALIGN(off, 8); + + if (ext.timestamp.flags & 0x01) { + flags |= IEEE80211_RADIOTAP_TIMESTAMP_FLAG_32BIT; + ts = (u32)ext.timestamp.device_timestamp; + } else { + flags |= IEEE80211_RADIOTAP_TIMESTAMP_FLAG_64BIT; + ts = ext.timestamp.device_timestamp; + } + + ts_le = cpu_to_le64(ts); + memcpy(rthdr + off, &ts_le, sizeof(ts_le)); + off += sizeof(ts_le); + + if (ext.timestamp.flags & 0x02) { + accuracy = ext.timestamp.accuracy; + flags |= IEEE80211_RADIOTAP_TIMESTAMP_FLAG_ACCURACY; + } + + acc_le = cpu_to_le16(accuracy); + memcpy(rthdr + off, &acc_le, sizeof(acc_le)); + off += sizeof(acc_le); + rthdr[off++] = (ext.timestamp.unit & 0x0f) | + ((ext.timestamp.position & 0x0f) << 4); + rthdr[off++] = flags; + } + } + + if (format == NXPWIFI_RATE_FORMAT_HE && has_ext) { + struct ieee80211_radiotap_he he = {0}; + u16 data1 = 0, data2 = 0, data3 = 0, data5 = 0, data6 = 0; + u8 bw_val = 0; + + memcpy((void *)&ext, (void *)(rxpd + 1), sizeof(ext)); + + off = ALIGN(off, 2); + hdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_HE)); + data1 |= IEEE80211_RADIOTAP_HE_DATA1_DATA_MCS_KNOWN; + data1 |= IEEE80211_RADIOTAP_HE_DATA1_BW_RU_ALLOC_KNOWN; + data1 |= IEEE80211_RADIOTAP_HE_DATA1_STBC_KNOWN; + data1 |= IEEE80211_RADIOTAP_HE_DATA1_CODING_KNOWN; + data2 |= IEEE80211_RADIOTAP_HE_DATA2_GI_KNOWN; + data3 = mcs << 8; + data3 |= FIELD_PREP(IEEE80211_RADIOTAP_HE_DATA3_DATA_MCS, mcs); + + if (stbc) + data3 |= IEEE80211_RADIOTAP_HE_DATA3_STBC; + + switch (bw) { + case 0: + bw_val |= IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC_20MHZ; + break; + case 1: + bw_val |= IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC_40MHZ; + break; + case 2: + bw_val |= IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC_80MHZ; + break; + case 3: + bw_val |= IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC_160MHZ; + break; + } + + data5 |= FIELD_PREP(IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC, + bw_val); + + switch (gi) { + case 0: + data5 |= IEEE80211_RADIOTAP_HE_DATA5_GI_0_8; + break; + case 1: + data5 |= IEEE80211_RADIOTAP_HE_DATA5_GI_1_6; + break; + case 2: + data5 |= IEEE80211_RADIOTAP_HE_DATA5_GI_3_2; + break; + } + + data6 |= FIELD_PREP(IEEE80211_RADIOTAP_HE_DATA6_NSTS, nss); + he.data1 = cpu_to_le16(data1); + he.data2 = cpu_to_le16(data2); + he.data3 = cpu_to_le16(data3); + he.data5 = cpu_to_le16(data5); + he.data6 = cpu_to_le16(data6); + memcpy(rthdr + off, &he, sizeof(he)); + off += sizeof(he); + } + + hdr->it_len = cpu_to_le16(off); + + if (off > sizeof(rthdr)) + return -EINVAL; + + /* Remove RXPD */ + skb_pull(skb, rx_pkt_offset); + + /* Ensure enough headroom */ + if (skb_cow_head(skb, off)) + return -ENOMEM; + + /* Push radiotap header */ + skb_push(skb, off); + memcpy(skb->data, rthdr, off); + skb_reset_mac_header(skb); + skb->ip_summed = CHECKSUM_NONE; + skb->pkt_type = PACKET_OTHERHOST; + skb->dev = priv->netdev; + skb->protocol = htons(ETH_P_802_2); + netif_rx(skb); + return 0; +} + +/* Process received packet and pass to net stack; reuse or build skb as needed. */ +int nxpwifi_recv_packet(struct nxpwifi_private *priv, struct sk_buff *skb) +{ + struct nxpwifi_sta_node *src_node; + struct ethhdr *p_ethhdr; + + if (!skb) + return -ENOMEM; + + priv->stats.rx_bytes += skb->len; + priv->stats.rx_packets++; + + if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { + p_ethhdr = (void *)skb->data; + rcu_read_lock(); + src_node = nxpwifi_get_sta_entry(priv, p_ethhdr->h_source); + if (src_node) { + src_node->stats.last_rx = jiffies; + src_node->stats.rx_bytes += skb->len; + src_node->stats.rx_packets++; + } + rcu_read_unlock(); + } + + skb->dev = priv->netdev; + skb->protocol = eth_type_trans(skb, priv->netdev); + skb->ip_summed = CHECKSUM_NONE; + + netif_rx(skb); + return 0; +} + +/* IOCTL completion callback: wake waiters or process response as needed. */ +int nxpwifi_complete_cmd(struct nxpwifi_adapter *adapter, + struct cmd_ctrl_node *cmd_node) +{ + WARN_ON(!cmd_node->wait_q_enabled); + nxpwifi_dbg(adapter, CMD, "cmd completed: status=%d\n", + adapter->cmd_wait_q.status); + + *cmd_node->condition = true; + wake_up_interruptible(&adapter->cmd_wait_q.wait); + + return 0; +} + +/* Find STA entry by MAC under rcu_read_lock(); return NULL if not found. */ +struct nxpwifi_sta_node * +nxpwifi_get_sta_entry(struct nxpwifi_private *priv, const u8 *mac) +{ + struct nxpwifi_sta_node *node; + struct nxpwifi_sta_node *found = NULL; + + if (!mac) + return NULL; + list_for_each_entry_rcu(node, &priv->sta_list, list) { + if (!memcmp(node->mac_addr, mac, ETH_ALEN)) { + found = node; + break; + } + } + + return found; +} + +struct nxpwifi_sta_node * +nxpwifi_get_sta_entry_rcu(struct nxpwifi_private *priv, const u8 *mac) +{ + struct nxpwifi_sta_node *node; + + rcu_read_lock(); + node = nxpwifi_get_sta_entry(priv, mac); + rcu_read_unlock(); + + return node; +} + +/* Add STA entry by MAC; return existing entry or NULL on invalid MAC. */ +struct nxpwifi_sta_node * +nxpwifi_add_sta_entry(struct nxpwifi_private *priv, const u8 *mac) +{ + struct nxpwifi_sta_node *node; + + if (!mac) + return NULL; + + spin_lock_bh(&priv->sta_list_spinlock); + node = nxpwifi_get_sta_entry_rcu(priv, mac); + + if (node) + goto done; + + node = kzalloc_obj(*node, GFP_ATOMIC); + if (!node) + goto done; + + memcpy(node->mac_addr, mac, ETH_ALEN); + list_add_tail_rcu(&node->list, &priv->sta_list); + +done: + spin_unlock_bh(&priv->sta_list_spinlock); + return node; +} + +/* Parse HT cap IE from association IEs and set STA HT parameters. */ +void +nxpwifi_set_sta_ht_cap(struct nxpwifi_private *priv, const u8 *ies, + int ies_len, struct nxpwifi_sta_node *node) +{ + struct element *ht_cap_ie; + const struct ieee80211_ht_cap *ht_cap; + + if (!ies) + return; + + ht_cap_ie = (void *)cfg80211_find_ie(WLAN_EID_HT_CAPABILITY, ies, + ies_len); + if (ht_cap_ie) { + ht_cap = (void *)(ht_cap_ie + 1); + node->is_11n_enabled = 1; + node->max_amsdu = le16_to_cpu(ht_cap->cap_info) & + IEEE80211_HT_CAP_MAX_AMSDU ? + NXPWIFI_TX_DATA_BUF_SIZE_8K : + NXPWIFI_TX_DATA_BUF_SIZE_4K; + } else { + node->is_11n_enabled = 0; + } +} + +/* Delete a station from list; called under cfg80211 mutex. */ + +void nxpwifi_del_sta_entry(struct nxpwifi_private *priv, const u8 *mac) +{ + struct nxpwifi_sta_node *node; + + list_for_each_entry_rcu(node, &priv->sta_list, list) { + if (!memcmp(node->mac_addr, mac, ETH_ALEN)) { + list_del_rcu(&node->list); + kfree_rcu(node, rcu); + break; + } + } +} + +/* Delete all stations from list. */ +void nxpwifi_del_all_sta_list(struct nxpwifi_private *priv) +{ + struct nxpwifi_sta_node *node, *tmp; + + spin_lock_bh(&priv->sta_list_spinlock); + + list_for_each_entry_safe(node, tmp, &priv->sta_list, list) { + list_del_rcu(&node->list); + kfree_rcu(node, rcu); + } + + INIT_LIST_HEAD(&priv->sta_list); + spin_unlock_bh(&priv->sta_list_spinlock); +} + +/* Add one histogram sample. */ +void nxpwifi_hist_data_add(struct nxpwifi_private *priv, + u8 rx_rate, s8 snr, s8 nflr) +{ + struct nxpwifi_histogram_data *phist_data = priv->hist_data; + + if (atomic_read(&phist_data->num_samples) > NXPWIFI_HIST_MAX_SAMPLES) + nxpwifi_hist_data_reset(priv); + nxpwifi_hist_data_set(priv, rx_rate, snr, nflr); +} + +/* function to add histogram record */ +void nxpwifi_hist_data_set(struct nxpwifi_private *priv, u8 rx_rate, s8 snr, + s8 nflr) +{ + struct nxpwifi_histogram_data *phist_data = priv->hist_data; + s8 nf = -nflr; + s8 rssi = snr - nflr; + + atomic_inc(&phist_data->num_samples); + atomic_inc(&phist_data->rx_rate[rx_rate]); + atomic_inc(&phist_data->snr[snr + 128]); + atomic_inc(&phist_data->noise_flr[nf + 128]); + atomic_inc(&phist_data->sig_str[rssi + 128]); +} + +/* function to reset histogram data during init/reset */ +void nxpwifi_hist_data_reset(struct nxpwifi_private *priv) +{ + int ix; + struct nxpwifi_histogram_data *phist_data = priv->hist_data; + + atomic_set(&phist_data->num_samples, 0); + for (ix = 0; ix < NXPWIFI_MAX_AC_RX_RATES; ix++) + atomic_set(&phist_data->rx_rate[ix], 0); + for (ix = 0; ix < NXPWIFI_MAX_SNR; ix++) + atomic_set(&phist_data->snr[ix], 0); + for (ix = 0; ix < NXPWIFI_MAX_NOISE_FLR; ix++) + atomic_set(&phist_data->noise_flr[ix], 0); + for (ix = 0; ix < NXPWIFI_MAX_SIG_STRENGTH; ix++) + atomic_set(&phist_data->sig_str[ix], 0); +} + +void *nxpwifi_alloc_dma_align_buf(int rx_len, gfp_t flags) +{ + struct sk_buff *skb; + int buf_len, pad; + + buf_len = rx_len + NXPWIFI_RX_HEADROOM + NXPWIFI_DMA_ALIGN_SZ; + + skb = __dev_alloc_skb(buf_len, flags); + + if (!skb) + return NULL; + + skb_reserve(skb, NXPWIFI_RX_HEADROOM); + + pad = NXPWIFI_ALIGN_ADDR(skb->data, NXPWIFI_DMA_ALIGN_SZ) - + (long)skb->data; + + skb_reserve(skb, pad); + + return skb; +} +EXPORT_SYMBOL_GPL(nxpwifi_alloc_dma_align_buf); + +void nxpwifi_fw_dump_event(struct nxpwifi_private *priv) +{ + nxpwifi_send_cmd(priv, HOST_CMD_FW_DUMP_EVENT, HOST_ACT_GEN_SET, + 0, NULL, true); +} +EXPORT_SYMBOL_GPL(nxpwifi_fw_dump_event); + +int nxpwifi_append_data_tlv(u16 id, u8 *data, int len, u8 *pos, u8 *cmd_end) +{ + struct nxpwifi_ie_types_data *tlv; + u16 header_len = sizeof(struct nxpwifi_ie_types_header); + + tlv = (struct nxpwifi_ie_types_data *)pos; + tlv->header.len = cpu_to_le16(len); + + if (id == WLAN_EID_EXT_HE_CAPABILITY) { + if ((pos + header_len + len + 1) > cmd_end) + return 0; + + tlv->header.type = cpu_to_le16(WLAN_EID_EXTENSION); + tlv->data[0] = WLAN_EID_EXT_HE_CAPABILITY; + memcpy(tlv->data + 1, data, len); + } else { + if ((pos + header_len + len) > cmd_end) + return 0; + + tlv->header.type = cpu_to_le16(id); + memcpy(tlv->data, data, len); + } + + return (header_len + len); +} + +static int nxpwifi_get_vdll_image(struct nxpwifi_adapter *adapter, u32 vdll_len) +{ + struct vdll_dnld_ctrl *ctrl = &adapter->vdll_ctrl; + bool req_fw = false; + u32 offset; + + if (ctrl->vdll_mem) { + nxpwifi_dbg(adapter, EVENT, + "VDLL mem is not empty: %p old_len=%d new_len=%d\n", + ctrl->vdll_mem, ctrl->vdll_len, vdll_len); + vfree(ctrl->vdll_mem); + ctrl->vdll_mem = NULL; + ctrl->vdll_len = 0; + } + + ctrl->vdll_mem = vmalloc(vdll_len); + if (!ctrl->vdll_mem) + return -ENOMEM; + + if (!adapter->firmware) { + req_fw = true; + if (request_firmware(&adapter->firmware, adapter->fw_name, + adapter->dev)) + return -ENOENT; + } + + if (adapter->firmware) { + if (vdll_len < adapter->firmware->size) { + offset = adapter->firmware->size - vdll_len; + memcpy(ctrl->vdll_mem, adapter->firmware->data + offset, + vdll_len); + } else { + nxpwifi_dbg(adapter, ERROR, + "Invalid VDLL length = %d, fw_len=%d\n", + vdll_len, (int)adapter->firmware->size); + return -EINVAL; + } + if (req_fw) { + release_firmware(adapter->firmware); + adapter->firmware = NULL; + } + } + + ctrl->vdll_len = vdll_len; + nxpwifi_dbg(adapter, MSG, "VDLL image: len=%d\n", ctrl->vdll_len); + + return 0; +} + +int nxpwifi_download_vdll_block(struct nxpwifi_adapter *adapter, + u8 *block, u16 block_len) +{ + struct vdll_dnld_ctrl *ctrl = &adapter->vdll_ctrl; + struct host_cmd_ds_command *host_cmd; + u16 msg_len = block_len + S_DS_GEN; + int ret = 0; + + skb_trim(ctrl->skb, 0); + skb_put_zero(ctrl->skb, msg_len); + + host_cmd = (struct host_cmd_ds_command *)(ctrl->skb->data); + + host_cmd->command = cpu_to_le16(HOST_CMD_VDLL); + host_cmd->seq_num = cpu_to_le16(0xFF00); + host_cmd->size = cpu_to_le16(msg_len); + memcpy(ctrl->skb->data + S_DS_GEN, block, block_len); + + skb_push(ctrl->skb, adapter->intf_hdr_len); + ret = adapter->if_ops.host_to_card(adapter, NXPWIFI_TYPE_VDLL, + ctrl->skb, NULL); + skb_pull(ctrl->skb, adapter->intf_hdr_len); + + if (ret) + nxpwifi_dbg(adapter, ERROR, + "Fail to download VDLL: block: %p, len: %d\n", + block, block_len); + + return ret; +} + +int nxpwifi_process_vdll_event(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct vdll_ind_event *vdll_evt = + (struct vdll_ind_event *)(skb->data + sizeof(u32)); + u16 type = le16_to_cpu(vdll_evt->type); + u16 vdll_id = le16_to_cpu(vdll_evt->vdll_id); + u32 offset = le32_to_cpu(vdll_evt->offset); + u16 block_len = le16_to_cpu(vdll_evt->block_len); + struct vdll_dnld_ctrl *ctrl = &adapter->vdll_ctrl; + int ret = 0; + + switch (type) { + case VDLL_IND_TYPE_REQ: + nxpwifi_dbg(adapter, EVENT, + "VDLL IND (REG): ID: %d, offset: %#x, len: %d\n", + vdll_id, offset, block_len); + if (offset <= ctrl->vdll_len) { + block_len = + min((u32)block_len, ctrl->vdll_len - offset); + if (!adapter->cmd_sent) { + ret = nxpwifi_download_vdll_block(adapter, + ctrl->vdll_mem + + offset, + block_len); + if (ret) + nxpwifi_dbg(adapter, ERROR, + "Download VDLL failed\n"); + } else { + nxpwifi_dbg(adapter, EVENT, + "Delay download VDLL block\n"); + ctrl->pending_block_len = block_len; + ctrl->pending_block = ctrl->vdll_mem + offset; + } + } else { + nxpwifi_dbg(adapter, ERROR, + "Err Req: offset=%#x, len=%d, vdll_len=%d\n", + offset, block_len, ctrl->vdll_len); + ret = -EINVAL; + } + break; + case VDLL_IND_TYPE_OFFSET: + nxpwifi_dbg(adapter, EVENT, + "VDLL IND (OFFSET): offset: %#x\n", offset); + ret = nxpwifi_get_vdll_image(adapter, offset); + break; + case VDLL_IND_TYPE_ERR_SIG: + case VDLL_IND_TYPE_ERR_ID: + case VDLL_IND_TYPE_SEC_ERR_ID: + nxpwifi_dbg(adapter, ERROR, "VDLL IND: error: %d\n", type); + break; + case VDLL_IND_TYPE_INTF_RESET: + nxpwifi_dbg(adapter, EVENT, "VDLL IND: interface reset\n"); + break; + default: + nxpwifi_dbg(adapter, ERROR, "VDLL IND: unknown type: %d", type); + ret = -EINVAL; + break; + } + + return ret; +} + +u64 nxpwifi_roc_cookie(struct nxpwifi_adapter *adapter) +{ + adapter->roc_cookie_counter++; + + /* wow, you wrapped 64 bits ... more likely a bug */ + if (WARN_ON(adapter->roc_cookie_counter == 0)) + adapter->roc_cookie_counter++; + + return adapter->roc_cookie_counter; +} + +static bool nxpwifi_can_queue_work(struct nxpwifi_adapter *adapter) +{ + if (test_bit(NXPWIFI_SURPRISE_REMOVED, &adapter->work_flags) || + test_bit(NXPWIFI_IS_CMD_TIMEDOUT, &adapter->work_flags) || + test_bit(NXPWIFI_IS_SUSPENDED, &adapter->work_flags)) { + nxpwifi_dbg(adapter, WARN, + "queueing nxpwifi work while going to suspend\n"); + return false; + } + + return true; +} + +void nxpwifi_queue_work(struct nxpwifi_adapter *adapter, + struct work_struct *work) +{ + if (!nxpwifi_can_queue_work(adapter)) + return; + + queue_work(adapter->workqueue, work); +} +EXPORT_SYMBOL(nxpwifi_queue_work); + +void nxpwifi_queue_delayed_work(struct nxpwifi_adapter *adapter, + struct delayed_work *dwork, + unsigned long delay) +{ + if (!nxpwifi_can_queue_work(adapter)) + return; + + queue_delayed_work(adapter->workqueue, dwork, delay); +} +EXPORT_SYMBOL(nxpwifi_queue_delayed_work); + +void nxpwifi_queue_wiphy_work(struct nxpwifi_adapter *adapter, + struct wiphy_work *work) +{ + if (!nxpwifi_can_queue_work(adapter)) + return; + + wiphy_work_queue(adapter->wiphy, work); +} + +void nxpwifi_queue_delayed_wiphy_work(struct nxpwifi_adapter *adapter, + struct wiphy_delayed_work *dwork, + unsigned long delay) +{ + if (!nxpwifi_can_queue_work(adapter)) + return; + + wiphy_delayed_work_queue(adapter->wiphy, dwork, delay); +} diff --git a/drivers/net/wireless/nxp/nxpwifi/util.h b/drivers/net/wireless/nxp/nxpwifi/util.h new file mode 100644 index 000000000000..1a47c8c5b530 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/util.h @@ -0,0 +1,155 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * NXP Wireless LAN device driver: utility functions + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_UTIL_H_ +#define _NXPWIFI_UTIL_H_ +#include "fw.h" + +struct nxpwifi_adapter; + +struct nxpwifi_private; + +struct nxpwifi_dma_mapping { + dma_addr_t addr; + size_t len; +}; + +struct nxpwifi_cb { + struct nxpwifi_dma_mapping dma_mapping; + union { + struct nxpwifi_rxinfo rx_info; + struct nxpwifi_txinfo tx_info; + }; +}; + +/* size/addr for nxpwifi_debug_info */ +#define item_size(n) (sizeof_field(struct nxpwifi_debug_info, n)) +#define item_addr(n) (offsetof(struct nxpwifi_debug_info, n)) + +/* size/addr for struct nxpwifi_adapter */ +#define adapter_item_size(n) (sizeof_field(struct nxpwifi_adapter, n)) +#define adapter_item_addr(n) (offsetof(struct nxpwifi_adapter, n)) + +struct nxpwifi_debug_data { + char name[32]; /* variable/array name */ + u32 size; /* size of the variable/array */ + size_t addr; /* address of the variable/array */ + int num; /* number of variables in an array */ +}; + +static inline struct nxpwifi_rxinfo *NXPWIFI_SKB_RXCB(struct sk_buff *skb) +{ + struct nxpwifi_cb *cb = (struct nxpwifi_cb *)skb->cb; + + BUILD_BUG_ON(sizeof(struct nxpwifi_cb) > sizeof(skb->cb)); + return &cb->rx_info; +} + +static inline struct nxpwifi_txinfo *NXPWIFI_SKB_TXCB(struct sk_buff *skb) +{ + struct nxpwifi_cb *cb = (struct nxpwifi_cb *)skb->cb; + + return &cb->tx_info; +} + +static inline void nxpwifi_store_mapping(struct sk_buff *skb, + struct nxpwifi_dma_mapping *mapping) +{ + struct nxpwifi_cb *cb = (struct nxpwifi_cb *)skb->cb; + + memcpy(&cb->dma_mapping, mapping, sizeof(*mapping)); +} + +static inline void nxpwifi_get_mapping(struct sk_buff *skb, + struct nxpwifi_dma_mapping *mapping) +{ + struct nxpwifi_cb *cb = (struct nxpwifi_cb *)skb->cb; + + memcpy(mapping, &cb->dma_mapping, sizeof(*mapping)); +} + +static inline dma_addr_t NXPWIFI_SKB_DMA_ADDR(struct sk_buff *skb) +{ + struct nxpwifi_dma_mapping mapping; + + nxpwifi_get_mapping(skb, &mapping); + + return mapping.addr; +} + +int nxpwifi_debug_info_to_buffer(struct nxpwifi_private *priv, char *buf, + struct nxpwifi_debug_info *info); + +static inline void le16_unaligned_add_cpu(__le16 *var, u16 val) +{ + put_unaligned_le16(get_unaligned_le16(var) + val, var); +} + +/* + * Iterate over TLVs safely. + * Ensures no out-of-bound access even if firmware sends malformed data. + */ +#define nxpwifi_for_each_tlv(tlv, buf, buf_len) \ + for (tlv = (const struct nxpwifi_tlv *)(buf); \ + (u8 *)(tlv) + sizeof(*tlv) <= (u8 *)(buf) + (buf_len) && \ + (u8 *)(tlv) + sizeof(*tlv) + le16_to_cpu(tlv->len) <= \ + (u8 *)(buf) + (buf_len); \ + tlv = (const struct nxpwifi_tlv *)((u8 *)(tlv) + sizeof(*tlv) + \ + le16_to_cpu(tlv->len))) + +/* Return first TLV matching @type in given buffer. */ +static inline const struct nxpwifi_tlv * +nxpwifi_find_tlv(u16 type, const u8 *buf, u32 buf_len) +{ + const struct nxpwifi_tlv *tlv; + + nxpwifi_for_each_tlv(tlv, buf, buf_len) { + if (le16_to_cpu(tlv->type) == type) + return tlv; + } + + return NULL; +} + +int nxpwifi_append_data_tlv(u16 id, u8 *data, int len, u8 *pos, u8 *cmd_end); + +int nxpwifi_download_vdll_block(struct nxpwifi_adapter *adapter, + u8 *block, u16 block_len); + +int nxpwifi_process_vdll_event(struct nxpwifi_private *priv, + struct sk_buff *skb); + +u64 nxpwifi_roc_cookie(struct nxpwifi_adapter *adapter); + +void nxpwifi_queue_work(struct nxpwifi_adapter *adapter, + struct work_struct *work); + +void nxpwifi_queue_delayed_work(struct nxpwifi_adapter *adapter, + struct delayed_work *dwork, + unsigned long delay); + +void nxpwifi_queue_wiphy_work(struct nxpwifi_adapter *adapter, + struct wiphy_work *work); + +void nxpwifi_queue_delayed_wiphy_work(struct nxpwifi_adapter *adapter, + struct wiphy_delayed_work *dwork, + unsigned long delay); + +/* + * Firmware cannot run AP and STA on different channels simultaneously, + * and doing so may trigger a crash. Check whether check_chan can be set + * safely; return true if allowed, false if another channel is already + * active in firmware. + */ +bool nxpwifi_is_channel_setting_allowable(struct nxpwifi_private *priv, + struct ieee80211_channel *check_chan); + +void nxpwifi_convert_chan_to_band_cfg(struct nxpwifi_private *priv, + u8 *band_cfg, + struct cfg80211_chan_def *chan_def); + +#endif /* !_NXPWIFI_UTIL_H_ */ diff --git a/drivers/net/wireless/nxp/nxpwifi/wmm.c b/drivers/net/wireless/nxp/nxpwifi/wmm.c new file mode 100644 index 000000000000..a79e1138eeef --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/wmm.c @@ -0,0 +1,1313 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP Wireless LAN device driver: WMM + * + * Copyright 2011-2024 NXP + */ + +#include "cfg.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "wmm.h" +#include "11n.h" + +/* Maximum value FW can accept for driver delay in packet transmission */ +#define DRV_PKT_DELAY_TO_FW_MAX 512 + +#define WMM_QUEUED_PACKET_LOWER_LIMIT 180 + +#define WMM_QUEUED_PACKET_UPPER_LIMIT 200 + +/* Offset for TOS field in the IP header */ +#define IPTOS_OFFSET 5 + +static bool disable_tx_amsdu; + +/* + * This table inverses the tos_to_tid operation to get a priority + * which is in sequential order, and can be compared. + * Use this to compare the priority of two different TIDs. + */ +const u8 tos_to_tid_inv[] = { + 0x02, /* from tos_to_tid[2] = 0 */ + 0x00, /* from tos_to_tid[0] = 1 */ + 0x01, /* from tos_to_tid[1] = 2 */ + 0x03, + 0x04, + 0x05, + 0x06, + 0x07 +}; + +/* WMM information element */ +static const u8 wmm_info_ie[] = { WLAN_EID_VENDOR_SPECIFIC, 0x07, + 0x00, 0x50, 0xf2, 0x02, + 0x00, 0x01, 0x00 +}; + +static const u8 wmm_aci_to_qidx_map[] = { WMM_AC_BE, + WMM_AC_BK, + WMM_AC_VI, + WMM_AC_VO +}; + +static u8 tos_to_tid[] = { + /* TID DSCP_P2 DSCP_P1 DSCP_P0 WMM_AC */ + 0x01, /* 0 1 0 AC_BK */ + 0x02, /* 0 0 0 AC_BK */ + 0x00, /* 0 0 1 AC_BE */ + 0x03, /* 0 1 1 AC_BE */ + 0x04, /* 1 0 0 AC_VI */ + 0x05, /* 1 0 1 AC_VI */ + 0x06, /* 1 1 0 AC_VO */ + 0x07 /* 1 1 1 AC_VO */ +}; + +static u8 ac_to_tid[4][2] = { {1, 2}, {0, 3}, {4, 5}, {6, 7} }; + +/* Debug prints the priority parameters for a WMM AC. */ +static void +nxpwifi_wmm_ac_debug_print(const struct ieee80211_wmm_ac_param *ac_param) +{ + static const char * const ac_str[] = { "BK", "BE", "VI", "VO" }; + + pr_debug("info: WMM AC_%s: ACI=%d, ACM=%d, Aifsn=%d, ", + ac_str[wmm_aci_to_qidx_map[(ac_param->aci_aifsn + & NXPWIFI_ACI) >> 5]], + (ac_param->aci_aifsn & NXPWIFI_ACI) >> 5, + (ac_param->aci_aifsn & NXPWIFI_ACM) >> 4, + ac_param->aci_aifsn & NXPWIFI_AIFSN); + pr_debug("EcwMin=%d, EcwMax=%d, TxopLimit=%d\n", + ac_param->cw & NXPWIFI_ECW_MIN, + (ac_param->cw & NXPWIFI_ECW_MAX) >> 4, + le16_to_cpu(ac_param->txop_limit)); +} + +/* Allocates a route address list. */ +static struct nxpwifi_ra_list_tbl * +nxpwifi_wmm_allocate_ralist_node(struct nxpwifi_adapter *adapter, const u8 *ra) +{ + struct nxpwifi_ra_list_tbl *ra_list; + + ra_list = kzalloc_obj(*ra_list, GFP_ATOMIC); + if (!ra_list) + return NULL; + + INIT_LIST_HEAD(&ra_list->list); + skb_queue_head_init(&ra_list->skb_head); + + memcpy(ra_list->ra, ra, ETH_ALEN); + + ra_list->total_pkt_count = 0; + + nxpwifi_dbg(adapter, INFO, "info: allocated ra_list %p\n", ra_list); + + return ra_list; +} + +/* + * Returns random no between 16 and 32 to be used as threshold for no of + * packets after which BA setup is initiated. + */ +static u8 nxpwifi_get_random_ba_threshold(void) +{ + u64 ns; + /* + * setup ba_packet_threshold here random number between + * [BA_SETUP_PACKET_OFFSET, + * BA_SETUP_PACKET_OFFSET+BA_SETUP_MAX_PACKET_THRESHOLD-1] + */ + ns = ktime_get_ns(); + ns += (ns >> 32) + (ns >> 16); + + return ((u8)ns % BA_SETUP_MAX_PACKET_THRESHOLD) + BA_SETUP_PACKET_OFFSET; +} + +/* Allocates and adds a RA list for all TIDs with the given RA. */ +void nxpwifi_ralist_add(struct nxpwifi_private *priv, const u8 *ra) +{ + int i; + struct nxpwifi_ra_list_tbl *ra_list; + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_sta_node *node; + + for (i = 0; i < MAX_NUM_TID; ++i) { + ra_list = nxpwifi_wmm_allocate_ralist_node(adapter, ra); + nxpwifi_dbg(adapter, INFO, + "info: created ra_list %p\n", ra_list); + + if (!ra_list) + break; + + ra_list->is_11n_enabled = 0; + ra_list->ba_status = BA_SETUP_NONE; + ra_list->amsdu_in_ampdu = false; + if (!nxpwifi_queuing_ra_based(priv)) { + ra_list->is_11n_enabled = IS_11N_ENABLED(priv); + } else { + rcu_read_lock(); + node = nxpwifi_get_sta_entry(priv, ra); + if (node) + ra_list->tx_paused = node->tx_pause; + ra_list->is_11n_enabled = + nxpwifi_is_sta_11n_enabled(priv, node); + if (ra_list->is_11n_enabled) + ra_list->max_amsdu = node->max_amsdu; + rcu_read_unlock(); + } + + nxpwifi_dbg(adapter, DATA, "data: ralist %p: is_11n_enabled=%d\n", + ra_list, ra_list->is_11n_enabled); + + if (ra_list->is_11n_enabled) { + ra_list->ba_pkt_count = 0; + ra_list->ba_packet_thr = + nxpwifi_get_random_ba_threshold(); + } + list_add_tail(&ra_list->list, + &priv->wmm.tid_tbl_ptr[i].ra_list); + } +} + +/* Sets the WMM queue priorities to their default values. */ +static void nxpwifi_wmm_default_queue_priorities(struct nxpwifi_private *priv) +{ + /* Default queue priorities: VO->VI->BE->BK */ + priv->wmm.queue_priority[0] = WMM_AC_VO; + priv->wmm.queue_priority[1] = WMM_AC_VI; + priv->wmm.queue_priority[2] = WMM_AC_BE; + priv->wmm.queue_priority[3] = WMM_AC_BK; +} + +/* Map ACs to TIDs. */ +static void +nxpwifi_wmm_queue_priorities_tid(struct nxpwifi_private *priv) +{ + struct nxpwifi_wmm_desc *wmm = &priv->wmm; + u8 *queue_priority = wmm->queue_priority; + int i; + + for (i = 0; i < 4; ++i) { + tos_to_tid[7 - (i * 2)] = ac_to_tid[queue_priority[i]][1]; + tos_to_tid[6 - (i * 2)] = ac_to_tid[queue_priority[i]][0]; + } + + for (i = 0; i < MAX_NUM_TID; ++i) + priv->tos_to_tid_inv[tos_to_tid[i]] = (u8)i; + + atomic_set(&wmm->highest_queued_prio, HIGH_PRIO_TID); +} + +/* Initializes WMM priority queues. */ +void +nxpwifi_wmm_setup_queue_priorities(struct nxpwifi_private *priv, + struct ieee80211_wmm_param_ie *wmm_ie) +{ + u16 cw_min, avg_back_off, tmp[4]; + u32 i, j, num_ac; + u8 ac_idx; + + if (!wmm_ie || !priv->wmm_enabled) { + /* WMM is not enabled, just set the defaults and return */ + nxpwifi_wmm_default_queue_priorities(priv); + return; + } + + nxpwifi_dbg(priv->adapter, INFO, + "info: WMM Parameter element: version=%d,\t" + "qos_info Parameter Set Count=%d, Reserved=%#x\n", + wmm_ie->version, wmm_ie->qos_info & + IEEE80211_WMM_IE_AP_QOSINFO_PARAM_SET_CNT_MASK, + wmm_ie->reserved); + + for (num_ac = 0; num_ac < ARRAY_SIZE(wmm_ie->ac); num_ac++) { + u8 ecw = wmm_ie->ac[num_ac].cw; + u8 aci_aifsn = wmm_ie->ac[num_ac].aci_aifsn; + + cw_min = (1 << (ecw & NXPWIFI_ECW_MIN)) - 1; + avg_back_off = (cw_min >> 1) + (aci_aifsn & NXPWIFI_AIFSN); + + ac_idx = wmm_aci_to_qidx_map[(aci_aifsn & NXPWIFI_ACI) >> 5]; + priv->wmm.queue_priority[ac_idx] = ac_idx; + tmp[ac_idx] = avg_back_off; + + nxpwifi_dbg(priv->adapter, INFO, + "info: WMM: CWmax=%d CWmin=%d Avg Back-off=%d\n", + (1 << ((ecw & NXPWIFI_ECW_MAX) >> 4)) - 1, + cw_min, avg_back_off); + nxpwifi_wmm_ac_debug_print(&wmm_ie->ac[num_ac]); + } + + /* Bubble sort */ + for (i = 0; i < num_ac; i++) { + for (j = 1; j < num_ac - i; j++) { + if (tmp[j - 1] > tmp[j]) { + swap(tmp[j - 1], tmp[j]); + swap(priv->wmm.queue_priority[j - 1], + priv->wmm.queue_priority[j]); + } else if (tmp[j - 1] == tmp[j]) { + if (priv->wmm.queue_priority[j - 1] + < priv->wmm.queue_priority[j]) + swap(priv->wmm.queue_priority[j - 1], + priv->wmm.queue_priority[j]); + } + } + } + + nxpwifi_wmm_queue_priorities_tid(priv); +} + +/* Evaluates whether or not an AC is to be downgraded. */ +static enum nxpwifi_wmm_ac_e +nxpwifi_wmm_eval_downgrade_ac(struct nxpwifi_private *priv, + enum nxpwifi_wmm_ac_e eval_ac) +{ + int down_ac; + enum nxpwifi_wmm_ac_e ret_ac; + struct nxpwifi_wmm_ac_status *ac_status; + + ac_status = &priv->wmm.ac_status[eval_ac]; + + if (!ac_status->disabled) + /* Okay to use this AC, its enabled */ + return eval_ac; + + /* Setup a default return value of the lowest priority */ + ret_ac = WMM_AC_BK; + + /* + * Find the highest AC that is enabled and does not require + * admission control. The spec disallows downgrading to an AC, + * which is enabled due to a completed admission control. + * Unadmitted traffic is not to be sent on an AC with admitted + * traffic. + */ + for (down_ac = WMM_AC_BK; down_ac < eval_ac; down_ac++) { + ac_status = &priv->wmm.ac_status[down_ac]; + + if (!ac_status->disabled && !ac_status->flow_required) + /* + * AC is enabled and does not require admission + * control + */ + ret_ac = (enum nxpwifi_wmm_ac_e)down_ac; + } + + return ret_ac; +} + +/* Downgrades WMM priority queue. */ +void +nxpwifi_wmm_setup_ac_downgrade(struct nxpwifi_private *priv) +{ + int ac_val; + + nxpwifi_dbg(priv->adapter, INFO, "info: WMM: AC Priorities:\t" + "BK(0), BE(1), VI(2), VO(3)\n"); + + if (!priv->wmm_enabled) { + /* WMM is not enabled, default priorities */ + for (ac_val = WMM_AC_BK; ac_val <= WMM_AC_VO; ac_val++) + priv->wmm.ac_down_graded_vals[ac_val] = + (enum nxpwifi_wmm_ac_e)ac_val; + } else { + for (ac_val = WMM_AC_BK; ac_val <= WMM_AC_VO; ac_val++) { + priv->wmm.ac_down_graded_vals[ac_val] = + nxpwifi_wmm_eval_downgrade_ac + (priv, (enum nxpwifi_wmm_ac_e)ac_val); + nxpwifi_dbg(priv->adapter, INFO, + "info: WMM: AC PRIO %d maps to %d\n", + ac_val, + priv->wmm.ac_down_graded_vals[ac_val]); + } + } +} + +/* Converts the IP TOS field to an WMM AC Queue assignment. */ +static enum nxpwifi_wmm_ac_e +nxpwifi_wmm_convert_tos_to_ac(struct nxpwifi_adapter *adapter, u32 tos) +{ + /* Map of TOS UP values to WMM AC */ + static const enum nxpwifi_wmm_ac_e tos_to_ac[] = { + WMM_AC_BE, + WMM_AC_BK, + WMM_AC_BK, + WMM_AC_BE, + WMM_AC_VI, + WMM_AC_VI, + WMM_AC_VO, + WMM_AC_VO + }; + + if (tos >= ARRAY_SIZE(tos_to_ac)) + return WMM_AC_BE; + + return tos_to_ac[tos]; +} + +/* + * Evaluates a given TID and downgrades it to a lower TID if the WMM Parameter + * element received from the AP indicates that the AP is disabled (due to call + * admission control (ACM bit). + */ +u8 nxpwifi_wmm_downgrade_tid(struct nxpwifi_private *priv, u32 tid) +{ + enum nxpwifi_wmm_ac_e ac, ac_down; + u8 new_tid; + + ac = nxpwifi_wmm_convert_tos_to_ac(priv->adapter, tid); + ac_down = priv->wmm.ac_down_graded_vals[ac]; + + /* + * Send the index to tid array, picking from the array will be + * taken care by dequeuing function + */ + new_tid = ac_to_tid[ac_down][tid % 2]; + + return new_tid; +} + +/* Initializes the WMM state information and the WMM data path queues. */ +void +nxpwifi_wmm_init(struct nxpwifi_adapter *adapter) +{ + int i, j; + struct nxpwifi_private *priv; + + for (j = 0; j < adapter->priv_num; ++j) { + priv = adapter->priv[j]; + + for (i = 0; i < MAX_NUM_TID; ++i) { + if (!disable_tx_amsdu && + adapter->tx_buf_size > NXPWIFI_TX_DATA_BUF_SIZE_2K) + priv->aggr_prio_tbl[i].amsdu = + priv->tos_to_tid_inv[i]; + else + priv->aggr_prio_tbl[i].amsdu = + BA_STREAM_NOT_ALLOWED; + priv->aggr_prio_tbl[i].ampdu_ap = + priv->tos_to_tid_inv[i]; + priv->aggr_prio_tbl[i].ampdu_user = + priv->tos_to_tid_inv[i]; + } + + priv->aggr_prio_tbl[6].amsdu = + priv->aggr_prio_tbl[6].ampdu_ap = + priv->aggr_prio_tbl[6].ampdu_user = + BA_STREAM_NOT_ALLOWED; + + priv->aggr_prio_tbl[7].amsdu = + priv->aggr_prio_tbl[7].ampdu_ap = + priv->aggr_prio_tbl[7].ampdu_user = + BA_STREAM_NOT_ALLOWED; + + nxpwifi_set_ba_params(priv); + nxpwifi_reset_11n_rx_seq_num(priv); + + priv->wmm.drv_pkt_delay_max = NXPWIFI_WMM_DRV_DELAY_MAX; + atomic_set(&priv->wmm.tx_pkts_queued, 0); + atomic_set(&priv->wmm.highest_queued_prio, HIGH_PRIO_TID); + } +} + +bool nxpwifi_bypass_txlist_empty(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_private *priv; + int i; + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + if (!skb_queue_empty(&priv->bypass_txq)) + return false; + } + + return true; +} + +/* Checks if WMM Tx queue is empty. */ +bool nxpwifi_wmm_lists_empty(struct nxpwifi_adapter *adapter) +{ + int i; + struct nxpwifi_private *priv; + + for (i = 0; i < adapter->priv_num; ++i) { + priv = adapter->priv[i]; + if (!priv->port_open) + continue; + if (atomic_read(&priv->wmm.tx_pkts_queued)) + return false; + } + + return true; +} + +/* Deletes all packets in an RA list node. */ +static void +nxpwifi_wmm_del_pkts_in_ralist_node(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ra_list) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct sk_buff *skb, *tmp; + + skb_queue_walk_safe(&ra_list->skb_head, skb, tmp) { + skb_unlink(skb, &ra_list->skb_head); + nxpwifi_write_data_complete(adapter, skb, 0, -1); + } +} + +/* Deletes all packets in an RA list. */ +static void +nxpwifi_wmm_del_pkts_in_ralist(struct nxpwifi_private *priv, + struct list_head *ra_list_head) +{ + struct nxpwifi_ra_list_tbl *ra_list; + + list_for_each_entry(ra_list, ra_list_head, list) + nxpwifi_wmm_del_pkts_in_ralist_node(priv, ra_list); +} + +/* Deletes all packets in all RA lists. */ +static void nxpwifi_wmm_cleanup_queues(struct nxpwifi_private *priv) +{ + int i; + + for (i = 0; i < MAX_NUM_TID; i++) + nxpwifi_wmm_del_pkts_in_ralist + (priv, &priv->wmm.tid_tbl_ptr[i].ra_list); + + atomic_set(&priv->wmm.tx_pkts_queued, 0); + atomic_set(&priv->wmm.highest_queued_prio, HIGH_PRIO_TID); +} + +/* Deletes all route addresses from all RA lists. */ +static void nxpwifi_wmm_delete_all_ralist(struct nxpwifi_private *priv) +{ + struct nxpwifi_ra_list_tbl *ra_list, *tmp_node; + int i; + + for (i = 0; i < MAX_NUM_TID; ++i) { + nxpwifi_dbg(priv->adapter, INFO, + "info: ra_list: freeing buf for tid %d\n", i); + list_for_each_entry_safe(ra_list, tmp_node, + &priv->wmm.tid_tbl_ptr[i].ra_list, + list) { + list_del(&ra_list->list); + kfree(ra_list); + } + + INIT_LIST_HEAD(&priv->wmm.tid_tbl_ptr[i].ra_list); + } +} + +static int nxpwifi_free_ack_frame(int id, void *p, void *data) +{ + pr_warn("Have pending ack frames!\n"); + kfree_skb(p); + return 0; +} + +/* Cleans up the Tx and Rx queues. */ +void +nxpwifi_clean_txrx(struct nxpwifi_private *priv) +{ + struct sk_buff *skb, *tmp; + unsigned long index; + void *entry; + + nxpwifi_11n_cleanup_reorder_tbl(priv); + spin_lock_bh(&priv->wmm.ra_list_spinlock); + + nxpwifi_wmm_cleanup_queues(priv); + nxpwifi_11n_delete_all_tx_ba_stream_tbl(priv); + + if (priv->adapter->if_ops.cleanup_mpa_buf) + priv->adapter->if_ops.cleanup_mpa_buf(priv->adapter); + + nxpwifi_wmm_delete_all_ralist(priv); + memcpy(tos_to_tid, ac_to_tid, sizeof(tos_to_tid)); + + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + + skb_queue_walk_safe(&priv->bypass_txq, skb, tmp) { + skb_unlink(skb, &priv->bypass_txq); + nxpwifi_write_data_complete(priv->adapter, skb, 0, -1); + } + atomic_set(&priv->adapter->bypass_tx_pending, 0); + + xa_for_each(&priv->ack_status_frames, index, entry) { + nxpwifi_free_ack_frame(index, entry, NULL); + xa_erase(&priv->ack_status_frames, index); + } + + xa_destroy(&priv->ack_status_frames); +} + +/* Retrieves a particular RA list node, matching with the given TID and RA address. */ +struct nxpwifi_ra_list_tbl * +nxpwifi_wmm_get_ralist_node(struct nxpwifi_private *priv, u8 tid, + const u8 *ra_addr) +{ + struct nxpwifi_ra_list_tbl *ra_list; + + list_for_each_entry(ra_list, &priv->wmm.tid_tbl_ptr[tid].ra_list, + list) { + if (!memcmp(ra_list->ra, ra_addr, ETH_ALEN)) + return ra_list; + } + + return NULL; +} + +void nxpwifi_update_ralist_tx_pause(struct nxpwifi_private *priv, u8 *mac, + u8 tx_pause) +{ + struct nxpwifi_ra_list_tbl *ra_list; + u32 pkt_cnt = 0, tx_pkts_queued; + int i; + + spin_lock_bh(&priv->wmm.ra_list_spinlock); + + for (i = 0; i < MAX_NUM_TID; ++i) { + ra_list = nxpwifi_wmm_get_ralist_node(priv, i, mac); + if (ra_list && ra_list->tx_paused != tx_pause) { + pkt_cnt += ra_list->total_pkt_count; + ra_list->tx_paused = tx_pause; + if (tx_pause) + priv->wmm.pkts_paused[i] += + ra_list->total_pkt_count; + else + priv->wmm.pkts_paused[i] -= + ra_list->total_pkt_count; + } + } + + if (pkt_cnt) { + tx_pkts_queued = atomic_read(&priv->wmm.tx_pkts_queued); + if (tx_pause) + tx_pkts_queued -= pkt_cnt; + else + tx_pkts_queued += pkt_cnt; + + atomic_set(&priv->wmm.tx_pkts_queued, tx_pkts_queued); + atomic_set(&priv->wmm.highest_queued_prio, HIGH_PRIO_TID); + } + spin_unlock_bh(&priv->wmm.ra_list_spinlock); +} + +/* Retrieves an RA list node for a given TID and RA address pair. */ +struct nxpwifi_ra_list_tbl * +nxpwifi_wmm_get_queue_raptr(struct nxpwifi_private *priv, u8 tid, + const u8 *ra_addr) +{ + struct nxpwifi_ra_list_tbl *ra_list; + + ra_list = nxpwifi_wmm_get_ralist_node(priv, tid, ra_addr); + if (ra_list) + return ra_list; + nxpwifi_ralist_add(priv, ra_addr); + + return nxpwifi_wmm_get_ralist_node(priv, tid, ra_addr); +} + +/* Deletes RA list nodes for given mac for all TIDs. */ +void +nxpwifi_wmm_del_peer_ra_list(struct nxpwifi_private *priv, const u8 *ra_addr) +{ + struct nxpwifi_ra_list_tbl *ra_list; + int i; + + spin_lock_bh(&priv->wmm.ra_list_spinlock); + + for (i = 0; i < MAX_NUM_TID; ++i) { + ra_list = nxpwifi_wmm_get_ralist_node(priv, i, ra_addr); + + if (!ra_list) + continue; + nxpwifi_wmm_del_pkts_in_ralist_node(priv, ra_list); + if (ra_list->tx_paused) + priv->wmm.pkts_paused[i] -= ra_list->total_pkt_count; + else + atomic_sub(ra_list->total_pkt_count, + &priv->wmm.tx_pkts_queued); + list_del(&ra_list->list); + kfree(ra_list); + } + spin_unlock_bh(&priv->wmm.ra_list_spinlock); +} + +/* Checks if a particular RA list node exists in a given TID table index. */ +bool nxpwifi_is_ralist_valid(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ra_list, int ptr_index) +{ + struct nxpwifi_ra_list_tbl *rlist; + + list_for_each_entry(rlist, &priv->wmm.tid_tbl_ptr[ptr_index].ra_list, + list) { + if (rlist == ra_list) + return true; + } + + return false; +} + +/* Adds a packet to bypass TX queue. */ +void +nxpwifi_wmm_add_buf_bypass_txqueue(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + skb_queue_tail(&priv->bypass_txq, skb); +} + +/* Adds a packet to WMM queue. */ +void +nxpwifi_wmm_add_buf_txqueue(struct nxpwifi_private *priv, + struct sk_buff *skb) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + u32 tid; + struct nxpwifi_ra_list_tbl *ra_list = NULL; + struct list_head list_head; + u8 ra[ETH_ALEN], tid_down; + struct ethhdr *eth_hdr = (struct ethhdr *)skb->data; + + memcpy(ra, eth_hdr->h_dest, ETH_ALEN); + + if (!priv->media_connected && !nxpwifi_is_skb_mgmt_frame(skb)) { + nxpwifi_dbg(adapter, DATA, "data: drop packet in disconnect\n"); + nxpwifi_write_data_complete(adapter, skb, 0, -1); + return; + } + + tid = skb->priority; + + spin_lock_bh(&priv->wmm.ra_list_spinlock); + + tid_down = nxpwifi_wmm_downgrade_tid(priv, tid); + + /* + * In case of infra as we have already created the list during + * association we just don't have to call get_queue_raptr, we will + * have only 1 raptr for a tid in case of infra + */ + if (!nxpwifi_queuing_ra_based(priv) && + !nxpwifi_is_skb_mgmt_frame(skb)) { + list_head = priv->wmm.tid_tbl_ptr[tid_down].ra_list; + ra_list = list_first_entry_or_null(&list_head, + struct nxpwifi_ra_list_tbl, + list); + } else { + memcpy(ra, skb->data, ETH_ALEN); + if (is_multicast_ether_addr(ra) || + nxpwifi_is_skb_mgmt_frame(skb)) + eth_broadcast_addr(ra); + ra_list = nxpwifi_wmm_get_queue_raptr(priv, tid_down, ra); + } + + if (!ra_list) { + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + nxpwifi_write_data_complete(adapter, skb, 0, -1); + return; + } + + skb_queue_tail(&ra_list->skb_head, skb); + + ra_list->ba_pkt_count++; + ra_list->total_pkt_count++; + + if (atomic_read(&priv->wmm.highest_queued_prio) < + priv->tos_to_tid_inv[tid_down]) + atomic_set(&priv->wmm.highest_queued_prio, + priv->tos_to_tid_inv[tid_down]); + + if (ra_list->tx_paused) + priv->wmm.pkts_paused[tid_down]++; + else + atomic_inc(&priv->wmm.tx_pkts_queued); + + spin_unlock_bh(&priv->wmm.ra_list_spinlock); +} + +/* Processes the get WMM status command response from firmware. */ +int nxpwifi_ret_wmm_get_status(struct nxpwifi_private *priv, + const struct host_cmd_ds_command *resp) +{ + u8 *curr; + u16 resp_len = le16_to_cpu(resp->size), tlv_len; + bool valid = true; + + struct nxpwifi_ie_types_data *tlv_hdr; + struct nxpwifi_ie_types_wmm_queue_status *wmm_qs; + struct ieee80211_wmm_param_ie *wmm_param_ie = NULL; + struct nxpwifi_wmm_ac_status *ac_status; + u32 base; + + nxpwifi_dbg(priv->adapter, INFO, + "info: WMM: WMM_GET_STATUS cmdresp received: %d\n", + resp_len); + + base = offsetofend(struct host_cmd_ds_command, params.get_wmm_status); + + if (resp_len < base) + return -EINVAL; + + curr = (u8 *)&resp->params.get_wmm_status; + resp_len -= base; + + while (resp_len >= sizeof(tlv_hdr->header) && valid) { + tlv_hdr = (struct nxpwifi_ie_types_data *)curr; + tlv_len = le16_to_cpu(tlv_hdr->header.len); + + if (resp_len < tlv_len + sizeof(tlv_hdr->header)) + break; + + switch (le16_to_cpu(tlv_hdr->header.type)) { + case TLV_TYPE_WMMQSTATUS: + if (tlv_len < + sizeof(struct nxpwifi_ie_types_wmm_queue_status) - + sizeof(tlv_hdr->header)) + break; + + wmm_qs = + (struct nxpwifi_ie_types_wmm_queue_status *)tlv_hdr; + + if (wmm_qs->queue_index >= IEEE80211_NUM_ACS) + break; + + ac_status = &priv->wmm.ac_status[wmm_qs->queue_index]; + ac_status->disabled = wmm_qs->disabled; + ac_status->flow_required = wmm_qs->flow_required; + ac_status->flow_created = wmm_qs->flow_created; + break; + + case WLAN_EID_VENDOR_SPECIFIC: + /* Need at least OUI(4) + WMM fixed fields */ + if (tlv_len + sizeof(tlv_hdr->header) < + offsetofend(struct ieee80211_wmm_param_ie, + qos_info)) + break; + + wmm_param_ie = + (struct ieee80211_wmm_param_ie *)(curr + 2); + + if (tlv_len + 2 > sizeof(struct ieee80211_wmm_param_ie)) + break; + + wmm_param_ie->len = (u8)tlv_len; + wmm_param_ie->element_id = WLAN_EID_VENDOR_SPECIFIC; + + memcpy(&priv->curr_bss_params.bss_descriptor.wmm_ie, + wmm_param_ie, wmm_param_ie->len + 2); + break; + + default: + valid = false; + break; + } + + curr += sizeof(tlv_hdr->header) + tlv_len; + resp_len -= sizeof(tlv_hdr->header) + tlv_len; + } + + nxpwifi_wmm_setup_queue_priorities(priv, wmm_param_ie); + nxpwifi_wmm_setup_ac_downgrade(priv); + + return 0; +} + +/* + * Callback handler from the command module to allow insertion of a WMM TLV. + * + * If the BSS we are associating to supports WMM, this function adds the + * required WMM Information element to the association request command buffer in + * the form of a NXP extended IEEE element. + */ +u32 +nxpwifi_wmm_process_association_req(struct nxpwifi_private *priv, + u8 **assoc_buf, + struct ieee80211_wmm_param_ie *wmm_ie, + struct ieee80211_ht_cap *ht_cap) +{ + struct nxpwifi_ie_types_wmm_param_set *wmm_tlv; + u32 ret_len = 0; + + /* Null checks */ + if (!assoc_buf) + return 0; + if (!(*assoc_buf)) + return 0; + + if (!wmm_ie) + return 0; + + nxpwifi_dbg(priv->adapter, INFO, + "info: WMM: process assoc req: bss->wmm_ie=%#x\n", + wmm_ie->element_id); + + if ((priv->wmm_required || + (ht_cap && (priv->config_bands & BAND_GN || + priv->config_bands & BAND_AN))) && + wmm_ie->element_id == WLAN_EID_VENDOR_SPECIFIC) { + wmm_tlv = (struct nxpwifi_ie_types_wmm_param_set *)*assoc_buf; + wmm_tlv->header.type = cpu_to_le16((u16)wmm_info_ie[0]); + wmm_tlv->header.len = cpu_to_le16((u16)wmm_info_ie[1]); + memcpy(wmm_tlv->wmm_ie, &wmm_info_ie[2], + le16_to_cpu(wmm_tlv->header.len)); + if (wmm_ie->qos_info & IEEE80211_WMM_IE_AP_QOSINFO_UAPSD) + memcpy((u8 *)(wmm_tlv->wmm_ie + + le16_to_cpu(wmm_tlv->header.len) + - sizeof(priv->wmm_qosinfo)), + &priv->wmm_qosinfo, sizeof(priv->wmm_qosinfo)); + + ret_len = sizeof(wmm_tlv->header) + + le16_to_cpu(wmm_tlv->header.len); + + *assoc_buf += ret_len; + } + + return ret_len; +} + +/* Computes the time delay in the driver queues for a given packet. */ +u8 +nxpwifi_wmm_compute_drv_pkt_delay(struct nxpwifi_private *priv, + const struct sk_buff *skb) +{ + u32 queue_delay = ktime_to_ms(net_timedelta(skb->tstamp)); + u8 ret_val; + + /* + * Queue delay is passed as a uint8 in units of 2ms (ms shifted + * by 1). Min value (other than 0) is therefore 2ms, max is 510ms. + * + * Pass max value if queue_delay is beyond the uint8 range + */ + ret_val = (u8)(min(queue_delay, priv->wmm.drv_pkt_delay_max) >> 1); + + nxpwifi_dbg(priv->adapter, DATA, "data: WMM: Pkt Delay: %d ms,\t" + "%d ms sent to FW\n", queue_delay, ret_val); + + return ret_val; +} + +/* Retrieves the highest priority RA list table pointer. */ +static struct nxpwifi_ra_list_tbl * +nxpwifi_wmm_get_highest_priolist_ptr(struct nxpwifi_adapter *adapter, + struct nxpwifi_private **priv, int *tid) +{ + struct nxpwifi_private *priv_tmp; + struct nxpwifi_ra_list_tbl *ptr; + struct nxpwifi_tid_tbl *tid_ptr; + atomic_t *hqp; + int i, j; + u8 to_tid; + + /* check the BSS with highest priority first */ + for (j = adapter->priv_num - 1; j >= 0; --j) { + /* iterate over BSS with the equal priority */ + list_for_each_entry(adapter->bss_prio_tbl[j].bss_prio_cur, + &adapter->bss_prio_tbl[j].bss_prio_head, + list) { +try_again: + priv_tmp = adapter->bss_prio_tbl[j].bss_prio_cur->priv; + + if (!priv_tmp->port_open || + (atomic_read(&priv_tmp->wmm.tx_pkts_queued) == 0)) + continue; + + /* iterate over the WMM queues of the BSS */ + hqp = &priv_tmp->wmm.highest_queued_prio; + for (i = atomic_read(hqp); i >= LOW_PRIO_TID; --i) { + spin_lock_bh(&priv_tmp->wmm.ra_list_spinlock); + + to_tid = tos_to_tid[i]; + tid_ptr = &(priv_tmp)->wmm.tid_tbl_ptr[to_tid]; + + /* iterate over receiver addresses */ + list_for_each_entry(ptr, &tid_ptr->ra_list, + list) { + if (!ptr->tx_paused && + !skb_queue_empty(&ptr->skb_head)) + /* holds both locks */ + goto found; + } + + spin_unlock_bh(&priv_tmp->wmm.ra_list_spinlock); + } + + if (atomic_read(&priv_tmp->wmm.tx_pkts_queued) != 0) { + atomic_set(&priv_tmp->wmm.highest_queued_prio, + HIGH_PRIO_TID); + /* + * Iterate current private once more, since + * there still exist packets in data queue + */ + goto try_again; + } else { + atomic_set(&priv_tmp->wmm.highest_queued_prio, + NO_PKT_PRIO_TID); + } + } + } + + return NULL; + +found: + /* holds ra_list_spinlock */ + if (atomic_read(hqp) > i) + atomic_set(hqp, i); + spin_unlock_bh(&priv_tmp->wmm.ra_list_spinlock); + + *priv = priv_tmp; + *tid = tos_to_tid[i]; + + return ptr; +} + +/* Rotates ra and bss lists so packets are picked round robin. */ +void nxpwifi_rotate_priolists(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ra, + int tid) +{ + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_bss_prio_tbl *tbl = adapter->bss_prio_tbl; + struct nxpwifi_tid_tbl *tid_ptr = &priv->wmm.tid_tbl_ptr[tid]; + + spin_lock_bh(&tbl[priv->bss_priority].bss_prio_lock); + /* + * dirty trick: we remove 'head' temporarily and reinsert it after + * curr bss node. imagine list to stay fixed while head is moved + */ + list_move(&tbl[priv->bss_priority].bss_prio_head, + &tbl[priv->bss_priority].bss_prio_cur->list); + spin_unlock_bh(&tbl[priv->bss_priority].bss_prio_lock); + + spin_lock_bh(&priv->wmm.ra_list_spinlock); + if (nxpwifi_is_ralist_valid(priv, ra, tid)) { + priv->wmm.packets_out[tid]++; + /* same as above */ + list_move(&tid_ptr->ra_list, &ra->list); + } + spin_unlock_bh(&priv->wmm.ra_list_spinlock); +} + +/* Checks if 11n aggregation is possible. */ +static bool +nxpwifi_is_11n_aggragation_possible(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ptr, + int max_buf_size) +{ + int count = 0, total_size = 0; + struct sk_buff *skb, *tmp; + int max_amsdu_size; + + if (priv->bss_role == NXPWIFI_BSS_ROLE_UAP && priv->ap_11n_enabled && + ptr->is_11n_enabled) + max_amsdu_size = min_t(int, ptr->max_amsdu, max_buf_size); + else + max_amsdu_size = max_buf_size; + + skb_queue_walk_safe(&ptr->skb_head, skb, tmp) { + total_size += skb->len; + if (total_size >= max_amsdu_size) + break; + if (++count >= MIN_NUM_AMSDU) + return true; + } + + return false; +} + +/* Sends a single packet to firmware for transmission. */ +static void +nxpwifi_send_single_packet(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ptr, int ptr_index) +__releases(&priv->wmm.ra_list_spinlock) +{ + struct sk_buff *skb, *skb_next; + struct nxpwifi_tx_param tx_param; + struct nxpwifi_adapter *adapter = priv->adapter; + struct nxpwifi_txinfo *tx_info; + + if (skb_queue_empty(&ptr->skb_head)) { + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + nxpwifi_dbg(adapter, DATA, "data: nothing to send\n"); + return; + } + + skb = skb_dequeue(&ptr->skb_head); + + tx_info = NXPWIFI_SKB_TXCB(skb); + nxpwifi_dbg(adapter, DATA, + "data: dequeuing the packet %p %p\n", ptr, skb); + + ptr->total_pkt_count--; + + if (!skb_queue_empty(&ptr->skb_head)) + skb_next = skb_peek(&ptr->skb_head); + else + skb_next = NULL; + + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + + tx_param.next_pkt_len = ((skb_next) ? skb_next->len + + sizeof(struct txpd) : 0); + + if (nxpwifi_process_tx(priv, skb, &tx_param) == -EBUSY) { + /* Queue the packet back at the head */ + spin_lock_bh(&priv->wmm.ra_list_spinlock); + + if (!nxpwifi_is_ralist_valid(priv, ptr, ptr_index)) { + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + nxpwifi_write_data_complete(adapter, skb, 0, -1); + return; + } + + skb_queue_tail(&ptr->skb_head, skb); + + ptr->total_pkt_count++; + ptr->ba_pkt_count++; + tx_info->flags |= NXPWIFI_BUF_FLAG_REQUEUED_PKT; + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + } else { + nxpwifi_rotate_priolists(priv, ptr, ptr_index); + atomic_dec(&priv->wmm.tx_pkts_queued); + } +} + +/* Checks if the first packet in the given RA list is already processed or not. */ +static bool +nxpwifi_is_ptr_processed(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ptr) +{ + struct sk_buff *skb; + struct nxpwifi_txinfo *tx_info; + + if (skb_queue_empty(&ptr->skb_head)) + return false; + + skb = skb_peek(&ptr->skb_head); + + tx_info = NXPWIFI_SKB_TXCB(skb); + if (tx_info->flags & NXPWIFI_BUF_FLAG_REQUEUED_PKT) + return true; + + return false; +} + +/* Sends a single processed packet to firmware for transmission. */ +static void +nxpwifi_send_processed_packet(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ptr, int ptr_index) + __releases(&priv->wmm.ra_list_spinlock) +{ + struct nxpwifi_tx_param tx_param; + struct nxpwifi_adapter *adapter = priv->adapter; + int ret; + struct sk_buff *skb, *skb_next; + struct nxpwifi_txinfo *tx_info; + + if (skb_queue_empty(&ptr->skb_head)) { + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + return; + } + + skb = skb_dequeue(&ptr->skb_head); + + if (adapter->data_sent || adapter->tx_lock_flag) { + ptr->total_pkt_count--; + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + skb_queue_tail(&adapter->tx_data_q, skb); + atomic_dec(&priv->wmm.tx_pkts_queued); + atomic_inc(&adapter->tx_queued); + return; + } + + if (!skb_queue_empty(&ptr->skb_head)) + skb_next = skb_peek(&ptr->skb_head); + else + skb_next = NULL; + + tx_info = NXPWIFI_SKB_TXCB(skb); + + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + + tx_param.next_pkt_len = + ((skb_next) ? skb_next->len + + sizeof(struct txpd) : 0); + + ret = adapter->if_ops.host_to_card(adapter, NXPWIFI_TYPE_DATA, + skb, &tx_param); + + switch (ret) { + case -EBUSY: + nxpwifi_dbg(adapter, ERROR, "data: -EBUSY is returned\n"); + spin_lock_bh(&priv->wmm.ra_list_spinlock); + + if (!nxpwifi_is_ralist_valid(priv, ptr, ptr_index)) { + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + nxpwifi_write_data_complete(adapter, skb, 0, -1); + return; + } + + skb_queue_tail(&ptr->skb_head, skb); + + tx_info->flags |= NXPWIFI_BUF_FLAG_REQUEUED_PKT; + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + break; + case -EINPROGRESS: + break; + case 0: + nxpwifi_write_data_complete(adapter, skb, 0, ret); + break; + default: + nxpwifi_dbg(adapter, ERROR, "host_to_card failed: %#x\n", ret); + adapter->dbg.num_tx_host_to_card_failure++; + nxpwifi_write_data_complete(adapter, skb, 0, ret); + break; + } + + if (ret != -EBUSY) { + nxpwifi_rotate_priolists(priv, ptr, ptr_index); + atomic_dec(&priv->wmm.tx_pkts_queued); + spin_lock_bh(&priv->wmm.ra_list_spinlock); + ptr->total_pkt_count--; + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + } +} + +/* Dequeues a packet from the highest priority list and transmits it. */ +static int +nxpwifi_dequeue_tx_packet(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_ra_list_tbl *ptr; + struct nxpwifi_private *priv = NULL; + int ptr_index = 0; + u8 ra[ETH_ALEN]; + int tid_del = 0, tid = 0; + + ptr = nxpwifi_wmm_get_highest_priolist_ptr(adapter, &priv, &ptr_index); + if (!ptr) + return -ENOENT; + + tid = nxpwifi_get_tid(ptr); + + nxpwifi_dbg(adapter, DATA, "data: tid=%d\n", tid); + + spin_lock_bh(&priv->wmm.ra_list_spinlock); + if (!nxpwifi_is_ralist_valid(priv, ptr, ptr_index)) { + spin_unlock_bh(&priv->wmm.ra_list_spinlock); + return -EINVAL; + } + + if (nxpwifi_is_ptr_processed(priv, ptr)) { + nxpwifi_send_processed_packet(priv, ptr, ptr_index); + /* + * ra_list_spinlock has been freed in + * nxpwifi_send_processed_packet() + */ + return 0; + } + + if (!ptr->is_11n_enabled || + ptr->ba_status || + priv->wps.session_enable) { + if (ptr->is_11n_enabled && + ptr->ba_status && + ptr->amsdu_in_ampdu && + nxpwifi_is_amsdu_allowed(priv, tid) && + nxpwifi_is_11n_aggragation_possible(priv, ptr, + adapter->tx_buf_size)) + nxpwifi_11n_aggregate_pkt(priv, ptr, ptr_index); + /* + * ra_list_spinlock has been freed in + * nxpwifi_11n_aggregate_pkt() + */ + else + nxpwifi_send_single_packet(priv, ptr, ptr_index); + /* + * ra_list_spinlock has been freed in + * nxpwifi_send_single_packet() + */ + } else { + if (nxpwifi_is_ampdu_allowed(priv, ptr, tid) && + ptr->ba_pkt_count > ptr->ba_packet_thr) { + if (nxpwifi_space_avail_for_new_ba_stream(adapter)) { + nxpwifi_create_ba_tbl(priv, ptr->ra, tid, + BA_SETUP_INPROGRESS); + nxpwifi_send_addba(priv, tid, ptr->ra); + } else if (nxpwifi_find_stream_to_delete + (priv, tid, &tid_del, ra)) { + nxpwifi_create_ba_tbl(priv, ptr->ra, tid, + BA_SETUP_INPROGRESS); + nxpwifi_send_delba(priv, tid_del, ra, 1); + } + } + if (nxpwifi_is_amsdu_allowed(priv, tid) && + nxpwifi_is_11n_aggragation_possible(priv, ptr, + adapter->tx_buf_size)) + nxpwifi_11n_aggregate_pkt(priv, ptr, ptr_index); + /* + * ra_list_spinlock has been freed in + * nxpwifi_11n_aggregate_pkt() + */ + else + nxpwifi_send_single_packet(priv, ptr, ptr_index); + /* + * ra_list_spinlock has been freed in + * nxpwifi_send_single_packet() + */ + } + return 0; +} + +void nxpwifi_process_bypass_tx(struct nxpwifi_adapter *adapter) +{ + struct nxpwifi_tx_param tx_param; + struct sk_buff *skb; + struct nxpwifi_txinfo *tx_info; + struct nxpwifi_private *priv; + int i; + + if (adapter->data_sent || adapter->tx_lock_flag) + return; + + for (i = 0; i < adapter->priv_num; ++i) { + priv = adapter->priv[i]; + + if (skb_queue_empty(&priv->bypass_txq)) + continue; + + skb = skb_dequeue(&priv->bypass_txq); + tx_info = NXPWIFI_SKB_TXCB(skb); + + /* no aggregation for bypass packets */ + tx_param.next_pkt_len = 0; + + if (nxpwifi_process_tx(priv, skb, &tx_param) == -EBUSY) { + skb_queue_head(&priv->bypass_txq, skb); + tx_info->flags |= NXPWIFI_BUF_FLAG_REQUEUED_PKT; + } else { + atomic_dec(&adapter->bypass_tx_pending); + } + } +} + +/* Transmits the highest priority packet awaiting in the WMM Queues. */ +void +nxpwifi_wmm_process_tx(struct nxpwifi_adapter *adapter) +{ + do { + if (nxpwifi_dequeue_tx_packet(adapter)) + break; + if (adapter->iface_type != NXPWIFI_SDIO) { + if (adapter->data_sent || + adapter->tx_lock_flag) + break; + } else { + if (atomic_read(&adapter->tx_queued) >= + NXPWIFI_MAX_PKTS_TXQ) + break; + } + } while (!nxpwifi_wmm_lists_empty(adapter)); +} diff --git a/drivers/net/wireless/nxp/nxpwifi/wmm.h b/drivers/net/wireless/nxp/nxpwifi/wmm.h new file mode 100644 index 000000000000..6241c2c5fcc4 --- /dev/null +++ b/drivers/net/wireless/nxp/nxpwifi/wmm.h @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * NXP Wireless LAN device driver: WMM + * + * Copyright 2011-2024 NXP + */ + +#ifndef _NXPWIFI_WMM_H_ +#define _NXPWIFI_WMM_H_ + +enum ieee_types_wmm_aciaifsn_bitmasks { + NXPWIFI_AIFSN = (BIT(0) | BIT(1) | BIT(2) | BIT(3)), + NXPWIFI_ACM = BIT(4), + NXPWIFI_ACI = (BIT(5) | BIT(6)), +}; + +enum ieee_types_wmm_ecw_bitmasks { + NXPWIFI_ECW_MIN = (BIT(0) | BIT(1) | BIT(2) | BIT(3)), + NXPWIFI_ECW_MAX = (BIT(4) | BIT(5) | BIT(6) | BIT(7)), +}; + +extern const u16 nxpwifi_1d_to_wmm_queue[]; +extern const u8 tos_to_tid_inv[]; + +/* Retrieve the TID of the given RA list. */ +static inline int +nxpwifi_get_tid(struct nxpwifi_ra_list_tbl *ptr) +{ + struct sk_buff *skb; + + if (skb_queue_empty(&ptr->skb_head)) + return 0; + + skb = skb_peek(&ptr->skb_head); + + return skb->priority; +} + +void nxpwifi_wmm_add_buf_txqueue(struct nxpwifi_private *priv, + struct sk_buff *skb); +void nxpwifi_wmm_add_buf_bypass_txqueue(struct nxpwifi_private *priv, + struct sk_buff *skb); +void nxpwifi_ralist_add(struct nxpwifi_private *priv, const u8 *ra); +void nxpwifi_rotate_priolists(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ra, int tid); + +bool nxpwifi_wmm_lists_empty(struct nxpwifi_adapter *adapter); +bool nxpwifi_bypass_txlist_empty(struct nxpwifi_adapter *adapter); +void nxpwifi_wmm_process_tx(struct nxpwifi_adapter *adapter); +void nxpwifi_process_bypass_tx(struct nxpwifi_adapter *adapter); +bool nxpwifi_is_ralist_valid(struct nxpwifi_private *priv, + struct nxpwifi_ra_list_tbl *ra_list, int tid); + +u8 nxpwifi_wmm_compute_drv_pkt_delay(struct nxpwifi_private *priv, + const struct sk_buff *skb); +void nxpwifi_wmm_init(struct nxpwifi_adapter *adapter); + +u32 nxpwifi_wmm_process_association_req(struct nxpwifi_private *priv, + u8 **assoc_buf, + struct ieee80211_wmm_param_ie *wmmie, + struct ieee80211_ht_cap *htcap); + +void nxpwifi_wmm_setup_queue_priorities(struct nxpwifi_private *priv, + struct ieee80211_wmm_param_ie *wmm_ie); +void nxpwifi_wmm_setup_ac_downgrade(struct nxpwifi_private *priv); +int nxpwifi_ret_wmm_get_status(struct nxpwifi_private *priv, + const struct host_cmd_ds_command *resp); +struct nxpwifi_ra_list_tbl * +nxpwifi_wmm_get_queue_raptr(struct nxpwifi_private *priv, u8 tid, + const u8 *ra_addr); +u8 nxpwifi_wmm_downgrade_tid(struct nxpwifi_private *priv, u32 tid); +void nxpwifi_update_ralist_tx_pause(struct nxpwifi_private *priv, u8 *mac, + u8 tx_pause); + +struct nxpwifi_ra_list_tbl *nxpwifi_wmm_get_ralist_node(struct nxpwifi_private + *priv, u8 tid, const u8 *ra_addr); +#endif /* !_NXPWIFI_WMM_H_ */ From f9c349592b74e96cecadd7d427f0b3dd6320d489 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 12 Jun 2026 09:22:59 +0200 Subject: [PATCH 040/562] soc: document merges Signed-off-by: Arnd Bergmann --- arch/arm/arm-soc-for-next-contents.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/arm/arm-soc-for-next-contents.txt b/arch/arm/arm-soc-for-next-contents.txt index 472935c56fec..79d144190272 100644 --- a/arch/arm/arm-soc-for-next-contents.txt +++ b/arch/arm/arm-soc-for-next-contents.txt @@ -107,6 +107,8 @@ soc/dt git://git.kernel.org/pub/scm/linux/kernel/git/frank.li/linux tags/imx-binding-7.2 sunxi/dt-2 https://git.kernel.org/pub/scm/linux/kernel/git/sunxi/linux tags/sunxi-dt-for-7.2-2 + bst/dt + https://github.com/BlackSesame-SoC/linux tags/bst-arm64-emmc-driver-dts-for-v7.2 soc/drivers fsl/soc-driver @@ -182,6 +184,8 @@ soc/defconfig git://git.kernel.org/pub/scm/linux/kernel/git/frank.li/linux tags/imx-defconfig-7.2 patch arm64: configs: Update defconfig for AST2700 platform support + bst/defconfig + https://github.com/BlackSesame-SoC/linux tags/bst-arm64-emmc-driver-defconfig-for-v7.2 soc/late From 972ea78305bddb6a1db0586357914f125e913ffe Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Mon, 15 Jun 2026 17:58:30 +0200 Subject: [PATCH 041/562] soc: document merges Signed-off-by: Arnd Bergmann --- arch/arm/arm-soc-for-next-contents.txt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/arch/arm/arm-soc-for-next-contents.txt b/arch/arm/arm-soc-for-next-contents.txt index 79d144190272..a1a3167f1381 100644 --- a/arch/arm/arm-soc-for-next-contents.txt +++ b/arch/arm/arm-soc-for-next-contents.txt @@ -109,6 +109,8 @@ soc/dt https://git.kernel.org/pub/scm/linux/kernel/git/sunxi/linux tags/sunxi-dt-for-7.2-2 bst/dt https://github.com/BlackSesame-SoC/linux tags/bst-arm64-emmc-driver-dts-for-v7.2 + patch + arm64: dts: aspeed: Fix duplicate pinctrl labels and address scheme soc/drivers fsl/soc-driver @@ -156,6 +158,11 @@ soc/drivers https://git.kernel.org/pub/scm/linux/kernel/git/conor/linux tags/cache-for-v7.2 microchip/dt-bindings https://git.kernel.org/pub/scm/linux/kernel/git/conor/linux tags/riscv-soc-drivers-for-v7.2 + patch + Revert "Documentation: ABI: add sysfs interface for ZynqMP CSU registers" + Revert "firmware: zynqmp: Add dynamic CSU register discovery and sysfs interface" + drivers/memory-2 + https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux-mem-ctrl tags/memory-controller-drv-7.2-2 soc/defconfig patch @@ -190,8 +197,4 @@ soc/defconfig soc/late arm/fixes - (75ef233975589d9a8c88bc8822a7c725c71ff650) - https://git.kernel.org/pub/scm/linux/kernel/git/conor/linux tags/riscv-soc-fixes-for-v7.1-rc7 - (6fc5666aa576c6c3bec256fa31d72653210319d5) - https://git.kernel.org/pub/scm/linux/kernel/git/mmind/linux-rockchip tags/v7.1-rockchip-arm32fixe From d0ce5f3f926a700d0329951101acddc85ea7ea99 Mon Sep 17 00:00:00 2001 From: Seth Forshee Date: Tue, 2 Jun 2026 21:54:06 +0000 Subject: [PATCH 042/562] firmware: arm_ffa: Respect firmware advertised RX/TX buffer size limits FFA_FEATURES reports the minimum size and alignment boundary required for RXTX_MAP. In FF-A v1.2 and later it can also report a maximum buffer size, with zero meaning that no maximum is enforced. The driver only used the minimum value and then rounded it up to PAGE_SIZE before invoking RXTX_MAP after commit 83210251fd70 ("firmware: arm_ffa: Use the correct buffer size during RXTX_MAP"). On systems where PAGE_SIZE is larger than the advertised minimum, this can exceed a non-zero maximum reported by firmware. Older implementations do not advertise a maximum and may also reject the rounded-up size. Decode the maximum size and clamp the page-aligned minimum to it when it is present. If no maximum is advertised and RXTX_MAP rejects the rounded size with INVALID_PARAMETERS, retry with the advertised minimum size. Record drv_info->rxtx_bufsz only after RXTX_MAP succeeds so it reflects the size registered with firmware. While there, also update RXTX_MAP_MIN_BUFSZ() to use FIELD_GET() for consistency. Fixes: 83210251fd70 ("firmware: arm_ffa: Use the correct buffer size during RXTX_MAP") Suggested-by: Sudeep Holla Signed-off-by: Seth Forshee Link: https://patch.msgid.link/20260602-b4-ffa-rxtx-map-fixes-v2-1-7cb06508da84@nvidia.com (sudeep.holla: Minor rewording subject and commit message) Signed-off-by: Sudeep Holla --- drivers/firmware/arm_ffa/driver.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c index 0f468362c288..bc2685331b27 100644 --- a/drivers/firmware/arm_ffa/driver.c +++ b/drivers/firmware/arm_ffa/driver.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -59,7 +60,9 @@ (FIELD_PREP(SENDER_ID_MASK, (s)) | FIELD_PREP(RECEIVER_ID_MASK, (r))) #define RXTX_MAP_MIN_BUFSZ_MASK GENMASK(1, 0) -#define RXTX_MAP_MIN_BUFSZ(x) ((x) & RXTX_MAP_MIN_BUFSZ_MASK) +#define RXTX_MAP_MAX_BUFSZ_MASK GENMASK(31, 16) +#define RXTX_MAP_MIN_BUFSZ(x) (FIELD_GET(RXTX_MAP_MIN_BUFSZ_MASK, (x))) +#define RXTX_MAP_MAX_BUFSZ(x) (FIELD_GET(RXTX_MAP_MAX_BUFSZ_MASK, (x))) #define FFA_MAX_NOTIFICATIONS 64 @@ -2101,7 +2104,7 @@ static int ffa_probe(struct platform_device *pdev) { int ret; u32 buf_sz; - size_t rxtx_bufsz = SZ_4K; + size_t rxtx_min_bufsz = SZ_4K, rxtx_max_bufsz = 0, rxtx_bufsz; if (IS_BUILTIN(CONFIG_ARM_FFA_TRANSPORT) && is_protected_kvm_enabled() && !is_pkvm_initialized()) @@ -2132,15 +2135,18 @@ static int ffa_probe(struct platform_device *pdev) ret = ffa_features(FFA_FN_NATIVE(RXTX_MAP), 0, &buf_sz, NULL); if (!ret) { if (RXTX_MAP_MIN_BUFSZ(buf_sz) == 1) - rxtx_bufsz = SZ_64K; + rxtx_min_bufsz = SZ_64K; else if (RXTX_MAP_MIN_BUFSZ(buf_sz) == 2) - rxtx_bufsz = SZ_16K; + rxtx_min_bufsz = SZ_16K; else - rxtx_bufsz = SZ_4K; + rxtx_min_bufsz = SZ_4K; + + rxtx_max_bufsz = RXTX_MAP_MAX_BUFSZ(buf_sz) * SZ_4K; + if (rxtx_max_bufsz != 0 && rxtx_max_bufsz < rxtx_min_bufsz) + rxtx_max_bufsz = rxtx_min_bufsz; } - rxtx_bufsz = PAGE_ALIGN(rxtx_bufsz); - drv_info->rxtx_bufsz = rxtx_bufsz; + rxtx_bufsz = min_not_zero(PAGE_ALIGN(rxtx_min_bufsz), rxtx_max_bufsz); drv_info->rx_buffer = alloc_pages_exact(rxtx_bufsz, GFP_KERNEL); if (!drv_info->rx_buffer) { ret = -ENOMEM; @@ -2156,10 +2162,17 @@ static int ffa_probe(struct platform_device *pdev) ret = ffa_rxtx_map(virt_to_phys(drv_info->tx_buffer), virt_to_phys(drv_info->rx_buffer), rxtx_bufsz / FFA_PAGE_SIZE); + if (ret == -EINVAL && !rxtx_max_bufsz && rxtx_min_bufsz < rxtx_bufsz) { + rxtx_bufsz = rxtx_min_bufsz; + ret = ffa_rxtx_map(virt_to_phys(drv_info->tx_buffer), + virt_to_phys(drv_info->rx_buffer), + rxtx_bufsz / FFA_PAGE_SIZE); + } if (ret) { pr_err("failed to register FFA RxTx buffers\n"); goto free_pages; } + drv_info->rxtx_bufsz = rxtx_bufsz; mutex_init(&drv_info->rx_lock); mutex_init(&drv_info->tx_lock); From fa4c1901a34e800b0ad00f3e887c14fb8d67a3c7 Mon Sep 17 00:00:00 2001 From: Unnathi Chalicheemala Date: Wed, 17 Jun 2026 16:35:00 -0700 Subject: [PATCH 043/562] firmware: arm_ffa: Fix NULL dereference in ffa_partition_info_get() ffa_partition_info_get() passes uuid_str directly to uuid_parse() without a NULL check. When a caller passes NULL, uuid_parse() -> __uuid_parse() -> uuid_is_valid() dereferences the pointer, causing a kernel panic: Unable to handle kernel NULL pointer dereference at virtual address 0000000000000040 pc : uuid_parse+0x40/0xac lr : ffa_partition_info_get+0x1c/0x94 [arm_ffa] Add a NULL guard before uuid_parse() so a NULL argument returns -ENODEV instead of crashing. Callers are expected to always supply a valid partition UUID, so NULL is not a supported input. Fixes: d0c0bce83122 ("firmware: arm_ffa: Setup in-kernel users of FFA partitions") Signed-off-by: Unnathi Chalicheemala Link: https://patch.msgid.link/20260617-ffa_partition_nullptr_fix-v2-1-bc801b4ce34c@oss.qualcomm.com Signed-off-by: Sudeep Holla --- drivers/firmware/arm_ffa/driver.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c index bc2685331b27..d475ff83132d 100644 --- a/drivers/firmware/arm_ffa/driver.c +++ b/drivers/firmware/arm_ffa/driver.c @@ -1142,7 +1142,7 @@ static int ffa_partition_info_get(const char *uuid_str, uuid_t uuid; struct ffa_partition_info *pbuf; - if (uuid_parse(uuid_str, &uuid)) { + if (!uuid_str || uuid_parse(uuid_str, &uuid)) { pr_err("invalid uuid (%s)\n", uuid_str); return -ENODEV; } From 4cb62e371ffadd8be14febb0f9aba8c4d69570af Mon Sep 17 00:00:00 2001 From: Haoxiang Li Date: Wed, 24 Jun 2026 14:19:10 +0800 Subject: [PATCH 044/562] KVM: s390: pci: Fix GISC refcount leak on AIF enable failure kvm_s390_gisc_register() registers the guest ISC before pinning the guest interrupt forwarding pages and allocating the AISB bit. If any of the later setup steps fails, the function unwinds the pinned pages and other local state, but does not unregister the GISC reference. Add the missing kvm_s390_gisc_unregister() to the error unwind path. Fixes: 3c5a1b6f0a18 ("KVM: s390: pci: provide routines for enabling/disabling interrupt forwarding") Cc: stable@vger.kernel.org Signed-off-by: Haoxiang Li Reviewed-by: Matthew Rosato Tested-by: Matthew Rosato Acked-by: Claudio Imbrenda Reviewed-by: Christian Borntraeger Signed-off-by: Claudio Imbrenda Message-ID: <20260624061910.2794734-1-haoxiang_li2024@163.com> --- arch/s390/kvm/pci.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/s390/kvm/pci.c b/arch/s390/kvm/pci.c index 5b075c38998e..686113be0530 100644 --- a/arch/s390/kvm/pci.c +++ b/arch/s390/kvm/pci.c @@ -328,6 +328,7 @@ static int kvm_s390_pci_aif_enable(struct zpci_dev *zdev, struct zpci_fib *fib, unpin1: unpin_user_page(aibv_page); out: + kvm_s390_gisc_unregister(kvm, fib->fmt0.isc); return rc; } From 2059ff101d0a58f738d352ba451203a6b38373c8 Mon Sep 17 00:00:00 2001 From: Atish Patra Date: Thu, 25 Jun 2026 14:00:29 -0600 Subject: [PATCH 045/562] riscv: efi: Power off via EFI runtime services when available When booted via UEFI with runtime services enabled, EFI Reset Shutdown is the firmware-preferred shutdown path: it lets firmware run its own shutdown hooks which may invoke SBI SRST extension internally. However, RISC-V always powers off via the SBI SRST extension today and EFI runtime path is never used even when firmware provides it. Enable the poweroff via EFI by overriding efi_poweroff_required() Signed-off-by: Atish Patra Reviewed-by: Sunil V L Link: https://patch.msgid.link/20260615-efi_reset_shutdown-v1-1-9414edcbbab0@meta.com Signed-off-by: Paul Walmsley --- arch/riscv/kernel/efi.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/riscv/kernel/efi.c b/arch/riscv/kernel/efi.c index b64bf1624a05..2d3cc57b4535 100644 --- a/arch/riscv/kernel/efi.c +++ b/arch/riscv/kernel/efi.c @@ -95,3 +95,8 @@ int __init efi_set_mapping_permissions(struct mm_struct *mm, md->num_pages << EFI_PAGE_SHIFT, set_permissions, md); } + +bool efi_poweroff_required(void) +{ + return efi_enabled(EFI_RUNTIME_SERVICES); +} From 175022c779e176d38ec6dadeda93995d4c078631 Mon Sep 17 00:00:00 2001 From: Atish Patra Date: Thu, 25 Jun 2026 14:00:29 -0600 Subject: [PATCH 046/562] riscv: Restart via EFI runtime services when available Firmware-preferred reset and EFI capsule update support requires reset via EFI runtime services rather than direction M-mode firmware invocation via SBI. Unlike poweroff, restart mechanism is directly controlled from machine_restart function though. Prefer the EFI runtime ResetSystem() service for restart when UEFI runtime services are available. Signed-off-by: Atish Patra Reviewed-by: Sunil V L Link: https://patch.msgid.link/20260615-efi_reset_shutdown-v1-2-9414edcbbab0@meta.com Signed-off-by: Paul Walmsley --- arch/riscv/kernel/reset.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/arch/riscv/kernel/reset.c b/arch/riscv/kernel/reset.c index 912288572226..541e2162c85a 100644 --- a/arch/riscv/kernel/reset.c +++ b/arch/riscv/kernel/reset.c @@ -3,6 +3,7 @@ * Copyright (C) 2012 Regents of the University of California */ +#include #include #include @@ -17,6 +18,12 @@ EXPORT_SYMBOL(pm_power_off); void machine_restart(char *cmd) { + /* + * UpdateCapsule() depends on the system being reset via ResetSystem(). + */ + if (efi_enabled(EFI_RUNTIME_SERVICES)) + efi_reboot(reboot_mode, NULL); + do_kernel_restart(cmd); while (1); } From 33f5663db5d1ef98207fa7f98650031f0ef88d17 Mon Sep 17 00:00:00 2001 From: Vivian Wang Date: Tue, 16 Jun 2026 21:51:31 +0800 Subject: [PATCH 047/562] ACPI: tables: Add missing #include drivers/acpi/tables.c uses NR_FIX_BTMAPS without including . This isn't a problem for existing archs, but would be when ARCH_HAS_ACPI_TABLE_UPGRADE is enabled for RISC-V. Add the missing include. Signed-off-by: Vivian Wang Link: https://patch.msgid.link/20260616-riscv-acpi-table-upgrade-v1-1-45902d2dedf9@iscas.ac.cn Signed-off-by: Paul Walmsley --- drivers/acpi/tables.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/acpi/tables.c b/drivers/acpi/tables.c index 4286e4af1092..eb93c060426f 100644 --- a/drivers/acpi/tables.c +++ b/drivers/acpi/tables.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "internal.h" #ifdef CONFIG_ACPI_CUSTOM_DSDT From 798246e5edfb3aa0b2d6dca46f41014d0b99b209 Mon Sep 17 00:00:00 2001 From: Vivian Wang Date: Tue, 16 Jun 2026 21:51:32 +0800 Subject: [PATCH 048/562] riscv: acpi: Enable ARCH_HAS_ACPI_TABLE_UPGRADE Implement the various required hooks and enable ARCH_HAS_ACPI_TABLE_UPGRADE to allow use for ACPI_TABLE_UPGRADE, which is useful for debugging ACPI table problems. The implementation is based on arm64's of the same feature due to the similarities of the requirements of the two platforms. Signed-off-by: Vivian Wang Link: https://patch.msgid.link/20260616-riscv-acpi-table-upgrade-v1-2-45902d2dedf9@iscas.ac.cn [pjw@kernel.org: updated to apply] Signed-off-by: Paul Walmsley --- arch/riscv/Kconfig | 1 + arch/riscv/include/asm/acpi.h | 2 ++ arch/riscv/kernel/acpi.c | 5 +++++ arch/riscv/kernel/setup.c | 2 ++ 4 files changed, 10 insertions(+) diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig index c0a6992933e4..cb3d85abf595 100644 --- a/arch/riscv/Kconfig +++ b/arch/riscv/Kconfig @@ -23,6 +23,7 @@ config RISCV select ARCH_ENABLE_MEMORY_HOTPLUG if SPARSEMEM_VMEMMAP select ARCH_ENABLE_SPLIT_PMD_PTLOCK if PGTABLE_LEVELS > 2 select ARCH_ENABLE_THP_MIGRATION if TRANSPARENT_HUGEPAGE + select ARCH_HAS_ACPI_TABLE_UPGRADE if ACPI select ARCH_HAS_BINFMT_FLAT select ARCH_HAS_CC_CAN_LINK select ARCH_HAS_CURRENT_STACK_POINTER diff --git a/arch/riscv/include/asm/acpi.h b/arch/riscv/include/asm/acpi.h index 26ab37c171bc..d59bd06347cc 100644 --- a/arch/riscv/include/asm/acpi.h +++ b/arch/riscv/include/asm/acpi.h @@ -92,4 +92,6 @@ void acpi_map_cpus_to_nodes(void); static inline void acpi_map_cpus_to_nodes(void) { } #endif /* CONFIG_ACPI_NUMA */ +#define ACPI_TABLE_UPGRADE_MAX_PHYS MEMBLOCK_ALLOC_ACCESSIBLE + #endif /*_ASM_ACPI_H*/ diff --git a/arch/riscv/kernel/acpi.c b/arch/riscv/kernel/acpi.c index 068e0b404b6f..efd52a5d05b5 100644 --- a/arch/riscv/kernel/acpi.c +++ b/arch/riscv/kernel/acpi.c @@ -353,3 +353,8 @@ int acpi_get_cpu_uid(unsigned int cpu, u32 *uid) return 0; } EXPORT_SYMBOL_GPL(acpi_get_cpu_uid); + +void __init arch_reserve_mem_area(acpi_physical_address addr, size_t size) +{ + memblock_mark_nomap(addr, size); +} diff --git a/arch/riscv/kernel/setup.c b/arch/riscv/kernel/setup.c index 52d1d2b8f338..a32344bb220d 100644 --- a/arch/riscv/kernel/setup.c +++ b/arch/riscv/kernel/setup.c @@ -321,6 +321,8 @@ void __init setup_arch(char **cmdline_p) efi_init(); paging_init(); + acpi_table_upgrade(); + /* Parse the ACPI tables for possible boot-time configuration */ acpi_boot_table_init(); From f63f829e2f49d613e2422c18ef3ed2b299b65319 Mon Sep 17 00:00:00 2001 From: Hao Li Date: Wed, 24 Jun 2026 18:00:14 +0800 Subject: [PATCH 049/562] mm/slub: deduplicate NUMA policy calculation in allocation paths Currently, alloc_from_pcs() and __slab_alloc_node() both calculate the NUMA policy independently. Since they are called consecutively in paths like __kmalloc_nolock_noprof() and slab_alloc_node(), this leads to redundant code snippets. Introduce a helper function to resolve the NUMA policy once, eliminating the duplicated code and reducing execution overhead. Also remove __slab_alloc_node() function because it is almost empty. The callers of __slab_alloc_node now call ___slab_alloc() directly. Additional notes: Previously, when slab_strict_numa was enabled, alloc_from_pcs() and __slab_alloc_node() could each resolve the task mempolicy, so MPOL_INTERLEAVE or MPOL_WEIGHTED_INTERLEAVE could advance the interleave state twice for a single object allocation attempt. And each retry will also advance the interleave state. With this change, the strict NUMA node is resolved once and reused by both alloc_from_pcs() and ___slab_alloc() in each retry. This is a behavior change, but it better matches the intent of selecting one policy node for one allocation attempt. Signed-off-by: Hao Li Reviewed-by: Harry Yoo (Oracle) Link: https://patch.msgid.link/20260624100320.430115-1-hao.li@linux.dev Signed-off-by: Vlastimil Babka (SUSE) --- mm/slub.c | 42 +++++++++--------------------------------- 1 file changed, 9 insertions(+), 33 deletions(-) diff --git a/mm/slub.c b/mm/slub.c index 9ec774dc7009..9f754cf1c187 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -4526,11 +4526,8 @@ static void *___slab_alloc(struct kmem_cache *s, gfp_t gfpflags, int node, return object; } -static void *__slab_alloc_node(struct kmem_cache *s, gfp_t gfpflags, int node, - const struct slab_alloc_context *ac) +static __always_inline int apply_strict_numa_policy(int node) { - void *object; - #ifdef CONFIG_NUMA if (static_branch_unlikely(&strict_numa) && node == NUMA_NO_NODE) { @@ -4551,10 +4548,7 @@ static void *__slab_alloc_node(struct kmem_cache *s, gfp_t gfpflags, int node, } } #endif - - object = ___slab_alloc(s, gfpflags, node, ac); - - return object; + return node; } static __fastpath_inline @@ -4759,28 +4753,6 @@ void *alloc_from_pcs(struct kmem_cache *s, gfp_t gfp, unsigned int alloc_flags, bool node_requested; void *object; -#ifdef CONFIG_NUMA - if (static_branch_unlikely(&strict_numa) && - node == NUMA_NO_NODE) { - - struct mempolicy *mpol = current->mempolicy; - - if (mpol) { - /* - * Special BIND rule support. If the local node - * is in permitted set then do not redirect - * to a particular node. - * Otherwise we apply the memory policy to get - * the node we need to allocate on. - */ - if (mpol->mode != MPOL_BIND || - !node_isset(numa_mem_id(), mpol->nodes)) - - node = mempolicy_slab_node(); - } - } -#endif - node_requested = IS_ENABLED(CONFIG_NUMA) && node != NUMA_NO_NODE; /* @@ -4930,10 +4902,12 @@ static __fastpath_inline void *slab_alloc_node(struct kmem_cache *s, if (unlikely(object)) goto out; + node = apply_strict_numa_policy(node); + object = alloc_from_pcs(s, gfpflags, ac->alloc_flags, node); if (unlikely(!object)) - object = __slab_alloc_node(s, gfpflags, node, ac); + object = ___slab_alloc(s, gfpflags, node, ac); maybe_wipe_obj_freeptr(s, object); @@ -5416,6 +5390,8 @@ static void *__kmalloc_nolock_noprof(DECL_TOKEN_PARAMS(size, token), gfp_t gfp_f if (!IS_ENABLED(CONFIG_SMP) && in_nmi()) return NULL; + node = apply_strict_numa_policy(node); + retry: if (unlikely(size > KMALLOC_MAX_CACHE_SIZE)) return NULL; @@ -5440,10 +5416,10 @@ static void *__kmalloc_nolock_noprof(DECL_TOKEN_PARAMS(size, token), gfp_t gfp_f /* * Do not call slab_alloc_node(), since trylock mode isn't * compatible with slab_pre_alloc_hook/should_failslab and - * kfence_alloc. Hence call __slab_alloc_node() (at most twice) + * kfence_alloc. Hence call ___slab_alloc() (at most twice) * and slab_post_alloc_hook() directly. */ - ret = __slab_alloc_node(s, gfp_flags, node, ac); + ret = ___slab_alloc(s, gfp_flags, node, ac); /* * It's possible we failed due to trylock as we preempted someone with From b0b6ec46e025fd46c344915a42bc535d9b15a1fb Mon Sep 17 00:00:00 2001 From: Li RongQing Date: Thu, 4 Jun 2026 19:03:18 +0800 Subject: [PATCH 050/562] mm/mempool: Untangle CONFIG_SLUB_DEBUG_ON abuse and switch to static key The mempool subsystem historically wrapped its debugging logic inside an merely defines compile-time defaults for SLUB and caused two flaws: 1. On production kernels where CONFIG_SLUB_DEBUG=y but CONFIG_SLUB_DEBUG_ON=n, mempool debugging was completely compiled out at compile time. 2. On kernels with CONFIG_SLUB_DEBUG_ON=y, mempool debugging stayed active even if a user explicitly disabled slub debugging at boot time. Clean up this mess by removing the #ifdef and switching to a runtime static key (mempool_debug_enabled), allowing mempool debugging to be toggled cleanly via its own boot parameter. Suggested-by: Vlastimil Babka (SUSE) Signed-off-by: Li RongQing Cc: Vlastimil Babka Cc: Harry Yoo Cc: Andrew Morton Cc: Hao Li Cc: Christoph Lameter Cc: David Rientjes Cc: Roman Gushchin Cc: Matthew Wilcox Cc: Usama Arif Reviewed-by: SeongJae Park Reviewed-by: Harry Yoo (Oracle) Link: https://patch.msgid.link/20260604110318.2089-1-lirongqing@baidu.com Signed-off-by: Vlastimil Babka (SUSE) --- .../admin-guide/kernel-parameters.txt | 5 +++ mm/mempool.c | 35 +++++++++++++------ 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index b5493a7f8f22..ca755fe185e5 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -3977,6 +3977,11 @@ Kernel parameters Note that even when enabled, there are a few cases where the feature is not effective. + mempool_debug [MM] + Enable mempool debugging. This enables element + poison checking when freeing elements back to the + pool. Useful for debugging mempool corruption. + memtest= [KNL,X86,ARM,M68K,PPC,RISCV,EARLY] Enable memtest Format: default : 0 diff --git a/mm/mempool.c b/mm/mempool.c index 473a029fa31f..cb74e718b2c6 100644 --- a/mm/mempool.c +++ b/mm/mempool.c @@ -16,11 +16,28 @@ #include #include #include +#include +#include #include "slab.h" static DECLARE_FAULT_ATTR(fail_mempool_alloc); static DECLARE_FAULT_ATTR(fail_mempool_alloc_bulk); +/* + * Debugging support for mempool using static key. + * + * This allows enabling mempool debug at boot time via: + * mempool_debug + */ +static DEFINE_STATIC_KEY_FALSE(mempool_debug_enabled); + +static int __init mempool_debug_setup(char *str) +{ + static_branch_enable(&mempool_debug_enabled); + return 1; +} +__setup("mempool_debug", mempool_debug_setup); + static int __init mempool_faul_inject_init(void) { int error; @@ -37,7 +54,6 @@ static int __init mempool_faul_inject_init(void) } late_initcall(mempool_faul_inject_init); -#ifdef CONFIG_SLUB_DEBUG_ON static void poison_error(struct mempool *pool, void *element, size_t size, size_t byte) { @@ -140,14 +156,6 @@ static void poison_element(struct mempool *pool, void *element) #endif } } -#else /* CONFIG_SLUB_DEBUG_ON */ -static inline void check_element(struct mempool *pool, void *element) -{ -} -static inline void poison_element(struct mempool *pool, void *element) -{ -} -#endif /* CONFIG_SLUB_DEBUG_ON */ static __always_inline bool kasan_poison_element(struct mempool *pool, void *element) @@ -175,7 +183,10 @@ static void kasan_unpoison_element(struct mempool *pool, void *element) static __always_inline void add_element(struct mempool *pool, void *element) { BUG_ON(pool->min_nr != 0 && pool->curr_nr >= pool->min_nr); - poison_element(pool, element); + + if (static_branch_unlikely(&mempool_debug_enabled)) + poison_element(pool, element); + if (kasan_poison_element(pool, element)) pool->elements[pool->curr_nr++] = element; } @@ -186,7 +197,9 @@ static void *remove_element(struct mempool *pool) BUG_ON(pool->curr_nr < 0); kasan_unpoison_element(pool, element); - check_element(pool, element); + + if (static_branch_unlikely(&mempool_debug_enabled)) + check_element(pool, element); return element; } From e20d78396464674dd06ec06a53e2641f97f510b5 Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Wed, 3 Jun 2026 17:12:06 +0200 Subject: [PATCH 051/562] dt-bindings: firmware: xilinx: Add missing example for ZynqMP Document clock-controller under zynqmp-firmware in the binding example so ZynqMP DTs validate against xlnx,zynqmp-clk.yaml (Versal example already did). Acked-by: Conor Dooley Link: https://patch.msgid.link/da152696e367eee8717a644d9303d27df1a3107b.1780499520.git.michal.simek@amd.com Signed-off-by: Michal Simek --- .../bindings/firmware/xilinx/xlnx,zynqmp-firmware.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Documentation/devicetree/bindings/firmware/xilinx/xlnx,zynqmp-firmware.yaml b/Documentation/devicetree/bindings/firmware/xilinx/xlnx,zynqmp-firmware.yaml index d50438b0fca8..680082c29f01 100644 --- a/Documentation/devicetree/bindings/firmware/xilinx/xlnx,zynqmp-firmware.yaml +++ b/Documentation/devicetree/bindings/firmware/xilinx/xlnx,zynqmp-firmware.yaml @@ -132,6 +132,14 @@ examples: zynqmp_firmware: zynqmp-firmware { compatible = "xlnx,zynqmp-firmware"; #power-domain-cells = <1>; + clock-controller { + compatible = "xlnx,zynqmp-clk"; + clocks = <&pss_ref_clk>, <&video_clk>, <&pss_alt_ref_clk>, + <&aux_ref_clk>, <>_crx_ref_clk>; + clock-names = "pss_ref_clk", "video_clk", "pss_alt_ref_clk", + "aux_ref_clk", "gt_crx_ref_clk"; + #clock-cells = <1>; + }; soc-nvmem { compatible = "xlnx,zynqmp-nvmem-fw"; nvmem-layout { From b2c08e627b5eceba539dc05a968de890c6778c65 Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Wed, 3 Jun 2026 17:12:07 +0200 Subject: [PATCH 052/562] dt-bindings: clock: versal-clk: Fix mio_clk index range in clock-names pattern The clock-names pattern "^mio_clk[00-77]+.*$" was intended to constrain the MIO index to the valid range 00..77 (ZynqMP has 78 MIO pins), but a regex character class cannot express a multi-digit decimal range. Replace the bogus character class with an explicit alternation that enumerates the two-digit decimal values 00..77. Fixes: 03d4a1004053 ("dt-bindings: clock: versal: Convert the xlnx,zynqmp-clk.txt to yaml") Acked-by: Conor Dooley Link: https://patch.msgid.link/5662c24a9e65310fc6520afc95f1a639fe6d221e.1780499520.git.michal.simek@amd.com Signed-off-by: Michal Simek --- Documentation/devicetree/bindings/clock/xlnx,versal-clk.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/clock/xlnx,versal-clk.yaml b/Documentation/devicetree/bindings/clock/xlnx,versal-clk.yaml index bef109d163a8..d843d95801b5 100644 --- a/Documentation/devicetree/bindings/clock/xlnx,versal-clk.yaml +++ b/Documentation/devicetree/bindings/clock/xlnx,versal-clk.yaml @@ -116,7 +116,7 @@ allOf: - const: pss_alt_ref_clk - const: aux_ref_clk - const: gt_crx_ref_clk - - pattern: "^mio_clk[00-77]+.*$" + - pattern: "^mio_clk(0[0-9]|[1-6][0-9]|7[0-7])+.*$" - pattern: "gem[0-3]+_emio_clk.*$" - pattern: "swdt[0-1]+_ext_clk.*$" From 1f7af0cd15bef87238ef27c85a785f404878dc1c Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Wed, 3 Jun 2026 17:12:08 +0200 Subject: [PATCH 053/562] dt-bindings: clock: Move xlnx,zynqmp-clk to its own schema The ZynqMP clock controller binding shares only #clock-cells with the Versal bindings. Move it to a dedicated xlnx,zynqmp-clk.yaml schema. Also remove "(Optional clock)" from clock description because it is visible from schema itself. Suggested-by: Rob Herring Acked-by: Conor Dooley Link: https://patch.msgid.link/23d848e29176706548612c4a0751481d46176f11.1780499520.git.michal.simek@amd.com Signed-off-by: Michal Simek --- .../bindings/clock/xlnx,versal-clk.yaml | 50 +------------- .../bindings/clock/xlnx,zynqmp-clk.yaml | 68 +++++++++++++++++++ .../firmware/xilinx/xlnx,zynqmp-firmware.yaml | 7 +- 3 files changed, 76 insertions(+), 49 deletions(-) create mode 100644 Documentation/devicetree/bindings/clock/xlnx,zynqmp-clk.yaml diff --git a/Documentation/devicetree/bindings/clock/xlnx,versal-clk.yaml b/Documentation/devicetree/bindings/clock/xlnx,versal-clk.yaml index d843d95801b5..12d060c39bfc 100644 --- a/Documentation/devicetree/bindings/clock/xlnx,versal-clk.yaml +++ b/Documentation/devicetree/bindings/clock/xlnx,versal-clk.yaml @@ -17,9 +17,7 @@ description: | properties: compatible: oneOf: - - enum: - - xlnx,versal-clk - - xlnx,zynqmp-clk + - const: xlnx,versal-clk - items: - enum: - xlnx,versal-net-clk @@ -32,11 +30,11 @@ properties: description: List of clock specifiers which are external input clocks to the given clock controller. minItems: 2 - maxItems: 8 + maxItems: 3 clock-names: minItems: 2 - maxItems: 8 + maxItems: 3 required: - compatible @@ -87,39 +85,6 @@ allOf: - const: pl_alt_ref - const: alt_ref - - if: - properties: - compatible: - contains: - enum: - - xlnx,zynqmp-clk - - then: - properties: - clocks: - minItems: 5 - items: - - description: PS reference clock - - description: reference clock for video system - - description: alternative PS reference clock - - description: auxiliary reference clock - - description: transceiver reference clock - - description: (E)MIO clock source (Optional clock) - - description: GEM emio clock (Optional clock) - - description: Watchdog external clock (Optional clock) - - clock-names: - minItems: 5 - items: - - const: pss_ref_clk - - const: video_clk - - const: pss_alt_ref_clk - - const: aux_ref_clk - - const: gt_crx_ref_clk - - pattern: "^mio_clk(0[0-9]|[1-6][0-9]|7[0-7])+.*$" - - pattern: "gem[0-3]+_emio_clk.*$" - - pattern: "swdt[0-1]+_ext_clk.*$" - examples: - | firmware { @@ -134,13 +99,4 @@ examples: }; }; }; - - clock-controller { - #clock-cells = <1>; - compatible = "xlnx,zynqmp-clk"; - clocks = <&pss_ref_clk>, <&video_clk>, <&pss_alt_ref_clk>, - <&aux_ref_clk>, <>_crx_ref_clk>; - clock-names = "pss_ref_clk", "video_clk", "pss_alt_ref_clk", - "aux_ref_clk", "gt_crx_ref_clk"; - }; ... diff --git a/Documentation/devicetree/bindings/clock/xlnx,zynqmp-clk.yaml b/Documentation/devicetree/bindings/clock/xlnx,zynqmp-clk.yaml new file mode 100644 index 000000000000..ccea3184b041 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/xlnx,zynqmp-clk.yaml @@ -0,0 +1,68 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/clock/xlnx,zynqmp-clk.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Xilinx ZynqMP clock controller + +maintainers: + - Michal Simek + +description: + The clock controller is a hardware block of Xilinx ZynqMP clock tree. It + reads required input clock frequencies from the devicetree and acts as clock + provider for all clock consumers of PS clocks. + +properties: + compatible: + const: xlnx,zynqmp-clk + + "#clock-cells": + const: 1 + + clocks: + description: List of clock specifiers which are external input + clocks to the given clock controller. + minItems: 5 + items: + - description: PS reference clock + - description: reference clock for video system + - description: alternative PS reference clock + - description: auxiliary reference clock + - description: transceiver reference clock + - description: (E)MIO clock source + - description: GEM emio clock + - description: Watchdog external clock + + clock-names: + minItems: 5 + items: + - const: pss_ref_clk + - const: video_clk + - const: pss_alt_ref_clk + - const: aux_ref_clk + - const: gt_crx_ref_clk + - pattern: "^mio_clk(0[0-9]|[1-6][0-9]|7[0-7])+.*$" + - pattern: "gem[0-3]+_emio_clk.*$" + - pattern: "swdt[0-1]+_ext_clk.*$" + +required: + - compatible + - "#clock-cells" + - clocks + - clock-names + +additionalProperties: false + +examples: + - | + clock-controller { + compatible = "xlnx,zynqmp-clk"; + clocks = <&pss_ref_clk>, <&video_clk>, <&pss_alt_ref_clk>, + <&aux_ref_clk>, <>_crx_ref_clk>; + clock-names = "pss_ref_clk", "video_clk", "pss_alt_ref_clk", + "aux_ref_clk", "gt_crx_ref_clk"; + #clock-cells = <1>; + }; +... diff --git a/Documentation/devicetree/bindings/firmware/xilinx/xlnx,zynqmp-firmware.yaml b/Documentation/devicetree/bindings/firmware/xilinx/xlnx,zynqmp-firmware.yaml index 680082c29f01..72af37cdb103 100644 --- a/Documentation/devicetree/bindings/firmware/xilinx/xlnx,zynqmp-firmware.yaml +++ b/Documentation/devicetree/bindings/firmware/xilinx/xlnx,zynqmp-firmware.yaml @@ -48,8 +48,7 @@ properties: const: 1 clock-controller: - $ref: /schemas/clock/xlnx,versal-clk.yaml# - description: The clock controller is a hardware block of Xilinx versal + description: The clock controller is a hardware block of Xilinx SoC clock tree. It reads required input clock frequencies from the devicetree and acts as clock provider for all clock consumers of PS clocks.list of clock specifiers which are external input clocks to the given clock @@ -113,10 +112,14 @@ allOf: const: xlnx,zynqmp-firmware then: properties: + clock-controller: + $ref: /schemas/clock/xlnx,zynqmp-clk.yaml# pinctrl: $ref: /schemas/pinctrl/xlnx,zynqmp-pinctrl.yaml# else: properties: + clock-controller: + $ref: /schemas/clock/xlnx,versal-clk.yaml# pinctrl: $ref: /schemas/pinctrl/xlnx,versal-pinctrl.yaml# From 98c2e37f79c0471253dfd16a6bda256bdad41d49 Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Wed, 3 Jun 2026 17:12:09 +0200 Subject: [PATCH 054/562] dt-bindings: clock: versal-clk: Fix Versal NET clock validation The Versal NET clock controller compatible is specified as: compatible = "xlnx,versal-net-clk", "xlnx,versal-clk"; with xlnx,versal-clk listed as fallback. The original binding had two separate if/then blocks - one matching xlnx,versal-clk (2 clocks) and another matching xlnx,versal-net-clk (3 clocks). Since both compatible strings are present, both conditions matched simultaneously and JSON Schema applied the more restrictive 2-clock constraint, causing false "too long" validation errors for Versal NET. Define clock-names at the top-level and use if/then only to constrain the clock count (2 for Versal, 3 for Versal NET). Add a dedicated example for the Versal NET 3-clock configuration. Fixes: 39118392d19a ("dt-bindings: Remove alt_ref from versal") Acked-by: Conor Dooley Link: https://patch.msgid.link/b9676d7e4e6ccd6af99ffc3127dadfe0f4edb498.1780499520.git.michal.simek@amd.com Signed-off-by: Michal Simek --- .../bindings/clock/xlnx,versal-clk.yaml | 51 ++++++++----------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/Documentation/devicetree/bindings/clock/xlnx,versal-clk.yaml b/Documentation/devicetree/bindings/clock/xlnx,versal-clk.yaml index 12d060c39bfc..b533ffd082fd 100644 --- a/Documentation/devicetree/bindings/clock/xlnx,versal-clk.yaml +++ b/Documentation/devicetree/bindings/clock/xlnx,versal-clk.yaml @@ -30,11 +30,17 @@ properties: description: List of clock specifiers which are external input clocks to the given clock controller. minItems: 2 - maxItems: 3 + items: + - description: reference clock + - description: alternate reference clock for programmable logic + - description: alternate reference clock clock-names: minItems: 2 - maxItems: 3 + items: + - const: ref + - const: pl_alt_ref + - const: alt_ref required: - compatible @@ -50,40 +56,19 @@ allOf: compatible: contains: enum: - - xlnx,versal-clk - + - xlnx,versal-net-clk then: properties: clocks: - items: - - description: reference clock - - description: alternate reference clock for programmable logic - + minItems: 3 clock-names: - items: - - const: ref - - const: pl_alt_ref - - - if: - properties: - compatible: - contains: - enum: - - xlnx,versal-net-clk - - then: + minItems: 3 + else: properties: clocks: - items: - - description: reference clock - - description: alternate reference clock for programmable logic - - description: alternate reference clock - + maxItems: 2 clock-names: - items: - - const: ref - - const: pl_alt_ref - - const: alt_ref + maxItems: 2 examples: - | @@ -99,4 +84,12 @@ examples: }; }; }; + + - | + clock-controller { + compatible = "xlnx,versal-net-clk", "xlnx,versal-clk"; + clocks = <&ref>, <&pl_alt_ref>, <&alt_ref>; + clock-names = "ref", "pl_alt_ref", "alt_ref"; + #clock-cells = <1>; + }; ... From f608bce703fc31a2cdf67abe1de882d5bbc45142 Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Wed, 3 Jun 2026 17:12:10 +0200 Subject: [PATCH 055/562] arm64: versal-net: Switch Versal NET to firmware clock interface Switch Versal NET from using fixed clocks to the firmware-based clock interface (versal-net-clk.dtsi). This enables proper clock management through the platform firmware instead of relying on static fixed-clock definitions. Add DT macro headers for Versal NET and base Versal clocks, power domains and mandatory resets required by the clock dtsi. Link: https://patch.msgid.link/c7007b07b00ff00affda9fa67a40667284acb330.1780499520.git.michal.simek@amd.com Signed-off-by: Michal Simek --- .../arm64/boot/dts/xilinx/versal-net-clk.dtsi | 345 +++++++++++++----- arch/arm64/boot/dts/xilinx/xlnx-versal-clk.h | 123 +++++++ .../boot/dts/xilinx/xlnx-versal-net-clk.h | 74 ++++ .../boot/dts/xilinx/xlnx-versal-net-power.h | 38 ++ .../boot/dts/xilinx/xlnx-versal-net-resets.h | 53 +++ .../arm64/boot/dts/xilinx/xlnx-versal-power.h | 55 +++ .../boot/dts/xilinx/xlnx-versal-resets.h | 106 ++++++ 7 files changed, 695 insertions(+), 99 deletions(-) create mode 100644 arch/arm64/boot/dts/xilinx/xlnx-versal-clk.h create mode 100644 arch/arm64/boot/dts/xilinx/xlnx-versal-net-clk.h create mode 100644 arch/arm64/boot/dts/xilinx/xlnx-versal-net-power.h create mode 100644 arch/arm64/boot/dts/xilinx/xlnx-versal-net-resets.h create mode 100644 arch/arm64/boot/dts/xilinx/xlnx-versal-power.h create mode 100644 arch/arm64/boot/dts/xilinx/xlnx-versal-resets.h diff --git a/arch/arm64/boot/dts/xilinx/versal-net-clk.dtsi b/arch/arm64/boot/dts/xilinx/versal-net-clk.dtsi index b7a8a1a512cb..d3a27da90090 100644 --- a/arch/arm64/boot/dts/xilinx/versal-net-clk.dtsi +++ b/arch/arm64/boot/dts/xilinx/versal-net-clk.dtsi @@ -1,231 +1,378 @@ // SPDX-License-Identifier: GPL-2.0 /* - * dts file for Xilinx Versal NET fixed clock + * dts file for Xilinx Versal NET with PM * - * (C) Copyright 2022, Xilinx, Inc. - * (C) Copyright 2022 - 2025, Advanced Micro Devices, Inc. + * Copyright (C) 2022, Xilinx, Inc. + * Copyright (C) 2022 - 2026, Advanced Micro Devices, Inc. * * Michal Simek */ +#include +#include +#include "xlnx-versal-net-clk.h" +#include "xlnx-versal-net-power.h" +#include "xlnx-versal-net-resets.h" + / { - clk60: clk60 { + ref_clk: clock-33333333 { compatible = "fixed-clock"; + bootph-all; + clock-frequency = <33333333>; + clock-output-names = "ref_clk"; #clock-cells = <0>; - clock-frequency = <60000000>; }; - clk100: clk100 { + rtc_clk: clock-32768 { compatible = "fixed-clock"; + bootph-all; + clock-frequency = <32768>; + clock-output-names = "rtc_clk"; #clock-cells = <0>; - clock-frequency = <100000000>; }; - clk125: clk125 { - compatible = "fixed-clock"; + can0_clk: can0-clk { + compatible = "fixed-factor-clock"; + clocks = <&versal_net_clk CAN0_REF_2X>; + clock-div = <2>; + clock-mult = <1>; + clock-output-names = "can0_clk"; #clock-cells = <0>; - clock-frequency = <125000000>; }; - clk150: clk150 { - compatible = "fixed-clock"; + can1_clk: can1-clk { + compatible = "fixed-factor-clock"; + clocks = <&versal_net_clk CAN1_REF_2X>; + clock-div = <2>; + clock-mult = <1>; + clock-output-names = "can1_clk"; #clock-cells = <0>; - clock-frequency = <150000000>; }; - clk160: clk160 { - compatible = "fixed-clock"; - #clock-cells = <0>; - clock-frequency = <160000000>; + firmware { + versal_net_firmware: versal-net-firmware { + compatible = "xlnx,versal-net-firmware", "xlnx,versal-firmware"; + bootph-all; + method = "smc"; + #power-domain-cells = <1>; + + versal_net_reset: reset-controller { + compatible = "xlnx,versal-net-reset"; + #reset-cells = <1>; + }; + + versal_net_clk: clock-controller { + compatible = "xlnx,versal-net-clk", "xlnx,versal-clk"; + bootph-all; + clocks = <&ref_clk>, <&ref_clk>, <&ref_clk>; + clock-names = "ref", "pl_alt_ref", "alt_ref"; + #clock-cells = <1>; + }; + + versal_net_power: power-management { + compatible = "xlnx,zynqmp-power"; + mboxes = <&ipi_mailbox_pmu1 0>, + <&ipi_mailbox_pmu1 1>; + mbox-names = "tx", "rx"; + }; + }; }; - clk200: clk200 { - compatible = "fixed-clock"; - #clock-cells = <0>; - clock-frequency = <200000000>; + zynqmp-ipi { + compatible = "xlnx,zynqmp-ipi-mailbox"; + interrupt-parent = <&gic>; + interrupts = ; + xlnx,ipi-id = <2>; + #address-cells = <2>; + #size-cells = <2>; + ranges; + + ipi_mailbox_pmu1: mailbox@eb3f0440 { + compatible = "xlnx,zynqmp-ipi-dest-mailbox"; + reg = <0 0xeb3f0440 0 0x20>, + <0 0xeb3f0460 0 0x20>, + <0 0xeb3f0280 0 0x20>, + <0 0xeb3f02a0 0 0x20>; + reg-names = "local_request_region", "local_response_region", + "remote_request_region", "remote_response_region"; + #mbox-cells = <1>; + xlnx,ipi-id = <1>; + }; }; +}; - clk250: clk250 { - compatible = "fixed-clock"; - #clock-cells = <0>; - clock-frequency = <250000000>; - }; +&cpu0 { + clocks = <&versal_net_clk ACPU_0>; +}; - clk300: clk300 { - compatible = "fixed-clock"; - #clock-cells = <0>; - clock-frequency = <300000000>; - }; +&cpu100 { + clocks = <&versal_net_clk ACPU_0>; +}; - clk450: clk450 { - compatible = "fixed-clock"; - #clock-cells = <0>; - clock-frequency = <450000000>; - }; +&cpu200 { + clocks = <&versal_net_clk ACPU_0>; +}; - clk1200: clk1200 { - compatible = "fixed-clock"; - #clock-cells = <0>; - clock-frequency = <1200000000>; - }; +&cpu300 { + clocks = <&versal_net_clk ACPU_0>; +}; - firmware { - versal_net_firmware: versal-net-firmware { - compatible = "xlnx,versal-net-firmware", "xlnx,versal-firmware"; - bootph-all; - method = "smc"; - }; - }; +&cpu10000 { + clocks = <&versal_net_clk ACPU_1>; }; -&adma0 { - clocks = <&clk450>, <&clk450>; +&cpu10100 { + clocks = <&versal_net_clk ACPU_1>; }; -&adma1 { - clocks = <&clk450>, <&clk450>; +&cpu10200 { + clocks = <&versal_net_clk ACPU_1>; }; -&adma2 { - clocks = <&clk450>, <&clk450>; +&cpu10300 { + clocks = <&versal_net_clk ACPU_1>; }; -&adma3 { - clocks = <&clk450>, <&clk450>; +&cpu20000 { + clocks = <&versal_net_clk ACPU_2>; }; -&adma4 { - clocks = <&clk450>, <&clk450>; +&cpu20100 { + clocks = <&versal_net_clk ACPU_2>; }; -&adma5 { - clocks = <&clk450>, <&clk450>; +&cpu20200 { + clocks = <&versal_net_clk ACPU_2>; }; -&adma6 { - clocks = <&clk450>, <&clk450>; +&cpu20300 { + clocks = <&versal_net_clk ACPU_2>; }; -&adma7 { - clocks = <&clk450>, <&clk450>; +&cpu30000 { + clocks = <&versal_net_clk ACPU_3>; +}; + +&cpu30100 { + clocks = <&versal_net_clk ACPU_3>; +}; + +&cpu30200 { + clocks = <&versal_net_clk ACPU_3>; +}; + +&cpu30300 { + clocks = <&versal_net_clk ACPU_3>; }; &can0 { - clocks = <&clk160>, <&clk160>; + clocks = <&can0_clk>, <&versal_net_clk LPD_LSBUS>; + power-domains = <&versal_net_firmware PM_DEV_CAN_FD_0>; }; &can1 { - clocks = <&clk160>, <&clk160>; + clocks = <&can1_clk>, <&versal_net_clk LPD_LSBUS>; + power-domains = <&versal_net_firmware PM_DEV_CAN_FD_1>; }; &gem0 { - clocks = <&clk125>, <&clk125>, <&clk125>, <&clk125>, <&clk250>; + clocks = <&versal_net_clk LPD_LSBUS>, + <&versal_net_clk GEM0_REF>, <&versal_net_clk GEM0_TX>, + <&versal_net_clk GEM0_RX>, <&versal_net_clk GEM_TSU>; + power-domains = <&versal_net_firmware PM_DEV_GEM_0>; }; &gem1 { - clocks = <&clk125>, <&clk125>, <&clk125>, <&clk125>, <&clk250>; + clocks = <&versal_net_clk LPD_LSBUS>, + <&versal_net_clk GEM1_REF>, <&versal_net_clk GEM1_TX>, + <&versal_net_clk GEM1_RX>, <&versal_net_clk GEM_TSU>; + power-domains = <&versal_net_firmware PM_DEV_GEM_1>; }; &gpio0 { - clocks = <&clk100>; + clocks = <&versal_net_clk LPD_LSBUS>; + power-domains = <&versal_net_firmware PM_DEV_GPIO>; }; &gpio1 { - clocks = <&clk100>; + clocks = <&versal_net_clk PMC_LSBUS_REF>; + power-domains = <&versal_net_firmware PM_DEV_GPIO_PMC>; }; &i2c0 { - clocks = <&clk100>; + clocks = <&versal_net_clk I3C0_REF>; + power-domains = <&versal_net_firmware PM_DEV_I2C_0>; }; &i2c1 { - clocks = <&clk100>; + clocks = <&versal_net_clk I3C1_REF>; + power-domains = <&versal_net_firmware PM_DEV_I2C_1>; }; &i3c0 { - clocks = <&clk100>; + clocks = <&versal_net_clk I3C0_REF>; + power-domains = <&versal_net_firmware PM_DEV_I2C_0>; }; &i3c1 { - clocks = <&clk100>; + clocks = <&versal_net_clk I3C1_REF>; + power-domains = <&versal_net_firmware PM_DEV_I2C_1>; }; -&ospi { - clocks = <&clk200>; +&adma0 { + clocks = <&versal_net_clk ADMA>, <&versal_net_clk LPD_LSBUS>; + power-domains = <&versal_net_firmware PM_DEV_ADMA_0>; }; -&qspi { - clocks = <&clk300>, <&clk300>; +&adma1 { + clocks = <&versal_net_clk ADMA>, <&versal_net_clk LPD_LSBUS>; + power-domains = <&versal_net_firmware PM_DEV_ADMA_1>; }; -&rtc { - /* Nothing */ +&adma2 { + clocks = <&versal_net_clk ADMA>, <&versal_net_clk LPD_LSBUS>; + power-domains = <&versal_net_firmware PM_DEV_ADMA_2>; }; -&sdhci0 { - clocks = <&clk200>, <&clk200>, <&clk1200>; +&adma3 { + clocks = <&versal_net_clk ADMA>, <&versal_net_clk LPD_LSBUS>; + power-domains = <&versal_net_firmware PM_DEV_ADMA_3>; }; -&sdhci1 { - clocks = <&clk200>, <&clk200>, <&clk1200>; +&adma4 { + clocks = <&versal_net_clk ADMA>, <&versal_net_clk LPD_LSBUS>; + power-domains = <&versal_net_firmware PM_DEV_ADMA_4>; +}; + +&adma5 { + clocks = <&versal_net_clk ADMA>, <&versal_net_clk LPD_LSBUS>; + power-domains = <&versal_net_firmware PM_DEV_ADMA_5>; +}; + +&adma6 { + clocks = <&versal_net_clk ADMA>, <&versal_net_clk LPD_LSBUS>; + power-domains = <&versal_net_firmware PM_DEV_ADMA_6>; +}; + +&adma7 { + clocks = <&versal_net_clk ADMA>, <&versal_net_clk LPD_LSBUS>; + power-domains = <&versal_net_firmware PM_DEV_ADMA_7>; +}; + +&qspi { + clocks = <&versal_net_clk QSPI_REF>, <&versal_net_clk LPD_LSBUS>; + power-domains = <&versal_net_firmware PM_DEV_QSPI>; +}; + +&ospi { + clocks = <&versal_net_clk OSPI_REF>; + power-domains = <&versal_net_firmware PM_DEV_OSPI>; +}; + +&rtc { + clocks = <&rtc_clk>; + clock-names = "rtc"; + power-domains = <&versal_net_firmware PM_DEV_RTC>; }; &serial0 { - clocks = <&clk100>, <&clk100>; + clocks = <&versal_net_clk UART0_REF>, <&versal_net_clk LPD_LSBUS>; + power-domains = <&versal_net_firmware PM_DEV_UART_0>; }; &serial1 { - clocks = <&clk100>, <&clk100>; + clocks = <&versal_net_clk UART1_REF>, <&versal_net_clk LPD_LSBUS>; + power-domains = <&versal_net_firmware PM_DEV_UART_1>; +}; + +&sdhci0 { + clocks = <&versal_net_clk SDIO0_REF>, <&versal_net_clk LPD_LSBUS>, + <&versal_net_clk SD_DLL_REF>; + power-domains = <&versal_net_firmware PM_DEV_SDIO_0>; +}; + +&sdhci1 { + clocks = <&versal_net_clk SDIO1_REF>, <&versal_net_clk LPD_LSBUS>, + <&versal_net_clk SD_DLL_REF>; + power-domains = <&versal_net_firmware PM_DEV_SDIO_1>; }; &spi0 { - clocks = <&clk200>, <&clk200>; + clocks = <&versal_net_clk SPI0_REF>, <&versal_net_clk LPD_LSBUS>; + power-domains = <&versal_net_firmware PM_DEV_SPI_0>; }; &spi1 { - clocks = <&clk200>, <&clk200>; + clocks = <&versal_net_clk SPI1_REF>, <&versal_net_clk LPD_LSBUS>; + power-domains = <&versal_net_firmware PM_DEV_SPI_1>; }; &ttc0 { - clocks = <&clk150>; + clocks = <&versal_net_clk TTC0>; + power-domains = <&versal_net_firmware PM_DEV_TTC_0>; +}; + +&ttc1 { + clocks = <&versal_net_clk TTC1>; + power-domains = <&versal_net_firmware PM_DEV_TTC_1>; +}; + +&ttc2 { + clocks = <&versal_net_clk TTC2>; + power-domains = <&versal_net_firmware PM_DEV_TTC_2>; +}; + +&ttc3 { + clocks = <&versal_net_clk TTC3>; + power-domains = <&versal_net_firmware PM_DEV_TTC_3>; }; &usb0 { - clocks = <&clk60>, <&clk60>; + clocks = <&versal_net_clk USB0_BUS_REF>, <&versal_net_clk USB0_BUS_REF>; + power-domains = <&versal_net_firmware PM_DEV_USB_0>; + resets = <&versal_net_reset VERSAL_RST_USB_0>; }; &dwc3_0 { - clocks = <&clk60>; + clocks = <&versal_net_clk USB0_BUS_REF>; }; &usb1 { - clocks = <&clk60>, <&clk60>; + clocks = <&versal_net_clk USB1_BUS_REF>, <&versal_net_clk USB1_BUS_REF>; + power-domains = <&versal_net_firmware PM_DEV_USB_1>; + resets = <&versal_net_reset VERSAL_RST_USB_1>; }; &dwc3_1 { - clocks = <&clk60>; + clocks = <&versal_net_clk USB1_BUS_REF>; }; &wwdt0 { - clocks = <&clk150>; + clocks = <&versal_net_clk FPD_WWDT0>; + power-domains = <&versal_net_firmware PM_DEV_FPD_SWDT_0>; }; &wwdt1 { - clocks = <&clk150>; + clocks = <&versal_net_clk FPD_WWDT1>; + power-domains = <&versal_net_firmware PM_DEV_FPD_SWDT_1>; }; &wwdt2 { - clocks = <&clk150>; + clocks = <&versal_net_clk FPD_WWDT2>; + power-domains = <&versal_net_firmware PM_DEV_FPD_SWDT_2>; }; &wwdt3 { - clocks = <&clk150>; + clocks = <&versal_net_clk FPD_WWDT3>; + power-domains = <&versal_net_firmware PM_DEV_FPD_SWDT_3>; }; &lpd_wwdt0 { - clocks = <&clk150>; + clocks = <&versal_net_clk LPD_WWDT0>; + power-domains = <&versal_net_firmware PM_DEV_LPD_SWDT_0>; }; &lpd_wwdt1 { - clocks = <&clk150>; + clocks = <&versal_net_clk LPD_WWDT1>; + power-domains = <&versal_net_firmware PM_DEV_LPD_SWDT_1>; }; diff --git a/arch/arm64/boot/dts/xilinx/xlnx-versal-clk.h b/arch/arm64/boot/dts/xilinx/xlnx-versal-clk.h new file mode 100644 index 000000000000..d0c4abf78f30 --- /dev/null +++ b/arch/arm64/boot/dts/xilinx/xlnx-versal-clk.h @@ -0,0 +1,123 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2019 - 2022, Xilinx, Inc. + * Copyright (C) 2022 - 2026, Advanced Micro Devices, Inc. + */ + +#ifndef _XLNX_VERSAL_CLK_H +#define _XLNX_VERSAL_CLK_H + +#define PMC_PLL 1 +#define APU_PLL 2 +#define RPU_PLL 3 +#define CPM_PLL 4 +#define NOC_PLL 5 +#define PLL_MAX 6 +#define PMC_PRESRC 7 +#define PMC_POSTCLK 8 +#define PMC_PLL_OUT 9 +#define PPLL 10 +#define NOC_PRESRC 11 +#define NOC_POSTCLK 12 +#define NOC_PLL_OUT 13 +#define NPLL 14 +#define APU_PRESRC 15 +#define APU_POSTCLK 16 +#define APU_PLL_OUT 17 +#define APLL 18 +#define RPU_PRESRC 19 +#define RPU_POSTCLK 20 +#define RPU_PLL_OUT 21 +#define RPLL 22 +#define CPM_PRESRC 23 +#define CPM_POSTCLK 24 +#define CPM_PLL_OUT 25 +#define CPLL 26 +#define PPLL_TO_XPD 27 +#define NPLL_TO_XPD 28 +#define APLL_TO_XPD 29 +#define RPLL_TO_XPD 30 +#define EFUSE_REF 31 +#define SYSMON_REF 32 +#define IRO_SUSPEND_REF 33 +#define USB_SUSPEND 34 +#define SWITCH_TIMEOUT 35 +#define RCLK_PMC 36 +#define RCLK_LPD 37 +#define WDT 38 +#define TTC0 39 +#define TTC1 40 +#define TTC2 41 +#define TTC3 42 +#define GEM_TSU 43 +#define GEM_TSU_LB 44 +#define MUXED_IRO_DIV2 45 +#define MUXED_IRO_DIV4 46 +#define PSM_REF 47 +#define GEM0_RX 48 +#define GEM0_TX 49 +#define GEM1_RX 50 +#define GEM1_TX 51 +#define CPM_CORE_REF 52 +#define CPM_LSBUS_REF 53 +#define CPM_DBG_REF 54 +#define CPM_AUX0_REF 55 +#define CPM_AUX1_REF 56 +#define QSPI_REF 57 +#define OSPI_REF 58 +#define SDIO0_REF 59 +#define SDIO1_REF 60 +#define PMC_LSBUS_REF 61 +#define I2C_REF 62 +#define TEST_PATTERN_REF 63 +#define DFT_OSC_REF 64 +#define PMC_PL0_REF 65 +#define PMC_PL1_REF 66 +#define PMC_PL2_REF 67 +#define PMC_PL3_REF 68 +#define CFU_REF 69 +#define SPARE_REF 70 +#define NPI_REF 71 +#define HSM0_REF 72 +#define HSM1_REF 73 +#define SD_DLL_REF 74 +#define FPD_TOP_SWITCH 75 +#define FPD_LSBUS 76 +#define ACPU 77 +#define DBG_TRACE 78 +#define DBG_FPD 79 +#define LPD_TOP_SWITCH 80 +#define ADMA 81 +#define LPD_LSBUS 82 +#define CPU_R5 83 +#define CPU_R5_CORE 84 +#define CPU_R5_OCM 85 +#define CPU_R5_OCM2 86 +#define IOU_SWITCH 87 +#define GEM0_REF 88 +#define GEM1_REF 89 +#define GEM_TSU_REF 90 +#define USB0_BUS_REF 91 +#define UART0_REF 92 +#define UART1_REF 93 +#define SPI0_REF 94 +#define SPI1_REF 95 +#define CAN0_REF 96 +#define CAN1_REF 97 +#define I2C0_REF 98 +#define I2C1_REF 99 +#define DBG_LPD 100 +#define TIMESTAMP_REF 101 +#define DBG_TSTMP 102 +#define CPM_TOPSW_REF 103 +#define USB3_DUAL_REF 104 +#define OUTCLK_MAX 105 +#define REF_CLK 106 +#define PL_ALT_REF_CLK 107 +#define MUXED_IRO 108 +#define PL_EXT 109 +#define PL_LB 110 +#define MIO_50_OR_51 111 +#define MIO_24_OR_25 112 + +#endif diff --git a/arch/arm64/boot/dts/xilinx/xlnx-versal-net-clk.h b/arch/arm64/boot/dts/xilinx/xlnx-versal-net-clk.h new file mode 100644 index 000000000000..4a6add03c173 --- /dev/null +++ b/arch/arm64/boot/dts/xilinx/xlnx-versal-net-clk.h @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2022, Xilinx, Inc. + * Copyright (C) 2022 - 2026, Advanced Micro Devices, Inc. + */ + +#ifndef _XLNX_VERSAL_NET_CLK_H +#define _XLNX_VERSAL_NET_CLK_H + +#include "xlnx-versal-clk.h" + +#define CAN0_REF_2X 0x9e +#define CAN1_REF_2X 0xac +#define FPD_WWDT0 0xb5 +#define FPD_WWDT1 0xb6 +#define FPD_WWDT2 0xb7 +#define FPD_WWDT3 0xb8 +#define LPD_WWDT0 0xb9 +#define LPD_WWDT1 0xba +#define ACPU_0 0x98 +#define ACPU_1 0x9b +#define ACPU_2 0x9a +#define ACPU_3 0x99 +#define I3C0_REF 0x9d +#define I3C1_REF 0x9f +#define USB1_BUS_REF 0xae +#define LPD_WWDT 0xad + +/* Remove Versal specific node IDs */ +#undef APU_PLL +#undef RPU_PLL +#undef CPM_PLL +#undef APU_PRESRC +#undef APU_POSTCLK +#undef APU_PLL_OUT +#undef APLL +#undef RPU_PRESRC +#undef RPU_POSTCLK +#undef RPU_PLL_OUT +#undef RPLL +#undef CPM_PRESRC +#undef CPM_POSTCLK +#undef CPM_PLL_OUT +#undef CPLL +#undef APLL_TO_XPD +#undef RPLL_TO_XPD +#undef RCLK_PMC +#undef RCLK_LPD +#undef WDT +#undef MUXED_IRO_DIV2 +#undef MUXED_IRO_DIV4 +#undef PSM_REF +#undef CPM_CORE_REF +#undef CPM_LSBUS_REF +#undef CPM_DBG_REF +#undef CPM_AUX0_REF +#undef CPM_AUX1_REF +#undef CPU_R5 +#undef CPU_R5_CORE +#undef CPU_R5_OCM +#undef CPU_R5_OCM2 +#undef CAN0_REF +#undef CAN1_REF +#undef I2C0_REF +#undef I2C1_REF +#undef CPM_TOPSW_REF +#undef USB3_DUAL_REF +#undef MUXED_IRO +#undef PL_EXT +#undef PL_LB +#undef MIO_50_OR_51 +#undef MIO_24_OR_25 + +#endif diff --git a/arch/arm64/boot/dts/xilinx/xlnx-versal-net-power.h b/arch/arm64/boot/dts/xilinx/xlnx-versal-net-power.h new file mode 100644 index 000000000000..4f73593c966b --- /dev/null +++ b/arch/arm64/boot/dts/xilinx/xlnx-versal-net-power.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2022, Xilinx, Inc. + * Copyright (C) 2022 - 2026, Advanced Micro Devices, Inc. + */ + +#ifndef _XLNX_VERSAL_NET_POWER_H +#define _XLNX_VERSAL_NET_POWER_H + +#include "xlnx-versal-power.h" + +#define PM_DEV_USB_1 (0x182240d7U) +#define PM_DEV_FPD_SWDT_0 (0x182240dbU) +#define PM_DEV_FPD_SWDT_1 (0x182240dcU) +#define PM_DEV_FPD_SWDT_2 (0x182240ddU) +#define PM_DEV_FPD_SWDT_3 (0x182240deU) +#define PM_DEV_TCM_A_0A (0x183180cbU) +#define PM_DEV_TCM_A_0B (0x183180ccU) +#define PM_DEV_TCM_A_0C (0x183180cdU) +#define PM_DEV_RPU_A_0 (0x181100bfU) +#define PM_DEV_LPD_SWDT_0 (0x182240d9U) +#define PM_DEV_LPD_SWDT_1 (0x182240daU) + +/* Remove Versal specific node IDs */ +#undef PM_DEV_RPU0_0 +#undef PM_DEV_RPU0_1 +#undef PM_DEV_OCM_0 +#undef PM_DEV_OCM_1 +#undef PM_DEV_OCM_2 +#undef PM_DEV_OCM_3 +#undef PM_DEV_TCM_0_A +#undef PM_DEV_TCM_1_A +#undef PM_DEV_TCM_0_B +#undef PM_DEV_TCM_1_B +#undef PM_DEV_SWDT_FPD +#undef PM_DEV_AI + +#endif diff --git a/arch/arm64/boot/dts/xilinx/xlnx-versal-net-resets.h b/arch/arm64/boot/dts/xilinx/xlnx-versal-net-resets.h new file mode 100644 index 000000000000..edc5841df214 --- /dev/null +++ b/arch/arm64/boot/dts/xilinx/xlnx-versal-net-resets.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2022, Xilinx, Inc. + * Copyright (C) 2022 - 2026, Advanced Micro Devices, Inc. + */ + +#ifndef _XLNX_VERSAL_NET_RESETS_H +#define _XLNX_VERSAL_NET_RESETS_H + +#include "xlnx-versal-resets.h" + +#define VERSAL_RST_USB_1 (0xc1040c6U) + +/* Remove Versal specific reset IDs */ +#undef VERSAL_RST_ACPU_0_POR +#undef VERSAL_RST_ACPU_1_POR +#undef VERSAL_RST_OCM2_POR +#undef VERSAL_RST_APU +#undef VERSAL_RST_ACPU_0 +#undef VERSAL_RST_ACPU_1 +#undef VERSAL_RST_ACPU_L2 +#undef VERSAL_RST_RPU_ISLAND +#undef VERSAL_RST_RPU_AMBA +#undef VERSAL_RST_R5_0 +#undef VERSAL_RST_R5_1 +#undef VERSAL_RST_OCM2_RST +#undef VERSAL_RST_I2C_PMC +#undef VERSAL_RST_I2C_0 +#undef VERSAL_RST_I2C_1 +#undef VERSAL_RST_SWDT_FPD +#undef VERSAL_RST_SWDT_LPD +#undef VERSAL_RST_USB +#undef VERSAL_RST_DPC +#undef VERSAL_RST_DBG_TRACE +#undef VERSAL_RST_DBG_TSTMP +#undef VERSAL_RST_RPU0_DBG +#undef VERSAL_RST_RPU1_DBG +#undef VERSAL_RST_HSDP +#undef VERSAL_RST_CPMDBG +#undef VERSAL_RST_PCIE_CFG +#undef VERSAL_RST_PCIE_CORE0 +#undef VERSAL_RST_PCIE_CORE1 +#undef VERSAL_RST_PCIE_DMA +#undef VERSAL_RST_L2_0 +#undef VERSAL_RST_L2_1 +#undef VERSAL_RST_ADDR_REMAP +#undef VERSAL_RST_CPI0 +#undef VERSAL_RST_CPI1 +#undef VERSAL_RST_XRAM +#undef VERSAL_RST_AIE_ARRAY +#undef VERSAL_RST_AIE_SHIM + +#endif diff --git a/arch/arm64/boot/dts/xilinx/xlnx-versal-power.h b/arch/arm64/boot/dts/xilinx/xlnx-versal-power.h new file mode 100644 index 000000000000..c3450675658a --- /dev/null +++ b/arch/arm64/boot/dts/xilinx/xlnx-versal-power.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2019 - 2022, Xilinx, Inc. + * Copyright (C) 2022 - 2026, Advanced Micro Devices, Inc. + */ + +#ifndef _XLNX_VERSAL_POWER_H +#define _XLNX_VERSAL_POWER_H + +#define PM_DEV_RPU0_0 (0x18110005U) +#define PM_DEV_RPU0_1 (0x18110006U) +#define PM_DEV_OCM_0 (0x18314007U) +#define PM_DEV_OCM_1 (0x18314008U) +#define PM_DEV_OCM_2 (0x18314009U) +#define PM_DEV_OCM_3 (0x1831400aU) +#define PM_DEV_TCM_0_A (0x1831800bU) +#define PM_DEV_TCM_0_B (0x1831800cU) +#define PM_DEV_TCM_1_A (0x1831800dU) +#define PM_DEV_TCM_1_B (0x1831800eU) +#define PM_DEV_USB_0 (0x18224018U) +#define PM_DEV_GEM_0 (0x18224019U) +#define PM_DEV_GEM_1 (0x1822401aU) +#define PM_DEV_SPI_0 (0x1822401bU) +#define PM_DEV_SPI_1 (0x1822401cU) +#define PM_DEV_I2C_0 (0x1822401dU) +#define PM_DEV_I2C_1 (0x1822401eU) +#define PM_DEV_CAN_FD_0 (0x1822401fU) +#define PM_DEV_CAN_FD_1 (0x18224020U) +#define PM_DEV_UART_0 (0x18224021U) +#define PM_DEV_UART_1 (0x18224022U) +#define PM_DEV_GPIO (0x18224023U) +#define PM_DEV_TTC_0 (0x18224024U) +#define PM_DEV_TTC_1 (0x18224025U) +#define PM_DEV_TTC_2 (0x18224026U) +#define PM_DEV_TTC_3 (0x18224027U) +#define PM_DEV_SWDT_LPD (0x18224028U) +#define PM_DEV_SWDT_FPD (0x18224029U) +#define PM_DEV_OSPI (0x1822402aU) +#define PM_DEV_QSPI (0x1822402bU) +#define PM_DEV_GPIO_PMC (0x1822402cU) +#define PM_DEV_I2C_PMC (0x1822402dU) +#define PM_DEV_SDIO_0 (0x1822402eU) +#define PM_DEV_SDIO_1 (0x1822402fU) +#define PM_DEV_RTC (0x18224034U) +#define PM_DEV_ADMA_0 (0x18224035U) +#define PM_DEV_ADMA_1 (0x18224036U) +#define PM_DEV_ADMA_2 (0x18224037U) +#define PM_DEV_ADMA_3 (0x18224038U) +#define PM_DEV_ADMA_4 (0x18224039U) +#define PM_DEV_ADMA_5 (0x1822403aU) +#define PM_DEV_ADMA_6 (0x1822403bU) +#define PM_DEV_ADMA_7 (0x1822403cU) +#define PM_DEV_AI (0x18224072U) + +#endif diff --git a/arch/arm64/boot/dts/xilinx/xlnx-versal-resets.h b/arch/arm64/boot/dts/xilinx/xlnx-versal-resets.h new file mode 100644 index 000000000000..fe00f4a0ba65 --- /dev/null +++ b/arch/arm64/boot/dts/xilinx/xlnx-versal-resets.h @@ -0,0 +1,106 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020 - 2022, Xilinx, Inc. + * Copyright (C) 2022 - 2026, Advanced Micro Devices, Inc. + */ + +#ifndef _XLNX_VERSAL_RESETS_H +#define _XLNX_VERSAL_RESETS_H + +#define VERSAL_RST_PMC_POR (0xc30c001U) +#define VERSAL_RST_PMC (0xc410002U) +#define VERSAL_RST_PS_POR (0xc30c003U) +#define VERSAL_RST_PL_POR (0xc30c004U) +#define VERSAL_RST_NOC_POR (0xc30c005U) +#define VERSAL_RST_FPD_POR (0xc30c006U) +#define VERSAL_RST_ACPU_0_POR (0xc30c007U) +#define VERSAL_RST_ACPU_1_POR (0xc30c008U) +#define VERSAL_RST_OCM2_POR (0xc30c009U) +#define VERSAL_RST_PS_SRST (0xc41000aU) +#define VERSAL_RST_PL_SRST (0xc41000bU) +#define VERSAL_RST_NOC (0xc41000cU) +#define VERSAL_RST_NPI (0xc41000dU) +#define VERSAL_RST_SYS_RST_1 (0xc41000eU) +#define VERSAL_RST_SYS_RST_2 (0xc41000fU) +#define VERSAL_RST_SYS_RST_3 (0xc410010U) +#define VERSAL_RST_FPD (0xc410011U) +#define VERSAL_RST_PL0 (0xc410012U) +#define VERSAL_RST_PL1 (0xc410013U) +#define VERSAL_RST_PL2 (0xc410014U) +#define VERSAL_RST_PL3 (0xc410015U) +#define VERSAL_RST_APU (0xc410016U) +#define VERSAL_RST_ACPU_0 (0xc410017U) +#define VERSAL_RST_ACPU_1 (0xc410018U) +#define VERSAL_RST_ACPU_L2 (0xc410019U) +#define VERSAL_RST_ACPU_GIC (0xc41001aU) +#define VERSAL_RST_RPU_ISLAND (0xc41001bU) +#define VERSAL_RST_RPU_AMBA (0xc41001cU) +#define VERSAL_RST_R5_0 (0xc41001dU) +#define VERSAL_RST_R5_1 (0xc41001eU) +#define VERSAL_RST_SYSMON_PMC_SEQ_RST (0xc41001fU) +#define VERSAL_RST_SYSMON_PMC_CFG_RST (0xc410020U) +#define VERSAL_RST_SYSMON_FPD_CFG_RST (0xc410021U) +#define VERSAL_RST_SYSMON_FPD_SEQ_RST (0xc410022U) +#define VERSAL_RST_SYSMON_LPD (0xc410023U) +#define VERSAL_RST_PDMA_RST1 (0xc410024U) +#define VERSAL_RST_PDMA_RST0 (0xc410025U) +#define VERSAL_RST_ADMA (0xc410026U) +#define VERSAL_RST_TIMESTAMP (0xc410027U) +#define VERSAL_RST_OCM (0xc410028U) +#define VERSAL_RST_OCM2_RST (0xc410029U) +#define VERSAL_RST_IPI (0xc41002aU) +#define VERSAL_RST_SBI (0xc41002bU) +#define VERSAL_RST_LPD (0xc41002cU) +#define VERSAL_RST_QSPI (0xc10402dU) +#define VERSAL_RST_OSPI (0xc10402eU) +#define VERSAL_RST_SDIO_0 (0xc10402fU) +#define VERSAL_RST_SDIO_1 (0xc104030U) +#define VERSAL_RST_I2C_PMC (0xc104031U) +#define VERSAL_RST_GPIO_PMC (0xc104032U) +#define VERSAL_RST_GEM_0 (0xc104033U) +#define VERSAL_RST_GEM_1 (0xc104034U) +#define VERSAL_RST_SPARE (0xc104035U) +#define VERSAL_RST_USB_0 (0xc104036U) +#define VERSAL_RST_UART_0 (0xc104037U) +#define VERSAL_RST_UART_1 (0xc104038U) +#define VERSAL_RST_SPI_0 (0xc104039U) +#define VERSAL_RST_SPI_1 (0xc10403aU) +#define VERSAL_RST_CAN_FD_0 (0xc10403bU) +#define VERSAL_RST_CAN_FD_1 (0xc10403cU) +#define VERSAL_RST_I2C_0 (0xc10403dU) +#define VERSAL_RST_I2C_1 (0xc10403eU) +#define VERSAL_RST_GPIO_LPD (0xc10403fU) +#define VERSAL_RST_TTC_0 (0xc104040U) +#define VERSAL_RST_TTC_1 (0xc104041U) +#define VERSAL_RST_TTC_2 (0xc104042U) +#define VERSAL_RST_TTC_3 (0xc104043U) +#define VERSAL_RST_SWDT_FPD (0xc104044U) +#define VERSAL_RST_SWDT_LPD (0xc104045U) +#define VERSAL_RST_USB (0xc104046U) +#define VERSAL_RST_DPC (0xc208047U) +#define VERSAL_RST_PMCDBG (0xc208048U) +#define VERSAL_RST_DBG_TRACE (0xc208049U) +#define VERSAL_RST_DBG_FPD (0xc20804aU) +#define VERSAL_RST_DBG_TSTMP (0xc20804bU) +#define VERSAL_RST_RPU0_DBG (0xc20804cU) +#define VERSAL_RST_RPU1_DBG (0xc20804dU) +#define VERSAL_RST_HSDP (0xc20804eU) +#define VERSAL_RST_DBG_LPD (0xc20804fU) +#define VERSAL_RST_CPM_POR (0xc30c050U) +#define VERSAL_RST_CPM (0xc410051U) +#define VERSAL_RST_CPMDBG (0xc208052U) +#define VERSAL_RST_PCIE_CFG (0xc410053U) +#define VERSAL_RST_PCIE_CORE0 (0xc410054U) +#define VERSAL_RST_PCIE_CORE1 (0xc410055U) +#define VERSAL_RST_PCIE_DMA (0xc410056U) +#define VERSAL_RST_CMN (0xc410057U) +#define VERSAL_RST_L2_0 (0xc410058U) +#define VERSAL_RST_L2_1 (0xc410059U) +#define VERSAL_RST_ADDR_REMAP (0xc41005aU) +#define VERSAL_RST_CPI0 (0xc41005bU) +#define VERSAL_RST_CPI1 (0xc41005cU) +#define VERSAL_RST_XRAM (0xc30c05dU) +#define VERSAL_RST_AIE_ARRAY (0xc10405eU) +#define VERSAL_RST_AIE_SHIM (0xc10405fU) + +#endif From ab47d7b4b8c672c070457cb6a21eead4dc1c486f Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Sun, 26 Apr 2026 21:53:38 +0900 Subject: [PATCH 056/562] lib/Kconfig.debug: add CONFIG_DEBUG_AID_FOR_SYZBOT option This change is not for upstream. This change is for linux-next only. I want to temporarily enable more debug code for syzbot reports. Link: https://github.com/google/syzkaller/blob/master/docs/syzbot.md#no-custom-patches Signed-off-by: Tetsuo Handa --- lib/Kconfig.debug | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 1244dcac2294..5e2eeacd2451 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -2261,6 +2261,12 @@ config KCOV_SELFTEST On test failure, causes the kernel to panic. Recommended to be enabled, ensuring critical functionality works as intended. +config DEBUG_AID_FOR_SYZBOT + bool "Additional debug code for syzbot" + default n + help + This option is intended for testing by syzbot. + menuconfig RUNTIME_TESTING_MENU bool "Runtime Testing" default y From 5a12ab17833f85e167ff7a2f60095f9b46eaf3da Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Fri, 8 May 2026 08:58:25 +0900 Subject: [PATCH 057/562] lib/Kconfig.debug: enable CONFIG_PREEMPT_RT for syzbot kernels. This change is not for upstream. This change is for linux-next only. I am debugging bugs that happen with CONFIG_PREEMPT_RT=y kernels. Signed-off-by: Tetsuo Handa --- lib/Kconfig.debug | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 5e2eeacd2451..e2f976c3301b 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -2264,6 +2264,7 @@ config KCOV_SELFTEST config DEBUG_AID_FOR_SYZBOT bool "Additional debug code for syzbot" default n + select PREEMPT_RT if EXPERT && ARCH_SUPPORTS_RT && !COMPILE_TEST help This option is intended for testing by syzbot. From d42c3c3dd5ba598deb9cca6ee2420650b4af9398 Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Mon, 13 Apr 2026 06:49:43 +0900 Subject: [PATCH 058/562] locking/lockdep: make lockdep_print_held_locks() always print if CONFIG_DEBUG_AID_FOR_SYZBOT=y This change is not for upstream. This change is for linux-next only. When executing syzkaller testcases, many threads run on few number of CPUs. It is more helpful for syzbot reports to also include locks held by "waiting for CPU" tasks. Signed-off-by: Tetsuo Handa --- kernel/locking/lockdep.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/locking/lockdep.c b/kernel/locking/lockdep.c index 2d4c5bab5af8..f13883162cfe 100644 --- a/kernel/locking/lockdep.c +++ b/kernel/locking/lockdep.c @@ -796,8 +796,10 @@ static void lockdep_print_held_locks(struct task_struct *p) * It's not reliable to print a task's held locks if it's not sleeping * and it's not the current task. */ +#ifndef CONFIG_DEBUG_AID_FOR_SYZBOT if (p != current && task_is_running(p)) return; +#endif for (i = 0; i < depth; i++) { printk(" #%d: ", i); print_lock(p->held_locks + i); From bfe347a0819f2fb26d0c6d2fecc3b92c09a97ea0 Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Mon, 29 Jun 2026 20:26:36 +0900 Subject: [PATCH 059/562] net: update dev_put()/dev_hold() debugging This change is not for upstream. This change is for linux-next only. syzbot is still reporting unregister_netdevice: waiting for DEV to become free problem. Since commit 4c6c11ea0f7b ("net: refine dev_put()/dev_hold() debugging") is not sufficient for me, let's try to report all locations which called dev_put()/dev_hold(), with a hope that we can find some hints for locations where dev_put() is missing. Signed-off-by: Tetsuo Handa --- include/linux/netdevice.h | 15 +++ kernel/softirq.c | 4 + kernel/workqueue.c | 4 + net/core/dev.c | 187 ++++++++++++++++++++++++++++++++++++++ net/core/lock_debug.c | 1 + net/socket.c | 32 +++++-- 6 files changed, 236 insertions(+), 7 deletions(-) diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 9981d637f8b5..987c3c87f14f 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -2142,6 +2142,8 @@ enum netdev_reg_state { * * FIXME: cleanup struct net_device such that network protocol info * moves out. + * + * @netdev_trace_buffer_list: Linked list for debugging refcount leak. */ struct net_device { @@ -2298,6 +2300,9 @@ struct net_device { #if IS_ENABLED(CONFIG_TLS_DEVICE) const struct tlsdev_ops *tlsdev_ops; #endif +#ifdef CONFIG_NET_DEV_REFCNT_TRACKER + struct list_head netdev_trace_buffer_list; +#endif unsigned int operstate; unsigned char link_mode; @@ -3248,6 +3253,7 @@ enum netdev_cmd { NETDEV_OFFLOAD_XSTATS_REPORT_USED, NETDEV_OFFLOAD_XSTATS_REPORT_DELTA, NETDEV_XDP_FEAT_CHANGE, + NETDEV_DEBUG_UNREGISTER, }; const char *netdev_cmd_to_name(enum netdev_cmd cmd); @@ -4469,9 +4475,15 @@ static inline bool dev_nit_active(const struct net_device *dev) void dev_queue_xmit_nit(struct sk_buff *skb, struct net_device *dev); +void save_netdev_trace_buffer(struct net_device *dev, int delta); +int trim_netdev_trace(unsigned long *entries, int nr_entries); + static inline void __dev_put(struct net_device *dev) { if (dev) { +#ifdef CONFIG_NET_DEV_REFCNT_TRACKER + save_netdev_trace_buffer(dev, -1); +#endif #ifdef CONFIG_PCPU_DEV_REFCNT this_cpu_dec(*dev->pcpu_refcnt); #else @@ -4483,6 +4495,9 @@ static inline void __dev_put(struct net_device *dev) static inline void __dev_hold(struct net_device *dev) { if (dev) { +#ifdef CONFIG_NET_DEV_REFCNT_TRACKER + save_netdev_trace_buffer(dev, 1); +#endif #ifdef CONFIG_PCPU_DEV_REFCNT this_cpu_inc(*dev->pcpu_refcnt); #else diff --git a/kernel/softirq.c b/kernel/softirq.c index 4425d8dce44b..a3bd54de8259 100644 --- a/kernel/softirq.c +++ b/kernel/softirq.c @@ -576,6 +576,10 @@ static inline bool lockdep_softirq_start(void) { return false; } static inline void lockdep_softirq_end(bool in_hardirq) { } #endif +#ifdef CONFIG_NET_DEV_REFCNT_TRACKER +static noinline void handle_softirqs(bool ksirqd); +#endif + static void handle_softirqs(bool ksirqd) { unsigned long end = jiffies + MAX_SOFTIRQ_TIME; diff --git a/kernel/workqueue.c b/kernel/workqueue.c index 78068ae8f28a..78f25afb4a9d 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -3203,6 +3203,10 @@ static bool manage_workers(struct worker *worker) return true; } +#ifdef CONFIG_NET_DEV_REFCNT_TRACKER +static noinline void process_one_work(struct worker *worker, struct work_struct *work); +#endif + /** * process_one_work - process single work * @worker: self diff --git a/net/core/dev.c b/net/core/dev.c index 4b3d5cfdf6e0..4d47ef74d6aa 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -1875,6 +1875,7 @@ const char *netdev_cmd_to_name(enum netdev_cmd cmd) N(PRE_CHANGEADDR) N(OFFLOAD_XSTATS_ENABLE) N(OFFLOAD_XSTATS_DISABLE) N(OFFLOAD_XSTATS_REPORT_USED) N(OFFLOAD_XSTATS_REPORT_DELTA) N(XDP_FEAT_CHANGE) + N(DEBUG_UNREGISTER) } #undef N return "UNKNOWN_NETDEV_EVENT"; @@ -11575,6 +11576,14 @@ int netdev_refcnt_read(const struct net_device *dev) } EXPORT_SYMBOL(netdev_refcnt_read); +#ifdef CONFIG_NET_DEV_REFCNT_TRACKER +static void dump_netdev_trace_buffer(const struct net_device *dev); +static void erase_netdev_trace_buffer(const struct net_device *dev); +#else +static inline void dump_netdev_trace_buffer(const struct net_device *dev) { } +static inline void erase_netdev_trace_buffer(const struct net_device *dev) { } +#endif + int netdev_unregister_timeout_secs __read_mostly = 10; #define WAIT_REFS_MIN_MSECS 1 @@ -11648,11 +11657,16 @@ static struct net_device *netdev_wait_allrefs_any(struct list_head *list) if (time_after(jiffies, warning_time + READ_ONCE(netdev_unregister_timeout_secs) * HZ)) { + rtnl_lock(); list_for_each_entry(dev, list, todo_list) { pr_emerg("unregister_netdevice: waiting for %s to become free. Usage count = %d\n", dev->name, netdev_refcnt_read(dev)); ref_tracker_dir_print(&dev->refcnt_tracker, 10); + call_netdevice_notifiers(NETDEV_DEBUG_UNREGISTER, dev); + dump_netdev_trace_buffer(dev); } + __rtnl_unlock(); + rcu_barrier(); warning_time = jiffies; } @@ -12052,6 +12066,9 @@ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name, dev->priv_len = sizeof_priv; +#ifdef CONFIG_NET_DEV_REFCNT_TRACKER + INIT_LIST_HEAD(&dev->netdev_trace_buffer_list); +#endif ref_tracker_dir_init(&dev->refcnt_tracker, 128, "netdev"); #ifdef CONFIG_PCPU_DEV_REFCNT dev->pcpu_refcnt = alloc_percpu(int); @@ -12228,6 +12245,8 @@ void free_netdev(struct net_device *dev) mutex_destroy(&dev->lock); + erase_netdev_trace_buffer(dev); + /* Compatibility with error handling in drivers */ if (dev->reg_state == NETREG_UNINITIALIZED || dev->reg_state == NETREG_DUMMY) { @@ -13332,3 +13351,171 @@ static int __init net_dev_init(void) } subsys_initcall(net_dev_init); + +#ifdef CONFIG_NET_DEV_REFCNT_TRACKER + +#define NETDEV_TRACE_BUFFER_SIZE 32768 +static struct netdev_trace_buffer { + struct list_head list; + int prev_count; + atomic_t count; + int nr_entries; + unsigned long entries[20]; +} netdev_trace_buffer[NETDEV_TRACE_BUFFER_SIZE]; +static LIST_HEAD(netdev_trace_buffer_list); +static DEFINE_SPINLOCK(netdev_trace_buffer_lock); +static bool netdev_trace_buffer_exhausted; + +static int netdev_trace_buffer_init(void) +{ + int i; + + for (i = 0; i < NETDEV_TRACE_BUFFER_SIZE; i++) + list_add_tail(&netdev_trace_buffer[i].list, &netdev_trace_buffer_list); + return 0; +} +pure_initcall(netdev_trace_buffer_init); + +static void dump_netdev_trace_buffer(const struct net_device *dev) +{ + struct netdev_trace_buffer *ptr; + int count, balance = 0, pos = 0; + + list_for_each_entry_rcu(ptr, &dev->netdev_trace_buffer_list, list, + /* list elements can't go away. */ 1) { + pos++; + count = atomic_read(&ptr->count); + balance += count; + if (ptr->prev_count == count) + continue; + ptr->prev_count = count; + pr_info("Call trace for %s[%d] %+d at\n", dev->name, pos, count); + stack_trace_print(ptr->entries, ptr->nr_entries, 4); + cond_resched(); + } + if (!netdev_trace_buffer_exhausted) + pr_info("balance as of %s[%d] is %d\n", dev->name, pos, balance); +} + +static void erase_netdev_trace_buffer(const struct net_device *dev) +{ + struct netdev_trace_buffer *ptr; + unsigned long flags; + + spin_lock_irqsave(&netdev_trace_buffer_lock, flags); + while (!list_empty(&dev->netdev_trace_buffer_list)) { + ptr = list_first_entry(&dev->netdev_trace_buffer_list, typeof(*ptr), list); + list_del(&ptr->list); + list_add_tail(&ptr->list, &netdev_trace_buffer_list); + } + spin_unlock_irqrestore(&netdev_trace_buffer_lock, flags); +} + +int trim_netdev_trace(unsigned long *entries, int nr_entries) +{ +#ifdef CONFIG_KALLSYMS + char buffer[32] = { }; + char *cp; + int i; + + if (in_softirq()) { + static unsigned long __data_racy caller; + + if (!caller) { + for (i = 0; i < nr_entries; i++) { + snprintf(buffer, sizeof(buffer) - 1, "%ps", (void *)entries[i]); + cp = strchr(buffer, ' '); + if (cp) + *cp = '\0'; + if (!strcmp(buffer, "handle_softirqs")) { + caller = entries[i]; + break; + } + } + } + for (i = 0; i < nr_entries; i++) + if (entries[i] == caller) + return i + 1; + } else if (current->flags & PF_WQ_WORKER) { + static unsigned long __data_racy caller; + + if (!caller) { + for (i = 0; i < nr_entries; i++) { + snprintf(buffer, sizeof(buffer) - 1, "%ps", (void *)entries[i]); + cp = strchr(buffer, ' '); + if (cp) + *cp = '\0'; + if (!strcmp(buffer, "process_one_work")) { + caller = entries[i]; + break; + } + } + } + for (i = 0; i < nr_entries; i++) + if (entries[i] == caller) + return i + 1; + } else { + for (i = 0; i < nr_entries; i++) { + snprintf(buffer, sizeof(buffer) - 1, "%ps", (void *)entries[i]); + cp = strchr(buffer, ' '); + if (cp) + *cp = '\0'; + if (buffer[0] == 'k') { + if (!strcmp(buffer, "ksys_unshare")) + return i + 1; + } else if (buffer[0] == 's') { + if (!strcmp(buffer, "sock_sendmsg_nosec") || + !strcmp(buffer, "sock_recvmsg_nosec")) + return i + 1; + } else if (buffer[0] == '_') { + if (!strcmp(buffer, "__sys_bind") || + !strcmp(buffer, "__sock_release") || + !strcmp(buffer, "__sys_bpf")) + return i + 1; + } else { + if (!strcmp(buffer, "do_sock_setsockopt")) + return i + 1; + } + } + } +#endif + return nr_entries; +} +EXPORT_SYMBOL(trim_netdev_trace); + +void save_netdev_trace_buffer(struct net_device *dev, int delta) +{ + struct netdev_trace_buffer *ptr; + unsigned long entries[ARRAY_SIZE(ptr->entries)]; + unsigned long nr_entries; + unsigned long flags; + + if (in_nmi()) + return; + nr_entries = stack_trace_save(entries, ARRAY_SIZE(ptr->entries), 1); + nr_entries = trim_netdev_trace(entries, nr_entries); + list_for_each_entry_rcu(ptr, &dev->netdev_trace_buffer_list, list, + /* list elements can't go away. */ 1) { + if (ptr->nr_entries == nr_entries && + !memcmp(ptr->entries, entries, nr_entries * sizeof(unsigned long))) { + atomic_add(delta, &ptr->count); + return; + } + } + spin_lock_irqsave(&netdev_trace_buffer_lock, flags); + if (!list_empty(&netdev_trace_buffer_list)) { + ptr = list_first_entry(&netdev_trace_buffer_list, typeof(*ptr), list); + list_del(&ptr->list); + ptr->prev_count = 0; + atomic_set(&ptr->count, delta); + ptr->nr_entries = nr_entries; + memmove(ptr->entries, entries, nr_entries * sizeof(unsigned long)); + list_add_tail_rcu(&ptr->list, &dev->netdev_trace_buffer_list); + } else { + netdev_trace_buffer_exhausted = true; + } + spin_unlock_irqrestore(&netdev_trace_buffer_lock, flags); +} +EXPORT_SYMBOL(save_netdev_trace_buffer); + +#endif diff --git a/net/core/lock_debug.c b/net/core/lock_debug.c index 8a81c5430705..c1a44594c492 100644 --- a/net/core/lock_debug.c +++ b/net/core/lock_debug.c @@ -29,6 +29,7 @@ int netdev_debug_event(struct notifier_block *nb, unsigned long event, case NETDEV_DOWN: case NETDEV_REBOOT: case NETDEV_UNREGISTER: + case NETDEV_DEBUG_UNREGISTER: case NETDEV_CHANGEMTU: case NETDEV_CHANGEADDR: case NETDEV_PRE_CHANGEADDR: diff --git a/net/socket.c b/net/socket.c index 63c69a0fa74e..1d10fa2d91e2 100644 --- a/net/socket.c +++ b/net/socket.c @@ -698,7 +698,11 @@ struct socket *sock_alloc(void) } EXPORT_SYMBOL(sock_alloc); -static void __sock_release(struct socket *sock, struct inode *inode) +static +#ifdef CONFIG_NET_DEV_REFCNT_TRACKER +noinline +#endif +void __sock_release(struct socket *sock, struct inode *inode) { const struct proto_ops *ops = READ_ONCE(sock->ops); @@ -770,7 +774,13 @@ static noinline void call_trace_sock_send_length(struct sock *sk, int ret, trace_sock_send_length(sk, ret, 0); } -static inline int sock_sendmsg_nosec(struct socket *sock, struct msghdr *msg) +static +#ifdef CONFIG_NET_DEV_REFCNT_TRACKER +noinline +#else +inline +#endif +int sock_sendmsg_nosec(struct socket *sock, struct msghdr *msg) { int ret = INDIRECT_CALL_INET(READ_ONCE(sock->ops)->sendmsg, inet6_sendmsg, inet_sendmsg, sock, msg, @@ -1120,8 +1130,13 @@ static noinline void call_trace_sock_recv_length(struct sock *sk, int ret, int f trace_sock_recv_length(sk, ret, flags); } -static inline int sock_recvmsg_nosec(struct socket *sock, struct msghdr *msg, - int flags) +static +#ifdef CONFIG_NET_DEV_REFCNT_TRACKER +noinline +#else +inline +#endif +int sock_recvmsg_nosec(struct socket *sock, struct msghdr *msg, int flags) { int ret = INDIRECT_CALL_INET(READ_ONCE(sock->ops)->recvmsg, inet6_recvmsg, @@ -2624,9 +2639,12 @@ static int copy_msghdr_from_user(struct msghdr *kmsg, return err < 0 ? err : 0; } -static int ____sys_sendmsg(struct socket *sock, struct msghdr *msg_sys, - unsigned int flags, struct used_address *used_address, - unsigned int allowed_msghdr_flags) +static +#ifdef CONFIG_NET_DEV_REFCNT_TRACKER +noinline +#endif +int ____sys_sendmsg(struct socket *sock, struct msghdr *msg_sys, unsigned int flags, + struct used_address *used_address, unsigned int allowed_msghdr_flags) { unsigned char ctl[sizeof(struct cmsghdr) + 20] __aligned(sizeof(__kernel_size_t)); From 736de390972c25b87fe194540d18cab1cb0d58dc Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Mon, 29 Jun 2026 20:36:46 +0900 Subject: [PATCH 060/562] net: add "struct dst_entry" debugging This change is not for upstream. This change is for linux-next only. syzbot is reporting "struct dst_entry" leaks. unregister_netdevice: waiting for lo to become free. Usage count = 2 ref_tracker: netdev@ffff88807a79a630 has 1/1 users at __netdev_tracker_alloc include/linux/netdevice.h:4418 [inline] netdev_hold include/linux/netdevice.h:4447 [inline] dst_init+0xe6/0x490 net/core/dst.c:52 dst_alloc+0x12a/0x170 net/core/dst.c:94 rt_dst_alloc net/ipv4/route.c:1651 [inline] __mkroute_output net/ipv4/route.c:2655 [inline] (...snipped...) Let's try to report all trace hold/release calls. Signed-off-by: Tetsuo Handa --- include/net/dst.h | 22 ++++++++++- include/net/sock.h | 8 +++- net/bridge/br_nf_core.c | 1 + net/core/dev.c | 1 + net/core/dst.c | 85 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 113 insertions(+), 4 deletions(-) diff --git a/include/net/dst.h b/include/net/dst.h index 307073eae7f8..10b73fb45117 100644 --- a/include/net/dst.h +++ b/include/net/dst.h @@ -95,8 +95,19 @@ struct dst_entry { #ifdef CONFIG_64BIT struct lwtunnel_state *lwtstate; #endif +#ifdef CONFIG_NET_DEV_REFCNT_TRACKER + atomic_t dst_trace_seq; +#endif }; +#ifdef CONFIG_NET_DEV_REFCNT_TRACKER +void save_dst_trace_buffer(struct dst_entry *dst, int delta); +void dump_dst_trace_buffer(const struct net_device *dev); +#else +static inline void save_dst_trace_buffer(struct dst_entry *dst, int delta) { }; +static inline void dump_dst_trace_buffer(const struct net_device *dev) { }; +#endif + struct dst_metrics { u32 metrics[RTAX_MAX]; refcount_t refcnt; @@ -244,7 +255,10 @@ static inline void dst_hold(struct dst_entry *dst) * the placement of __rcuref in struct dst_entry */ BUILD_BUG_ON(offsetof(struct dst_entry, __rcuref) & 63); - WARN_ON(!rcuref_get(&dst->__rcuref)); + if (!rcuref_get(&dst->__rcuref)) + WARN_ON(1); + else + save_dst_trace_buffer(dst, 1); } static inline void dst_use_noref(struct dst_entry *dst, unsigned long time) @@ -308,7 +322,11 @@ static inline void skb_dst_copy(struct sk_buff *nskb, const struct sk_buff *oskb */ static inline bool dst_hold_safe(struct dst_entry *dst) { - return rcuref_get(&dst->__rcuref); + const bool ret = rcuref_get(&dst->__rcuref); + + if (ret) + save_dst_trace_buffer(dst, 1); + return ret; } /** diff --git a/include/net/sock.h b/include/net/sock.h index 51185222aac2..a043fd422089 100644 --- a/include/net/sock.h +++ b/include/net/sock.h @@ -2206,8 +2206,12 @@ sk_dst_get(const struct sock *sk) rcu_read_lock(); dst = rcu_dereference(sk->sk_dst_cache); - if (dst && !rcuref_get(&dst->__rcuref)) - dst = NULL; + if (dst) { + if (!rcuref_get(&dst->__rcuref)) + dst = NULL; + else + save_dst_trace_buffer(dst, 1); + } rcu_read_unlock(); return dst; } diff --git a/net/bridge/br_nf_core.c b/net/bridge/br_nf_core.c index a8c67035e23c..5b3a85b4be8f 100644 --- a/net/bridge/br_nf_core.c +++ b/net/bridge/br_nf_core.c @@ -70,6 +70,7 @@ void br_netfilter_rtable_init(struct net_bridge *br) struct rtable *rt = &br->fake_rtable; rcuref_init(&rt->dst.__rcuref, 1); + save_dst_trace_buffer(&rt->dst, 1); rt->dst.dev = br->dev; dst_init_metrics(&rt->dst, br->metrics, false); dst_metric_set(&rt->dst, RTAX_MTU, br->dev->mtu); diff --git a/net/core/dev.c b/net/core/dev.c index 4d47ef74d6aa..209af2f322ba 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -13381,6 +13381,7 @@ static void dump_netdev_trace_buffer(const struct net_device *dev) struct netdev_trace_buffer *ptr; int count, balance = 0, pos = 0; + dump_dst_trace_buffer(dev); list_for_each_entry_rcu(ptr, &dev->netdev_trace_buffer_list, list, /* list elements can't go away. */ 1) { pos++; diff --git a/net/core/dst.c b/net/core/dst.c index 092861133023..45452fa62e24 100644 --- a/net/core/dst.c +++ b/net/core/dst.c @@ -44,6 +44,82 @@ const struct dst_metrics dst_default_metrics = { }; EXPORT_SYMBOL(dst_default_metrics); +#ifdef CONFIG_NET_DEV_REFCNT_TRACKER + +#define DST_TRACE_BUFFER_SIZE 8192 +static struct dst_trace_buffer { + struct dst_entry *dst; // no-ref + struct net_device *ndev; // no-ref + int seq; + int delta; + u32 caller_id; + int nr_entries; + unsigned long entries[20]; +} dst_trace_buffer[DST_TRACE_BUFFER_SIZE]; +static bool dst_trace_buffer_exhausted; + +void dump_dst_trace_buffer(const struct net_device *ndev) +{ + struct dst_trace_buffer *ptr; + int count, balance = 0; + int i; + + for (i = 0; i < DST_TRACE_BUFFER_SIZE; i++) { + ptr = &dst_trace_buffer[i]; + if (!ptr->dst || ptr->ndev != ndev) + continue; + count = ptr->delta; + balance += count; + pr_info("Call trace for %s@%p[%d] %+d %c%u at\n", ndev->name, ptr->dst, ptr->seq, + count, ptr->caller_id & 0x80000000 ? 'C' : 'T', + ptr->caller_id & ~0x80000000); + stack_trace_print(ptr->entries, ptr->nr_entries, 4); + } + if (!dst_trace_buffer_exhausted) + pr_info("balance for %s@dst_entry is %d\n", ndev->name, balance); + else + pr_info("balance for %s@dst_entry is unknown\n", ndev->name); +} + +static void erase_dst_trace_buffer(struct dst_entry *dst) +{ + int i; + + for (i = 0; i < DST_TRACE_BUFFER_SIZE; i++) + if (dst_trace_buffer[i].dst == dst) + dst_trace_buffer[i].dst = NULL; +} + +void save_dst_trace_buffer(struct dst_entry *dst, int delta) +{ + struct dst_trace_buffer *ptr; + unsigned long entries[ARRAY_SIZE(ptr->entries)]; + unsigned long nr_entries; + int i; + + if (!dst->dev) + return; + nr_entries = stack_trace_save(entries, ARRAY_SIZE(ptr->entries), 1); + nr_entries = trim_netdev_trace(entries, nr_entries); + for (i = 0; i < DST_TRACE_BUFFER_SIZE; i++) { + ptr = &dst_trace_buffer[i]; + if (!ptr->dst && !cmpxchg(&ptr->dst, NULL, dst)) { + ptr->ndev = dst->dev; + ptr->seq = atomic_inc_return(&dst->dst_trace_seq); + ptr->delta = delta; + ptr->caller_id = in_task() ? task_pid_nr(current) : + 0x80000000 + raw_smp_processor_id(); + ptr->nr_entries = nr_entries; + memmove(ptr->entries, entries, nr_entries * sizeof(unsigned long)); + return; + } + } + dst_trace_buffer_exhausted = true; +} +EXPORT_SYMBOL(save_dst_trace_buffer); + +#endif + void dst_init(struct dst_entry *dst, struct dst_ops *ops, struct net_device *dev, int initial_obsolete, unsigned short flags) @@ -67,6 +143,7 @@ void dst_init(struct dst_entry *dst, struct dst_ops *ops, #endif dst->lwtstate = NULL; rcuref_init(&dst->__rcuref, 1); + save_dst_trace_buffer(dst, 1); INIT_LIST_HEAD(&dst->rt_uncached); dst->rt_uncached_list = NULL; dst->__use = 0; @@ -116,6 +193,10 @@ static void dst_destroy(struct dst_entry *dst) lwtstate_put(dst->lwtstate); +#ifdef CONFIG_NET_DEV_REFCNT_TRACKER + erase_dst_trace_buffer(dst); +#endif + if (dst->flags & DST_METADATA) metadata_dst_free((struct metadata_dst *)dst); else @@ -165,6 +246,8 @@ static void dst_count_dec(struct dst_entry *dst) void dst_release(struct dst_entry *dst) { + if (dst) + save_dst_trace_buffer(dst, -1); if (dst && rcuref_put(&dst->__rcuref)) { #ifdef CONFIG_DST_CACHE if (dst->flags & DST_METADATA) { @@ -182,6 +265,8 @@ EXPORT_SYMBOL(dst_release); void dst_release_immediate(struct dst_entry *dst) { + if (dst) + save_dst_trace_buffer(dst, -1); if (dst && rcuref_put(&dst->__rcuref)) { dst_count_dec(dst); dst_destroy(dst); From 3ae78a69d3a9a4ad1d3b267324bd742654f40965 Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Mon, 29 Jun 2026 20:41:17 +0900 Subject: [PATCH 061/562] loop: Fix NULL pointer dereference in lo_rw_aio() This is an experimental patch for testing. syzbot is reporting NULL pointer dereference in lo_rw_aio() [1][2]. An analysis by the Gemini AI collaborator [3] considers that this problem is caused by a timing shift primarily exposed by commit 65565ca5f99b ("block: unify the synchronous bi_end_io callbacks"), along with helper refactorings like commit 92c3737a2473 ("block: add a bio_submit_or_kill helper"). But due to difficulty of reproducing this race, discussion about what is happening and how to fix this problem is stalling. Also, we haven't identified how many filesystems are subjected to this problem. Therefore, this patch introduces a grace period for flushing pending I/O requests (which should be a good thing from the perspective of defensive programming) so that we won't hit NULL pointer dereference problem, and also emits BUG: message in order to help filesystem developers identify the caller of an I/O request that failed to wait for completion so that filesystem developers can fix such caller to wait for completion. Note that emitting BUG: message is enabled only if CONFIG_KCOV=y, for this check is a waste of computation resources for almost all users. Since Al Viro is reporting a problem with xfs/259 [4], this time also emit debug messages when unmount fails with -EBUSY. Link: https://syzkaller.appspot.com/bug?extid=cd8a9a308e879a4e2c28 [1] Link: https://syzkaller.appspot.com/bug?extid=bc273027d5643e48e5b3 [2] Link: https://lkml.kernel.org/r/fbb3edda-f108-4e5b-acf2-266f043f8125@I-love.SAKURA.ne.jp [3] Link: https://lkml.kernel.org/r/20260609175013.GH2636677@ZenIV [4] Signed-off-by: Tetsuo Handa --- drivers/block/loop.c | 87 +++++++++++++++++++++++++++++++++++++++++++- fs/namespace.c | 5 +++ 2 files changed, 90 insertions(+), 2 deletions(-) diff --git a/drivers/block/loop.c b/drivers/block/loop.c index 310de0463beb..7408f314a1fa 100644 --- a/drivers/block/loop.c +++ b/drivers/block/loop.c @@ -85,8 +85,27 @@ struct loop_cmd { struct bio_vec *bvec; struct cgroup_subsys_state *blkcg_css; struct cgroup_subsys_state *memcg_css; +#ifdef CONFIG_KCOV + unsigned long stack_entries[30]; + int stack_nr; + pid_t pid; + char comm[TASK_COMM_LEN]; +#endif }; +static void loop_check_io_race(struct loop_device *lo, struct loop_cmd *cmd) +{ +#ifdef CONFIG_KCOV + if (unlikely(data_race(READ_ONCE(lo->lo_state)) == Lo_rundown && + disk_openers(lo->lo_disk) == 0)) { + pr_err("BUG: %s/%u is doing I/O request on loop%d in Lo_rundown state.\n", + cmd->comm, cmd->pid, lo->lo_number); + printk("Call Trace:\n"); + stack_trace_print(cmd->stack_entries, cmd->stack_nr, 4); + } +#endif +} + #define LOOP_IDLE_WORKER_TIMEOUT (60 * HZ) #define LOOP_DEFAULT_HW_Q_DEPTH 128 @@ -1743,8 +1762,63 @@ static void lo_release(struct gendisk *disk) need_clear = (lo->lo_state == Lo_rundown); mutex_unlock(&lo->lo_mutex); - if (need_clear) + if (need_clear) { + printk("Flush: task=%s[%d] dev=loop%d state=%d\n", + current->comm, current->pid, lo->lo_number, lo->lo_state); + /* + * Temporarily release disk->open_mutex in order to flush pending I/O + * requests before clearing the backing device. + * + * This is a layering violation. But since bdev->bd_disk->fops->release() + * (which is mapped to lo_release()) is the final function which + * blkdev_put_whole() from bdev_release() calls immediately before + * releasing disk->open_mutex, this changes nothing except opens a new + * race window for allowing disk->fops->open() (which is mapped to + * lo_open()) to be called. + * + * Even if lo_open() is called from blkdev_get_whole() due to this race, + * the Lo_rundown state guarantees that lo_open() will fail with -ENXIO. + * Thus, there will be effectively no change caused by this violation. + */ + mutex_unlock(&lo->lo_disk->open_mutex); + /* + * Now that loop_queue_rq() sees lo->lo_state != Lo_bound, + * wait for already started loop_queue_rq() to complete. + */ + synchronize_rcu(); + /* + * Now that no more works are scheduled by loop_queue_rq(), + * wait for already scheduled works to complete. + */ + drain_workqueue(lo->workqueue); + /* + * Now that no more AIO requests are scheduled by lo_rw_aio(), + * wait for already started AIO to complete. + * + * Due to synchronize_rcu() + drain_workqueue() sequence above, + * calling blk_mq_unfreeze_queue() immediately after blk_mq_freeze_queue() + * returns has to be safe, for loop_queue_rq() no longer schedules new + * lo_rw_aio() works and lo_rw_aio() no longer submits new AIO requests. + * + * Deferring blk_mq_unfreeze_queue() does not help because we are about + * to clear the backing device and drop the refcount for the backing device. + * There is nothing we can do if blk_mq_freeze_queue() fails to flush. + */ + blk_mq_unfreeze_queue(lo->lo_queue, blk_mq_freeze_queue(lo->lo_queue)); + /* + * Perform remaining cleanup, with disk->open_mutex held. + * + * The lo->lo_state should remain Lo_rundown despite we temporarily + * released disk->open_mutex, for I am the only and the last user of + * this loop device because lo_open() cannot succeed. + */ + mutex_lock(&lo->lo_disk->open_mutex); + if (WARN_ON(data_race(READ_ONCE(lo->lo_state)) != Lo_rundown)) + return; + printk("Teardown: task=%s[%d] dev=loop%d state=%d\n", + current->comm, current->pid, lo->lo_number, lo->lo_state); __loop_clr_fd(lo); + } } static void lo_free_disk(struct gendisk *disk) @@ -1851,10 +1925,18 @@ static blk_status_t loop_queue_rq(struct blk_mq_hw_ctx *hctx, struct loop_cmd *cmd = blk_mq_rq_to_pdu(rq); struct loop_device *lo = rq->q->queuedata; +#ifdef CONFIG_KCOV + cmd->stack_nr = stack_trace_save(cmd->stack_entries, ARRAY_SIZE(cmd->stack_entries), 0); + cmd->pid = current->pid; + get_task_comm(cmd->comm, current); +#endif + blk_mq_start_request(rq); - if (data_race(READ_ONCE(lo->lo_state)) != Lo_bound) + if (data_race(READ_ONCE(lo->lo_state)) != Lo_bound) { + loop_check_io_race(lo, cmd); return BLK_STS_IOERR; + } switch (req_op(rq)) { case REQ_OP_FLUSH: @@ -1897,6 +1979,7 @@ static void loop_handle_cmd(struct loop_cmd *cmd) int ret = 0; struct mem_cgroup *old_memcg = NULL; + loop_check_io_race(lo, cmd); if (write && (lo->lo_flags & LO_FLAGS_READ_ONLY)) { ret = -EIO; goto failed; diff --git a/fs/namespace.c b/fs/namespace.c index 3d5cd5bf3b05..c040b755ac7d 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -1893,6 +1893,8 @@ static int do_umount(struct mount *mnt, int flags) */ lock_mount_hash(); if (!list_empty(&mnt->mnt_mounts) || mnt_get_count(mnt) != 2) { + printk("%s: task=%s[%d] !list_empty(&mnt->mnt_mounts)=%d mnt_get_count(mnt)=%d\n", __func__, + current->comm, current->pid, !list_empty(&mnt->mnt_mounts), mnt_get_count(mnt)); unlock_mount_hash(); return -EBUSY; } @@ -1960,6 +1962,9 @@ static int do_umount(struct mount *mnt, int flags) if (!propagate_mount_busy(mnt, 2)) { umount_tree(mnt, UMOUNT_PROPAGATE|UMOUNT_SYNC); retval = 0; + } else { + printk("%s: task=%s[%d] propagate_mount_busy()!=0\n", __func__, + current->comm, current->pid); } } out: From 118c9c57c9a4b28209489521da229b58b43458ec Mon Sep 17 00:00:00 2001 From: Andreas Kemnade Date: Tue, 16 Jun 2026 19:51:52 +0200 Subject: [PATCH 062/562] ARM: omap2plus_defconfig: enable things required by iwd Several crypto related things are missing for opreation of iwd, turn them on according to the list being printed out. :~# /usr/libexec/iwd & :~# No HMAC(SHA1) support found No HMAC(MD5) support found No CMAC(AES) support found No HMAC(SHA256) support not found No HMAC(SHA512) support found, certain TLS connections might fail DES support not found AES support not found No CBC(DES3_EDE) support found, certain TLS connections might fail No CBC(AES) support found, WPS will not be available No Diffie-Hellman support found, WPS will not be available The following options are missing in the kernel: CONFIG_CRYPTO_USER_API_HASH CONFIG_CRYPTO_USER_API_SKCIPHER CONFIG_KEY_DH_OPERATIONS CONFIG_CRYPTO_ECB CONFIG_CRYPTO_MD5 CONFIG_CRYPTO_CBC CONFIG_CRYPTO_SHA256 CONFIG_CRYPTO_AES CONFIG_CRYPTO_DES CONFIG_CRYPTO_CMAC CONFIG_CRYPTO_HMAC CONFIG_CRYPTO_SHA512 CONFIG_CRYPTO_SHA1 Apparently missing USER_API_SKCIPHER did also hide some things for iwd. Signed-off-by: Andreas Kemnade Link: https://patch.msgid.link/20260616175152.1373709-1-andreas@kemnade.info Signed-off-by: Kevin Hilman (TI) --- arch/arm/configs/omap2plus_defconfig | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/arch/arm/configs/omap2plus_defconfig b/arch/arm/configs/omap2plus_defconfig index ad5ae1636dee..fa6fb8b27f93 100644 --- a/arch/arm/configs/omap2plus_defconfig +++ b/arch/arm/configs/omap2plus_defconfig @@ -257,6 +257,9 @@ CONFIG_RXKAD=y CONFIG_CFG80211=m CONFIG_CFG80211_WEXT=y CONFIG_MAC80211=m +CONFIG_RFKILL=m +CONFIG_RFKILL_INPUT=y +CONFIG_RFKILL_GPIO=m CONFIG_PCI=y CONFIG_PCI_MSI=y CONFIG_PCI_DRA7XX_EP=y @@ -703,7 +706,15 @@ CONFIG_NFS_V4=y CONFIG_ROOT_NFS=y CONFIG_NLS_CODEPAGE_437=y CONFIG_NLS_ISO8859_1=y +CONFIG_KEY_DH_OPERATIONS=y CONFIG_SECURITY=y +CONFIG_CRYPTO_DH_RFC7919_GROUPS=y +CONFIG_CRYPTO_DES=m +CONFIG_CRYPTO_DEFLATE=y +CONFIG_CRYPTO_LZO=y +CONFIG_CRYPTO_ZSTD=y +CONFIG_CRYPTO_USER_API_HASH=m +CONFIG_CRYPTO_USER_API_SKCIPHER=m CONFIG_CRYPTO_GHASH_ARM_CE=m CONFIG_CRYPTO_AES=m CONFIG_CRYPTO_AES_ARM_BS=m From 6c3b60493693b7681bda206515a82cd3f99af543 Mon Sep 17 00:00:00 2001 From: Matthew Leung Date: Thu, 25 Jun 2026 20:38:58 +0000 Subject: [PATCH 063/562] dt-bindings: PCI: qcom: Document the Hawi PCIe Controller Add a dedicated schema for the PCIe controllers found on the Hawi platform. Signed-off-by: Matthew Leung [mani: added MAINTAINERS entry] Signed-off-by: Manivannan Sadhasivam Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260625-hawi-pcie-v4-1-1a578603cd86@oss.qualcomm.com --- .../bindings/pci/qcom,hawi-pcie.yaml | 196 ++++++++++++++++++ MAINTAINERS | 1 + 2 files changed, 197 insertions(+) create mode 100644 Documentation/devicetree/bindings/pci/qcom,hawi-pcie.yaml diff --git a/Documentation/devicetree/bindings/pci/qcom,hawi-pcie.yaml b/Documentation/devicetree/bindings/pci/qcom,hawi-pcie.yaml new file mode 100644 index 000000000000..2c999ca6b205 --- /dev/null +++ b/Documentation/devicetree/bindings/pci/qcom,hawi-pcie.yaml @@ -0,0 +1,196 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/pci/qcom,hawi-pcie.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm Hawi PCI Express Root Complex + +maintainers: + - Bjorn Andersson + - Manivannan Sadhasivam + +description: + Qualcomm Hawi SoC (and compatible) PCIe root complex controller is based on + the Synopsys DesignWare PCIe IP. + +properties: + compatible: + const: qcom,hawi-pcie + + reg: + items: + - description: Qualcomm specific registers + - description: DesignWare PCIe registers + - description: External local bus interface registers + - description: ATU address space + - description: PCIe configuration space + - description: MHI registers + + reg-names: + items: + - const: parf + - const: dbi + - const: elbi + - const: atu + - const: config + - const: mhi + + clocks: + items: + - description: PCIe Auxiliary clock + - description: PCIe Configuration clock + - description: PCIe Master AXI clock + - description: PCIe Slave AXI clock + - description: PCIe Slave Q2A AXI clock + - description: PCIe Aggre NoC AXI clock + - description: PCIe Config NoC AXI clock + + clock-names: + items: + - const: aux + - const: cfg + - const: bus_master + - const: bus_slave + - const: slave_q2a + - const: noc_aggr + - const: cnoc_sf_axi + + interrupts: + minItems: 9 + maxItems: 9 + + interrupt-names: + items: + - const: msi0 + - const: msi1 + - const: msi2 + - const: msi3 + - const: msi4 + - const: msi5 + - const: msi6 + - const: msi7 + - const: global + + resets: + items: + - description: PCIe core reset + - description: PCIe link down reset + + reset-names: + items: + - const: pci + - const: link_down + +required: + - power-domains + - resets + - reset-names + +allOf: + - $ref: qcom,pcie-common.yaml# + +unevaluatedProperties: false + +examples: + - | + #include + #include + #include + + soc { + #address-cells = <2>; + #size-cells = <2>; + + pcie@1c00000 { + compatible = "qcom,hawi-pcie"; + reg = <0 0x01c00000 0 0x3000>, + <0 0x40000000 0 0xf1d>, + <0 0x40000f20 0 0xa8>, + <0 0x40001000 0 0x1000>, + <0 0x40100000 0 0x100000>, + <0 0x01c03000 0 0x1000>; + reg-names = "parf", "dbi", "elbi", "atu", "config", "mhi"; + ranges = <0x01000000 0x0 0x00000000 0x0 0x40200000 0x0 0x100000>, + <0x02000000 0x0 0x40300000 0x0 0x40300000 0x0 0x3d00000>; + + bus-range = <0x00 0xff>; + device_type = "pci"; + linux,pci-domain = <0>; + num-lanes = <2>; + + #address-cells = <3>; + #size-cells = <2>; + + clocks = <&gcc_pcie_0_aux_clk>, + <&gcc_pcie_0_cfg_ahb_clk>, + <&gcc_pcie_0_mstr_axi_clk>, + <&gcc_pcie_0_slv_axi_clk>, + <&gcc_pcie_0_slv_q2a_axi_clk>, + <&gcc_aggre_noc_pcie_axi_clk>, + <&gcc_cnoc_pcie_sf_axi_clk>; + clock-names = "aux", + "cfg", + "bus_master", + "bus_slave", + "slave_q2a", + "noc_aggr", + "cnoc_sf_axi"; + + dma-coherent; + + interrupts = , + , + , + , + , + , + , + , + ; + interrupt-names = "msi0", "msi1", "msi2", "msi3", + "msi4", "msi5", "msi6", "msi7", "global"; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 0x7>; + interrupt-map = <0 0 0 1 &intc 0 0 GIC_ESPI 213 IRQ_TYPE_LEVEL_HIGH>, /* int_a */ + <0 0 0 2 &intc 0 0 GIC_ESPI 214 IRQ_TYPE_LEVEL_HIGH>, /* int_b */ + <0 0 0 3 &intc 0 0 GIC_ESPI 215 IRQ_TYPE_LEVEL_HIGH>, /* int_c */ + <0 0 0 4 &intc 0 0 GIC_ESPI 216 IRQ_TYPE_LEVEL_HIGH>; /* int_d */ + + interconnects = <&pcie_anoc_master_pcie_0 QCOM_ICC_TAG_ALWAYS + &mc_virt_slave_ebi1 QCOM_ICC_TAG_ALWAYS>, + <&gem_noc_master_appss_proc QCOM_ICC_TAG_ACTIVE_ONLY + &cnoc_main_slave_pcie_0 QCOM_ICC_TAG_ACTIVE_ONLY>; + interconnect-names = "pcie-mem", "cpu-pcie"; + + iommu-map = <0x0 &apps_smmu 0x1000 0x1>, + <0x100 &apps_smmu 0x1001 0x1>; + + pinctrl-0 = <&pcie0_default_state>; + pinctrl-names = "default"; + + power-domains = <&gcc_pcie_0_phy_gdsc>; + + resets = <&gcc_pcie_0_bcr>, + <&gcc_pcie_0_link_down_bcr>; + reset-names = "pci", "link_down"; + + msi-map = <0x0 &gic_its 0x1000 0x1>, + <0x100 &gic_its 0x1001 0x1>; + msi-map-mask = <0xff00>; + + pcie@0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + bus-range = <0x01 0xff>; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + phys = <&pcie0_phy>; + wake-gpios = <&tlmm 104 GPIO_ACTIVE_HIGH>; + reset-gpios = <&tlmm 102 GPIO_ACTIVE_LOW>; + }; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 15011f5752a9..1b213e9571af 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -21011,6 +21011,7 @@ M: Manivannan Sadhasivam L: linux-pci@vger.kernel.org L: linux-arm-msm@vger.kernel.org S: Maintained +F: Documentation/devicetree/bindings/pci/qcom,*.yaml F: drivers/pci/controller/dwc/pcie-qcom-common.c F: drivers/pci/controller/dwc/pcie-qcom.c From 67db6fece5db59c5fd0d7c0a514eac5c7f37be8a Mon Sep 17 00:00:00 2001 From: Matthew Leung Date: Thu, 25 Jun 2026 20:38:59 +0000 Subject: [PATCH 064/562] PCI: qcom: Add support for Hawi Add support for the Hawi platform which has two PCIe controllers: one capable of Gen3 x2 operation and one capable of Gen4 x1 operation. Signed-off-by: Matthew Leung Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260625-hawi-pcie-v4-2-1a578603cd86@oss.qualcomm.com --- drivers/pci/controller/dwc/pcie-qcom.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index d8eb52857f69..89ae006fb6c3 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -2282,6 +2282,7 @@ static int qcom_pcie_resume_noirq(struct device *dev) } static const struct of_device_id qcom_pcie_match[] = { + { .compatible = "qcom,hawi-pcie", .data = &cfg_1_9_0 }, { .compatible = "qcom,pcie-apq8064", .data = &cfg_2_1_0 }, { .compatible = "qcom,pcie-apq8084", .data = &cfg_1_0_0 }, { .compatible = "qcom,pcie-ipq4019", .data = &cfg_2_4_0 }, From 1133089b07785841258623e6337c9d89d7913638 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Tue, 16 Jun 2026 21:45:19 +0530 Subject: [PATCH 065/562] PCI: qcom: Skip PERST# GPIOs provided by downstream PCIe devices Currently, the pcie-qcom driver recursively parses the PERST# GPIO from all child nodes defined in DT and acquires them. But this creates issues with PERST# GPIO provided by one of the child devices like the PCIe switch port. In this case, the RC driver cannot acquire the PERST# GPIO since it will be provided by the child PCIe device which was not yet enumerated during RC driver probe. Fix this by checking if the GPIO provider is a child of the RC's DT node (i.e., sits behind this PCIe controller). If so, skip it, as PERST# should be controlled by the respective PCIe client driver implementation. GPIOs provided by external GPIO controllers (e.g., TLMM in Qcom SoCs) continue to be handled normally. Fixes: 2fd60a2edb83 ("PCI: qcom: Parse PERST# from all PCIe bridge nodes") Signed-off-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260616-pci-qcom-perst-fix-v1-1-27600d6ae357@oss.qualcomm.com --- drivers/pci/controller/dwc/pcie-qcom.c | 37 ++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index 89ae006fb6c3..b193c989b2b8 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -1820,6 +1820,23 @@ static const struct pci_ecam_ops pci_qcom_ecam_ops = { } }; +/* Check if @node is a child of @dev in DT */ +static bool qcom_pcie_is_child_node(struct device *dev, + struct device_node *node) +{ + struct device_node *parent; + + for (parent = of_get_parent(node); parent; + parent = of_get_next_parent(parent)) { + if (parent == dev->of_node) { + of_node_put(parent); + return true; + } + } + + return false; +} + /* Parse PERST# from all nodes in depth first manner starting from @np */ static int qcom_pcie_parse_perst(struct qcom_pcie *pcie, struct qcom_pcie_port *port, @@ -1827,6 +1844,7 @@ static int qcom_pcie_parse_perst(struct qcom_pcie *pcie, { struct device *dev = pcie->pci->dev; struct qcom_pcie_perst *perst; + struct device_node *gpio_np; struct gpio_desc *reset; int ret; @@ -1840,6 +1858,25 @@ static int qcom_pcie_parse_perst(struct qcom_pcie *pcie, if (!of_find_property(np, "reset-gpios", NULL)) goto parse_child_node; + /* + * Skip GPIOs provided by a PCIe device which is a child of the Root + * Complex (e.g., a PCIe switch with GPIO controller capability). Such + * controllers won't be available at RC probe time and their PERST# + * should be controlled by the respective PCI client driver + * implementation. + */ + gpio_np = of_parse_phandle(np, "reset-gpios", 0); + if (!gpio_np) { + dev_err(dev, "Failed to parse GPIO provider\n"); + return -EINVAL; + } + + if (qcom_pcie_is_child_node(dev, gpio_np)) { + of_node_put(gpio_np); + goto parse_child_node; + } + of_node_put(gpio_np); + reset = devm_fwnode_gpiod_get(dev, of_fwnode_handle(np), "reset", GPIOD_OUT_HIGH, "PERST#"); if (IS_ERR(reset)) { From 4799d62294366557277a02220fd5d47804d98f5b Mon Sep 17 00:00:00 2001 From: Ahmed Yaseen Date: Tue, 19 May 2026 18:12:13 +0000 Subject: [PATCH 066/562] platform/x86: asus-armoury: gate PPT writes behind active fan curve MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On models flagged with requires_fan_curve in the DMI power_data table (30 entries), the BIOS ACPI method SPLX only writes PPT values to the EC when the fan mode is set to Manual (FANM=4). FANM is set to 4 by the DEFC method when a custom fan curve is written. Without an active custom fan curve, the WMI DEVS call returns success but the firmware silently ignores the PPT value, so userspace observes no effect from its write. Gate writes to ASUS_WMI_DEVID_PPT_{PL1_SPL,PL2_SPPT,PL3_FPPT,APU_SPPT, PLAT_SPPT} on a check of asus_wmi_custom_fan_curve_is_enabled(), and return -EBUSY with a pr_warn_once() when no fan curve is active on an affected model. Export the helper from asus-wmi so asus-armoury can call it across module boundaries. Signed-off-by: Ahmed Yaseen Reviewed-by: Denis Benato Link: https://patch.msgid.link/20260519181155.46044-2-yaseen@ghoul.dev Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-armoury.c | 20 ++++++++++++++++++ drivers/platform/x86/asus-wmi.c | 24 ++++++++++++++++++++++ include/linux/platform_data/x86/asus-wmi.h | 5 +++++ 3 files changed, 49 insertions(+) diff --git a/drivers/platform/x86/asus-armoury.c b/drivers/platform/x86/asus-armoury.c index 495dc1e31d40..f2a880eb0cdf 100644 --- a/drivers/platform/x86/asus-armoury.c +++ b/drivers/platform/x86/asus-armoury.c @@ -93,6 +93,8 @@ struct asus_armoury_priv { u32 mini_led_dev_id; u32 gpu_mux_dev_id; + + bool requires_fan_curve; }; static struct asus_armoury_priv asus_armoury = { @@ -216,6 +218,22 @@ static int armoury_set_devstate(struct kobj_attribute *attr, u32 result; int err; + /* On some models, PPT changes require an active fan curve */ + if (asus_armoury.requires_fan_curve) { + switch (dev_id) { + case ASUS_WMI_DEVID_PPT_PL1_SPL: + case ASUS_WMI_DEVID_PPT_PL2_SPPT: + case ASUS_WMI_DEVID_PPT_PL3_FPPT: + case ASUS_WMI_DEVID_PPT_APU_SPPT: + case ASUS_WMI_DEVID_PPT_PLAT_SPPT: + if (!asus_wmi_custom_fan_curve_is_enabled()) { + pr_warn_once("PPT change requires an active fan curve on this model. Enable a custom fan curve first.\n"); + return -EBUSY; + } + break; + } + } + /* * Prevent developers from bricking devices or issuing dangerous * commands that can be difficult or impossible to recover from. @@ -1010,6 +1028,8 @@ static void init_rog_tunables(void) return; } + asus_armoury.requires_fan_curve = power_data->requires_fan_curve; + /* Initialize AC power tunables */ ac_limits = power_data->ac_data; if (ac_limits) { diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 3c9ef826551d..c7c8fcfc1d72 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -4053,6 +4053,30 @@ static int asus_wmi_custom_fan_curve_init(struct asus_wmi *asus) return 0; } +/* + * Returns true if at least one custom fan curve is active + * + * Used by asus-armoury to check if PPT writes will be accepted by the BIOS + * on models that require an active fan curve for TDP changes. + */ +bool asus_wmi_custom_fan_curve_is_enabled(void) +{ + struct fan_curve_data *curves; + struct asus_wmi *asus; + + guard(spinlock_irqsave)(&asus_ref.lock); + asus = asus_ref.asus; + if (!asus) + return false; + + curves = asus->custom_fan_curves; + + return (asus->cpu_fan_curve_available && curves[FAN_CURVE_DEV_CPU].enabled) || + (asus->gpu_fan_curve_available && curves[FAN_CURVE_DEV_GPU].enabled) || + (asus->mid_fan_curve_available && curves[FAN_CURVE_DEV_MID].enabled); +} +EXPORT_SYMBOL_NS_GPL(asus_wmi_custom_fan_curve_is_enabled, "ASUS_WMI"); + /* Throttle thermal policy ****************************************************/ static int throttle_thermal_policy_write(struct asus_wmi *asus) { diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h index c29962d5baac..b5ed8c83ace1 100644 --- a/include/linux/platform_data/x86/asus-wmi.h +++ b/include/linux/platform_data/x86/asus-wmi.h @@ -203,6 +203,7 @@ int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval); int asus_hid_register_listener(struct asus_hid_listener *cdev); void asus_hid_unregister_listener(struct asus_hid_listener *cdev); int asus_hid_event(enum asus_hid_event event); +bool asus_wmi_custom_fan_curve_is_enabled(void); #else static inline void set_ally_mcu_hack(enum asus_ally_mcu_hack status) { @@ -234,6 +235,10 @@ static inline int asus_hid_event(enum asus_hid_event event) { return -ENODEV; } +static inline bool asus_wmi_custom_fan_curve_is_enabled(void) +{ + return false; +} #endif #endif /* __PLATFORM_DATA_X86_ASUS_WMI_H */ From 2f70f3d2fd3d9e0e36348386a660f77d19a294a6 Mon Sep 17 00:00:00 2001 From: David Glushkov Date: Thu, 28 May 2026 20:33:58 +0200 Subject: [PATCH 067/562] platform/x86: msi-ec: Add MSI Raider A18 HX A9WJG EC firmware MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for EC firmware 182LIMS1.111, found on the MSI Raider A18 HX A9WJG. The out-of-tree msi-ec driver probes successfully on this machine and exports the msi-ec platform device. Without this entry, the in-tree driver rejects the EC firmware as unsupported. Tested on MSI Raider A18 HX A9WJG with BIOS E182LAMS.31A. Signed-off-by: David Glushkov Link: https://patch.msgid.link/20260528183358.552782-1-david.glushkov@sntiq.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/msi-ec.c | 81 +++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/drivers/platform/x86/msi-ec.c b/drivers/platform/x86/msi-ec.c index 0157e233e430..f963d68c6d84 100644 --- a/drivers/platform/x86/msi-ec.c +++ b/drivers/platform/x86/msi-ec.c @@ -1129,6 +1129,86 @@ static struct msi_ec_conf CONF13 __initdata = { }, }; +static const char * const ALLOWED_FW_14[] __initconst = { + "1824EMS1.108", // Raider 18 HX AI A2XWJG (MS-1824) + "182LIMS1.108", // Vector A18 HX A9WHG + "182LIMS1.111", // MSI Raider A18 HX A9WJG / Vector A18 HX A9WHG + "182KIMS1.113", // Raider A18 HX A7VIG + NULL +}; + +static struct msi_ec_conf CONF14 __initdata = { + .allowed_fw = ALLOWED_FW_14, + .charge_control = { + .address = 0xd7, + .offset_start = 0x8a, + .offset_end = 0x80, + .range_min = 0x8a, + .range_max = 0xe4, + }, + .webcam = { + .address = 0x2e, + .block_address = 0x2f, + .bit = 1, + }, + .fn_win_swap = { + .address = 0xe8, + .bit = 4, + }, + .cooler_boost = { + .address = 0x98, + .bit = 7, + }, + .shift_mode = { + .address = 0xd2, + .modes = { + { SM_ECO_NAME, 0xc2 }, + { SM_COMFORT_NAME, 0xc1 }, + { SM_TURBO_NAME, 0xc4 }, + MSI_EC_MODE_NULL + }, + }, + .super_battery = { + .address = 0xeb, + .mask = 0x0f, + }, + .fan_mode = { + .address = 0xd4, + .modes = { + { FM_AUTO_NAME, 0x0d }, + { FM_SILENT_NAME, 0x1d }, + { FM_ADVANCED_NAME, 0x8d }, + MSI_EC_MODE_NULL + }, + }, + .cpu = { + .rt_temp_address = 0x68, + .rt_fan_speed_address = 0x71, + .rt_fan_speed_base_min = 0x00, + .rt_fan_speed_base_max = 0x96, + .bs_fan_speed_address = MSI_EC_ADDR_UNSUPP, + .bs_fan_speed_base_min = 0x00, + .bs_fan_speed_base_max = 0x0f, + }, + .gpu = { + .rt_temp_address = 0x80, + .rt_fan_speed_address = 0x89, + }, + .leds = { + .micmute_led_address = 0x2c, + .mute_led_address = 0x2d, + .bit = 1, + }, + .kbd_bl = { + .bl_mode_address = MSI_EC_ADDR_UNSUPP, + .bl_modes = { 0x00, 0x08 }, + .max_mode = 1, + .bl_state_address = MSI_EC_ADDR_UNSUPP, + .state_base_value = 0x80, + .max_state = 3, + }, +}; + static struct msi_ec_conf *CONFIGS[] __initdata = { &CONF0, &CONF1, @@ -1144,6 +1224,7 @@ static struct msi_ec_conf *CONFIGS[] __initdata = { &CONF11, &CONF12, &CONF13, + &CONF14, NULL }; From 2d79283eec8dbdca246ca22154d84cebcb2780c4 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Sat, 30 May 2026 19:08:07 +0200 Subject: [PATCH 068/562] platform/x86: uniwill-laptop: Add keyboard backlight support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Many Uniwill-based devices support either a white-only or fully features RGB keyboard backlight. Add support for this feature and handle the associated WMI events. Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260530170813.10166-2-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- .../admin-guide/laptops/uniwill-laptop.rst | 13 + drivers/platform/x86/uniwill/uniwill-acpi.c | 392 +++++++++++++++++- 2 files changed, 397 insertions(+), 8 deletions(-) diff --git a/Documentation/admin-guide/laptops/uniwill-laptop.rst b/Documentation/admin-guide/laptops/uniwill-laptop.rst index 24b41dbab886..2b1e5da703a5 100644 --- a/Documentation/admin-guide/laptops/uniwill-laptop.rst +++ b/Documentation/admin-guide/laptops/uniwill-laptop.rst @@ -77,6 +77,19 @@ LED class device. The default name of this LED class device is ``uniwill:multico See Documentation/ABI/testing/sysfs-driver-uniwill-laptop for details on how to control the various animation modes of the lightbar. +Keyboard Backlight +------------------ + +The ``uniwill-laptop`` driver supports controlling the keyboard backlight using the standard +LED class interface. The default name of this LED class device is ``uniwill:white:kbd_backlight`` +when the keyboard backlight supports only a single color, or ``uniwill:multicolor:kbd_backlight`` +when the keyboard backlight supports RGB colors. The maximum intensity for each color channel +in RGB mode is 50. + +Keep in mind that due to hardware design choices, the driver does not support the RGB value +``0x000000`` (black), instead it will fall back to ``0x010101`` (faint white). In order to +disable the keyboard backlight, the standard LED brightness setting has to be used instead. + Configurable TGP ---------------- diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index ab063ead45b9..fd040197b189 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -110,8 +110,31 @@ #define EC_ADDR_BAT_CYCLE_COUNT_2 0x04A7 #define EC_ADDR_PROJECT_ID 0x0740 +#define PROJECT_ID_NONE 0x00 +#define PROJECT_ID_GI 0x01 +#define PROJECT_ID_GJ 0x02 +#define PROJECT_ID_GK 0x03 +#define PROJECT_ID_GICN 0x04 +#define PROJECT_ID_GJCN 0x05 +#define PROJECT_ID_GK5CN_X 0x06 +#define PROJECT_ID_GK7CN_S 0x07 +#define PROJECT_ID_GK7CPCS_GK5CQ7Z 0x08 +#define PROJECT_ID_PF 0x09 +#define PROJECT_ID_GK5CP_4X_5X_6X 0x0A +#define PROJECT_ID_IDP 0x0B +#define PROJECT_ID_IDY_6Y 0x0C +#define PROJECT_ID_IDY_7Y 0x0D +#define PROJECT_ID_PF4MU_PF4MN_PF5MU 0x0E +#define PROJECT_ID_CML_GAMING 0x0F +#define PROJECT_ID_GK7NXXR 0x10 +#define PROJECT_ID_GM5MU1Y 0x11 #define PROJECT_ID_PH4TRX1 0x12 +#define PROJECT_ID_PH4TUX1 0x13 +#define PROJECT_ID_PH4TQX1 0x14 #define PROJECT_ID_PH6TRX1 0x15 +#define PROJECT_ID_PH6TQXX 0x16 +#define PROJECT_ID_PHXAXXX 0x17 +#define PROJECT_ID_PHXPXXX 0x18 #define EC_ADDR_AP_OEM 0x0741 #define ENABLE_MANUAL_CTRL BIT(0) @@ -214,6 +237,7 @@ #define FAN_TABLE_OFFICE_MODE BIT(2) #define FAN_V3 BIT(3) #define DEFAULT_MODE BIT(4) +#define ENABLE_CHINA_MODE BIT(6) #define EC_ADDR_PL1_SETTING 0x0783 @@ -225,11 +249,11 @@ #define FAN_CURVE_LENGTH 5 #define EC_ADDR_KBD_STATUS 0x078C -#define KBD_WHITE_ONLY BIT(0) // ~single color -#define KBD_SINGLE_COLOR_OFF BIT(1) +#define KBD_WHITE_ONLY BIT(0) +#define KBD_POWER_OFF BIT(1) #define KBD_TURBO_LEVEL_MASK GENMASK(3, 2) #define KBD_APPLY BIT(4) -#define KBD_BRIGHTNESS GENMASK(7, 5) +#define KBD_BRIGHTNESS_MASK GENMASK(7, 5) #define EC_ADDR_FAN_CTRL 0x078E #define FAN3P5 BIT(1) @@ -320,6 +344,9 @@ #define LED_CHANNELS 3 #define LED_MAX_BRIGHTNESS 200 +#define KBD_LED_CHANNELS 3 +#define KBD_LED_MAX_INTENSITY 50 + #define UNIWILL_FEATURE_FN_LOCK BIT(0) #define UNIWILL_FEATURE_SUPER_KEY BIT(1) #define UNIWILL_FEATURE_TOUCHPAD_TOGGLE BIT(2) @@ -333,6 +360,7 @@ #define UNIWILL_FEATURE_SECONDARY_FAN BIT(9) #define UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL BIT(10) #define UNIWILL_FEATURE_USB_C_POWER_PRIORITY BIT(11) +#define UNIWILL_FEATURE_KEYBOARD_BACKLIGHT BIT(12) enum usb_c_power_priority_options { USB_C_POWER_PRIORITY_CHARGING = 0, @@ -344,6 +372,7 @@ struct uniwill_data { acpi_handle handle; struct regmap *regmap; unsigned int features; + u8 project_id; struct acpi_battery_hook hook; struct mutex battery_lock; /* Protects the list of currently registered batteries */ union { @@ -362,6 +391,18 @@ struct uniwill_data { struct mutex led_lock; /* Protects writes to the lightbar registers */ struct led_classdev_mc led_mc_cdev; struct mc_subled led_mc_subled_info[LED_CHANNELS]; + bool single_color_kbd; + u8 kbd_led_max_brightness; + unsigned int last_kbd_status; + union { + struct { + /* Protects writes to the RGB keyboard backlight registers */ + struct mutex kbd_rgb_led_lock; + struct led_classdev_mc kbd_led_mc_cdev; + struct mc_subled kbd_led_mc_subled_info[KBD_LED_CHANNELS]; + }; + struct led_classdev kbd_led_cdev; + }; struct mutex input_lock; /* Protects input sequence during notify */ struct input_dev *input_device; struct notifier_block nb; @@ -376,6 +417,7 @@ struct uniwill_battery_entry { struct uniwill_device_descriptor { unsigned int features; + u8 kbd_led_max_brightness; /* Executed during driver probing */ int (*probe)(struct uniwill_data *data); }; @@ -427,6 +469,9 @@ static const struct key_entry uniwill_keymap[] = { { KE_KEY, UNIWILL_OSD_KBDILLUMDOWN, { KEY_KBDILLUMDOWN }}, { KE_KEY, UNIWILL_OSD_KBDILLUMUP, { KEY_KBDILLUMUP }}, + /* Reported when the EC changed the keyboard backlight brightness */ + { KE_IGNORE, UNIWILL_OSD_BACKLIGHT_LEVEL_CHANGE, { KEY_UNKNOWN }}, + /* Reported when the user wants to toggle the microphone mute status */ { KE_KEY, UNIWILL_OSD_MIC_MUTE, { KEY_MICMUTE }}, @@ -435,11 +480,6 @@ static const struct key_entry uniwill_keymap[] = { /* Reported when the user wants to toggle the brightness of the keyboard */ { KE_KEY, UNIWILL_OSD_KBDILLUMTOGGLE, { KEY_KBDILLUMTOGGLE }}, - { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL0, { KEY_KBDILLUMTOGGLE }}, - { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL1, { KEY_KBDILLUMTOGGLE }}, - { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL2, { KEY_KBDILLUMTOGGLE }}, - { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL3, { KEY_KBDILLUMTOGGLE }}, - { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL4, { KEY_KBDILLUMTOGGLE }}, /* FIXME: find out the exact meaning of those events */ { KE_IGNORE, UNIWILL_OSD_BAT_CHARGE_FULL_24_H, { KEY_UNKNOWN }}, @@ -547,6 +587,11 @@ static bool uniwill_writeable_reg(struct device *dev, unsigned int reg) case EC_ADDR_LIGHTBAR_AC_BLUE: case EC_ADDR_BIOS_OEM: case EC_ADDR_TRIGGER: + case EC_ADDR_RGB_RED: + case EC_ADDR_RGB_GREEN: + case EC_ADDR_RGB_BLUE: + case EC_ADDR_BIOS_OEM_2: + case EC_ADDR_KBD_STATUS: case EC_ADDR_OEM_4: case EC_ADDR_CHARGE_CTRL: case EC_ADDR_LIGHTBAR_BAT_CTRL: @@ -583,8 +628,14 @@ static bool uniwill_readable_reg(struct device *dev, unsigned int reg) case EC_ADDR_BIOS_OEM: case EC_ADDR_PWM_1: case EC_ADDR_PWM_2: + case EC_ADDR_SUPPORT_2: case EC_ADDR_TRIGGER: case EC_ADDR_SWITCH_STATUS: + case EC_ADDR_RGB_RED: + case EC_ADDR_RGB_GREEN: + case EC_ADDR_RGB_BLUE: + case EC_ADDR_BIOS_OEM_2: + case EC_ADDR_KBD_STATUS: case EC_ADDR_OEM_4: case EC_ADDR_CHARGE_CTRL: case EC_ADDR_LIGHTBAR_BAT_CTRL: @@ -616,8 +667,10 @@ static bool uniwill_volatile_reg(struct device *dev, unsigned int reg) case EC_ADDR_BIOS_OEM: case EC_ADDR_PWM_1: case EC_ADDR_PWM_2: + case EC_ADDR_SUPPORT_2: case EC_ADDR_TRIGGER: case EC_ADDR_SWITCH_STATUS: + case EC_ADDR_KBD_STATUS: case EC_ADDR_OEM_4: case EC_ADDR_CHARGE_CTRL: case EC_ADDR_USB_C_POWER_PRIORITY: @@ -1441,6 +1494,246 @@ static int uniwill_led_init(struct uniwill_data *data) &init_data); } +static int uniwill_notify_kbd_led(struct uniwill_data *data, int brightness) +{ + struct led_classdev *led_cdev; + int ret; + + if (data->single_color_kbd) + led_cdev = &data->kbd_led_cdev; + else + led_cdev = &data->kbd_led_mc_cdev.led_cdev; + + guard(mutex)(&led_cdev->led_access); + + /* Sync the LED brightness with the actual hardware state */ + ret = led_update_brightness(led_cdev); + if (ret < 0) + return ret; + + led_classdev_notify_brightness_hw_changed(led_cdev, brightness); + + return 0; +} + +#define KBD_LED_MASK (KBD_BRIGHTNESS_MASK | KBD_APPLY | KBD_POWER_OFF) + +static int uniwill_kbd_led_write_brightness(struct uniwill_data *data, int brightness) +{ + /* KBD_POWER_OFF is always implicitly cleared */ + unsigned int regval = FIELD_PREP(KBD_BRIGHTNESS_MASK, brightness) | KBD_APPLY; + + /* We must ensure that the "apply" bit is always written */ + return regmap_write_bits(data->regmap, EC_ADDR_KBD_STATUS, KBD_LED_MASK, regval); +} + +static int uniwill_kbd_led_read_brightness(struct uniwill_data *data) +{ + unsigned int regval; + int ret; + + ret = regmap_read(data->regmap, EC_ADDR_KBD_STATUS, ®val); + if (ret < 0) + return ret; + + return min(FIELD_GET(KBD_BRIGHTNESS_MASK, regval), data->kbd_led_max_brightness); +} + +static int uniwill_kbd_led_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct uniwill_data *data = container_of(led_cdev, struct uniwill_data, kbd_led_cdev); + + return uniwill_kbd_led_write_brightness(data, brightness); +} + +static enum led_brightness uniwill_kbd_led_brightness_get(struct led_classdev *led_cdev) +{ + struct uniwill_data *data = container_of(led_cdev, struct uniwill_data, kbd_led_cdev); + + return uniwill_kbd_led_read_brightness(data); +} + +static const unsigned int uniwill_kbd_led_channel_to_reg[KBD_LED_CHANNELS] = { + EC_ADDR_RGB_RED, + EC_ADDR_RGB_GREEN, + EC_ADDR_RGB_BLUE, +}; + +static int uniwill_kbd_led_mc_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct led_classdev_mc *led_mc_cdev = lcdev_to_mccdev(led_cdev); + struct uniwill_data *data = container_of(led_mc_cdev, struct uniwill_data, kbd_led_mc_cdev); + unsigned int min_intensity = 0; + unsigned int regval; + int ret; + + guard(mutex)(&data->kbd_rgb_led_lock); + + /* + * The EC interprets a RGB value of 0x000000 as a command to restore + * the device-specfic default RGB value. Work around this by writing + * a RGB value of 0x010101 (faint white) instead. + */ + if (data->kbd_led_mc_subled_info[0].intensity == 0 && + data->kbd_led_mc_subled_info[1].intensity == 0 && + data->kbd_led_mc_subled_info[2].intensity == 0) + min_intensity = 1; + + for (int i = 0; i < KBD_LED_CHANNELS; i++) { + regval = max(data->kbd_led_mc_subled_info[i].intensity, min_intensity); + ret = regmap_write(data->regmap, uniwill_kbd_led_channel_to_reg[i], regval); + if (ret < 0) + return ret; + } + + ret = regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, RGB_APPLY_COLOR, RGB_APPLY_COLOR); + if (ret < 0) + return ret; + + return uniwill_kbd_led_write_brightness(data, brightness); +} + +static enum led_brightness uniwill_kbd_led_mc_brightness_get(struct led_classdev *led_cdev) +{ + struct led_classdev_mc *led_mc_cdev = lcdev_to_mccdev(led_cdev); + struct uniwill_data *data = container_of(led_mc_cdev, struct uniwill_data, kbd_led_mc_cdev); + + return uniwill_kbd_led_read_brightness(data); +} + +static int uniwill_kbd_led_init(struct uniwill_data *data) +{ + unsigned int color_indices[KBD_LED_CHANNELS] = { + LED_COLOR_ID_RED, + LED_COLOR_ID_GREEN, + LED_COLOR_ID_BLUE, + }; + struct led_init_data init_data = { + .devicename = DRIVER_NAME, + .devname_mandatory = true, + }; + bool intensity_all_zeros = true; + bool needs_trigger = false; + unsigned int regval; + int ret; + + if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) + return 0; + + ret = regmap_read(data->regmap, EC_ADDR_SUPPORT_2, ®val); + if (ret < 0) + return ret; + + if (!(regval & CHINA_MODE)) { + ret = regmap_set_bits(data->regmap, EC_ADDR_BIOS_OEM_2, ENABLE_CHINA_MODE); + if (ret < 0) + return ret; + } + + ret = regmap_read(data->regmap, EC_ADDR_KBD_STATUS, ®val); + if (ret < 0) + return ret; + + regval |= KBD_APPLY; + regval &= ~KBD_POWER_OFF; + ret = regmap_write(data->regmap, EC_ADDR_KBD_STATUS, regval); + if (ret < 0) + return ret; + + switch (data->project_id) { + case PROJECT_ID_PF: + case PROJECT_ID_PF4MU_PF4MN_PF5MU: + case PROJECT_ID_PH4TRX1: + case PROJECT_ID_PH4TUX1: + case PROJECT_ID_PH4TQX1: + case PROJECT_ID_PH6TRX1: + case PROJECT_ID_PH6TQXX: + case PROJECT_ID_PHXAXXX: + case PROJECT_ID_PHXPXXX: + data->single_color_kbd = true; + break; + default: + data->single_color_kbd = regval & KBD_WHITE_ONLY; + break; + } + + if (data->single_color_kbd) { + init_data.default_label = "white:" LED_FUNCTION_KBD_BACKLIGHT; + data->kbd_led_cdev.max_brightness = data->kbd_led_max_brightness; + data->kbd_led_cdev.color = LED_COLOR_ID_WHITE; + data->kbd_led_cdev.flags = LED_BRIGHT_HW_CHANGED | LED_REJECT_NAME_CONFLICT; + data->kbd_led_cdev.brightness_set_blocking = uniwill_kbd_led_brightness_set; + data->kbd_led_cdev.brightness_get = uniwill_kbd_led_brightness_get; + + return devm_led_classdev_register_ext(data->dev, &data->kbd_led_cdev, &init_data); + } + + for (int i = 0; i < KBD_LED_CHANNELS; i++) { + data->kbd_led_mc_subled_info[i].color_index = color_indices[i]; + + ret = regmap_read(data->regmap, uniwill_kbd_led_channel_to_reg[i], ®val); + if (ret < 0) + return ret; + + /* + * Make sure that the initial intensity value is not greater than + * the maximum intensity. + */ + if (regval > KBD_LED_MAX_INTENSITY) { + regval = KBD_LED_MAX_INTENSITY; + ret = regmap_write(data->regmap, uniwill_kbd_led_channel_to_reg[i], regval); + if (ret < 0) + return ret; + + needs_trigger = true; + } + + if (regval) + intensity_all_zeros = false; + + data->kbd_led_mc_subled_info[i].intensity = regval; + data->kbd_led_mc_subled_info[i].max_intensity = KBD_LED_MAX_INTENSITY; + data->kbd_led_mc_subled_info[i].channel = i; + } + + /* See uniwill_kbd_led_mc_brightness_set() for an explaination. */ + if (intensity_all_zeros) { + for (int i = 0; i < KBD_LED_CHANNELS; i++) { + data->kbd_led_mc_subled_info[i].intensity = 1; + ret = regmap_write(data->regmap, uniwill_kbd_led_channel_to_reg[i], 1); + if (ret < 0) + return ret; + } + + needs_trigger = true; + } + + if (needs_trigger) { + ret = regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, RGB_APPLY_COLOR, + RGB_APPLY_COLOR); + if (ret < 0) + return ret; + } + + ret = devm_mutex_init(data->dev, &data->kbd_rgb_led_lock); + if (ret < 0) + return ret; + + init_data.default_label = "multicolor:" LED_FUNCTION_KBD_BACKLIGHT; + data->kbd_led_mc_cdev.led_cdev.max_brightness = data->kbd_led_max_brightness; + data->kbd_led_mc_cdev.led_cdev.color = LED_COLOR_ID_MULTI; + data->kbd_led_mc_cdev.led_cdev.flags = LED_BRIGHT_HW_CHANGED | LED_REJECT_NAME_CONFLICT; + data->kbd_led_mc_cdev.led_cdev.brightness_set_blocking = uniwill_kbd_led_mc_brightness_set; + data->kbd_led_mc_cdev.led_cdev.brightness_get = uniwill_kbd_led_mc_brightness_get; + data->kbd_led_mc_cdev.subled_info = data->kbd_led_mc_subled_info; + data->kbd_led_mc_cdev.num_colors = KBD_LED_CHANNELS; + + return devm_led_classdev_multicolor_register_ext(data->dev, &data->kbd_led_mc_cdev, + &init_data); +} + static unsigned int uniwill_sanitize_battery_threshold(unsigned int value) { /* 0 means "charging threshold not active" */ @@ -1789,6 +2082,31 @@ static int uniwill_notifier_call(struct notifier_block *nb, unsigned long action sysfs_notify(&data->dev->kobj, NULL, "fn_lock"); return NOTIFY_OK; + case UNIWILL_OSD_KB_LED_LEVEL0: + if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) + return NOTIFY_DONE; + + return notifier_from_errno(uniwill_notify_kbd_led(data, 0)); + case UNIWILL_OSD_KB_LED_LEVEL1: + if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) + return NOTIFY_DONE; + + return notifier_from_errno(uniwill_notify_kbd_led(data, 1)); + case UNIWILL_OSD_KB_LED_LEVEL2: + if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) + return NOTIFY_DONE; + + return notifier_from_errno(uniwill_notify_kbd_led(data, 2)); + case UNIWILL_OSD_KB_LED_LEVEL3: + if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) + return NOTIFY_DONE; + + return notifier_from_errno(uniwill_notify_kbd_led(data, 3)); + case UNIWILL_OSD_KB_LED_LEVEL4: + if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) + return NOTIFY_DONE; + + return notifier_from_errno(uniwill_notify_kbd_led(data, 4)); default: mutex_lock(&data->input_lock); sparse_keymap_report_event(data->input_device, action, 1, true); @@ -1842,6 +2160,7 @@ static int uniwill_ec_init(struct uniwill_data *data) if (ret < 0) return ret; + data->project_id = value; dev_dbg(data->dev, "Project ID: %u\n", value); ret = regmap_set_bits(data->regmap, EC_ADDR_AP_OEM, ENABLE_MANUAL_CTRL); @@ -1885,6 +2204,7 @@ static int uniwill_probe(struct platform_device *pdev) return ret; data->features = device_descriptor.features; + data->kbd_led_max_brightness = device_descriptor.kbd_led_max_brightness; /* * Some devices might need to perform some device-specific initialization steps @@ -1905,6 +2225,10 @@ static int uniwill_probe(struct platform_device *pdev) if (ret < 0) return ret; + ret = uniwill_kbd_led_init(data); + if (ret < 0) + return ret; + ret = uniwill_hwmon_init(data); if (ret < 0) return ret; @@ -1976,6 +2300,31 @@ static int uniwill_suspend_battery(struct uniwill_data *data) return regmap_read(data->regmap, EC_ADDR_CHARGE_CTRL, &data->last_charge_ctrl); } +static int uniwill_suspend_kbd_led(struct uniwill_data *data) +{ + unsigned int regval; + int ret; + + if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) + return 0; + + ret = regmap_read(data->regmap, EC_ADDR_KBD_STATUS, ®val); + if (ret < 0) + return ret; + + /* + * Save the current keyboard backlight settings in order to restore them + * during resume. We cannot use the regmap code for that since this register + * needs to be declared as volatile because the brightness can be changed + * by the EC. + */ + data->last_kbd_status = regval; + FIELD_MODIFY(KBD_BRIGHTNESS_MASK, ®val, 0); + regval |= KBD_APPLY | KBD_POWER_OFF; + + return regmap_write(data->regmap, EC_ADDR_KBD_STATUS, regval); +} + static int uniwill_suspend_nvidia_ctgp(struct uniwill_data *data) { if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL)) @@ -2006,6 +2355,10 @@ static int uniwill_suspend(struct device *dev) if (ret < 0) return ret; + ret = uniwill_suspend_kbd_led(data); + if (ret < 0) + return ret; + ret = uniwill_suspend_nvidia_ctgp(data); if (ret < 0) return ret; @@ -2052,6 +2405,23 @@ static int uniwill_resume_battery(struct uniwill_data *data) return 0; } +static int uniwill_resume_kbd_led(struct uniwill_data *data) +{ + int ret; + + if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) + return 0; + + ret = regmap_write(data->regmap, EC_ADDR_KBD_STATUS, data->last_kbd_status | KBD_APPLY); + if (ret < 0) + return ret; + + if (data->single_color_kbd) + return 0; + + return regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, RGB_APPLY_COLOR, RGB_APPLY_COLOR); +} + static int uniwill_resume_nvidia_ctgp(struct uniwill_data *data) { if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL)) @@ -2096,6 +2466,10 @@ static int uniwill_resume(struct device *dev) if (ret < 0) return ret; + ret = uniwill_resume_kbd_led(data); + if (ret < 0) + return ret; + ret = uniwill_resume_nvidia_ctgp(data); if (ret < 0) return ret; @@ -2745,6 +3119,8 @@ static int __init uniwill_init(void) if (force) { /* Assume that the device supports all features except the charge limit */ device_descriptor.features = UINT_MAX & ~UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT; + /* Some models only support 3 brightness levels */ + device_descriptor.kbd_led_max_brightness = 4; pr_warn("Enabling potentially unsupported features\n"); } From c7f3d21c8f50d64202e191f0ced40190ca1f45f7 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Sat, 30 May 2026 19:08:08 +0200 Subject: [PATCH 069/562] platform/x86: uniwill-laptop: Handle screen-related events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The EC will report event 0xCC on some devices when the screen has been enabled/disabled during resume/suspend. Ignore this event because it is currently unused by the driver. Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260530170813.10166-3-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/uniwill/uniwill-acpi.c | 6 ++++++ drivers/platform/x86/uniwill/uniwill-wmi.h | 2 ++ 2 files changed, 8 insertions(+) diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index fd040197b189..b3be2f2dbdd8 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -95,6 +95,9 @@ #define EC_ADDR_MAIN_FAN_RPM_2 0x0465 +#define EC_ADDR_SCREEN_STATUS 0x0466 +#define SCREEN_SUSPENDED BIT(6) + #define EC_ADDR_SECOND_FAN_RPM_1 0x046C #define EC_ADDR_SECOND_FAN_RPM_2 0x046D @@ -488,6 +491,9 @@ static const struct key_entry uniwill_keymap[] = { /* Reported when the user wants to toggle the benchmark mode status */ { KE_IGNORE, UNIWILL_OSD_BENCHMARK_MODE_TOGGLE, { KEY_UNKNOWN }}, + /* Reported when the screen is enabled/disabled during resume/suspend */ + { KE_IGNORE, UNIWILL_OSD_SCREEN_STATE_CHANGED, { KEY_UNKNOWN }}, + /* Reported when the user wants to toggle the webcam */ { KE_IGNORE, UNIWILL_OSD_WEBCAM_TOGGLE, { KEY_UNKNOWN }}, diff --git a/drivers/platform/x86/uniwill/uniwill-wmi.h b/drivers/platform/x86/uniwill/uniwill-wmi.h index fb1910c0f741..b25b2f31211c 100644 --- a/drivers/platform/x86/uniwill/uniwill-wmi.h +++ b/drivers/platform/x86/uniwill/uniwill-wmi.h @@ -113,6 +113,8 @@ #define UNIWILL_OSD_BENCHMARK_MODE_TOGGLE 0xC0 +#define UNIWILL_OSD_SCREEN_STATE_CHANGED 0xCC + #define UNIWILL_OSD_WEBCAM_TOGGLE 0xCF #define UNIWILL_OSD_KBD_BACKLIGHT_CHANGED 0xF0 From e8a41051b95bc8a2dfda3a32b23528d098a7e907 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Sat, 30 May 2026 19:08:09 +0200 Subject: [PATCH 070/562] platform/x86: uniwill-laptop: Add AC auto boot support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some devices support a "AC auto boot" feature where the system will automatically boot when being connected to a power source. Add support for this feature. Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260530170813.10166-4-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- .../ABI/testing/sysfs-driver-uniwill-laptop | 11 ++++ .../admin-guide/laptops/uniwill-laptop.rst | 7 +++ drivers/platform/x86/uniwill/uniwill-acpi.c | 51 +++++++++++++++++++ 3 files changed, 69 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-driver-uniwill-laptop b/Documentation/ABI/testing/sysfs-driver-uniwill-laptop index 2397c65c969a..57272f906184 100644 --- a/Documentation/ABI/testing/sysfs-driver-uniwill-laptop +++ b/Documentation/ABI/testing/sysfs-driver-uniwill-laptop @@ -78,3 +78,14 @@ Description: Reading this file returns the profile names with the currently active one in brackets. + +What: /sys/bus/platform/devices/INOU0000:XX/ac_auto_boot +Date: March 2026 +KernelVersion: 7.1 +Contact: Armin Wolf +Description: + Allows userspace applications to configure if the device should boot automatically + when being connected to a power source. Writing "1"/"0" into this file + enables/disables this functionality. + + Reading this file returns the current status of the AC auto boot functionality. diff --git a/Documentation/admin-guide/laptops/uniwill-laptop.rst b/Documentation/admin-guide/laptops/uniwill-laptop.rst index 2b1e5da703a5..b6213fb1d3e0 100644 --- a/Documentation/admin-guide/laptops/uniwill-laptop.rst +++ b/Documentation/admin-guide/laptops/uniwill-laptop.rst @@ -98,6 +98,13 @@ allow it. See Documentation/ABI/testing/sysfs-driver-uniwill-laptop for details. +AC Auto Boot +------------ + +The ``uniwill-laptop`` driver allows the user to configure if the system should automatically +boot when being connected to a power source, see +Documentation/ABI/testing/sysfs-driver-uniwill-laptop for details. + References ========== diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index b3be2f2dbdd8..897c6163de53 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -112,6 +112,9 @@ #define EC_ADDR_BAT_CYCLE_COUNT_2 0x04A7 +#define EC_ADDR_OEM_9 0x0726 +#define AC_AUTO_BOOT_ENABLE BIT(3) + #define EC_ADDR_PROJECT_ID 0x0740 #define PROJECT_ID_NONE 0x00 #define PROJECT_ID_GI 0x01 @@ -364,6 +367,7 @@ #define UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL BIT(10) #define UNIWILL_FEATURE_USB_C_POWER_PRIORITY BIT(11) #define UNIWILL_FEATURE_KEYBOARD_BACKLIGHT BIT(12) +#define UNIWILL_FEATURE_AC_AUTO_BOOT BIT(13) enum usb_c_power_priority_options { USB_C_POWER_PRIORITY_CHARGING = 0, @@ -586,6 +590,7 @@ static const struct regmap_bus uniwill_ec_bus = { static bool uniwill_writeable_reg(struct device *dev, unsigned int reg) { switch (reg) { + case EC_ADDR_OEM_9: case EC_ADDR_AP_OEM: case EC_ADDR_LIGHTBAR_AC_CTRL: case EC_ADDR_LIGHTBAR_AC_RED: @@ -625,6 +630,7 @@ static bool uniwill_readable_reg(struct device *dev, unsigned int reg) case EC_ADDR_SECOND_FAN_RPM_1: case EC_ADDR_SECOND_FAN_RPM_2: case EC_ADDR_BAT_ALERT: + case EC_ADDR_OEM_9: case EC_ADDR_PROJECT_ID: case EC_ADDR_AP_OEM: case EC_ADDR_LIGHTBAR_AC_CTRL: @@ -1136,6 +1142,45 @@ static int usb_c_power_priority_init(struct uniwill_data *data) return 0; } +static ssize_t ac_auto_boot_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct uniwill_data *data = dev_get_drvdata(dev); + unsigned int regval; + bool enable; + int ret; + + ret = kstrtobool(buf, &enable); + if (ret < 0) + return ret; + + if (enable) + regval = AC_AUTO_BOOT_ENABLE; + else + regval = 0; + + ret = regmap_update_bits(data->regmap, EC_ADDR_OEM_9, AC_AUTO_BOOT_ENABLE, regval); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t ac_auto_boot_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct uniwill_data *data = dev_get_drvdata(dev); + unsigned int regval; + int ret; + + ret = regmap_read(data->regmap, EC_ADDR_OEM_9, ®val); + if (ret < 0) + return ret; + + return sysfs_emit(buf, "%d\n", !!(regval & AC_AUTO_BOOT_ENABLE)); +} + +static DEVICE_ATTR_RW(ac_auto_boot); + static struct attribute *uniwill_attrs[] = { /* Keyboard-related */ &dev_attr_fn_lock.attr, @@ -1147,6 +1192,7 @@ static struct attribute *uniwill_attrs[] = { /* Power-management-related */ &dev_attr_ctgp_offset.attr, &dev_attr_usb_c_power_priority.attr, + &dev_attr_ac_auto_boot.attr, NULL }; @@ -1186,6 +1232,11 @@ static umode_t uniwill_attr_is_visible(struct kobject *kobj, struct attribute *a return attr->mode; } + if (attr == &dev_attr_ac_auto_boot.attr) { + if (uniwill_device_supports(data, UNIWILL_FEATURE_AC_AUTO_BOOT)) + return attr->mode; + } + return 0; } From 58d88f08ab1ff8da359b78e3421566669f0d1dcd Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Sat, 30 May 2026 19:08:10 +0200 Subject: [PATCH 071/562] platform/x86: uniwill-laptop: Add support for USB powershare MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some devices support a "USB powershare" feature where the system will continue to provide power via the USB ports when hibernating or powered off. Add support for this feaure. Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260530170813.10166-5-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- .../ABI/testing/sysfs-driver-uniwill-laptop | 16 ++- .../admin-guide/laptops/uniwill-laptop.rst | 7 ++ drivers/platform/x86/uniwill/uniwill-acpi.c | 100 ++++++++++++++++++ 3 files changed, 122 insertions(+), 1 deletion(-) diff --git a/Documentation/ABI/testing/sysfs-driver-uniwill-laptop b/Documentation/ABI/testing/sysfs-driver-uniwill-laptop index 57272f906184..943f92c6b561 100644 --- a/Documentation/ABI/testing/sysfs-driver-uniwill-laptop +++ b/Documentation/ABI/testing/sysfs-driver-uniwill-laptop @@ -86,6 +86,20 @@ Contact: Armin Wolf Description: Allows userspace applications to configure if the device should boot automatically when being connected to a power source. Writing "1"/"0" into this file - enables/disables this functionality. + enables/disables this functionality. Enabling both AC auto boot and USB powershare + at the same time is not supported. Reading this file returns the current status of the AC auto boot functionality. + +What: /sys/bus/platform/devices/INOU0000:XX/usb_powershare_high +Date: March 2026 +KernelVersion: 7.1 +Contact: Armin Wolf +Description: + Allows userspace applications to configure if the device should continue to provide + power via the USB ports when hibernating or powered off. Might also increase the + power budget available to USB ports on some devices. Writing "1"/"0" into this + file enables/disables this functionality. Enabling both USB powershare and AC auto + boot at the same time is not supported. + + Reading this file returns the current status of the USB powershare functionality. diff --git a/Documentation/admin-guide/laptops/uniwill-laptop.rst b/Documentation/admin-guide/laptops/uniwill-laptop.rst index b6213fb1d3e0..be50b45b82ef 100644 --- a/Documentation/admin-guide/laptops/uniwill-laptop.rst +++ b/Documentation/admin-guide/laptops/uniwill-laptop.rst @@ -105,6 +105,13 @@ The ``uniwill-laptop`` driver allows the user to configure if the system should boot when being connected to a power source, see Documentation/ABI/testing/sysfs-driver-uniwill-laptop for details. +USB Powershare +-------------- + +The ``uniwill-laptop`` driver allows the user to configure if the system should continue to +provide power via the USB ports when hibernating or powered off, see +Documentation/ABI/testing/sysfs-driver-uniwill-laptop for details. + References ========== diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index 897c6163de53..00140c0a67a0 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -368,6 +368,7 @@ #define UNIWILL_FEATURE_USB_C_POWER_PRIORITY BIT(11) #define UNIWILL_FEATURE_KEYBOARD_BACKLIGHT BIT(12) #define UNIWILL_FEATURE_AC_AUTO_BOOT BIT(13) +#define UNIWILL_FEATURE_USB_POWERSHARE BIT(14) enum usb_c_power_priority_options { USB_C_POWER_PRIORITY_CHARGING = 0, @@ -393,6 +394,7 @@ struct uniwill_data { bool last_fn_lock_state; bool last_super_key_enable_state; bool last_touchpad_toggle_enable_state; + bool last_usb_powershare_high_state; struct mutex super_key_lock; /* Protects the toggling of the super key lock state */ struct list_head batteries; struct mutex led_lock; /* Protects writes to the lightbar registers */ @@ -1181,6 +1183,70 @@ static ssize_t ac_auto_boot_show(struct device *dev, struct device_attribute *at static DEVICE_ATTR_RW(ac_auto_boot); +static int uniwill_write_usb_powershare_high(struct uniwill_data *data, bool status) +{ + unsigned int value; + + if (status) + value = TRIGGER_USB_CHARGING; + else + value = 0; + + /* + * Normaly this RMW-sequence could also trigger the super key toggle, + * but the EC seems to take care that those bits are always read as 0. + */ + return regmap_update_bits(data->regmap, EC_ADDR_TRIGGER, TRIGGER_USB_CHARGING, value); +} + +static ssize_t usb_powershare_high_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct uniwill_data *data = dev_get_drvdata(dev); + bool enable; + int ret; + + ret = kstrtobool(buf, &enable); + if (ret < 0) + return ret; + + ret = uniwill_write_usb_powershare_high(data, enable); + if (ret < 0) + return ret; + + return count; +} + +static int uniwill_read_usb_powershare_high(struct uniwill_data *data, bool *status) +{ + unsigned int value; + int ret; + + ret = regmap_read(data->regmap, EC_ADDR_TRIGGER, &value); + if (ret < 0) + return ret; + + *status = !!(value & TRIGGER_USB_CHARGING); + + return 0; +} + +static ssize_t usb_powershare_high_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct uniwill_data *data = dev_get_drvdata(dev); + bool status; + int ret; + + ret = uniwill_read_usb_powershare_high(data, &status); + if (ret < 0) + return ret; + + return sysfs_emit(buf, "%d\n", status); +} + +static DEVICE_ATTR_RW(usb_powershare_high); + static struct attribute *uniwill_attrs[] = { /* Keyboard-related */ &dev_attr_fn_lock.attr, @@ -1193,6 +1259,7 @@ static struct attribute *uniwill_attrs[] = { &dev_attr_ctgp_offset.attr, &dev_attr_usb_c_power_priority.attr, &dev_attr_ac_auto_boot.attr, + &dev_attr_usb_powershare_high.attr, NULL }; @@ -1237,6 +1304,11 @@ static umode_t uniwill_attr_is_visible(struct kobject *kobj, struct attribute *a return attr->mode; } + if (attr == &dev_attr_usb_powershare_high.attr) { + if (uniwill_device_supports(data, UNIWILL_FEATURE_USB_POWERSHARE)) + return attr->mode; + } + return 0; } @@ -2382,6 +2454,18 @@ static int uniwill_suspend_kbd_led(struct uniwill_data *data) return regmap_write(data->regmap, EC_ADDR_KBD_STATUS, regval); } +static int uniwill_suspend_usb_powershare(struct uniwill_data *data) +{ + if (!uniwill_device_supports(data, UNIWILL_FEATURE_USB_POWERSHARE)) + return 0; + + /* + * EC_ADDR_TRIGGER is marked as volatile, so we have to restore it + * ourselves. + */ + return uniwill_read_usb_powershare_high(data, &data->last_usb_powershare_high_state); +} + static int uniwill_suspend_nvidia_ctgp(struct uniwill_data *data) { if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL)) @@ -2416,6 +2500,10 @@ static int uniwill_suspend(struct device *dev) if (ret < 0) return ret; + ret = uniwill_suspend_usb_powershare(data); + if (ret < 0) + return ret; + ret = uniwill_suspend_nvidia_ctgp(data); if (ret < 0) return ret; @@ -2479,6 +2567,14 @@ static int uniwill_resume_kbd_led(struct uniwill_data *data) return regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, RGB_APPLY_COLOR, RGB_APPLY_COLOR); } +static int uniwill_resume_usb_powershare(struct uniwill_data *data) +{ + if (!uniwill_device_supports(data, UNIWILL_FEATURE_USB_POWERSHARE)) + return 0; + + return uniwill_write_usb_powershare_high(data, data->last_usb_powershare_high_state); +} + static int uniwill_resume_nvidia_ctgp(struct uniwill_data *data) { if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL)) @@ -2527,6 +2623,10 @@ static int uniwill_resume(struct device *dev) if (ret < 0) return ret; + ret = uniwill_resume_usb_powershare(data); + if (ret < 0) + return ret; + ret = uniwill_resume_nvidia_ctgp(data); if (ret < 0) return ret; From c1c6633e3f1f9e38b5b9a26137f87a997445d306 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Sat, 30 May 2026 19:08:11 +0200 Subject: [PATCH 072/562] platform/x86: uniwill-laptop: Add support for the MACHENIKE L16 Pro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A user has reported that the driver works on the MACHENIKE L16 Pro. Add the necessary device descriptor and DMI entry to allow the driver to automatically load on this device. Reported-by: zatrit Closes: https://github.com/Wer-Wolf/uniwill-laptop/pull/11 Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260530170813.10166-6-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/uniwill/uniwill-acpi.c | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index 00140c0a67a0..0011c553c2cb 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -2657,6 +2657,20 @@ static struct platform_driver uniwill_driver = { .shutdown = uniwill_shutdown, }; +static struct uniwill_device_descriptor machenike_l16p_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_GPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN | + UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL | + UNIWILL_FEATURE_KEYBOARD_BACKLIGHT | + UNIWILL_FEATURE_AC_AUTO_BOOT | + UNIWILL_FEATURE_USB_POWERSHARE, + .kbd_led_max_brightness = 4, +}; + static struct uniwill_device_descriptor lapqc71a_lapqc71b_descriptor __initdata = { .features = UNIWILL_FEATURE_SUPER_KEY | UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT | @@ -2809,6 +2823,14 @@ static struct uniwill_device_descriptor pf5pu1g_descriptor __initdata = { }; static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + { + .ident = "MACHENIKE L16 Pro", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "MACHENIKE"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "L16P"), + }, + .driver_data = &machenike_l16p_descriptor, + }, { .ident = "XMG FUSION 15 (L19)", .matches = { From 62f334005d59723bcef864b82e95f6da6bbca41d Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Sat, 30 May 2026 19:08:12 +0200 Subject: [PATCH 073/562] platform/x86: uniwill-laptop: Add support for the AiStone X4SP4NAL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A user has reported that the driver works on the AiStone X4SP4NAL. Add the necessary device descriptor and DMI entry to allow the driver to automatically load on this device. Reported-by: Michael Seifert Closes: https://github.com/Wer-Wolf/uniwill-laptop/pull/10 Tested-by: Michael Seifert Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260530170813.10166-7-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/uniwill/uniwill-acpi.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index 0011c553c2cb..d688ffca3b5e 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -2822,7 +2822,28 @@ static struct uniwill_device_descriptor pf5pu1g_descriptor __initdata = { UNIWILL_FEATURE_PRIMARY_FAN, }; +static struct uniwill_device_descriptor x4sp4nal_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_BATTERY_CHARGE_MODES | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN | + UNIWILL_FEATURE_KEYBOARD_BACKLIGHT | + UNIWILL_FEATURE_AC_AUTO_BOOT | + UNIWILL_FEATURE_USB_POWERSHARE, + .kbd_led_max_brightness = 2, +}; + static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + { + .ident = "AiStone X4SP4NAL", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "AiStone"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X4SP4NAL"), + }, + .driver_data = &x4sp4nal_descriptor, + }, { .ident = "MACHENIKE L16 Pro", .matches = { From e451ae739a2c6fa4e9cc1be44678f6ff9223430d Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Sat, 30 May 2026 19:08:13 +0200 Subject: [PATCH 074/562] platform/x86: uniwill-laptop: Add lightbar support for LAPQC71A/B MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The LAPQC71A and LAPQC71B both feature a RGB lightbar with 36 brightness levels per color component. Extend the device descriptor to supply the maximum brightness of the lightbar and whitelist both models for UNIWILL_FEATURE_LIGHTBAR. Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260530170813.10166-8-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/uniwill/uniwill-acpi.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index d688ffca3b5e..f55b239bd4d1 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -348,7 +348,6 @@ #define FAN_TABLE_LENGTH 16 #define LED_CHANNELS 3 -#define LED_MAX_BRIGHTNESS 200 #define KBD_LED_CHANNELS 3 #define KBD_LED_MAX_INTENSITY 50 @@ -398,6 +397,7 @@ struct uniwill_data { struct mutex super_key_lock; /* Protects the toggling of the super key lock state */ struct list_head batteries; struct mutex led_lock; /* Protects writes to the lightbar registers */ + u8 lightbar_max_brightness; struct led_classdev_mc led_mc_cdev; struct mc_subled led_mc_subled_info[LED_CHANNELS]; bool single_color_kbd; @@ -427,6 +427,7 @@ struct uniwill_battery_entry { struct uniwill_device_descriptor { unsigned int features; u8 kbd_led_max_brightness; + u8 lightbar_max_brightness; /* Executed during driver probing */ int (*probe)(struct uniwill_data *data); }; @@ -1514,7 +1515,7 @@ static int uniwill_led_brightness_set(struct led_classdev *led_cdev, enum led_br for (int i = 0; i < LED_CHANNELS; i++) { /* Prevent the brightness values from overflowing */ - value = min(LED_MAX_BRIGHTNESS, data->led_mc_subled_info[i].brightness); + value = min(data->lightbar_max_brightness, data->led_mc_subled_info[i].brightness); ret = regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i], value); if (ret < 0) return ret; @@ -1583,14 +1584,14 @@ static int uniwill_led_init(struct uniwill_data *data) return ret; data->led_mc_cdev.led_cdev.color = LED_COLOR_ID_MULTI; - data->led_mc_cdev.led_cdev.max_brightness = LED_MAX_BRIGHTNESS; + data->led_mc_cdev.led_cdev.max_brightness = data->lightbar_max_brightness; data->led_mc_cdev.led_cdev.flags = LED_REJECT_NAME_CONFLICT; data->led_mc_cdev.led_cdev.brightness_set_blocking = uniwill_led_brightness_set; if (value & LIGHTBAR_S0_OFF) data->led_mc_cdev.led_cdev.brightness = 0; else - data->led_mc_cdev.led_cdev.brightness = LED_MAX_BRIGHTNESS; + data->led_mc_cdev.led_cdev.brightness = data->lightbar_max_brightness; for (int i = 0; i < LED_CHANNELS; i++) { data->led_mc_subled_info[i].color_index = color_indices[i]; @@ -1603,7 +1604,7 @@ static int uniwill_led_init(struct uniwill_data *data) * Make sure that the initial intensity value is not greater than * the maximum brightness. */ - value = min(LED_MAX_BRIGHTNESS, value); + value = min(data->lightbar_max_brightness, value); ret = regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i], value); if (ret < 0) return ret; @@ -2334,6 +2335,7 @@ static int uniwill_probe(struct platform_device *pdev) data->features = device_descriptor.features; data->kbd_led_max_brightness = device_descriptor.kbd_led_max_brightness; + data->lightbar_max_brightness = device_descriptor.lightbar_max_brightness; /* * Some devices might need to perform some device-specific initialization steps @@ -2673,11 +2675,13 @@ static struct uniwill_device_descriptor machenike_l16p_descriptor __initdata = { static struct uniwill_device_descriptor lapqc71a_lapqc71b_descriptor __initdata = { .features = UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_LIGHTBAR | UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT | UNIWILL_FEATURE_CPU_TEMP | UNIWILL_FEATURE_GPU_TEMP | UNIWILL_FEATURE_PRIMARY_FAN | UNIWILL_FEATURE_SECONDARY_FAN, + .lightbar_max_brightness = 36, }; static struct uniwill_device_descriptor lapac71h_descriptor __initdata = { @@ -2701,6 +2705,7 @@ static struct uniwill_device_descriptor lapkc71f_descriptor __initdata = { UNIWILL_FEATURE_GPU_TEMP | UNIWILL_FEATURE_PRIMARY_FAN | UNIWILL_FEATURE_SECONDARY_FAN, + .lightbar_max_brightness = 200, }; /* @@ -3321,6 +3326,8 @@ static int __init uniwill_init(void) device_descriptor.features = UINT_MAX & ~UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT; /* Some models only support 3 brightness levels */ device_descriptor.kbd_led_max_brightness = 4; + /* Some models only support 36 brightness levels per color component */ + device_descriptor.lightbar_max_brightness = 200; pr_warn("Enabling potentially unsupported features\n"); } From 385bf4f87b059d9a2bd9663c7a5f1a8fec6f724e Mon Sep 17 00:00:00 2001 From: Radhey Kalra Date: Mon, 15 Jun 2026 14:40:32 +0530 Subject: [PATCH 075/562] platform/x86: hp-wmi: Introduce board-specific feature data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hp_wmi DMI table is about to carry more than thermal-profile data. Replace the direct thermal_profile_params .driver_data pointers with hp_wmi_board_params and rename the table/setup helper accordingly. No functional changes intended. Signed-off-by: Radhey Kalra Link: https://patch.msgid.link/20260615091034.987029-2-radheykalra901@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-wmi.c | 127 +++++++++++++++++++------------ 1 file changed, 80 insertions(+), 47 deletions(-) diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index 8ba286ed8721..ee8375d28672 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -133,11 +133,35 @@ static const struct thermal_profile_params omen_v1_no_ec_thermal_params = { .ec_tp_offset = HP_NO_THERMAL_PROFILE_OFFSET, }; -/* - * A generic pointer for the currently-active board's thermal profile - * parameters. - */ -static struct thermal_profile_params *active_thermal_profile_params; +struct hp_wmi_board_params { + const struct thermal_profile_params *thermal_profile; +}; + +static const struct hp_wmi_board_params victus_s_board_params = { + .thermal_profile = &victus_s_thermal_params, +}; + +static const struct hp_wmi_board_params omen_v1_board_params = { + .thermal_profile = &omen_v1_thermal_params, +}; + +static const struct hp_wmi_board_params omen_v1_legacy_board_params = { + .thermal_profile = &omen_v1_legacy_thermal_params, +}; + +static const struct hp_wmi_board_params omen_v1_no_ec_board_params = { + .thermal_profile = &omen_v1_no_ec_thermal_params, +}; + +static const struct hp_wmi_board_params *active_board_params; + +static const struct thermal_profile_params *hp_wmi_thermal_profile(void) +{ + if (!active_board_params) + return NULL; + + return active_board_params->thermal_profile; +} /* DMI board names of devices that should use the omen specific path for * thermal profiles. @@ -187,87 +211,87 @@ static const char * const victus_thermal_profile_boards[] = { "8A25", }; -/* DMI Board names of Victus 16-r and Victus 16-s laptops */ -static const struct dmi_system_id victus_s_thermal_profile_boards[] __initconst = { +/* DMI board-specific feature data for Omen and Victus laptops. */ +static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8902") }, - .driver_data = (void *)&omen_v1_legacy_thermal_params, + .driver_data = (void *)&omen_v1_legacy_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A44") }, - .driver_data = (void *)&omen_v1_legacy_thermal_params, + .driver_data = (void *)&omen_v1_legacy_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A4D") }, - .driver_data = (void *)&omen_v1_legacy_thermal_params, + .driver_data = (void *)&omen_v1_legacy_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BAB") }, - .driver_data = (void *)&omen_v1_thermal_params, + .driver_data = (void *)&omen_v1_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8B2F") }, - .driver_data = (void *)&victus_s_thermal_params, + .driver_data = (void *)&victus_s_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BBE") }, - .driver_data = (void *)&victus_s_thermal_params, + .driver_data = (void *)&victus_s_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BC2") }, - .driver_data = (void *)&omen_v1_thermal_params, + .driver_data = (void *)&omen_v1_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BCA") }, - .driver_data = (void *)&omen_v1_thermal_params, + .driver_data = (void *)&omen_v1_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BCD") }, - .driver_data = (void *)&omen_v1_thermal_params, + .driver_data = (void *)&omen_v1_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BD4") }, - .driver_data = (void *)&victus_s_thermal_params, + .driver_data = (void *)&victus_s_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BD5") }, - .driver_data = (void *)&victus_s_thermal_params, + .driver_data = (void *)&victus_s_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C76") }, - .driver_data = (void *)&omen_v1_thermal_params, + .driver_data = (void *)&omen_v1_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C77") }, - .driver_data = (void *)&omen_v1_thermal_params, + .driver_data = (void *)&omen_v1_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C78") }, - .driver_data = (void *)&omen_v1_thermal_params, + .driver_data = (void *)&omen_v1_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C99") }, - .driver_data = (void *)&victus_s_thermal_params, + .driver_data = (void *)&victus_s_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C9C") }, - .driver_data = (void *)&victus_s_thermal_params, + .driver_data = (void *)&victus_s_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D26") }, - .driver_data = (void *)&omen_v1_legacy_thermal_params, + .driver_data = (void *)&omen_v1_legacy_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D41") }, - .driver_data = (void *)&omen_v1_no_ec_thermal_params, + .driver_data = (void *)&omen_v1_no_ec_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D87") }, - .driver_data = (void *)&omen_v1_no_ec_thermal_params, + .driver_data = (void *)&omen_v1_no_ec_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8E35") }, - .driver_data = (void *)&omen_v1_legacy_thermal_params, + .driver_data = (void *)&omen_v1_legacy_board_params, }, {}, }; @@ -1874,7 +1898,10 @@ static int platform_profile_victus_s_get_ec(enum platform_profile_option *profil u8 current_dstate, current_gpu_slowdown_temp, tp; const struct thermal_profile_params *params; - params = active_thermal_profile_params; + params = hp_wmi_thermal_profile(); + if (!params) + return -ENODEV; + if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN || params->ec_tp_offset == HP_NO_THERMAL_PROFILE_OFFSET) { *profile = active_platform_profile; @@ -1886,10 +1913,10 @@ static int platform_profile_victus_s_get_ec(enum platform_profile_option *profil return ret; /* - * We cannot use active_thermal_profile_params here, because boards - * like 8C78 have tp == 0x0 || tp == 0x1 after cold boot, but logically - * it should have tp == 0x30 || tp == 0x31, as corrected by the Omen - * Gaming Hub on windows. Hence accept both of these values. + * Boards like 8C78 have tp == 0x0 || tp == 0x1 after cold boot, + * but logically it should have tp == 0x30 || tp == 0x31, as + * corrected by the Omen Gaming Hub on windows. Hence accept both + * of these values. */ if (tp == victus_s_thermal_params.performance || tp == omen_v1_thermal_params.performance) { @@ -1924,12 +1951,12 @@ static int platform_profile_victus_s_get_ec(enum platform_profile_option *profil static int platform_profile_victus_s_set_ec(enum platform_profile_option profile) { - struct thermal_profile_params *params; + const struct thermal_profile_params *params; bool gpu_ctgp_enable, gpu_ppab_enable; u8 gpu_dstate; /* Test shows 1 = 100%, 2 = 50%, 3 = 25%, 4 = 12.5% */ int err, tp; - params = active_thermal_profile_params; + params = hp_wmi_thermal_profile(); if (!params) return -ENODEV; @@ -2195,6 +2222,7 @@ static const struct platform_profile_ops hp_wmi_platform_profile_ops = { static int thermal_profile_setup(struct platform_device *device) { const struct platform_profile_ops *ops; + const struct thermal_profile_params *params; int err, tp; if (is_omen_thermal_profile()) { @@ -2226,13 +2254,17 @@ static int thermal_profile_setup(struct platform_device *device) ops = &platform_profile_victus_ops; } else if (is_victus_s_thermal_profile()) { + params = hp_wmi_thermal_profile(); + if (!params) + return -ENODEV; + /* * For an unknown EC layout board, platform_profile_victus_s_get_ec(), * behaves like a wrapper around active_platform_profile, to avoid using * uninitialized data, we default to PLATFORM_PROFILE_BALANCED. */ - if (active_thermal_profile_params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN || - active_thermal_profile_params->ec_tp_offset == HP_NO_THERMAL_PROFILE_OFFSET) { + if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN || + params->ec_tp_offset == HP_NO_THERMAL_PROFILE_OFFSET) { active_platform_profile = PLATFORM_PROFILE_BALANCED; } else { err = platform_profile_victus_s_get_ec(&active_platform_profile); @@ -2693,24 +2725,25 @@ static int hp_wmi_hwmon_init(void) return 0; } -static void __init setup_active_thermal_profile_params(void) +static void __init setup_active_board_params(void) { const struct dmi_system_id *id; + const struct thermal_profile_params *params; - /* - * Currently only victus_s devices use the - * active_thermal_profile_params - */ - id = dmi_first_match(victus_s_thermal_profile_boards); + id = dmi_first_match(hp_wmi_feature_boards); if (id) { + active_board_params = id->driver_data; + params = hp_wmi_thermal_profile(); + if (!params) + return; + /* * Marking this boolean is required to ensure that * is_victus_s_thermal_profile() behaves like a valid * wrapper. */ is_victus_s_board = true; - active_thermal_profile_params = id->driver_data; - if (active_thermal_profile_params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) { + if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) { pr_warn("Unknown EC layout for board %s. Thermal profile readback will be disabled. Please report this to platform-driver-x86@vger.kernel.org\n", dmi_get_system_info(DMI_BOARD_NAME)); } @@ -2745,10 +2778,10 @@ static int __init hp_wmi_init(void) } /* - * Setup active board's thermal profile parameters before - * starting platform driver probe. + * Setup active board feature data before starting platform + * driver probe. */ - setup_active_thermal_profile_params(); + setup_active_board_params(); err = platform_driver_probe(&hp_wmi_driver, hp_wmi_bios_setup); if (err) goto err_unregister_device; From 9ed0f2562f9a6060955954f0d0ca72f12ed1a705 Mon Sep 17 00:00:00 2001 From: Radhey Kalra Date: Mon, 15 Jun 2026 14:40:33 +0530 Subject: [PATCH 076/562] platform/x86: hp-wmi: Drive fan control from board data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the board-specific .driver_data to describe fan-control support and fan-speed read callbacks. Existing boards keep the same Victus fan-control path, but the hwmon code no longer hardcodes that decision through is_victus_s_thermal_profile(). No functional changes intended. Signed-off-by: Radhey Kalra Link: https://patch.msgid.link/20260615091034.987029-3-radheykalra901@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-wmi.c | 79 ++++++++++++++++++++++++++------ 1 file changed, 64 insertions(+), 15 deletions(-) diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index ee8375d28672..11de5c70ce4d 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -133,24 +133,41 @@ static const struct thermal_profile_params omen_v1_no_ec_thermal_params = { .ec_tp_offset = HP_NO_THERMAL_PROFILE_OFFSET, }; +struct hp_wmi_fan_profile_params { + int (*get_fan_speed)(int fan); + bool fan_table; +}; + struct hp_wmi_board_params { const struct thermal_profile_params *thermal_profile; + const struct hp_wmi_fan_profile_params *fan_profile; +}; + +static int hp_wmi_get_fan_speed_victus_s(int fan); + +static const struct hp_wmi_fan_profile_params victus_s_fan_profile_params = { + .get_fan_speed = hp_wmi_get_fan_speed_victus_s, + .fan_table = true, }; static const struct hp_wmi_board_params victus_s_board_params = { .thermal_profile = &victus_s_thermal_params, + .fan_profile = &victus_s_fan_profile_params, }; static const struct hp_wmi_board_params omen_v1_board_params = { .thermal_profile = &omen_v1_thermal_params, + .fan_profile = &victus_s_fan_profile_params, }; static const struct hp_wmi_board_params omen_v1_legacy_board_params = { .thermal_profile = &omen_v1_legacy_thermal_params, + .fan_profile = &victus_s_fan_profile_params, }; static const struct hp_wmi_board_params omen_v1_no_ec_board_params = { .thermal_profile = &omen_v1_no_ec_thermal_params, + .fan_profile = &victus_s_fan_profile_params, }; static const struct hp_wmi_board_params *active_board_params; @@ -1813,6 +1830,38 @@ static bool is_victus_s_thermal_profile(void) return is_victus_s_board; } +static const struct hp_wmi_fan_profile_params *hp_wmi_fan_profile(void) +{ + if (!active_board_params) + return NULL; + + return active_board_params->fan_profile; +} + +static bool hp_wmi_fan_control_supported(void) +{ + const struct hp_wmi_fan_profile_params *params = hp_wmi_fan_profile(); + + return params && params->get_fan_speed; +} + +static bool hp_wmi_fan_table_supported(void) +{ + const struct hp_wmi_fan_profile_params *params = hp_wmi_fan_profile(); + + return params && params->fan_table; +} + +static int hp_wmi_get_active_fan_speed(int fan) +{ + const struct hp_wmi_fan_profile_params *params = hp_wmi_fan_profile(); + + if (!params || !params->get_fan_speed) + return -EOPNOTSUPP; + + return params->get_fan_speed(fan); +} + static int victus_s_gpu_thermal_profile_get(bool *ctgp_enable, bool *ppab_enable, u8 *dstate, @@ -2432,7 +2481,7 @@ static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv) switch (priv->mode) { case PWM_MODE_MAX: - if (is_victus_s_thermal_profile()) { + if (hp_wmi_fan_control_supported()) { ret = hp_wmi_get_fan_count_userdefine_trigger(); if (ret < 0) return ret; @@ -2444,7 +2493,7 @@ static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv) secs_to_jiffies(KEEP_ALIVE_DELAY_SECS)); return 0; case PWM_MODE_MANUAL: - if (!is_victus_s_thermal_profile()) + if (!hp_wmi_fan_control_supported()) return -EOPNOTSUPP; ret = hp_wmi_fan_speed_set(priv, pwm_to_rpm(priv->pwm, priv)); if (ret < 0) @@ -2453,7 +2502,7 @@ static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv) secs_to_jiffies(KEEP_ALIVE_DELAY_SECS)); return 0; case PWM_MODE_AUTO: - if (is_victus_s_thermal_profile()) { + if (hp_wmi_fan_control_supported()) { ret = hp_wmi_get_fan_count_userdefine_trigger(); if (ret < 0) return ret; @@ -2477,12 +2526,12 @@ static umode_t hp_wmi_hwmon_is_visible(const void *data, { switch (type) { case hwmon_pwm: - if (attr == hwmon_pwm_input && !is_victus_s_thermal_profile()) + if (attr == hwmon_pwm_input && !hp_wmi_fan_control_supported()) return 0; return 0644; case hwmon_fan: - if (is_victus_s_thermal_profile()) { - if (hp_wmi_get_fan_speed_victus_s(channel) >= 0) + if (hp_wmi_fan_control_supported()) { + if (hp_wmi_get_active_fan_speed(channel) >= 0) return 0444; } else { if (hp_wmi_get_fan_speed(channel) >= 0) @@ -2506,8 +2555,8 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, priv = dev_get_drvdata(dev); switch (type) { case hwmon_fan: - if (is_victus_s_thermal_profile()) - ret = hp_wmi_get_fan_speed_victus_s(channel); + if (hp_wmi_fan_control_supported()) + ret = hp_wmi_get_active_fan_speed(channel); else ret = hp_wmi_get_fan_speed(channel); if (ret < 0) @@ -2516,10 +2565,10 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, return 0; case hwmon_pwm: if (attr == hwmon_pwm_input) { - if (!is_victus_s_thermal_profile()) + if (!hp_wmi_fan_control_supported()) return -EOPNOTSUPP; - rpm = hp_wmi_get_fan_speed_victus_s(channel); + rpm = hp_wmi_get_active_fan_speed(channel); if (rpm < 0) return rpm; *val = rpm_to_pwm(rpm / 100, priv); @@ -2553,7 +2602,7 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, switch (type) { case hwmon_pwm: if (attr == hwmon_pwm_input) { - if (!is_victus_s_thermal_profile()) + if (!hp_wmi_fan_control_supported()) return -EOPNOTSUPP; /* PWM input is invalid when not in manual mode */ if (priv->mode != PWM_MODE_MANUAL) @@ -2570,13 +2619,13 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, priv->mode = PWM_MODE_MAX; return hp_wmi_apply_fan_settings(priv); case PWM_MODE_MANUAL: - if (!is_victus_s_thermal_profile()) + if (!hp_wmi_fan_control_supported()) return -EOPNOTSUPP; /* * When switching to manual mode, set fan speed to * current RPM values to ensure a smooth transition. */ - rpm = hp_wmi_get_fan_speed_victus_s(channel); + rpm = hp_wmi_get_active_fan_speed(channel); if (rpm < 0) return rpm; priv->pwm = rpm_to_pwm(rpm / 100, priv); @@ -2642,8 +2691,8 @@ static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv) /* Default behaviour on hwmon init is automatic mode */ priv->mode = PWM_MODE_AUTO; - /* Bypass all non-Victus S devices */ - if (!is_victus_s_thermal_profile()) + /* Bypass devices without fan control support. */ + if (!hp_wmi_fan_table_supported()) return 0; ret = hp_wmi_perform_query(HPWMI_VICTUS_S_GET_FAN_TABLE_QUERY, From 5417f92a1d460bf10e3f316863ba059cc7c845eb Mon Sep 17 00:00:00 2001 From: Radhey Kalra Date: Mon, 15 Jun 2026 14:40:34 +0530 Subject: [PATCH 077/562] platform/x86: hp-wmi: Add Victus 15-fb0xxx support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HP Victus 15-fb0xxx board 8A3D exposes the Victus fan table and accepts the existing Victus fan-speed WMI control path. Add a DMI match using the Victus S thermal-profile and fan-control data. Signed-off-by: Radhey Kalra Link: https://patch.msgid.link/20260615091034.987029-4-radheykalra901@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-wmi.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index 11de5c70ce4d..0dcf2901259e 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -234,6 +234,10 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8902") }, .driver_data = (void *)&omen_v1_legacy_board_params, }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A3D") }, + .driver_data = (void *)&victus_s_board_params, + }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A44") }, .driver_data = (void *)&omen_v1_legacy_board_params, From a3ec96c8b7bc3a643779ddd7daa8a6f482e4917c Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Fri, 12 Jun 2026 19:34:48 +0200 Subject: [PATCH 078/562] platform/x86: dell-privacy: Fix race condition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Accessing priv->features_present needs to happen with the list mutex being held, otherwise priv can be freed at any moment. Fixes: 8af9fa37b8a3 ("platform/x86: dell-privacy: Add support for Dell hardware privacy") Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260612173451.467629-2-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/dell/dell-wmi-privacy.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/dell/dell-wmi-privacy.c b/drivers/platform/x86/dell/dell-wmi-privacy.c index f9d275b2f900..366e5b8dc868 100644 --- a/drivers/platform/x86/dell/dell-wmi-privacy.c +++ b/drivers/platform/x86/dell/dell-wmi-privacy.c @@ -92,11 +92,11 @@ bool dell_privacy_has_mic_mute(void) { struct privacy_wmi_data *priv; - mutex_lock(&list_mutex); + guard(mutex)(&list_mutex); + priv = list_first_entry_or_null(&wmi_list, struct privacy_wmi_data, list); - mutex_unlock(&list_mutex); return priv && (priv->features_present & BIT(DELL_PRIVACY_TYPE_AUDIO)); } From f58553cda9aa84e1598a640870b137350cc24027 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Fri, 12 Jun 2026 19:34:49 +0200 Subject: [PATCH 079/562] platform/x86: dell-wmi-base: Fix resource leak on module load failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We need to properly clean up the SMBIOS request and the privacy driver when the module load fails. Fixes: 8af9fa37b8a3 ("platform/x86: dell-privacy: Add support for Dell hardware privacy") Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260612173451.467629-3-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/dell/dell-wmi-base.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/dell/dell-wmi-base.c b/drivers/platform/x86/dell/dell-wmi-base.c index 997383ba1846..fd6a508cd902 100644 --- a/drivers/platform/x86/dell/dell-wmi-base.c +++ b/drivers/platform/x86/dell/dell-wmi-base.c @@ -843,9 +843,22 @@ static int __init dell_wmi_init(void) err = dell_privacy_register_driver(); if (err) - return err; + goto out_smbios; - return wmi_driver_register(&dell_wmi_driver); + err = wmi_driver_register(&dell_wmi_driver); + if (err) + goto out_privacy; + + return 0; + +out_privacy: + dell_privacy_unregister_driver(); + +out_smbios: + if (wmi_requires_smbios_request) + dell_wmi_events_set_enabled(false); + + return err; } late_initcall(dell_wmi_init); From cfe467c730f658e080ffc57bef36f0260333c5aa Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Fri, 12 Jun 2026 19:34:50 +0200 Subject: [PATCH 080/562] platform/x86: dell-wmi-base: Fix handling of ultra performance key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The commit message of commit 5fbd827eb9c2 ("platform/x86: dell-wmi: Recognise or support new switches") states that the ultra performance key contains additional data after the type and code fields. The event data passed to dell_wmi_process_key() is already parsed, so "buffer" already starts after those two fields. Use the correct index for accessing the first data field to avoid a potential buffer overread. Fixes: 5fbd827eb9c2 ("platform/x86: dell-wmi: Recognise or support new switches") Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260612173451.467629-4-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/dell/dell-wmi-base.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/dell/dell-wmi-base.c b/drivers/platform/x86/dell/dell-wmi-base.c index fd6a508cd902..38a6b3ae2f75 100644 --- a/drivers/platform/x86/dell/dell-wmi-base.c +++ b/drivers/platform/x86/dell/dell-wmi-base.c @@ -456,7 +456,7 @@ static int dell_wmi_process_key(struct wmi_device *wdev, int type, int code, __l key++; used = 1; } else if (type == 0x0012 && code == 0x000d && remaining > 0) { - value = (le16_to_cpu(buffer[2]) == 2); + value = (le16_to_cpu(buffer[0]) == 2); used = 1; } From 74850eb0012b94c77366b0476e4f3b1db349a4d2 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Fri, 12 Jun 2026 19:34:51 +0200 Subject: [PATCH 081/562] platform/x86: dell-ddv: Use no_free_ptr() to simplify error handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use no_free_ptr() inside dell_wmi_ddv_query_buffer() in order to be able to use __free() with the result of the WMI call. Suggested-by: Ilpo Järvinen Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260612173451.467629-5-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/dell/dell-wmi-ddv.c | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/drivers/platform/x86/dell/dell-wmi-ddv.c b/drivers/platform/x86/dell/dell-wmi-ddv.c index 736d9b1fdcfb..f8903ced461b 100644 --- a/drivers/platform/x86/dell/dell-wmi-ddv.c +++ b/drivers/platform/x86/dell/dell-wmi-ddv.c @@ -196,40 +196,30 @@ static int dell_wmi_ddv_query_integer(struct wmi_device *wdev, enum dell_ddv_met static int dell_wmi_ddv_query_buffer(struct wmi_device *wdev, enum dell_ddv_method method, u32 arg, struct dell_wmi_buffer **result) { - struct dell_wmi_buffer *buffer; struct wmi_buffer output; size_t buffer_size; int ret; - ret = dell_wmi_ddv_query(wdev, method, arg, &output, sizeof(*buffer)); + ret = dell_wmi_ddv_query(wdev, method, arg, &output, sizeof(struct dell_wmi_buffer)); if (ret < 0) return ret; - buffer = output.data; - if (!le32_to_cpu(buffer->raw_size)) { - ret = -ENODATA; + struct dell_wmi_buffer *buffer __free(kfree) = output.data; - goto err_free; - } + if (!le32_to_cpu(buffer->raw_size)) + return -ENODATA; buffer_size = struct_size(buffer, raw_data, le32_to_cpu(buffer->raw_size)); if (buffer_size > output.length) { dev_warn(&wdev->dev, FW_WARN "Dell WMI buffer size (%zu) exceeds WMI buffer size (%zu)\n", buffer_size, output.length); - ret = -EMSGSIZE; - - goto err_free; + return -EMSGSIZE; } - *result = buffer; + *result = no_free_ptr(buffer); return 0; - -err_free: - kfree(output.data); - - return ret; } static ssize_t dell_wmi_ddv_query_string(struct wmi_device *wdev, enum dell_ddv_method method, From 43a862be3002ac9385cee76da9b403d6107c890b Mon Sep 17 00:00:00 2001 From: "Derek J. Clark" Date: Fri, 12 Jun 2026 19:16:53 -0700 Subject: [PATCH 082/562] platform/x86: msi-wmi: Reformat msi_wmi_notify() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reformats msi_wmi_notify() to use a switch statement that reduces nesting and prepares the function to support additional ACPI types. Signed-off-by: Derek J. Clark Reviewed-by: Armin Wolf Link: https://patch.msgid.link/20260613021654.933618-2-derekjohn.clark@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/msi-wmi.c | 71 +++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 32 deletions(-) diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c index 4a7ac85c4db4..d00ced756581 100644 --- a/drivers/platform/x86/msi-wmi.c +++ b/drivers/platform/x86/msi-wmi.c @@ -172,44 +172,51 @@ static const struct backlight_ops msi_backlight_ops = { static void msi_wmi_notify(union acpi_object *obj, void *context) { - struct key_entry *key; + struct key_entry *key = NULL; + int eventcode = 0; - if (obj && obj->type == ACPI_TYPE_INTEGER) { - int eventcode = obj->integer.value; + if (!obj) + return; + + switch (obj->type) { + case ACPI_TYPE_INTEGER: + eventcode = obj->integer.value; pr_debug("Eventcode: 0x%x\n", eventcode); - key = sparse_keymap_entry_from_scancode(msi_wmi_input_dev, - eventcode); - if (!key) { - pr_info("Unknown key pressed - %x\n", eventcode); + break; + default: + pr_info("Unknown event received\n"); + return; + } + + key = sparse_keymap_entry_from_scancode(msi_wmi_input_dev, eventcode); + + if (!key) { + pr_info("Unknown key pressed - 0x%x\n", eventcode); + return; + } + + if (event_wmi->quirk_last_pressed) { + ktime_t cur = ktime_get_real(); + ktime_t diff = ktime_sub(cur, last_pressed); + /* Ignore event if any event happened in a 50 ms + * timeframe -> Key press may result in 10-20 GPEs + */ + if (ktime_to_us(diff) < 1000 * 50) { + pr_debug("Suppressed key event 0x%X - Last press was %lld us ago\n", + key->code, ktime_to_us(diff)); return; } + last_pressed = cur; + } - if (event_wmi->quirk_last_pressed) { - ktime_t cur = ktime_get_real(); - ktime_t diff = ktime_sub(cur, last_pressed); - /* Ignore event if any event happened in a 50 ms - timeframe -> Key press may result in 10-20 GPEs */ - if (ktime_to_us(diff) < 1000 * 50) { - pr_debug("Suppressed key event 0x%X - " - "Last press was %lld us ago\n", - key->code, ktime_to_us(diff)); - return; - } - last_pressed = cur; - } + /* Brightness is served via acpi video driver */ + if (key->type == KE_KEY && + (backlight || (key->code == MSI_KEY_BRIGHTNESSUP || + key->code == MSI_KEY_BRIGHTNESSDOWN))) + return; - if (key->type == KE_KEY && - /* Brightness is served via acpi video driver */ - (backlight || - (key->code != MSI_KEY_BRIGHTNESSUP && - key->code != MSI_KEY_BRIGHTNESSDOWN))) { - pr_debug("Send key: 0x%X - Input layer keycode: %d\n", - key->code, key->keycode); - sparse_keymap_report_entry(msi_wmi_input_dev, key, 1, - true); - } - } else - pr_info("Unknown event received\n"); + pr_debug("Send key: 0x%X - Input layer keycode: %d\n", key->code, key->keycode); + sparse_keymap_report_entry(msi_wmi_input_dev, key, 1, true); } static int __init msi_wmi_backlight_setup(void) From 0c716d1848ac86cd4aa4ea2a997bf1e835017869 Mon Sep 17 00:00:00 2001 From: "Derek J. Clark" Date: Fri, 12 Jun 2026 19:16:54 -0700 Subject: [PATCH 083/562] platform/x86: msi-wmi: Add MSI Claw M-Center keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MSI Claw devices produce WMI events through the MSI WMI hotkeys GUID for some of their buttons. When pressed, these cause spam in the kernel. For the majority of devices these events can be safely ignored as they are duplicated by the AT Translated Set 2 Keyboard device exposed as an evdev. For the MSI Claw A8 BZ2EM model's M-Center Menu button (left of the screen) there is no associated keyboard event, so this event must be exposed. Map this button to the same scancode produced by the AT Keyboard device on other models. This does cause double F15 events on the A1M, 7 AI+ A2VM, and 8 AI+ A2VM, but it appears to be harmless in my testing. Signed-off-by: Derek J. Clark Reviewed-by: Armin Wolf Link: https://patch.msgid.link/20260613021654.933618-3-derekjohn.clark@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/msi-wmi.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c index d00ced756581..c9db750fe5ae 100644 --- a/drivers/platform/x86/msi-wmi.c +++ b/drivers/platform/x86/msi-wmi.c @@ -46,6 +46,12 @@ enum msi_scancodes { WIND_KEY_WLAN = 0x5f, /* Fn+F11 Wi-Fi toggle */ WIND_KEY_TURBO, /* Fn+F10 turbo mode toggle */ WIND_KEY_ECO = 0x69, /* Fn+F10 ECO mode toggle */ + /* MSI Claw keys */ + CLAW_KEY_VOLUMEDOWN = 0x21, + CLAW_KEY_CENTER = 0x29, /* MSI M-Center main menu */ + CLAW_KEY_QUICK_LONG = 0x2a, /* MSI M-Center quick access long hold */ + CLAW_KEY_VOLUMEUP = 0x32, + CLAW_KEY_QUICK_SHORT = 0x58, /* MSI M-Center quick access short press */ }; static struct key_entry msi_wmi_keymap[] = { { KE_KEY, MSI_KEY_BRIGHTNESSUP, {KEY_BRIGHTNESSUP} }, @@ -69,6 +75,15 @@ static struct key_entry msi_wmi_keymap[] = { { KE_KEY, WIND_KEY_TURBO, {KEY_PROG1} }, { KE_KEY, WIND_KEY_ECO, {KEY_PROG2} }, + /* These are MSI Claw keys, used for MSI M-Center in Windows */ + { KE_KEY, CLAW_KEY_CENTER, {KEY_F15} }, + + /* These MSI Claw keys work without WMI. Ignore them to avoid double keycodes */ + { KE_IGNORE, CLAW_KEY_QUICK_SHORT }, + { KE_IGNORE, CLAW_KEY_QUICK_LONG }, + { KE_IGNORE, CLAW_KEY_VOLUMEUP }, + { KE_IGNORE, CLAW_KEY_VOLUMEDOWN }, + { KE_END, 0 } }; @@ -183,6 +198,17 @@ static void msi_wmi_notify(union acpi_object *obj, void *context) eventcode = obj->integer.value; pr_debug("Eventcode: 0x%x\n", eventcode); break; + case ACPI_TYPE_BUFFER: + /* Field returns u8[2] here, but is u32 by spec. Allow "oversized" buffers. */ + if (obj->buffer.length < 2) + return; + + /* pointer[0] is key ID, pointer[1] is active state. We don't get release + * events, so ignore the active state and treat as autorelease. + */ + eventcode = obj->buffer.pointer[0]; + pr_debug("Eventcode: 0x%x\n", eventcode); + break; default: pr_info("Unknown event received\n"); return; From 0df706e5e724418283233cd02fff2e7afc682776 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Sun, 14 Jun 2026 13:53:53 +0900 Subject: [PATCH 084/562] platform/x86: dell-wmi-sysman: Don't hex dump attribute security buffer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit set_attribute() populates the security area of the BIOS attribute request buffer with the current admin password via populate_security_buffer(), then dumps the whole request buffer with print_hex_dump_bytes(). This can expose the plaintext admin password in the kernel log. The same issue was fixed for the password attribute path by commit d1a196e0a6dc ("platform/x86: dell-wmi-sysman: Don't hex dump plaintext password data"). Remove the remaining dump from the BIOS attribute path. Fixes: e8a60aa7404b ("platform/x86: Introduce support for Systems Management Driver over WMI for Dell Systems") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5 Signed-off-by: HyeongJun An Link: https://patch.msgid.link/20260614045353.143500-1-sammiee5311@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c b/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c index db278ff4cc4d..154e115af4b4 100644 --- a/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c +++ b/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c @@ -84,7 +84,6 @@ int set_attribute(const char *a_name, const char *a_value) if (ret < 0) goto out; - print_hex_dump_bytes("set attribute data: ", DUMP_PREFIX_NONE, buffer, buffer_size); ret = call_biosattributes_interface(wmi_priv.bios_attr_wdev, buffer, buffer_size, SETATTRIBUTE_METHOD_ID); From ace16d4d3f38c74d5934b7f7fb31c601ab32cd87 Mon Sep 17 00:00:00 2001 From: Julian Haarmann Date: Sun, 14 Jun 2026 22:30:26 +0200 Subject: [PATCH 085/562] platform/x86: lenovo/ymc: Only match lower byte in WMI lid switch query response MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On newer Lenovo Yoga devices like the "Yoga 9 2-in-1 14IPH11 - Type 83SE", the hinge switch WMI query returns extra data in the upper bits (e.g. 0x50001 laptop mode, 0x50002 tablet mode, ect.). The driver previously checked for exact matches (0x01 laptop, 0x02 tablet, ect.) causing newer switches to not work. Mask the WMI query result to only match the lower byte and ignore upper bits. Signed-off-by: Julian Haarmann Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260614203235.235724-1-julian.haarmann@student.kit.edu Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/ymc.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/lenovo/ymc.c b/drivers/platform/x86/lenovo/ymc.c index 1b73a55f1b89..015e046b0fce 100644 --- a/drivers/platform/x86/lenovo/ymc.c +++ b/drivers/platform/x86/lenovo/ymc.c @@ -8,6 +8,8 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include +#include +#include #include #include #include @@ -20,6 +22,8 @@ #define LENOVO_YMC_QUERY_INSTANCE 0 #define LENOVO_YMC_QUERY_METHOD 0x01 +#define LENOVO_YMC_STATE_MASK GENMASK(7, 0) + static bool force; module_param(force, bool, 0444); MODULE_PARM_DESC(force, "Force loading on boards without a convertible DMI chassis-type"); @@ -85,7 +89,9 @@ static void lenovo_ymc_notify(struct wmi_device *wdev, union acpi_object *data) "WMI event data is not an integer\n"); goto free_obj; } - code = obj->integer.value; + + /* strip upper bits (e.g. 0x50000) on newer devices */ + code = FIELD_GET(LENOVO_YMC_STATE_MASK, obj->integer.value); if (!sparse_keymap_report_event(priv->input_dev, code, 1, true)) dev_warn(&wdev->dev, "Unknown key %d pressed\n", code); From d3f2ecd21924bc67c44ebd608e157a94b4ba4b62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Mon, 15 Jun 2026 13:28:59 +0200 Subject: [PATCH 086/562] platform/surface: aggregator: Consistently define ssam_device_ids using named initializers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .driver_data member of the the two struct ssam_device_id arrays were initialized by list expressions. This isn't easily readable if you don't work with the Surface System Aggregator core regularily. Using named initializers is more explicit and thus easier to parse and also more robust to changes of the struct definition. This robustness is relevant for a planned change to struct ssam_device_id replacing .driver_data by an anonymous union. This change doesn't introduce changes to the compiled ssam_device_id arrays. Signed-off-by: Uwe Kleine-König (The Capable Hub) Reviewed-by: Maximilian Luz Link: https://patch.msgid.link/4421c8c959452d8a717ebc7cc905ad9c2912680c.1781522576.git.u.kleine-koenig@baylibre.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/surface/surface_aggregator_hub.c | 9 +++++++-- drivers/platform/surface/surface_aggregator_tabletsw.c | 9 +++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/drivers/platform/surface/surface_aggregator_hub.c b/drivers/platform/surface/surface_aggregator_hub.c index 8b8b80228c14..541e9180f976 100644 --- a/drivers/platform/surface/surface_aggregator_hub.c +++ b/drivers/platform/surface/surface_aggregator_hub.c @@ -348,8 +348,13 @@ static const struct ssam_hub_desc kip_hub = { /* -- Driver registration. -------------------------------------------------- */ static const struct ssam_device_id ssam_hub_match[] = { - { SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_KIP, 0x00), (unsigned long)&kip_hub }, - { SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_BAS, 0x00), (unsigned long)&base_hub }, + { + SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_KIP, 0x00), + .driver_data = (unsigned long)&kip_hub, + }, { + SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_BAS, 0x00), + .driver_data = (unsigned long)&base_hub, + }, { } }; MODULE_DEVICE_TABLE(ssam, ssam_hub_match); diff --git a/drivers/platform/surface/surface_aggregator_tabletsw.c b/drivers/platform/surface/surface_aggregator_tabletsw.c index ffa36ed92897..13031c329553 100644 --- a/drivers/platform/surface/surface_aggregator_tabletsw.c +++ b/drivers/platform/surface/surface_aggregator_tabletsw.c @@ -622,8 +622,13 @@ static const struct ssam_tablet_sw_desc ssam_pos_sw_desc = { /* -- Driver registration. -------------------------------------------------- */ static const struct ssam_device_id ssam_tablet_sw_match[] = { - { SSAM_SDEV(KIP, SAM, 0x00, 0x01), (unsigned long)&ssam_kip_sw_desc }, - { SSAM_SDEV(POS, SAM, 0x00, 0x01), (unsigned long)&ssam_pos_sw_desc }, + { + SSAM_SDEV(KIP, SAM, 0x00, 0x01), + .driver_data = (unsigned long)&ssam_kip_sw_desc, + }, { + SSAM_SDEV(POS, SAM, 0x00, 0x01), + .driver_data = (unsigned long)&ssam_pos_sw_desc, + }, { }, }; MODULE_DEVICE_TABLE(ssam, ssam_tablet_sw_match); From 2f0fab3e8ef925e10dcfa04d1df9cdf1015eda8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Mon, 15 Jun 2026 14:51:37 +0200 Subject: [PATCH 087/562] power: supply: surface_{battery,charger}: Consistently define ssam_device_ids using named initializers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .driver_data member of the the two struct ssam_device_id arrays were initialized by list expressions. This isn't easily readable if you don't work with the Surface System Aggregator core regularily. Using named initializers is more explicit and thus easier to parse and also more robust to changes of the struct definition. This robustness is relevant for a planned change to struct ssam_device_id replacing .driver_data by an anonymous union. While touching these arrays, also drop the comma after the list terminators. This change doesn't introduce changes to the compiled ssam_device_id arrays. Signed-off-by: Uwe Kleine-König (The Capable Hub) Reviewed-by: Maximilian Luz Link: https://patch.msgid.link/bc8eff03b2f36c82af5a75fc7114c277228921db.1781526433.git.u.kleine-koenig@baylibre.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/power/supply/surface_battery.c | 11 ++++++++--- drivers/power/supply/surface_charger.c | 7 +++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c index c759add4df49..1273b6082311 100644 --- a/drivers/power/supply/surface_battery.c +++ b/drivers/power/supply/surface_battery.c @@ -852,9 +852,14 @@ static const struct spwr_psy_properties spwr_psy_props_bat2_sb3 = { }; static const struct ssam_device_id surface_battery_match[] = { - { SSAM_SDEV(BAT, SAM, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat1 }, - { SSAM_SDEV(BAT, KIP, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat2_sb3 }, - { }, + { + SSAM_SDEV(BAT, SAM, 0x01, 0x00), + .driver_data = (unsigned long)&spwr_psy_props_bat1, + }, { + SSAM_SDEV(BAT, KIP, 0x01, 0x00), + .driver_data = (unsigned long)&spwr_psy_props_bat2_sb3, + }, + { } }; MODULE_DEVICE_TABLE(ssam, surface_battery_match); diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c index 90b823848c99..d4bba6b41794 100644 --- a/drivers/power/supply/surface_charger.c +++ b/drivers/power/supply/surface_charger.c @@ -260,8 +260,11 @@ static const struct spwr_psy_properties spwr_psy_props_adp1 = { }; static const struct ssam_device_id surface_ac_match[] = { - { SSAM_SDEV(BAT, SAM, 0x01, 0x01), (unsigned long)&spwr_psy_props_adp1 }, - { }, + { + SSAM_SDEV(BAT, SAM, 0x01, 0x01), + .driver_data = (unsigned long)&spwr_psy_props_adp1, + }, + { } }; MODULE_DEVICE_TABLE(ssam, surface_ac_match); From f3aaddb8a085eda92d4c9792e031ead8237f79aa Mon Sep 17 00:00:00 2001 From: Markus Elfring Date: Wed, 17 Jun 2026 10:50:15 +0200 Subject: [PATCH 088/562] platform/x86: toshiba_bluetooth: Use more common error handling code in toshiba_bt_rfkill_probe() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use an existing label once more so that a bit of exception handling can be better reused at the end of this function implementation. This issue was detected by using the Coccinelle software. Signed-off-by: Markus Elfring Link: https://patch.msgid.link/fa7a5865-6dda-4305-ab48-e0c9310520c8@web.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/toshiba_bluetooth.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/platform/x86/toshiba_bluetooth.c b/drivers/platform/x86/toshiba_bluetooth.c index e00abba58c7c..bf199fba4999 100644 --- a/drivers/platform/x86/toshiba_bluetooth.c +++ b/drivers/platform/x86/toshiba_bluetooth.c @@ -252,10 +252,8 @@ static int toshiba_bt_rfkill_probe(struct platform_device *pdev) platform_set_drvdata(pdev, bt_dev); result = toshiba_bluetooth_sync_status(bt_dev); - if (result) { - kfree(bt_dev); - return result; - } + if (result) + goto err_free_bt_dev; bt_dev->rfk = rfkill_alloc("Toshiba Bluetooth", &pdev->dev, From 110ef9f94f84d425ab55d09b2d70ed12ceffdf08 Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Thu, 2 Jul 2026 08:04:31 +0900 Subject: [PATCH 089/562] apparmor: temporarily disable in syzbot kernels. This change is not for upstream. This change is for linux-next only. This is a workaround until the following build failure is fixed. security/apparmor/apparmorfs.c:486:12: warning: 'decompress_zstd' used but never defined Signed-off-by: Tetsuo Handa --- security/apparmor/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig index 1e3bd44643da..1c5608068ba3 100644 --- a/security/apparmor/Kconfig +++ b/security/apparmor/Kconfig @@ -2,6 +2,7 @@ config SECURITY_APPARMOR bool "AppArmor support" depends on SECURITY && NET + depends on !DEBUG_AID_FOR_SYZBOT select AUDIT select SECURITY_PATH select SECURITYFS From 85b5acfefcf9abd79b9672a54a3b9f48560e7986 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Tue, 30 Jun 2026 10:22:11 +0300 Subject: [PATCH 090/562] mm/mm_init: don't overlap NORMAL and MOVABLE zones with kernelcore=mirror When kernelcore or movablecore kernel parameters define size of the NORMAL and MOVABLE zones as percents of the total memory or by absolute value, ZONE_NORMAL is clamped at the beginning of ZONE_MOVABLE. However, when kernelcore=mirror the ZONE_NORMAL span is not changed but rather pages from ZONE_MOVABLE counted as absent in ZONE_NORMAL. Make the behaviour of kernelcore= parameter uniform and treat mirror just as another way to size the zones. Co-developed-by: Wei Yang Signed-off-by: Wei Yang Link: https://patch.msgid.link/20260630072212.624305-2-rppt@kernel.org Signed-off-by: Mike Rapoport (Microsoft) --- mm/mm_init.c | 36 +++--------------------------------- 1 file changed, 3 insertions(+), 33 deletions(-) diff --git a/mm/mm_init.c b/mm/mm_init.c index 0f64909e8d20..57923dd33d06 100644 --- a/mm/mm_init.c +++ b/mm/mm_init.c @@ -1174,9 +1174,8 @@ static void __init adjust_zone_range_for_zone_movable(int nid, arch_zone_highest_possible_pfn[movable_zone]); /* Adjust for ZONE_MOVABLE starting within this range */ - } else if (!mirrored_kernelcore && - *zone_start_pfn < zone_movable_pfn[nid] && - *zone_end_pfn > zone_movable_pfn[nid]) { + } else if (*zone_start_pfn < zone_movable_pfn[nid] && + *zone_end_pfn > zone_movable_pfn[nid]) { *zone_end_pfn = zone_movable_pfn[nid]; /* Check if this whole range is within ZONE_MOVABLE */ @@ -1224,40 +1223,11 @@ static unsigned long __init zone_absent_pages_in_node(int nid, unsigned long zone_start_pfn, unsigned long zone_end_pfn) { - unsigned long nr_absent; - /* zone is empty, we don't have any absent pages */ if (zone_start_pfn == zone_end_pfn) return 0; - nr_absent = __absent_pages_in_range(nid, zone_start_pfn, zone_end_pfn); - - /* - * ZONE_MOVABLE handling. - * Treat pages to be ZONE_MOVABLE in ZONE_NORMAL as absent pages - * and vice versa. - */ - if (mirrored_kernelcore && zone_movable_pfn[nid]) { - unsigned long start_pfn, end_pfn; - struct memblock_region *r; - - for_each_mem_region(r) { - start_pfn = clamp(memblock_region_memory_base_pfn(r), - zone_start_pfn, zone_end_pfn); - end_pfn = clamp(memblock_region_memory_end_pfn(r), - zone_start_pfn, zone_end_pfn); - - if (zone_type == ZONE_MOVABLE && - memblock_is_mirror(r)) - nr_absent += end_pfn - start_pfn; - - if (zone_type == ZONE_NORMAL && - !memblock_is_mirror(r)) - nr_absent += end_pfn - start_pfn; - } - } - - return nr_absent; + return __absent_pages_in_range(nid, zone_start_pfn, zone_end_pfn); } /* From 3be62316839ac82f934988a2bcdcaed5c1b56d39 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Tue, 30 Jun 2026 10:22:12 +0300 Subject: [PATCH 091/562] mm/mm_init: drop overlap_memmap_init() When ZONE_NORMAL and ZONE_MOVABLE could overlap because kernelcore=mirror didn't reduce the span of ZONE_NORMAL, initialization of the memory map had to skip overlapping pages during initialization of ZONE_MOVABLE to avoid double initialization of the same struct pages. Since kernelcore=mirror works now the same way as other variants of kernelcore=/movablecore=, and adjusts the span of ZONE_NORMAL, there can't be an overlap between ZONE_NORMAL and ZONE_MOVABLE. Remove overlap_memmap_init(). Co-developed-by: Wei Yang Signed-off-by: Wei Yang Reviewed-by: David Hildenbrand (Arm) Link: https://patch.msgid.link/20260630072212.624305-3-rppt@kernel.org Signed-off-by: Mike Rapoport (Microsoft) --- mm/mm_init.c | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/mm/mm_init.c b/mm/mm_init.c index 57923dd33d06..838b5a0ad98d 100644 --- a/mm/mm_init.c +++ b/mm/mm_init.c @@ -799,28 +799,6 @@ void __meminit init_deferred_page(unsigned long pfn, int nid) __init_deferred_page(pfn, nid); } -/* If zone is ZONE_MOVABLE but memory is mirrored, it is an overlapped init */ -static bool __meminit -overlap_memmap_init(unsigned long zone, unsigned long *pfn) -{ - static struct memblock_region *r __meminitdata; - - if (mirrored_kernelcore && zone == ZONE_MOVABLE) { - if (!r || *pfn >= memblock_region_memory_end_pfn(r)) { - for_each_mem_region(r) { - if (*pfn < memblock_region_memory_end_pfn(r)) - break; - } - } - if (*pfn >= memblock_region_memory_base_pfn(r) && - memblock_is_mirror(r)) { - *pfn = memblock_region_memory_end_pfn(r); - return true; - } - } - return false; -} - /* * Only struct pages that correspond to ranges defined by memblock.memory * are zeroed and initialized by going through __init_single_page() during @@ -907,8 +885,6 @@ void __meminit memmap_init_range(unsigned long size, int nid, unsigned long zone * function. They do not exist on hotplugged memory. */ if (context == MEMINIT_EARLY) { - if (overlap_memmap_init(zone, &pfn)) - continue; if (defer_init(nid, pfn, zone_end_pfn)) { deferred_struct_pages = true; break; From b2602886c61c4609c61fc12f9c04f6486f04748a Mon Sep 17 00:00:00 2001 From: Seongjun Hong Date: Wed, 1 Jul 2026 14:06:33 +0000 Subject: [PATCH 092/562] slab: remove unused SL_CPU slab_stat_type Since the removal of the per-cpu slab in commit 32c894c7274b ("slab: remove struct kmem_cache_cpu"), show_slab_objects() no longer has a branch handling SO_CPU, so cpu_slabs_show() always produces "0". Emit "0\n" directly instead, matching the sibling cpu_partial and slabs_cpu_partial stubs, and remove the now-unused SO_CPU macro and SL_CPU enum value. No functional change intended; the cpu_slabs sysfs attribute continues to read 0. Signed-off-by: Seongjun Hong Reviewed-by: Harry Yoo (Oracle) Reviewed-by: Hao Li Link: https://patch.msgid.link/20260701140634.71608-1-hsj0512@snu.ac.kr Signed-off-by: Vlastimil Babka (SUSE) --- mm/slub.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mm/slub.c b/mm/slub.c index 9f754cf1c187..550efd79d146 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -8968,14 +8968,12 @@ static void process_slab(struct loc_track *t, struct kmem_cache *s, enum slab_stat_type { SL_ALL, /* All slabs */ SL_PARTIAL, /* Only partially allocated slabs */ - SL_CPU, /* Only slabs used for cpu caches */ SL_OBJECTS, /* Determine allocated objects not slabs */ SL_TOTAL /* Determine object capacity not slabs */ }; #define SO_ALL (1 << SL_ALL) #define SO_PARTIAL (1 << SL_PARTIAL) -#define SO_CPU (1 << SL_CPU) #define SO_OBJECTS (1 << SL_OBJECTS) #define SO_TOTAL (1 << SL_TOTAL) @@ -9164,7 +9162,7 @@ SLAB_ATTR_RO(partial); static ssize_t cpu_slabs_show(struct kmem_cache *s, char *buf) { - return show_slab_objects(s, buf, SO_CPU); + return sysfs_emit(buf, "0\n"); } SLAB_ATTR_RO(cpu_slabs); From ea0c7a9244594f1d71268e8636a8601217e4839d Mon Sep 17 00:00:00 2001 From: Seongjun Hong Date: Wed, 1 Jul 2026 14:17:46 +0000 Subject: [PATCH 093/562] docs: ABI: sysfs-kernel-slab: mark cpu_partial attributes deprecated The per-cpu slab and per-cpu partial slab mechanisms were removed when SLUB was fully converted to per-cpu sheaves in Linux 7.0. The cpu_slabs, slabs_cpu_partial and cpu_partial sysfs attributes were kept as stubs that always return 0 for backwards compatibility, but their documentation still described them as if they were functional. Update the three descriptions to state that the attributes are deprecated and always read 0, and note that they are retained only for compatibility. While here, fix a "partialli" typo in the slabs_cpu_partial description. Signed-off-by: Seongjun Hong Acked-by: Harry Yoo (Oracle) Link: https://patch.msgid.link/20260701141755.85119-1-hsj0512@snu.ac.kr Signed-off-by: Vlastimil Babka (SUSE) --- Documentation/ABI/testing/sysfs-kernel-slab | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-kernel-slab b/Documentation/ABI/testing/sysfs-kernel-slab index b26e4299f822..c52034e3e794 100644 --- a/Documentation/ABI/testing/sysfs-kernel-slab +++ b/Documentation/ABI/testing/sysfs-kernel-slab @@ -113,8 +113,10 @@ KernelVersion: 2.6.22 Contact: Pekka Enberg , Christoph Lameter Description: - The cpu_slabs file is read-only and displays how many cpu slabs - are active and their NUMA locality. + The cpu_slabs file is read-only. It is deprecated and always + reads "0" since the removal of per-cpu slabs in Linux 7.0. It + previously displayed how many cpu slabs were active and their + NUMA locality. The file is kept for backwards compatibility. What: /sys/kernel/slab//cpuslab_flush Date: April 2009 @@ -509,12 +511,16 @@ What: /sys/kernel/slab//slabs_cpu_partial Date: Aug 2011 Contact: Christoph Lameter Description: - This read-only file shows the number of partialli allocated - frozen slabs. + This read-only file is deprecated and always reads "0(0)" since + the removal of per-cpu partial slabs in Linux 7.0. It previously + showed the number of partially allocated frozen slabs. The file + is kept for backwards compatibility. What: /sys/kernel/slab//cpu_partial Date: Aug 2011 Contact: Christoph Lameter Description: - This read-only file shows the number of per cpu partial - pages to keep around. + This file is deprecated and always reads "0" since the removal of + per-cpu partial slabs in Linux 7.0. It previously showed the + number of per-cpu partial pages to keep around. The file is kept + for backwards compatibility. From 599c56dda3c4b9db0674bf42d03a4f128ab2a158 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Tue, 16 Jun 2026 17:25:10 +0200 Subject: [PATCH 094/562] watchdog: Use named initializers for platform_device_id arrays MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Named initializers are better readable and more robust to changes of the struct definition. This robustness is relevant for a planned change to struct platform_device_id replacing .driver_data by an anonymous union. For one driver drop the unused assignment to .driver_data instead. While touching these arrays unify spacing and usage of commas. There is no effect on the compiled arrays. Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://lore.kernel.org/r/22bc09d0c9c8dfe75a205b0a9ccc98ccfba1de10.1781622532.git.u.kleine-koenig@baylibre.com Signed-off-by: Guenter Roeck --- drivers/watchdog/cros_ec_wdt.c | 4 ++-- drivers/watchdog/max63xx_wdt.c | 14 +++++++------- drivers/watchdog/max77620_wdt.c | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/drivers/watchdog/cros_ec_wdt.c b/drivers/watchdog/cros_ec_wdt.c index 9ffe7f505645..180784e92f2f 100644 --- a/drivers/watchdog/cros_ec_wdt.c +++ b/drivers/watchdog/cros_ec_wdt.c @@ -179,8 +179,8 @@ static int __maybe_unused cros_ec_wdt_resume(struct platform_device *pdev) } static const struct platform_device_id cros_ec_wdt_id[] = { - { DRV_NAME, 0 }, - {} + { .name = DRV_NAME }, + { } }; static struct platform_driver cros_ec_wdt_driver = { diff --git a/drivers/watchdog/max63xx_wdt.c b/drivers/watchdog/max63xx_wdt.c index 21935f9620e4..a9db6118fdfa 100644 --- a/drivers/watchdog/max63xx_wdt.c +++ b/drivers/watchdog/max63xx_wdt.c @@ -246,13 +246,13 @@ static int max63xx_wdt_probe(struct platform_device *pdev) } static const struct platform_device_id max63xx_id_table[] = { - { "max6369_wdt", (kernel_ulong_t)max6369_table, }, - { "max6370_wdt", (kernel_ulong_t)max6369_table, }, - { "max6371_wdt", (kernel_ulong_t)max6371_table, }, - { "max6372_wdt", (kernel_ulong_t)max6371_table, }, - { "max6373_wdt", (kernel_ulong_t)max6373_table, }, - { "max6374_wdt", (kernel_ulong_t)max6373_table, }, - { }, + { .name = "max6369_wdt", .driver_data = (kernel_ulong_t)max6369_table }, + { .name = "max6370_wdt", .driver_data = (kernel_ulong_t)max6369_table }, + { .name = "max6371_wdt", .driver_data = (kernel_ulong_t)max6371_table }, + { .name = "max6372_wdt", .driver_data = (kernel_ulong_t)max6371_table }, + { .name = "max6373_wdt", .driver_data = (kernel_ulong_t)max6373_table }, + { .name = "max6374_wdt", .driver_data = (kernel_ulong_t)max6373_table }, + { } }; MODULE_DEVICE_TABLE(platform, max63xx_id_table); diff --git a/drivers/watchdog/max77620_wdt.c b/drivers/watchdog/max77620_wdt.c index d3ced783a5f4..5fa891818e8d 100644 --- a/drivers/watchdog/max77620_wdt.c +++ b/drivers/watchdog/max77620_wdt.c @@ -236,9 +236,9 @@ static int max77620_wdt_probe(struct platform_device *pdev) } static const struct platform_device_id max77620_wdt_devtype[] = { - { "max77620-watchdog", (kernel_ulong_t)&max77620_wdt_data }, - { "max77714-watchdog", (kernel_ulong_t)&max77714_wdt_data }, - { }, + { .name = "max77620-watchdog", .driver_data = (kernel_ulong_t)&max77620_wdt_data }, + { .name = "max77714-watchdog", .driver_data = (kernel_ulong_t)&max77714_wdt_data }, + { } }; MODULE_DEVICE_TABLE(platform, max77620_wdt_devtype); From bc54a071a02dc396af9a61a7055b646194cb23d4 Mon Sep 17 00:00:00 2001 From: Jingyi Wang Date: Mon, 29 Jun 2026 00:09:05 -0700 Subject: [PATCH 095/562] dt-bindings: watchdog: Document Qualcomm Maili watchdog Add devicetree binding for watchdog present on Qualcomm Maili SoC. Signed-off-by: Jingyi Wang Acked-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20260629-maili-watchdog-v2-1-5cb9c83a581c@oss.qualcomm.com Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/watchdog/qcom-wdt.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/watchdog/qcom-wdt.yaml b/Documentation/devicetree/bindings/watchdog/qcom-wdt.yaml index 74117f5726a7..4ff61102e407 100644 --- a/Documentation/devicetree/bindings/watchdog/qcom-wdt.yaml +++ b/Documentation/devicetree/bindings/watchdog/qcom-wdt.yaml @@ -27,6 +27,7 @@ properties: - qcom,apss-wdt-ipq9574 - qcom,apss-wdt-ipq9650 - qcom,apss-wdt-kaanapali + - qcom,apss-wdt-maili - qcom,apss-wdt-msm8226 - qcom,apss-wdt-msm8974 - qcom,apss-wdt-msm8994 From ab897e584fcb5e99ea00e72ec0dacaf62fb7778a Mon Sep 17 00:00:00 2001 From: Matthieu Buffet Date: Wed, 1 Jul 2026 23:46:27 +0200 Subject: [PATCH 096/562] landlock: Fix TCP Fast Open connection bypass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The documentation of the socket_connect() LSM hook states that it controls connecting a socket to a remote address. It has not been the case since the addition of TCP Fast Open (RFC 7413) support, which allows opening a TCP connection (thus, setting a socket's destination address) via the MSG_FASTOPEN flag passed to sendto()/sendmsg()/sendmmsg(). The problem then got duplicated into MPTCP. Landlock did not take it into account when its TCP support was added, leaving a bypass of TCP connect policy. Ideally a call to the LSM hook would be added in the fastopen code path, in order to fix this generically. But connect() hooks are designed to run with the socket locked, unlike sendmsg() hooks. Closes: https://github.com/landlock-lsm/linux/issues/41 Fixes: fff69fb03dde ("landlock: Support network rules with TCP bind and connect") Signed-off-by: Matthieu Buffet Link: https://patch.msgid.link/20260701214628.33319-1-matthieu@buffet.re [mic: Wrap commit message] Signed-off-by: Mickaël Salaün --- security/landlock/net.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/security/landlock/net.c b/security/landlock/net.c index cbff59ec3aba..46c17116fcf4 100644 --- a/security/landlock/net.c +++ b/security/landlock/net.c @@ -351,6 +351,14 @@ static int hook_socket_sendmsg(struct socket *const sock, access_mask_t access_request; int ret = 0; + if ((msg->msg_flags & MSG_FASTOPEN) && address && sk_is_tcp(sock->sk)) { + ret = current_check_access_socket( + sock, address, addrlen, LANDLOCK_ACCESS_NET_CONNECT_TCP, + true); + if (ret != 0) + return ret; + } + if (sk_is_udp(sock->sk)) access_request = LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP; else From 22b5f1fe549c53fa04c7fca4e4b571415aa28d24 Mon Sep 17 00:00:00 2001 From: Matthieu Buffet Date: Wed, 1 Jul 2026 23:46:28 +0200 Subject: [PATCH 097/562] selftests/landlock: Add test for TCP fast open MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enforce that TCP Fast Open is controlled by LANDLOCK_ACCESS_NET_CONNECT_TCP. Semantics of connect() and sendmsg(MSG_FASTOPEN) should be identical from Landlock's perspective. Also enforce error code consistency, since UDP sockets ignore the MSG_FASTOPEN flag while Unix sockets reject it. Signed-off-by: Matthieu Buffet Link: https://patch.msgid.link/20260701214628.33319-2-matthieu@buffet.re Signed-off-by: Mickaël Salaün --- tools/testing/selftests/landlock/net_test.c | 94 +++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c index 2ed1f76b7a8b..2e4dc5025b04 100644 --- a/tools/testing/selftests/landlock/net_test.c +++ b/tools/testing/selftests/landlock/net_test.c @@ -1281,6 +1281,100 @@ TEST_F(protocol, connect_unspec) EXPECT_EQ(0, close(bind_fd)); } +TEST_F(protocol, tcp_fastopen) +{ + const bool restricted = variant->sandbox == TCP_SANDBOX && + variant->prot.type == SOCK_STREAM && + (variant->prot.protocol == IPPROTO_TCP || variant->prot.protocol == IPPROTO_IP) && + (variant->prot.domain == AF_INET || variant->prot.domain == AF_INET6); + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = LANDLOCK_ACCESS_NET_CONNECT_TCP, + }; + int bind_fd, client_fd, status; + char buf; + pid_t child; + + bind_fd = socket_variant(&self->srv0); + ASSERT_LE(0, bind_fd); + EXPECT_EQ(0, bind_variant(bind_fd, &self->srv0)); + if (self->srv0.protocol.type == SOCK_STREAM) + EXPECT_EQ(0, listen(bind_fd, backlog)); + + child = fork(); + ASSERT_LE(0, child); + if (child == 0) { + int connect_fd, ret; + + /* Closes listening socket for the child. */ + EXPECT_EQ(0, close(bind_fd)); + + connect_fd = socket_variant(&self->srv0); + ASSERT_LE(0, connect_fd); + + if (variant->sandbox == TCP_SANDBOX) { + const int ruleset_fd = landlock_create_ruleset( + &ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + enforce_ruleset(_metadata, ruleset_fd); + EXPECT_EQ(0, close(ruleset_fd)); + } + + /* Fast Open with no address. */ + ret = sendto_variant(connect_fd, NULL, NULL, 0, MSG_FASTOPEN); + if (self->srv0.protocol.domain == AF_UNIX) { + EXPECT_EQ(-ENOTCONN, ret); + } else if (self->srv0.protocol.type == SOCK_DGRAM) { + EXPECT_EQ(-EDESTADDRREQ, ret); + } else { + EXPECT_EQ(-EINVAL, ret); + } + + /* Fast Open to a denied address. */ + ret = sendto_variant(connect_fd, &self->srv0, "A", 1, MSG_FASTOPEN); + if (restricted) { + EXPECT_EQ(-EACCES, ret); + } else if (self->srv0.protocol.domain == AF_UNIX && + self->srv0.protocol.type == SOCK_STREAM) { + EXPECT_EQ(-EOPNOTSUPP, ret); + } else { + EXPECT_EQ(0, ret); + } + + EXPECT_EQ(0, close(connect_fd)); + _exit(_metadata->exit_code); + return; + } + + client_fd = bind_fd; + if (!restricted && self->srv0.protocol.type == SOCK_STREAM && + self->srv0.protocol.domain != AF_UNIX) { + client_fd = accept(bind_fd, NULL, 0); + ASSERT_LE(0, client_fd); + } + + if (restricted) { + EXPECT_EQ(-1, read(client_fd, &buf, 1)); + EXPECT_EQ(ENOTCONN, errno); + } else if (self->srv0.protocol.domain == AF_UNIX && + self->srv0.protocol.type == SOCK_STREAM) { + EXPECT_EQ(-1, read(client_fd, &buf, 1)); + EXPECT_EQ(EINVAL, errno); + } else { + EXPECT_EQ(1, read(client_fd, &buf, 1)); + EXPECT_EQ('A', buf); + } + + EXPECT_EQ(child, waitpid(child, &status, 0)); + EXPECT_EQ(1, WIFEXITED(status)); + EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); + + if (client_fd != bind_fd) + EXPECT_LE(0, close(client_fd)); + + EXPECT_EQ(0, close(bind_fd)); +} + TEST_F(protocol, sendmsg_stream) { int srv0_fd, tmp_fd, client_fd, res; From e0008b108aaf8c5aa22930b2ceadf8f894562acb Mon Sep 17 00:00:00 2001 From: Hrushiraj Gandhi Date: Mon, 8 Jun 2026 11:39:39 +0530 Subject: [PATCH 098/562] dt-bindings: arm: rockchip: add Vicharak Axon board Add the device tree binding for the Vicharak Axon single-board computer based on the Rockchip RK3588 SoC. Acked-by: Krzysztof Kozlowski Signed-off-by: Hrushiraj Gandhi Link: https://patch.msgid.link/20260608060940.52549-2-hrushirajg23@gmail.com Signed-off-by: Heiko Stuebner --- Documentation/devicetree/bindings/arm/rockchip.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/arm/rockchip.yaml b/Documentation/devicetree/bindings/arm/rockchip.yaml index e3ec99df43d8..40aa2ff08c88 100644 --- a/Documentation/devicetree/bindings/arm/rockchip.yaml +++ b/Documentation/devicetree/bindings/arm/rockchip.yaml @@ -1311,6 +1311,11 @@ properties: - const: turing,rk1 - const: rockchip,rk3588 + - description: Vicharak Axon + items: + - const: vicharak,axon + - const: rockchip,rk3588 + - description: WolfVision PF5 mainboard items: - const: wolfvision,rk3568-pf5 From e08c3389c78dbefd31a57df8807cf57ef6f3c9b1 Mon Sep 17 00:00:00 2001 From: Hrushiraj Gandhi Date: Mon, 8 Jun 2026 11:39:40 +0530 Subject: [PATCH 099/562] arm64: dts: rockchip: add Vicharak Axon board Add initial support for the Vicharak Axon single-board computer based on the Rockchip RK3588 SoC. The board supports: - eMMC storage - microSD card - Gigabit Ethernet - HDMI output (dual HDMI) - HDMI input - USB 2.0 host ports - PCIe 2.0 slots - PCIe 3.0 x4 slot - SATA - RTC - Status LEDs The board uses an RK806 PMIC and provides the regulators required by the RK3588 SoC. Signed-off-by: Hrushiraj Gandhi Link: https://patch.msgid.link/20260608060940.52549-3-hrushirajg23@gmail.com Signed-off-by: Heiko Stuebner --- arch/arm64/boot/dts/rockchip/Makefile | 1 + .../dts/rockchip/rk3588-vicharak-axon.dts | 917 ++++++++++++++++++ 2 files changed, 918 insertions(+) create mode 100644 arch/arm64/boot/dts/rockchip/rk3588-vicharak-axon.dts diff --git a/arch/arm64/boot/dts/rockchip/Makefile b/arch/arm64/boot/dts/rockchip/Makefile index 7623c136ce21..14efcd605686 100644 --- a/arch/arm64/boot/dts/rockchip/Makefile +++ b/arch/arm64/boot/dts/rockchip/Makefile @@ -214,6 +214,7 @@ dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-tiger-haikou.dtb dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-tiger-haikou-video-demo.dtbo dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-toybrick-x0.dtb dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-turing-rk1.dtb +dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-vicharak-axon.dtb dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-youyeetoo-yy3588.dtb dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-coolpi-4b.dtb dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-evb1-v10.dtb diff --git a/arch/arm64/boot/dts/rockchip/rk3588-vicharak-axon.dts b/arch/arm64/boot/dts/rockchip/rk3588-vicharak-axon.dts new file mode 100644 index 000000000000..d1899e969f35 --- /dev/null +++ b/arch/arm64/boot/dts/rockchip/rk3588-vicharak-axon.dts @@ -0,0 +1,917 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) + +/dts-v1/; + +#include +#include +#include +#include +#include +#include "rk3588.dtsi" + +/ { + model = "Vicharak Axon"; + compatible = "vicharak,axon", "rockchip,rk3588"; + + aliases { + mmc0 = &sdmmc; + mmc1 = &sdhci; + serial2 = &uart2; + }; + + chosen { + stdout-path = "serial2:1500000n8"; + }; + + hdmi0-con { + compatible = "hdmi-connector"; + type = "a"; + + port { + hdmi0_con_in: endpoint { + remote-endpoint = <&hdmi0_out_con>; + }; + }; + }; + + hdmi1-con { + compatible = "hdmi-connector"; + type = "a"; + + port { + hdmi1_con_in: endpoint { + remote-endpoint = <&hdmi1_out_con>; + }; + }; + }; + + leds { + compatible = "gpio-leds"; + + power_led: power-led { + color = ; + function = LED_FUNCTION_STATUS; + gpios = <&pca9554 0 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "heartbeat"; + }; + + status_led: status-led { + color = ; + function = LED_FUNCTION_STATUS; + gpios = <&pca9554 1 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "none"; + }; + }; + + vcc12v_dcin: regulator-vcc12v-dcin { + compatible = "regulator-fixed"; + regulator-name = "vcc12v_dcin"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <12000000>; + regulator-max-microvolt = <12000000>; + }; + + vcc3v3_io_expander: regulator-vcc3v3-io-expander { + compatible = "regulator-fixed"; + regulator-name = "vcc3v3_io_expander"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + }; + + vcc3v3_pcie20_sata30: regulator-vcc3v3-pcie20-sata30 { + compatible = "regulator-fixed"; + enable-active-high; + gpios = <&gpio0 RK_PA0 GPIO_ACTIVE_HIGH>; + regulator-name = "vcc3v3_pcie20_sata30"; + regulator-boot-on; + regulator-always-on; + regulator-max-microvolt = <3300000>; + regulator-min-microvolt = <3300000>; + vin-supply = <&vcc12v_dcin>; + }; + + vcc3v3_pcie30: regulator-vcc3v3-pcie30 { + compatible = "regulator-fixed"; + enable-active-high; + gpios = <&gpio0 RK_PB2 GPIO_ACTIVE_HIGH>; + regulator-name = "vcc3v3_pcie30"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + vin-supply = <&vcc12v_dcin>; + }; + + vcc5v0_sys: regulator-vcc5v0-sys { + compatible = "regulator-fixed"; + regulator-name = "vcc5v0_sys"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + vin-supply = <&vcc12v_dcin>; + }; + + vcc5v0_usb20_host: regulator-vcc5v0-usb20-host { + compatible = "regulator-fixed"; + regulator-name = "vcc5v0_usb20_host"; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + vin-supply = <&vcc5v0_sys>; + }; + + vcc_1v1_nldo_s3: regulator-vcc-1v1-nldo-s3 { + compatible = "regulator-fixed"; + regulator-name = "vcc_1v1_nldo_s3"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1100000>; + regulator-max-microvolt = <1100000>; + vin-supply = <&vcc5v0_sys>; + }; +}; + +&combphy0_ps { + status = "okay"; +}; + +&combphy1_ps { + status = "okay"; +}; + +&combphy2_psu { + status = "okay"; +}; + +&cpu_b0 { + cpu-supply = <&vdd_cpu_big0_s0>; +}; + +&cpu_b1 { + cpu-supply = <&vdd_cpu_big0_s0>; +}; + +&cpu_b2 { + cpu-supply = <&vdd_cpu_big1_s0>; +}; + +&cpu_b3 { + cpu-supply = <&vdd_cpu_big1_s0>; +}; + +&cpu_l0 { + cpu-supply = <&vdd_cpu_lit_s0>; +}; + +&cpu_l1 { + cpu-supply = <&vdd_cpu_lit_s0>; +}; + +&cpu_l2 { + cpu-supply = <&vdd_cpu_lit_s0>; +}; + +&cpu_l3 { + cpu-supply = <&vdd_cpu_lit_s0>; +}; + +&gmac1 { + clock_in_out = "output"; + phy-handle = <&rgmii_phy>; + phy-mode = "rgmii-rxid"; + phy-supply = <&vcc_3v3_s3>; + pinctrl-0 = <&gmac1_rgmii_bus + &gmac1_rgmii_clk + &gmac1_rx_bus2 + &gmac1_tx_bus2 + &gmac1_miim>; + pinctrl-names = "default"; + snps,reset-active-low; + snps,reset-delays-us = <0 20000 100000>; + snps,reset-gpio = <&gpio3 RK_PB7 GPIO_ACTIVE_LOW>; + tx_delay = <0x43>; + status = "okay"; +}; + +&gpu { + mali-supply = <&vdd_gpu_s0>; + status = "okay"; +}; + +&hdmi0 { + status = "okay"; +}; + +&hdmi0_in { + hdmi0_in_vp0: endpoint { + remote-endpoint = <&vp0_out_hdmi0>; + }; +}; + +&hdmi0_out { + hdmi0_out_con: endpoint { + remote-endpoint = <&hdmi0_con_in>; + }; +}; + +&hdmi0_sound { + status = "okay"; +}; + +&hdmi1 { + status = "okay"; +}; + +&hdmi1_in { + hdmi1_in_vp1: endpoint { + remote-endpoint = <&vp1_out_hdmi1>; + }; +}; + +&hdmi1_out { + hdmi1_out_con: endpoint { + remote-endpoint = <&hdmi1_con_in>; + }; +}; + +&hdmi1_sound { + status = "okay"; +}; + +&hdmi_receiver { + hpd-gpios = <&gpio4 RK_PB0 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&hdmim1_rx_cec &hdmim1_rx_hpdin &hdmim1_rx_scl &hdmim1_rx_sda &hdmirx_hpd>; + pinctrl-names = "default"; + status = "okay"; +}; + +&hdmi_receiver_cma { + status = "okay"; +}; + +&hdptxphy0 { + status = "okay"; +}; + +&hdptxphy1 { + status = "okay"; +}; + +&i2c0 { + pinctrl-0 = <&i2c0m2_xfer>; + pinctrl-names = "default"; + status = "okay"; + + vdd_cpu_big0_s0: regulator@42 { + compatible = "rockchip,rk8602"; + reg = <0x42>; + fcs,suspend-voltage-selector = <1>; + regulator-name = "vdd_cpu_big0_s0"; + regulator-always-on; + regulator-boot-on; + regulator-max-microvolt = <1050000>; + regulator-min-microvolt = <550000>; + regulator-ramp-delay = <2300>; + vin-supply = <&vcc5v0_sys>; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vdd_cpu_big1_s0: regulator@43 { + compatible = "rockchip,rk8603", "rockchip,rk8602"; + reg = <0x43>; + fcs,suspend-voltage-selector = <1>; + regulator-name = "vdd_cpu_big1_s0"; + regulator-always-on; + regulator-boot-on; + regulator-max-microvolt = <1050000>; + regulator-min-microvolt = <550000>; + regulator-ramp-delay = <2300>; + vin-supply = <&vcc5v0_sys>; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + hym8563: rtc@51 { + compatible = "haoyu,hym8563"; + reg = <0x51>; + #clock-cells = <0>; + clock-output-names = "hym8563"; + interrupt-parent = <&gpio0>; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&rtc_int>; + wakeup-source; + }; +}; + +&i2c1 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c1m2_xfer>; + status = "okay"; + + vdd_npu_s0: regulator@42 { + compatible = "rockchip,rk8602"; + reg = <0x42>; + fcs,suspend-voltage-selector = <1>; + regulator-name = "vdd_npu_s0"; + regulator-boot-on; + regulator-enable-ramp-delay = <500>; + regulator-min-microvolt = <550000>; + regulator-max-microvolt = <950000>; + regulator-ramp-delay = <2300>; + vin-supply = <&vcc5v0_sys>; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; +}; + +&i2c6 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c6m0_xfer>; + status = "okay"; + + pca9554: gpio@24 { + compatible = "nxp,pca9554"; + reg = <0x24>; + #gpio-cells = <2>; + gpio-controller; + vcc-supply = <&vcc3v3_io_expander>; + }; +}; + +&i2s5_8ch { + status = "okay"; +}; + +&i2s6_8ch { + status = "okay"; +}; + +&sdhci { + bus-width = <8>; + full-pwr-cycle-in-suspend; + mmc-hs400-1_8v; + mmc-hs400-enhanced-strobe; + no-sd; + no-sdio; + non-removable; + vmmc-supply = <&vcc_3v3_s3>; + vqmmc-supply = <&vcc_1v8_s3>; + status = "okay"; +}; + +&sdmmc { + bus-width = <4>; + cap-mmc-highspeed; + cap-sd-highspeed; + cd-gpios = <&gpio0 RK_PA4 GPIO_ACTIVE_LOW>; + disable-wp; + max-frequency = <200000000>; + no-mmc; + no-sdio; + sd-uhs-sdr104; + vmmc-supply = <&vcc_3v3_s3>; + vqmmc-supply = <&vccio_sd_s0>; + status = "okay"; +}; + +&spi2 { + assigned-clock-rates = <200000000>; + assigned-clocks = <&cru CLK_SPI2>; + num-cs = <1>; + pinctrl-0 = <&spi2m2_cs0 &spi2m2_pins>; + pinctrl-names = "default"; + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + pmic@0 { + compatible = "rockchip,rk806"; + reg = <0>; + #gpio-cells = <2>; + gpio-controller; + interrupt-parent = <&gpio0>; + interrupts = <7 IRQ_TYPE_LEVEL_LOW>; + pinctrl-names = "default", "pmic-power-off"; + pinctrl-0 = <&pmic_pins>, <&rk806_dvs1_null>, + <&rk806_dvs2_null>, <&rk806_dvs3_null>; + pinctrl-1 = <&rk806_dvs1_pwrdn>; + spi-max-frequency = <1000000>; + + vcc1-supply = <&vcc5v0_sys>; + vcc2-supply = <&vcc5v0_sys>; + vcc3-supply = <&vcc5v0_sys>; + vcc4-supply = <&vcc5v0_sys>; + vcc5-supply = <&vcc5v0_sys>; + vcc6-supply = <&vcc5v0_sys>; + vcc7-supply = <&vcc5v0_sys>; + vcc8-supply = <&vcc5v0_sys>; + vcc9-supply = <&vcc5v0_sys>; + vcc10-supply = <&vcc5v0_sys>; + vcc11-supply = <&vcc_2v0_pldo_s3>; + vcc12-supply = <&vcc5v0_sys>; + vcc13-supply = <&vcc_1v1_nldo_s3>; + vcc14-supply = <&vcc_1v1_nldo_s3>; + vcca-supply = <&vcc5v0_sys>; + + rk806_dvs1_null: rk806_dvs1_null { + pins = "gpio_pwrctrl1"; + function = "pin_fun0"; + }; + + rk806_dvs1_slp: rk806_dvs1_slp { + pins = "gpio_pwrctrl1"; + function = "pin_fun1"; + }; + + rk806_dvs1_pwrdn: rk806_dvs1_pwrdn { + pins = "gpio_pwrctrl1"; + function = "pin_fun2"; + }; + + rk806_dvs1_rst: rk806_dvs1_rst { + pins = "gpio_pwrctrl1"; + function = "pin_fun3"; + }; + + rk806_dvs2_null: rk806_dvs2_null { + pins = "gpio_pwrctrl2"; + function = "pin_fun0"; + }; + + rk806_dvs2_slp: rk806_dvs2_slp { + pins = "gpio_pwrctrl2"; + function = "pin_fun1"; + }; + + rk806_dvs2_pwrdn: rk806_dvs2_pwrdn { + pins = "gpio_pwrctrl2"; + function = "pin_fun2"; + }; + + rk806_dvs2_rst: rk806_dvs2_rst { + pins = "gpio_pwrctrl2"; + function = "pin_fun3"; + }; + + rk806_dvs2_dvs: rk806_dvs2_dvs { + pins = "gpio_pwrctrl2"; + function = "pin_fun4"; + }; + + rk806_dvs2_gpio: rk806_dvs2_gpio { + pins = "gpio_pwrctrl2"; + function = "pin_fun5"; + }; + + rk806_dvs3_null: rk806_dvs3_null { + pins = "gpio_pwrctrl3"; + function = "pin_fun0"; + }; + + rk806_dvs3_slp: rk806_dvs3_slp { + pins = "gpio_pwrctrl3"; + function = "pin_fun1"; + }; + + rk806_dvs3_pwrdn: rk806_dvs3_pwrdn { + pins = "gpio_pwrctrl3"; + function = "pin_fun2"; + }; + + rk806_dvs3_rst: rk806_dvs3_rst { + pins = "gpio_pwrctrl3"; + function = "pin_fun3"; + }; + + rk806_dvs3_dvs: rk806_dvs3_dvs { + pins = "gpio_pwrctrl3"; + function = "pin_fun4"; + }; + + rk806_dvs3_gpio: rk806_dvs3_gpio { + pins = "gpio_pwrctrl3"; + function = "pin_fun5"; + }; + + regulators { + vdd_gpu_s0: dcdc-reg1 { + regulator-name = "vdd_gpu_s0"; + regulator-boot-on; + regulator-enable-ramp-delay = <400>; + regulator-min-microvolt = <550000>; + regulator-max-microvolt = <950000>; + regulator-ramp-delay = <12500>; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vdd_cpu_lit_s0: dcdc-reg2 { + regulator-name = "vdd_cpu_lit_s0"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <550000>; + regulator-max-microvolt = <950000>; + regulator-ramp-delay = <12500>; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vdd_log_s0: dcdc-reg3 { + regulator-name = "vdd_log_s0"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <675000>; + regulator-max-microvolt = <750000>; + regulator-ramp-delay = <12500>; + + regulator-state-mem { + regulator-off-in-suspend; + regulator-suspend-microvolt = <750000>; + }; + }; + + vdd_vdenc_s0: dcdc-reg4 { + regulator-name = "vdd_vdenc_s0"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <550000>; + regulator-max-microvolt = <950000>; + regulator-ramp-delay = <12500>; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vdd_ddr_s0: dcdc-reg5 { + regulator-name = "vdd_ddr_s0"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <675000>; + regulator-max-microvolt = <900000>; + regulator-ramp-delay = <12500>; + + regulator-state-mem { + regulator-off-in-suspend; + regulator-suspend-microvolt = <850000>; + }; + }; + + vdd2_ddr_s3: dcdc-reg6 { + regulator-name = "vdd2_ddr_s3"; + regulator-always-on; + regulator-boot-on; + + regulator-state-mem { + regulator-on-in-suspend; + }; + }; + + vcc_2v0_pldo_s3: dcdc-reg7 { + regulator-name = "vdd_2v0_pldo_s3"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <2000000>; + regulator-max-microvolt = <2000000>; + + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <2000000>; + }; + }; + + vcc_3v3_s3: dcdc-reg8 { + regulator-name = "vcc_3v3_s3"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <3300000>; + }; + }; + + vddq_ddr_s0: dcdc-reg9 { + regulator-name = "vddq_ddr_s0"; + regulator-always-on; + regulator-boot-on; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vcc_1v8_s3: dcdc-reg10 { + regulator-name = "vcc_1v8_s3"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <1800000>; + }; + }; + + avcc_1v8_s0: pldo-reg1 { + regulator-name = "avcc_1v8_s0"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vcc_1v8_s0: pldo-reg2 { + regulator-name = "vcc_1v8_s0"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + + regulator-state-mem { + regulator-off-in-suspend; + regulator-suspend-microvolt = <1800000>; + }; + }; + + avdd_1v2_s0: pldo-reg3 { + regulator-name = "avdd_1v2_s0"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vcc_3v3_s0: pldo-reg4 { + regulator-name = "vcc_3v3_s0"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vccio_sd_s0: pldo-reg5 { + regulator-name = "vccio_sd_s0"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <3300000>; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + pldo6_s3: pldo-reg6 { + regulator-name = "pldo6_s3"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <1800000>; + }; + }; + + vdd_0v75_s3: nldo-reg1 { + regulator-name = "vdd_0v75_s3"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <750000>; + regulator-max-microvolt = <750000>; + + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <750000>; + }; + }; + + vdd_ddr_pll_s0: nldo-reg2 { + regulator-name = "vdd_ddr_pll_s0"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <850000>; + regulator-max-microvolt = <850000>; + + regulator-state-mem { + regulator-off-in-suspend; + regulator-suspend-microvolt = <850000>; + }; + }; + + avdd_0v75_s0: nldo-reg3 { + regulator-name = "avdd_0v75_s0"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <837500>; + regulator-max-microvolt = <837500>; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vdd_0v85_s0: nldo-reg4 { + regulator-name = "vdd_0v85_s0"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <850000>; + regulator-max-microvolt = <850000>; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vdd_0v75_s0: nldo-reg5 { + regulator-name = "vdd_0v75_s0"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <750000>; + regulator-max-microvolt = <750000>; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + }; + }; +}; + +&mdio1 { + rgmii_phy: phy@1 { + compatible = "ethernet-phy-ieee802.3-c22"; + reg = <0x1>; + }; +}; + +&pcie2x1l0 { + pinctrl-names = "default"; + pinctrl-0 = <&pcie2_0_rst>; + reset-gpios = <&gpio4 RK_PA5 GPIO_ACTIVE_HIGH>; + vpcie3v3-supply = <&vcc3v3_pcie20_sata30>; + status = "okay"; +}; + +&pcie2x1l1 { + pinctrl-names = "default"; + pinctrl-0 = <&pcie2_1_rst>; + reset-gpios = <&gpio4 RK_PA2 GPIO_ACTIVE_HIGH>; + vpcie3v3-supply = <&vcc3v3_pcie20_sata30>; + status = "okay"; +}; + +&pcie30phy { + status = "okay"; +}; + +&pcie3x4 { + pinctrl-names = "default"; + pinctrl-0 = <&pcie3_reset>; + reset-gpios = <&gpio4 RK_PB6 GPIO_ACTIVE_HIGH>; + vpcie3v3-supply = <&vcc3v3_pcie30>; + status = "okay"; +}; + +&pinctrl { + hdmirx { + hdmirx_hpd: hdmirx-5v-detection { + rockchip,pins = <4 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + + hym8563 { + rtc_int: rtc-int { + rockchip,pins = + <0 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + + pcie2 { + pcie2_0_rst: pcie2-0-rst { + rockchip,pins = + <4 RK_PA5 RK_FUNC_GPIO &pcfg_pull_none>; + }; + + pcie2_1_rst: pcie2-1-rst { + rockchip,pins = + <4 RK_PA2 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + + pcie3 { + pcie3_reset: pcie3-reset { + rockchip,pins = + <4 RK_PB6 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; +}; + +&saradc { + vref-supply = <&avcc_1v8_s0>; + status = "okay"; +}; + +&sata0 { + status = "okay"; +}; + +&tsadc { + status = "okay"; +}; + +&u2phy2 { + status = "okay"; +}; + +&u2phy2_host { + phy-supply = <&vcc5v0_usb20_host>; + status = "okay"; +}; + +&u2phy3 { + status = "okay"; +}; + +&u2phy3_host { + phy-supply = <&vcc5v0_usb20_host>; + status = "okay"; +}; + +&uart2 { + pinctrl-names = "default"; + pinctrl-0 = <&uart2m0_xfer>; + status = "okay"; +}; + +&usb_host0_ehci { + status = "okay"; +}; + +&usb_host0_ohci { + status = "okay"; +}; + +&usb_host1_ehci { + status = "okay"; +}; + +&usb_host1_ohci { + status = "okay"; +}; + +&vop { + status = "okay"; +}; + +&vop_mmu { + status = "okay"; +}; + +&vp0 { + vp0_out_hdmi0: endpoint@ROCKCHIP_VOP2_EP_HDMI0 { + reg = ; + remote-endpoint = <&hdmi0_in_vp0>; + }; +}; + +&vp1 { + vp1_out_hdmi1: endpoint@ROCKCHIP_VOP2_EP_HDMI1 { + reg = ; + remote-endpoint = <&hdmi1_in_vp1>; + }; +}; From b4a5e628936cc01ccfa713115bc56b46e798ebff Mon Sep 17 00:00:00 2001 From: Hrushiraj Gandhi Date: Sat, 27 Jun 2026 15:56:32 +0530 Subject: [PATCH 100/562] dt-bindings: arm: rockchip: Add Vicharak Vaaman2 Add device tree binding documentation for the Vicharak Vaaman2, a single-board computer based on the Rockchip RK3588 SoC. Signed-off-by: Hrushiraj Gandhi Acked-by: Conor Dooley Link: https://patch.msgid.link/20260627102633.86222-2-hrushirajg23@gmail.com Signed-off-by: Heiko Stuebner --- Documentation/devicetree/bindings/arm/rockchip.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/arm/rockchip.yaml b/Documentation/devicetree/bindings/arm/rockchip.yaml index 40aa2ff08c88..aa66a15be233 100644 --- a/Documentation/devicetree/bindings/arm/rockchip.yaml +++ b/Documentation/devicetree/bindings/arm/rockchip.yaml @@ -1316,6 +1316,11 @@ properties: - const: vicharak,axon - const: rockchip,rk3588 + - description: Vicharak Vaaman2 + items: + - const: vicharak,vaaman2 + - const: rockchip,rk3588 + - description: WolfVision PF5 mainboard items: - const: wolfvision,rk3568-pf5 From 0aec094dff13ab5526a3e602243576c3ac993ec3 Mon Sep 17 00:00:00 2001 From: Hrushiraj Gandhi Date: Sat, 27 Jun 2026 15:56:33 +0530 Subject: [PATCH 101/562] arm64: dts: rockchip: Add Vicharak Vaaman2 board Add device tree for the Vicharak Vaaman2, a single-board computer based on the Rockchip RK3588 SoC. The board features: - RK3588 SoC with 4x Cortex-A76 (big) + 4x Cortex-A55 (little) - eMMC storage via SDHCI (HS400) - microSD card via SDMMC - RK806 PMIC on SPI2 providing all required power domains - Two RK8602/RK8603 CPU regulators on I2C0 (big clusters) - RK8602 NPU regulator on I2C1 - HYM8563 RTC on I2C0 - Status LED on GPIO2_C5 (active-low, heartbeat trigger) - UART2 as serial console at 1500000 baud - SARADC with 1.8V reference Signed-off-by: Hrushiraj Gandhi Link: https://patch.msgid.link/20260627102633.86222-3-hrushirajg23@gmail.com Signed-off-by: Heiko Stuebner --- arch/arm64/boot/dts/rockchip/Makefile | 1 + .../dts/rockchip/rk3588-vicharak-vaaman2.dts | 544 ++++++++++++++++++ 2 files changed, 545 insertions(+) create mode 100644 arch/arm64/boot/dts/rockchip/rk3588-vicharak-vaaman2.dts diff --git a/arch/arm64/boot/dts/rockchip/Makefile b/arch/arm64/boot/dts/rockchip/Makefile index 14efcd605686..519bb6c431ac 100644 --- a/arch/arm64/boot/dts/rockchip/Makefile +++ b/arch/arm64/boot/dts/rockchip/Makefile @@ -215,6 +215,7 @@ dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-tiger-haikou-video-demo.dtbo dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-toybrick-x0.dtb dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-turing-rk1.dtb dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-vicharak-axon.dtb +dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-vicharak-vaaman2.dtb dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-youyeetoo-yy3588.dtb dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-coolpi-4b.dtb dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-evb1-v10.dtb diff --git a/arch/arm64/boot/dts/rockchip/rk3588-vicharak-vaaman2.dts b/arch/arm64/boot/dts/rockchip/rk3588-vicharak-vaaman2.dts new file mode 100644 index 000000000000..fc9d5d135cd3 --- /dev/null +++ b/arch/arm64/boot/dts/rockchip/rk3588-vicharak-vaaman2.dts @@ -0,0 +1,544 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) + +/dts-v1/; + +#include +#include +#include +#include +#include +#include "rk3588.dtsi" + +/ { + model = "Vicharak Vaaman2"; + compatible = "vicharak,vaaman2", "rockchip,rk3588"; + + aliases { + mmc0 = &sdmmc; + mmc1 = &sdhci; + serial2 = &uart2; + }; + + chosen { + stdout-path = "serial2:1500000n8"; + }; + + leds { + compatible = "gpio-leds"; + + status_led: status-led { + gpios = <&gpio2 RK_PC5 GPIO_ACTIVE_LOW>; + function = LED_FUNCTION_STATUS; + default-state = "on"; + retain-state-suspended; + linux,default-trigger = "heartbeat"; + }; + }; + + vcc20v_dcin: regulator-vcc20v-dcin { + compatible = "regulator-fixed"; + regulator-name = "vcc20v_dcin"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <20000000>; + regulator-max-microvolt = <20000000>; + }; + + vcc5v0_sys: regulator-vcc5v0-sys { + compatible = "regulator-fixed"; + regulator-name = "vcc5v0_sys"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + vin-supply = <&vcc20v_dcin>; + }; + + vcc_1v1_nldo_s3: regulator-vcc-1v1-nldo-s3 { + compatible = "regulator-fixed"; + regulator-name = "vcc_1v1_nldo_s3"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1100000>; + regulator-max-microvolt = <1100000>; + vin-supply = <&vcc5v0_sys>; + }; +}; + +&cpu_b0 { + cpu-supply = <&vdd_cpu_big0_s0>; +}; + +&cpu_b1 { + cpu-supply = <&vdd_cpu_big0_s0>; +}; + +&cpu_b2 { + cpu-supply = <&vdd_cpu_big1_s0>; +}; + +&cpu_b3 { + cpu-supply = <&vdd_cpu_big1_s0>; +}; + +&cpu_l0 { + cpu-supply = <&vdd_cpu_lit_s0>; +}; + +&cpu_l1 { + cpu-supply = <&vdd_cpu_lit_s0>; +}; + +&cpu_l2 { + cpu-supply = <&vdd_cpu_lit_s0>; +}; + +&cpu_l3 { + cpu-supply = <&vdd_cpu_lit_s0>; +}; + +&i2c0 { + pinctrl-0 = <&i2c0m2_xfer>; + pinctrl-names = "default"; + status = "okay"; + + vdd_cpu_big0_s0: regulator@42 { + compatible = "rockchip,rk8602"; + reg = <0x42>; + fcs,suspend-voltage-selector = <1>; + regulator-always-on; + regulator-boot-on; + regulator-max-microvolt = <1050000>; + regulator-min-microvolt = <550000>; + regulator-name = "vdd_cpu_big0_s0"; + regulator-ramp-delay = <2300>; + vin-supply = <&vcc5v0_sys>; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vdd_cpu_big1_s0: regulator@43 { + compatible = "rockchip,rk8603", "rockchip,rk8602"; + reg = <0x43>; + fcs,suspend-voltage-selector = <1>; + regulator-always-on; + regulator-boot-on; + regulator-max-microvolt = <1050000>; + regulator-min-microvolt = <550000>; + regulator-name = "vdd_cpu_big1_s0"; + regulator-ramp-delay = <2300>; + vin-supply = <&vcc5v0_sys>; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + hym8563: rtc@51 { + compatible = "haoyu,hym8563"; + reg = <0x51>; + #clock-cells = <0>; + clock-output-names = "hym8563"; + interrupt-parent = <&gpio0>; + interrupts = ; + pinctrl-names = "default"; + pinctrl-0 = <&rtc_int>; + wakeup-source; + }; +}; + +&i2c1 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c1m2_xfer>; + status = "okay"; + + vdd_npu_s0: regulator@42 { + compatible = "rockchip,rk8602"; + reg = <0x42>; + fcs,suspend-voltage-selector = <1>; + regulator-boot-on; + regulator-enable-ramp-delay = <500>; + regulator-min-microvolt = <550000>; + regulator-max-microvolt = <950000>; + regulator-name = "vdd_npu_s0"; + regulator-ramp-delay = <2300>; + vin-supply = <&vcc5v0_sys>; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; +}; + +&sdhci { + bus-width = <8>; + full-pwr-cycle-in-suspend; + mmc-hs400-1_8v; + mmc-hs400-enhanced-strobe; + no-sd; + no-sdio; + non-removable; + vmmc-supply = <&vcc_3v3_s3>; + vqmmc-supply = <&vcc_1v8_s3>; + status = "okay"; +}; + +&sdmmc { + bus-width = <4>; + cap-mmc-highspeed; + cap-sd-highspeed; + cd-gpios = <&gpio0 RK_PA4 GPIO_ACTIVE_LOW>; + disable-wp; + max-frequency = <200000000>; + no-mmc; + no-sdio; + sd-uhs-sdr104; + vmmc-supply = <&vcc_3v3_s3>; + vqmmc-supply = <&vccio_sd_s0>; + status = "okay"; +}; + +&spi2 { + assigned-clock-rates = <200000000>; + assigned-clocks = <&cru CLK_SPI2>; + num-cs = <1>; + pinctrl-0 = <&spi2m2_cs0 &spi2m2_pins>; + pinctrl-names = "default"; + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + pmic@0 { + reg = <0>; + compatible = "rockchip,rk806"; + #gpio-cells = <2>; + gpio-controller; + interrupt-parent = <&gpio0>; + interrupts = <7 IRQ_TYPE_LEVEL_LOW>; + pinctrl-names = "default"; + pinctrl-0 = <&pmic_pins>, <&rk806_dvs1_null>, + <&rk806_dvs2_null>, <&rk806_dvs3_null>; + spi-max-frequency = <1000000>; + + vcc1-supply = <&vcc5v0_sys>; + vcc2-supply = <&vcc5v0_sys>; + vcc3-supply = <&vcc5v0_sys>; + vcc4-supply = <&vcc5v0_sys>; + vcc5-supply = <&vcc5v0_sys>; + vcc6-supply = <&vcc5v0_sys>; + vcc7-supply = <&vcc5v0_sys>; + vcc8-supply = <&vcc5v0_sys>; + vcc9-supply = <&vcc5v0_sys>; + vcc10-supply = <&vcc5v0_sys>; + vcc11-supply = <&vcc_2v0_pldo_s3>; + vcc12-supply = <&vcc5v0_sys>; + vcc13-supply = <&vcc_1v1_nldo_s3>; + vcc14-supply = <&vcc_1v1_nldo_s3>; + vcca-supply = <&vcc5v0_sys>; + + rk806_dvs1_null: dvs1-null-pins { + pins = "gpio_pwrctrl1"; + function = "pin_fun0"; + }; + + rk806_dvs2_null: dvs2-null-pins { + pins = "gpio_pwrctrl2"; + function = "pin_fun0"; + }; + + rk806_dvs3_null: dvs3-null-pins { + pins = "gpio_pwrctrl3"; + function = "pin_fun0"; + }; + + regulators { + vdd_gpu_s0: dcdc-reg1 { + regulator-boot-on; + regulator-enable-ramp-delay = <400>; + regulator-min-microvolt = <550000>; + regulator-max-microvolt = <950000>; + regulator-name = "vdd_gpu_s0"; + regulator-ramp-delay = <12500>; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vdd_cpu_lit_s0: dcdc-reg2 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <550000>; + regulator-max-microvolt = <950000>; + regulator-name = "vdd_cpu_lit_s0"; + regulator-ramp-delay = <12500>; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vdd_log_s0: dcdc-reg3 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <675000>; + regulator-max-microvolt = <750000>; + regulator-name = "vdd_log_s0"; + regulator-ramp-delay = <12500>; + + regulator-state-mem { + regulator-off-in-suspend; + regulator-suspend-microvolt = <750000>; + }; + }; + + vdd_vdenc_s0: dcdc-reg4 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <550000>; + regulator-max-microvolt = <950000>; + regulator-name = "vdd_vdenc_s0"; + regulator-ramp-delay = <12500>; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vdd_ddr_s0: dcdc-reg5 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <675000>; + regulator-max-microvolt = <900000>; + regulator-name = "vdd_ddr_s0"; + regulator-ramp-delay = <12500>; + + regulator-state-mem { + regulator-off-in-suspend; + regulator-suspend-microvolt = <850000>; + }; + }; + + vdd2_ddr_s3: dcdc-reg6 { + regulator-always-on; + regulator-boot-on; + regulator-name = "vdd2_ddr_s3"; + + regulator-state-mem { + regulator-on-in-suspend; + }; + }; + + vcc_2v0_pldo_s3: dcdc-reg7 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <2000000>; + regulator-max-microvolt = <2000000>; + regulator-name = "vdd_2v0_pldo_s3"; + + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <2000000>; + }; + }; + + vcc_3v3_s3: dcdc-reg8 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-name = "vcc_3v3_s3"; + + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <3300000>; + }; + }; + + vddq_ddr_s0: dcdc-reg9 { + regulator-always-on; + regulator-boot-on; + regulator-name = "vddq_ddr_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vcc_1v8_s3: dcdc-reg10 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-name = "vcc_1v8_s3"; + + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <1800000>; + }; + }; + + avcc_1v8_s0: pldo-reg1 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-name = "avcc_1v8_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vcc_1v8_s0: pldo-reg2 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-name = "vcc_1v8_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + regulator-suspend-microvolt = <1800000>; + }; + }; + + avdd_1v2_s0: pldo-reg3 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + regulator-name = "avdd_1v2_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vcc_3v3_s0: pldo-reg4 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-name = "vcc_3v3_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vccio_sd_s0: pldo-reg5 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <3300000>; + regulator-name = "vccio_sd_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + pldo6_s3: pldo-reg6 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-name = "pldo6_s3"; + + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <1800000>; + }; + }; + + vdd_0v75_s3: nldo-reg1 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <750000>; + regulator-max-microvolt = <750000>; + regulator-name = "vdd_0v75_s3"; + + regulator-state-mem { + regulator-on-in-suspend; + regulator-suspend-microvolt = <750000>; + }; + }; + + vdd_ddr_pll_s0: nldo-reg2 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <850000>; + regulator-max-microvolt = <850000>; + regulator-name = "vdd_ddr_pll_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + regulator-suspend-microvolt = <850000>; + }; + }; + + avdd_0v75_s0: nldo-reg3 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <837500>; + regulator-max-microvolt = <837500>; + regulator-name = "avdd_0v75_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vdd_0v85_s0: nldo-reg4 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <850000>; + regulator-max-microvolt = <850000>; + regulator-name = "vdd_0v85_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + + vdd_0v75_s0: nldo-reg5 { + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <750000>; + regulator-max-microvolt = <750000>; + regulator-name = "vdd_0v75_s0"; + + regulator-state-mem { + regulator-off-in-suspend; + }; + }; + }; + }; +}; + +&pinctrl { + hym8563 { + rtc_int: rtc-int { + rockchip,pins = + <0 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; +}; + +&saradc { + vref-supply = <&avcc_1v8_s0>; + status = "okay"; +}; + +&tsadc { + status = "okay"; +}; + +&uart2 { + pinctrl-names = "default"; + pinctrl-0 = <&uart2m0_xfer>; + status = "okay"; +}; From f820f61f079ad50869cee6541bbb6423f88536b7 Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Mon, 29 Jun 2026 21:26:31 +0530 Subject: [PATCH 102/562] platform/x86/amd/hsmp: Validate ACPI UID before parsing socket index MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hsmp_get_uid() passed the device UID directly to kstrtou16(uid + 2) without checking it. A NULL UID or one shorter than three characters would dereference a NULL pointer or read past the end of the string. Reject such UIDs with -EINVAL before stripping the "ID" prefix. Signed-off-by: Muralidhara M K Reviewed-by: Ilpo Järvinen Link: https://patch.msgid.link/20260625123337.886435-3-muralidhara.mk@amd.com Link: https://patch.msgid.link/20260629155634.1807598-2-muralidhara.mk@amd.com Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/hsmp/acpi.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/platform/x86/amd/hsmp/acpi.c b/drivers/platform/x86/amd/hsmp/acpi.c index 97ed71593bdf..4a1ce4cb25e7 100644 --- a/drivers/platform/x86/amd/hsmp/acpi.c +++ b/drivers/platform/x86/amd/hsmp/acpi.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -77,6 +78,8 @@ static inline int hsmp_get_uid(struct device *dev, u16 *sock_ind) * bytes to integer. */ uid = acpi_device_uid(ACPI_COMPANION(dev)); + if (!uid || strlen(uid) < 3) + return -EINVAL; return kstrtou16(uid + 2, 10, sock_ind); } From 1cacf5e8693dd1053d9044b6e6e4986bbbd355a1 Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Mon, 29 Jun 2026 21:26:32 +0530 Subject: [PATCH 103/562] platform/x86/amd/hsmp: Validate _DSD mailbox sub-package element count MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hsmp_read_acpi_dsd() dereferenced elements[0] and elements[1] of each mailbox sub-package before confirming the package actually held two elements, allowing an out-of-bounds read on a malformed _DSD. Verify package.count >= 2 first, then fetch the string and integer objects. Signed-off-by: Muralidhara M K Reviewed-by: Ilpo Järvinen Link: https://patch.msgid.link/20260625123337.886435-3-muralidhara.mk@amd.com Link: https://patch.msgid.link/20260629155634.1807598-3-muralidhara.mk@amd.com Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/hsmp/acpi.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/platform/x86/amd/hsmp/acpi.c b/drivers/platform/x86/amd/hsmp/acpi.c index 4a1ce4cb25e7..8c3185ae6395 100644 --- a/drivers/platform/x86/amd/hsmp/acpi.c +++ b/drivers/platform/x86/amd/hsmp/acpi.c @@ -151,12 +151,18 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) union acpi_object *msgobj, *msgstr, *msgint; msgobj = &mailbox_package->package.elements[j]; - msgstr = &msgobj->package.elements[0]; - msgint = &msgobj->package.elements[1]; /* package should have 1 string and 1 integer object */ if (msgobj->type != ACPI_TYPE_PACKAGE || - msgstr->type != ACPI_TYPE_STRING || + msgobj->package.count < 2) { + ret = -EINVAL; + goto free_buf; + } + + msgstr = &msgobj->package.elements[0]; + msgint = &msgobj->package.elements[1]; + + if (msgstr->type != ACPI_TYPE_STRING || msgint->type != ACPI_TYPE_INTEGER) { ret = -EINVAL; goto free_buf; From 0c963c5b4d68922f6fa2c120fde7a4fecc0ab46d Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Mon, 29 Jun 2026 21:26:33 +0530 Subject: [PATCH 104/562] platform/x86/amd/hsmp: Pass struct device explicitly to ACPI mailbox parsers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hsmp_read_acpi_crs() and hsmp_read_acpi_dsd() read the ACPI handle and emit error messages via sock->dev. Pass the struct device explicitly to both helpers instead of reading it back from sock->dev. This is a pure refactor with no functional change; it prepares for publishing sock->dev as the data-plane readiness gate only after the socket has been fully initialized, so the parsers must not depend on sock->dev already being set. Signed-off-by: Muralidhara M K Link: https://patch.msgid.link/20260629155634.1807598-4-muralidhara.mk@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/hsmp/acpi.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/platform/x86/amd/hsmp/acpi.c b/drivers/platform/x86/amd/hsmp/acpi.c index 8c3185ae6395..1fb09251d826 100644 --- a/drivers/platform/x86/amd/hsmp/acpi.c +++ b/drivers/platform/x86/amd/hsmp/acpi.c @@ -107,7 +107,7 @@ static acpi_status hsmp_resource(struct acpi_resource *res, void *data) return AE_OK; } -static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) +static int hsmp_read_acpi_dsd(struct device *dev, struct hsmp_socket *sock) { struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; union acpi_object *guid, *mailbox_package; @@ -116,10 +116,10 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) int ret = 0; int j; - status = acpi_evaluate_object_typed(ACPI_HANDLE(sock->dev), "_DSD", NULL, + status = acpi_evaluate_object_typed(ACPI_HANDLE(dev), "_DSD", NULL, &buf, ACPI_TYPE_PACKAGE); if (ACPI_FAILURE(status)) { - dev_err(sock->dev, "Failed to read mailbox reg offsets from DSD table, err: %s\n", + dev_err(dev, "Failed to read mailbox reg offsets from DSD table, err: %s\n", acpi_format_exception(status)); return -ENODEV; } @@ -142,7 +142,7 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) guid = &dsd->package.elements[0]; mailbox_package = &dsd->package.elements[1]; if (!is_acpi_hsmp_uuid(guid) || mailbox_package->type != ACPI_TYPE_PACKAGE) { - dev_err(sock->dev, "Invalid hsmp _DSD table data\n"); + dev_err(dev, "Invalid hsmp _DSD table data\n"); ret = -EINVAL; goto free_buf; } @@ -192,14 +192,14 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) return ret; } -static int hsmp_read_acpi_crs(struct hsmp_socket *sock) +static int hsmp_read_acpi_crs(struct device *dev, struct hsmp_socket *sock) { acpi_status status; - status = acpi_walk_resources(ACPI_HANDLE(sock->dev), METHOD_NAME__CRS, + status = acpi_walk_resources(ACPI_HANDLE(dev), METHOD_NAME__CRS, hsmp_resource, sock); if (ACPI_FAILURE(status)) { - dev_err(sock->dev, "Failed to look up MP1 base address from CRS method, err: %s\n", + dev_err(dev, "Failed to look up MP1 base address from CRS method, err: %s\n", acpi_format_exception(status)); return -EINVAL; } @@ -207,10 +207,10 @@ static int hsmp_read_acpi_crs(struct hsmp_socket *sock) return -EINVAL; /* The mapped region should be un-cached */ - sock->virt_base_addr = devm_ioremap_uc(sock->dev, sock->mbinfo.base_addr, + sock->virt_base_addr = devm_ioremap_uc(dev, sock->mbinfo.base_addr, sock->mbinfo.size); if (!sock->virt_base_addr) { - dev_err(sock->dev, "Failed to ioremap MP1 base address\n"); + dev_err(dev, "Failed to ioremap MP1 base address\n"); return -ENOMEM; } @@ -232,12 +232,12 @@ static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind) dev_set_drvdata(dev, sock); /* Read MP1 base address from CRS method */ - ret = hsmp_read_acpi_crs(sock); + ret = hsmp_read_acpi_crs(dev, sock); if (ret) return ret; /* Read mailbox offsets from DSD table */ - return hsmp_read_acpi_dsd(sock); + return hsmp_read_acpi_dsd(dev, sock); } static ssize_t hsmp_metric_tbl_acpi_read(struct file *filp, struct kobject *kobj, From ff7836fa850c2f815bc219f1e48f6ec8699f4ae7 Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Mon, 29 Jun 2026 21:26:34 +0530 Subject: [PATCH 105/562] platform/x86/amd/hsmp: Gate the data plane on a fully initialized socket MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hsmp_parse_acpi_table() published sock->dev before hsmp_read_acpi_crs() had mapped virt_base_addr. sock->dev is the readiness gate for the lock-free data plane, so on a multi-socket system - where socket 0 exposes /dev/hsmp before later sockets finish probing - an ioctl aimed at a socket still in bring-up could pass the gate and dereference a NULL virt_base_addr. Publish sock->dev last with smp_store_release() once virt_base_addr, the mailbox offsets and the semaphore are initialized, and read it with smp_load_acquire() in hsmp_send_message() so a non-NULL dev guarantees the rest of the socket state is visible. Signed-off-by: Muralidhara M K Link: https://patch.msgid.link/20260629155634.1807598-5-muralidhara.mk@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/hsmp/acpi.c | 18 ++++++++++++++++-- drivers/platform/x86/amd/hsmp/hsmp.c | 13 +++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/amd/hsmp/acpi.c b/drivers/platform/x86/amd/hsmp/acpi.c index 1fb09251d826..72f68cef1297 100644 --- a/drivers/platform/x86/amd/hsmp/acpi.c +++ b/drivers/platform/x86/amd/hsmp/acpi.c @@ -224,7 +224,6 @@ static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind) int ret; sock->sock_ind = sock_ind; - sock->dev = dev; sock->amd_hsmp_rdwr = amd_hsmp_acpi_rdwr; sema_init(&sock->hsmp_sem, 1); @@ -237,7 +236,22 @@ static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind) return ret; /* Read mailbox offsets from DSD table */ - return hsmp_read_acpi_dsd(dev, sock); + ret = hsmp_read_acpi_dsd(dev, sock); + if (ret) + return ret; + + /* + * Publish sock->dev last. hsmp_send_message() uses it (via + * smp_load_acquire()) as the readiness gate for the lock-free data + * plane, so it must become visible only after virt_base_addr, the + * mailbox offsets and the semaphore are fully initialized. On a + * multi-socket system socket 0 exposes /dev/hsmp before later sockets + * finish probing, so without this an ioctl aimed at a socket still in + * bring-up could pass the gate and dereference a NULL virt_base_addr. + */ + smp_store_release(&sock->dev, dev); + + return 0; } static ssize_t hsmp_metric_tbl_acpi_read(struct file *filp, struct kobject *kobj, diff --git a/drivers/platform/x86/amd/hsmp/hsmp.c b/drivers/platform/x86/amd/hsmp/hsmp.c index 6a26937fc2b5..1a87931136fd 100644 --- a/drivers/platform/x86/amd/hsmp/hsmp.c +++ b/drivers/platform/x86/amd/hsmp/hsmp.c @@ -223,6 +223,19 @@ int hsmp_send_message(struct hsmp_message *msg) sock_ind = array_index_nospec(msg->sock_ind, hsmp_pdev.num_sockets); sock = &hsmp_pdev.sock[sock_ind]; + /* + * A slot exists for every possible socket, but it is only usable once + * that socket has actually been probed. Reject messages aimed at a + * socket that was never brought up or is still in bring-up, so we never + * operate on a zero-initialized semaphore or an unmapped mailbox. A + * non-NULL dev also guarantees virt_base_addr, the mailbox offsets and + * the semaphore are visible. + * + * Pairs with smp_store_release(&sock->dev) in hsmp_parse_acpi_table(). + */ + if (!smp_load_acquire(&sock->dev)) + return -ENODEV; + ret = down_interruptible(&sock->hsmp_sem); if (ret < 0) return ret; From 98c522eb8d64c67a82d489dd9b616a45851553e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Fri, 3 Jul 2026 16:17:09 +0200 Subject: [PATCH 106/562] landlock: Fix kernel-doc for the nested quiet layer flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit kernel-doc emits "Excess struct member 'quiet' description in 'landlock_layer'" because "quiet" is a bitfield inside the named nested struct "flags", but its inline comment used the bare member name "@quiet:", which kernel-doc attributes to the enclosing landlock_layer. Use the canonical dotted notation "@flags.quiet:" so kernel-doc resolves the nested member, and include it in the generated documentation. Cc: Justin Suess Cc: Tingmao Wang Fixes: a260c0055665 ("landlock: Add a place for flags to layer rules") Link: https://patch.msgid.link/20260703141711.2016964-1-mic@digikod.net Signed-off-by: Mickaël Salaün --- security/landlock/ruleset.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h index 61f3c253d5c9..0437adf17428 100644 --- a/security/landlock/ruleset.h +++ b/security/landlock/ruleset.h @@ -35,8 +35,8 @@ struct landlock_layer { */ struct { /** - * @quiet: Suppresses denial logs for the object covered by this - * rule in this domain. For filesystem rules, this inherits + * @flags.quiet: Suppresses denial logs for the object covered by + * this rule in this domain. For filesystem rules, this inherits * down the file hierarchy. */ u8 quiet : 1; From 40d3c88df9d82d32eb52600a06a629520a7900fc Mon Sep 17 00:00:00 2001 From: Benjamin Marzinski Date: Thu, 2 Jul 2026 15:43:07 -0400 Subject: [PATCH 107/562] bitops: make the *_bit_le functions use unsigned long The *_bit_le functions use a signed integer for the bit number. However, the *_bit functions can use an unsigned long. This causes problems if there is a large bitmap and a bit number > 0x80000000 is passed in. Since that is a negative int, it will get sign extended to a long when getting passed to the *_bit function, turning it into a huge bit number. This usually ends up with the memory address wrapping around and the function accessing memory before the start of the bitmap. Avoid this by making the *_bit_le functions take an unsigned int. This can be triggered by faking a huge dm-mirror device, which uses bitmaps to track the mirror regions: This will access memory before the start of the sync_bits bitmap, and likely hit the guard page of the previously allocated clean_bits bitmap. I looked and didn't see any crazy code using the signed int to intentionally try and access bits before some address within the bitmap. Signed-off-by: Benjamin Marzinski Signed-off-by: Yury Norov --- include/asm-generic/bitops/le.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/include/asm-generic/bitops/le.h b/include/asm-generic/bitops/le.h index d51beff60375..e3b0da9a25f1 100644 --- a/include/asm-generic/bitops/le.h +++ b/include/asm-generic/bitops/le.h @@ -16,47 +16,47 @@ #endif -static inline int test_bit_le(int nr, const void *addr) +static inline int test_bit_le(unsigned long nr, const void *addr) { return test_bit(nr ^ BITOP_LE_SWIZZLE, addr); } -static inline void set_bit_le(int nr, void *addr) +static inline void set_bit_le(unsigned long nr, void *addr) { set_bit(nr ^ BITOP_LE_SWIZZLE, addr); } -static inline void clear_bit_le(int nr, void *addr) +static inline void clear_bit_le(unsigned long nr, void *addr) { clear_bit(nr ^ BITOP_LE_SWIZZLE, addr); } -static inline void __set_bit_le(int nr, void *addr) +static inline void __set_bit_le(unsigned long nr, void *addr) { __set_bit(nr ^ BITOP_LE_SWIZZLE, addr); } -static inline void __clear_bit_le(int nr, void *addr) +static inline void __clear_bit_le(unsigned long nr, void *addr) { __clear_bit(nr ^ BITOP_LE_SWIZZLE, addr); } -static inline int test_and_set_bit_le(int nr, void *addr) +static inline int test_and_set_bit_le(unsigned long nr, void *addr) { return test_and_set_bit(nr ^ BITOP_LE_SWIZZLE, addr); } -static inline int test_and_clear_bit_le(int nr, void *addr) +static inline int test_and_clear_bit_le(unsigned long nr, void *addr) { return test_and_clear_bit(nr ^ BITOP_LE_SWIZZLE, addr); } -static inline int __test_and_set_bit_le(int nr, void *addr) +static inline int __test_and_set_bit_le(unsigned long nr, void *addr) { return __test_and_set_bit(nr ^ BITOP_LE_SWIZZLE, addr); } -static inline int __test_and_clear_bit_le(int nr, void *addr) +static inline int __test_and_clear_bit_le(unsigned long nr, void *addr) { return __test_and_clear_bit(nr ^ BITOP_LE_SWIZZLE, addr); } From fa810d01c829a72941a1309f9ca5eb8814857bc7 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Thu, 18 Jun 2026 12:50:17 +0300 Subject: [PATCH 108/562] userfaultfd: prevent registration of special VMAs Vova Tokarev says: userfaultfd allows registration on shadow stack VMAs. With userfaultfd access, you can register on the shadow stack, discard a page ... and inject a page with chosen return addresses via UFFDIO_COPY. Update vma_can_userfault() to reject VM_SHADOW_STACK. While on it, also reject VM_SPECIAL so that if a driver would implement vm_uffd_ops, it wouldn't be possible to register special VMAs with userfaultfd. Since VM_SPECIAL includes VM_DONTEXPAND which is set but hugetlb, exclude hugetlb VMAs from the check for VM_SPECIAL. Link: https://lore.kernel.org/20260618095017.2553004-1-rppt@kernel.org Fixes: 54007f818206 ("mm: Introduce VM_SHADOW_STACK for shadow stack memory") Signed-off-by: Mike Rapoport (Microsoft) Reported-by: vova tokarev Acked-by: David Hildenbrand (Arm) Reviewed-by: Lorenzo Stoakes Cc: Al Viro Cc: Christian Brauner Cc: Jan Kara Cc: Linus Torvalds Cc: Mike Rapoport Cc: Oleg Nesterov Cc: Peter Xu Cc: Signed-off-by: Andrew Morton --- mm/userfaultfd.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mm/userfaultfd.c b/mm/userfaultfd.c index 246af12bf801..c3adedaaf7d5 100644 --- a/mm/userfaultfd.c +++ b/mm/userfaultfd.c @@ -2111,7 +2111,10 @@ static bool vma_can_userfault(struct vm_area_struct *vma, vm_flags_t vm_flags, { const struct vm_uffd_ops *ops = vma_uffd_ops(vma); - if (vma->vm_flags & VM_DROPPABLE) + if (vma->vm_flags & (VM_DROPPABLE | VM_SHADOW_STACK)) + return false; + + if (!is_vm_hugetlb_page(vma) && (vma->vm_flags & VM_SPECIAL)) return false; vm_flags &= __VM_UFFD_FLAGS; From c9d64994b006cd8bd2222fe6940c6777ab4ae2af Mon Sep 17 00:00:00 2001 From: Xie Yuanbin Date: Fri, 5 Jun 2026 16:12:13 +0800 Subject: [PATCH 109/562] mm/memory-failure: trace: change memory_failure_event to ras subsystem Commit 97f0b1345219 ("tracing: add trace event for memory-failure") introduced memory_failure_event in ras subsystem. commit 31807483d395 ("mm/memory-failure: remove the selection of RAS") changed memory_failure_event to memory_failure subsystem. This breaks the backward compatibility, some user programs rely on it. Change memory_failure_event to ras subsystem to keep backward compatibility. Link: https://lore.kernel.org/20260605081213.154660-1-xieyuanbin1@huawei.com Fixes: 31807483d395 ("mm/memory-failure: remove the selection of RAS") Signed-off-by: Xie Yuanbin Reported-by: Yi Lai Reported-by: Qiuxu Zhuo Closes: https://lore.kernel.org/linux-mm/CY8PR11MB7134346A3E4BB28ECA28D6E989132@CY8PR11MB7134.namprd11.prod.outlook.com Acked-by: David Hildenbrand (Arm) Reviewed-by: Qiuxu Zhuo Reviewed-by: Lance Yang Reviewed-by: Miaohe Lin Cc: Steven Rostedt Cc: Borislav Petkov Cc: Signed-off-by: Andrew Morton --- include/trace/events/memory-failure.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/include/trace/events/memory-failure.h b/include/trace/events/memory-failure.h index aa57cc8f896b..7a8ee5d1a44e 100644 --- a/include/trace/events/memory-failure.h +++ b/include/trace/events/memory-failure.h @@ -1,6 +1,10 @@ /* SPDX-License-Identifier: GPL-2.0 */ #undef TRACE_SYSTEM -#define TRACE_SYSTEM memory_failure +/* + * For historical versions, memory_failure_event is in ras subsystem, + * some user programs depend on it. + */ +#define TRACE_SYSTEM ras #define TRACE_INCLUDE_FILE memory-failure #if !defined(_TRACE_MEMORY_FAILURE_H) || defined(TRACE_HEADER_MULTI_READ) From b1892bc6e60bb26880743cce396b52cf55424649 Mon Sep 17 00:00:00 2001 From: Oscar Salvador Date: Mon, 1 Jun 2026 14:50:15 +0200 Subject: [PATCH 110/562] arch,x86: skip setting align_offset for hugetlb mappings On x86, arch_get_unmapped_area{_topdown} set align_offset in order to avoid cache aliasing on I$ on AMD family 15h when 'align_va_addr' is enabled. Prior to commit 7bd3f1e1a9ae ("mm: make hugetlb mappings go through mm_get_unmapped_area_vmflags"), we did not have to worry about that because hugetlb specific code did not set align_offset, but the above commit got rid of hugetlb specific code and started to route hugetlb mappings through the generic interface. Doing that has the effect of handing non-aligned hugetlb mappings to userspace, which is plain wrong, eventually leading to a BUG in __unmap_hugepage_range(). So, skip setting align_offset if we are dealing with a hugetlb mapping. Link: https://lore.kernel.org/20260601125015.216110-1-osalvador@suse.de Fixes: 7bd3f1e1a9ae ("mm: make hugetlb mappings go through mm_get_unmapped_area_vmflags") Signed-off-by: Oscar Salvador Reported-by: Karsten Desler Closes: https://lore.kernel.org/linux-mm/20260527143643.GO31091@soohrt.org/ Tested-by: Karsten Desler Cc: Borislav Petkov Cc: Dave Hansen Cc: "H. Peter Anvin" Cc: Ingo Molnar Cc: Thomas Gleixner Cc: Signed-off-by: Andrew Morton --- arch/x86/kernel/sys_x86_64.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/arch/x86/kernel/sys_x86_64.c b/arch/x86/kernel/sys_x86_64.c index 776ae6fa7f2d..60f876dce8e5 100644 --- a/arch/x86/kernel/sys_x86_64.c +++ b/arch/x86/kernel/sys_x86_64.c @@ -157,7 +157,12 @@ arch_get_unmapped_area(struct file *filp, unsigned long addr, unsigned long len, } if (filp) { info.align_mask = get_align_mask(filp); - info.align_offset += get_align_bits(); + /* + * Hugepages must remain hugepage-aligned, so skip adding an offset + * in case we enabled 'align_va_addr'. + */ + if (!is_file_hugepages(filp)) + info.align_offset += get_align_bits(); } return vm_unmapped_area(&info); @@ -222,7 +227,12 @@ arch_get_unmapped_area_topdown(struct file *filp, unsigned long addr0, if (filp) { info.align_mask = get_align_mask(filp); - info.align_offset += get_align_bits(); + /* + * Hugepages must remain hugepage-aligned, so skip adding an offset + * in case we enabled 'align_va_addr'. + */ + if (!is_file_hugepages(filp)) + info.align_offset += get_align_bits(); } addr = vm_unmapped_area(&info); if (!(addr & ~PAGE_MASK)) From d7494c0a9f3cc16d5e0ec4e307bbbd57483f4758 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Sun, 12 Apr 2026 15:00:10 +0800 Subject: [PATCH 111/562] device-dax: fix refcount leak in __devm_create_dev_dax() error path After device_initialize(), the embedded struct device in dev_dax is expected to be released through the device core with put_device(). In __devm_create_dev_dax(), several failure paths after device_initialize() free dev_dax directly instead of dropping the device reference, which bypasses the normal device core lifetime handling and leaks the reference held on the embedded struct device. Fix this by assigning dev->type before device_initialize(), so the release callback is available, use put_device() in the post-initialization error paths, and keep dev_dax range cleanup explicit since it is not handled by dev_dax_release(). Link: https://lore.kernel.org/20260412070010.2402830-1-lgs201920130244@gmail.com Fixes: c2f3011ee697f ("device-dax: add an allocation interface for device-dax instances") Signed-off-by: Guangshuo Li Cc: Dan Williams Cc: Dave Jiang Cc: Vishal Verma Cc: Signed-off-by: Andrew Morton --- drivers/dax/bus.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/dax/bus.c b/drivers/dax/bus.c index b809e1a264af..d8aea5fe0af2 100644 --- a/drivers/dax/bus.c +++ b/drivers/dax/bus.c @@ -1485,6 +1485,7 @@ static struct dev_dax *__devm_create_dev_dax(struct dev_dax_data *data) } dev = &dev_dax->dev; + dev->type = &dev_dax_type; device_initialize(dev); dev_set_name(dev, "dax%d.%d", dax_region->id, dev_dax->id); @@ -1531,7 +1532,6 @@ static struct dev_dax *__devm_create_dev_dax(struct dev_dax_data *data) dev->devt = inode->i_rdev; dev->bus = &dax_bus_type; dev->parent = parent; - dev->type = &dev_dax_type; rc = device_add(dev); if (rc) { @@ -1554,14 +1554,13 @@ static struct dev_dax *__devm_create_dev_dax(struct dev_dax_data *data) return dev_dax; err_alloc_dax: - kfree(dev_dax->pgmap); err_pgmap: free_dev_dax_ranges(dev_dax); err_range: - free_dev_dax_id(dev_dax); + put_device(dev); + return ERR_PTR(rc); err_id: kfree(dev_dax); - return ERR_PTR(rc); } From ba525498dcbd2d878f74cb467f8140e543d65b1e Mon Sep 17 00:00:00 2001 From: Farhad Alemi Date: Sun, 14 Jun 2026 06:25:55 -0700 Subject: [PATCH 112/562] cgroup/cpuset: rebind mm mempolicy to effective_mems, not mems_allowed Creating a child cpuset where cpuset.mems is never set leads to a div/0 when a VMA mempolicy with MPOL_F_RELATIVE_NODES rebinds in response to a CPU hotplug event. Reproduction steps: 1) Create a cgroup w/ cpuset controls (do not set cpuset.mems) 2) Move the task into the child cpuset 3) Create a VMA mempolicy for that task with MPOL_F_RELATIVE_NODES 4) unplug and hotplug a cpu echo 0 > /sys/devices/system/cpu/cpu1/online echo 1 > /sys/devices/system/cpu/cpu1/online 5) mempolicy rebind does a div/0 in mpol_relative_nodemask on the call to __nodes_fold() The cpuset code passes (cs->mems_allowed) which is not guaranteed to have nodes to the rebind routine. Use cs->effective_mems instead, which is guaranteed to have a non-empty nodemask. Link: https://lore.kernel.org/linux-mm/CA+0ovCgxbZkXa+OU8w3s84R3KNPNxxRfmsNR-udh+afQBbGNmw@mail.gmail.com/ Link: https://lore.kernel.org/all/CA+0ovCiEz6SP_sn3kN4Tb+_oC=eHMXy_Ffj=usV3wREdQrUtww@mail.gmail.com/ Link: https://lore.kernel.org/CA+0ovCgfHJHv5d1mzapWWvF-LhjppzDX8NPPLvCPZxPKg8RiYw@mail.gmail.com Fixes: ae1c802382f7 ("cpuset: apply cs->effective_{cpus,mems}") Signed-off-by: Farhad Alemi Suggested-by: Gregory Price Suggested-by: Waiman Long Closes: https://lore.kernel.org/linux-mm/CA+0ovCgxbZkXa+OU8w3s84R3KNPNxxRfmsNR-udh+afQBbGNmw@mail.gmail.com/ Acked-by: Waiman Long Cc: Alistair Popple Cc: Byungchul Park Cc: David Hildenbrand Cc: Gregory Price Cc: "Huang, Ying" Cc: Joshua Hahn Cc: Matthew Brost Cc: Rakie Kim Cc: Rasmus Villemoes Cc: Zi Yan Cc: Tejun Heo Cc: Signed-off-by: Andrew Morton --- kernel/cgroup/cpuset.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c index 591e3aa487fc..b21c31650583 100644 --- a/kernel/cgroup/cpuset.c +++ b/kernel/cgroup/cpuset.c @@ -2653,7 +2653,7 @@ void cpuset_update_tasks_nodemask(struct cpuset *cs) migrate = is_memory_migrate(cs); - mpol_rebind_mm(mm, &cs->mems_allowed); + mpol_rebind_mm(mm, &cs->effective_mems); if (migrate) cpuset_migrate_mm(mm, &cs->old_mems_allowed, &newmems); else From 0e4215512bc90e4c54197470e8d25e3ba6a77704 Mon Sep 17 00:00:00 2001 From: Stanislav Kinsburskii Date: Mon, 29 Jun 2026 16:30:14 -0700 Subject: [PATCH 113/562] lib: test_hmm: use device devt for coherent device range selection Commit af69016dab96 ("lib: test_hmm: implement a device release method") moved the initial dmirror_allocate_chunk() call before cdev_device_add(). That means the struct cdev has not been added yet, so cdev_add() has not initialized mdevice->cdevice.dev. The coherent-device range selection uses the device minor to choose between spm_addr_dev0 and spm_addr_dev1. Reading MINOR(mdevice->cdevice.dev) before cdev_add() therefore always sees an uninitialized dev_t. As a result, both coherent devices select the same physical range, and adding the second device fails due to the overlapping dev_pagemap range. Use mdevice->device.devt instead. It is initialized in dmirror_device_init() before dmirror_allocate_chunk() is called and is the same dev_t later passed to cdev_device_add(). Link: https://lore.kernel.org/178277581197.172200.16265155329935822153.stgit@skinsburskii Fixes: af69016dab96 ("lib: test_hmm: implement a device release method") Signed-off-by: Stanislav Kinsburskii Cc: Alistair Popple Cc: Balbir Singh Cc: Zenghui Yu (Huawei) Cc: Jason Gunthorpe Cc: Leon Romanovsky Cc: Signed-off-by: Andrew Morton --- lib/test_hmm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/test_hmm.c b/lib/test_hmm.c index 9c59d1ceb5b5..c4adbf98fac7 100644 --- a/lib/test_hmm.c +++ b/lib/test_hmm.c @@ -581,7 +581,7 @@ static int dmirror_allocate_chunk(struct dmirror_device *mdevice, devmem->pagemap.type = MEMORY_DEVICE_PRIVATE; break; case HMM_DMIRROR_MEMORY_DEVICE_COHERENT: - devmem->pagemap.range.start = (MINOR(mdevice->cdevice.dev) - 2) ? + devmem->pagemap.range.start = (MINOR(mdevice->device.devt) - 2) ? spm_addr_dev0 : spm_addr_dev1; devmem->pagemap.range.end = devmem->pagemap.range.start + From 7ca0f848e15a2d70c3216d4dac810a2befd69a04 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Mon, 29 Jun 2026 18:38:18 -0700 Subject: [PATCH 114/562] MAINTAINERS: s/SeongJae/SJ/ My legal and preferred first names are SeongJae and SJ, respectively. I was using the legal name for commits and tags, while using the preferred name for conversations. It sometimes confuses people including myself. Consistently use the preferred name. Together remove copyright notes on files. Those are only confusing for people who are not familiar with the law. Meanwhile, we can infer the information in a better way from git logs and public information. Link: https://lore.kernel.org/20260630013820.143366-1-sj@kernel.org Signed-off-by: SJ Park Acked-by: Lorenzo Stoakes Acked-by: David Hildenbrand (Arm) Cc: Liam R. Howlett Cc: Michal Hocko Cc: Mike Rapoport Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- .mailmap | 1 + .../ABI/testing/sysfs-kernel-mm-damon | 174 +++++++++--------- MAINTAINERS | 2 +- include/linux/damon.h | 2 - mm/damon/core.c | 2 - mm/damon/lru_sort.c | 2 - mm/damon/modules-common.c | 2 - mm/damon/modules-common.h | 2 - mm/damon/ops-common.c | 2 - mm/damon/ops-common.h | 2 - mm/damon/paddr.c | 2 - mm/damon/reclaim.c | 2 - mm/damon/sysfs-common.c | 2 - mm/damon/sysfs-common.h | 2 - mm/damon/sysfs-schemes.c | 2 - mm/damon/sysfs.c | 2 - mm/damon/tests/core-kunit.h | 4 - mm/damon/tests/sysfs-kunit.h | 2 - mm/damon/tests/vaddr-kunit.h | 4 - mm/damon/vaddr.c | 2 - 20 files changed, 89 insertions(+), 126 deletions(-) diff --git a/.mailmap b/.mailmap index be1f61db17a8..6116cce885d6 100644 --- a/.mailmap +++ b/.mailmap @@ -816,6 +816,7 @@ Simon Wunderlich Simon Wunderlich Simon Wunderlich Simon Wunderlich +SJ Park Sricharan Ramabadhran Srinivas Kandagatla Srinivas Kandagatla diff --git a/Documentation/ABI/testing/sysfs-kernel-mm-damon b/Documentation/ABI/testing/sysfs-kernel-mm-damon index b73e6bc28ea5..4fdec63a47d4 100644 --- a/Documentation/ABI/testing/sysfs-kernel-mm-damon +++ b/Documentation/ABI/testing/sysfs-kernel-mm-damon @@ -1,26 +1,26 @@ what: /sys/kernel/mm/damon/ Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Interface for Data Access MONitoring (DAMON). Contains files for controlling DAMON. For more details on DAMON itself, please refer to Documentation/admin-guide/mm/damon/index.rst. What: /sys/kernel/mm/damon/admin/ Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Interface for privileged users of DAMON. Contains files for controlling DAMON that aimed to be used by privileged users. What: /sys/kernel/mm/damon/admin/kdamonds/nr_kdamonds Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Writing a number 'N' to this file creates the number of directories for controlling each DAMON worker thread (kdamond) named '0' to 'N-1' under the kdamonds/ directory. What: /sys/kernel/mm/damon/admin/kdamonds//state Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Writing 'on' or 'off' to this file makes the kdamond starts or stops, respectively. Reading the file returns the keywords based on the current status. Writing 'commit' to this file @@ -40,33 +40,33 @@ Description: Writing 'on' or 'off' to this file makes the kdamond starts or What: /sys/kernel/mm/damon/admin/kdamonds//pid Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Reading this file returns the pid of the kdamond if it is running. What: /sys/kernel/mm/damon/admin/kdamonds//refresh_ms Date: Jul 2025 -Contact: SeongJae Park +Contact: SJ Park Description: Writing a value to this file sets the time interval for automatic DAMON status file contents update. Writing '0' disables the update. Reading this file returns the value. What: /sys/kernel/mm/damon/admin/kdamonds//contexts/nr_contexts Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Writing a number 'N' to this file creates the number of directories for controlling each DAMON context named '0' to 'N-1' under the contexts/ directory. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//avail_operations Date: Apr 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Reading this file returns the available monitoring operations sets on the currently running kernel. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//operations Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Writing a keyword for a monitoring operations set ('vaddr' for virtual address spaces monitoring, 'fvaddr' for fixed virtual address ranges monitoring, and 'paddr' for the physical address @@ -79,42 +79,42 @@ Description: Writing a keyword for a monitoring operations set ('vaddr' for What: /sys/kernel/mm/damon/admin/kdamonds//contexts//addr_unit Date: Aug 2025 -Contact: SeongJae Park +Contact: SJ Park Description: Writing an integer to this file sets the 'address unit' parameter of the given operations set of the context. Reading the file returns the last-written 'address unit' value. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//pause Date: Mar 2026 -Contact: SeongJae Park +Contact: SJ Park Description: Writing a boolean keyword to this file sets the 'pause' request parameter for the context. Reading the file returns the last-written 'pause' value. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//monitoring_attrs/intervals/sample_us Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Writing a value to this file sets the sampling interval of the DAMON context in microseconds as the value. Reading this file returns the value. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//monitoring_attrs/intervals/aggr_us Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Writing a value to this file sets the aggregation interval of the DAMON context in microseconds as the value. Reading this file returns the value. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//monitoring_attrs/intervals/update_us Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Writing a value to this file sets the update interval of the DAMON context in microseconds as the value. Reading this file returns the value. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//monitoring_attrs/intervals/intrvals_goal/access_bp Date: Feb 2025 -Contact: SeongJae Park +Contact: SJ Park Description: Writing a value to this file sets the monitoring intervals auto-tuning target DAMON-observed access events ratio within the given time interval (aggrs in same directory), in bp @@ -122,7 +122,7 @@ Description: Writing a value to this file sets the monitoring intervals What: /sys/kernel/mm/damon/admin/kdamonds//contexts//monitoring_attrs/intervals/intrvals_goal/aggrs Date: Feb 2025 -Contact: SeongJae Park +Contact: SJ Park Description: Writing a value to this file sets the time interval to achieve the monitoring intervals auto-tuning target DAMON-observed access events ratio (access_bp in same directory) within. @@ -130,14 +130,14 @@ Description: Writing a value to this file sets the time interval to achieve What: /sys/kernel/mm/damon/admin/kdamonds//contexts//monitoring_attrs/intervals/intrvals_goal/min_sample_us Date: Feb 2025 -Contact: SeongJae Park +Contact: SJ Park Description: Writing a value to this file sets the minimum value of auto-tuned sampling interval in microseconds. Reading this file returns the value. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//monitoring_attrs/intervals/intrvals_goal/max_sample_us Date: Feb 2025 -Contact: SeongJae Park +Contact: SJ Park Description: Writing a value to this file sets the maximum value of auto-tuned sampling interval in microseconds. Reading this file returns the value. @@ -145,42 +145,42 @@ Description: Writing a value to this file sets the maximum value of What: /sys/kernel/mm/damon/admin/kdamonds//contexts//monitoring_attrs/nr_regions/min WDate: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Writing a value to this file sets the minimum number of monitoring regions of the DAMON context as the value. Reading this file returns the value. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//monitoring_attrs/nr_regions/max Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Writing a value to this file sets the maximum number of monitoring regions of the DAMON context as the value. Reading this file returns the value. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//targets/nr_targets Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Writing a number 'N' to this file creates the number of directories for controlling each DAMON target of the context named '0' to 'N-1' under the contexts/ directory. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//targets//pid_target Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Writing to and reading from this file sets and gets the pid of the target process if the context is for virtual address spaces monitoring, respectively. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//targets//obsolete_target Date: Oct 2025 -Contact: SeongJae Park +Contact: SJ Park Description: Writing to and reading from this file sets and gets the obsoleteness of the matching parameters commit destination target. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//targets//regions/nr_regions Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Writing a number 'N' to this file creates the number of directories for setting each DAMON target memory region of the context named '0' to 'N-1' under the regions/ directory. In @@ -190,181 +190,181 @@ Description: Writing a number 'N' to this file creates the number of What: /sys/kernel/mm/damon/admin/kdamonds//contexts//targets//regions//start Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Writing to and reading from this file sets and gets the start address of the monitoring region. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//targets//regions//end Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Writing to and reading from this file sets and gets the end address of the monitoring region. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes/nr_schemes Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Writing a number 'N' to this file creates the number of directories for controlling each DAMON-based operation scheme of the context named '0' to 'N-1' under the schemes/ directory. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//action Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Writing to and reading from this file sets and gets the action of the scheme. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//target_nid Date: Jun 2024 -Contact: SeongJae Park +Contact: SJ Park Description: Action's target NUMA node id. Supported by only relevant actions. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//apply_interval_us Date: Sep 2023 -Contact: SeongJae Park +Contact: SJ Park Description: Writing a value to this file sets the action apply interval of the scheme in microseconds. Reading this file returns the value. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//access_pattern/sz/min Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Writing to and reading from this file sets and gets the minimum size of the scheme's target regions in bytes. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//access_pattern/sz/max Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Writing to and reading from this file sets and gets the maximum size of the scheme's target regions in bytes. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//access_pattern/nr_accesses/min Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Writing to and reading from this file sets and gets the manimum 'nr_accesses' of the scheme's target regions. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//access_pattern/nr_accesses/max Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Writing to and reading from this file sets and gets the maximum 'nr_accesses' of the scheme's target regions. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//access_pattern/age/min Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Writing to and reading from this file sets and gets the minimum 'age' of the scheme's target regions. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//access_pattern/age/max Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Writing to and reading from this file sets and gets the maximum 'age' of the scheme's target regions. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//quotas/ms Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Writing to and reading from this file sets and gets the time quota of the scheme in milliseconds. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//quotas/bytes Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Writing to and reading from this file sets and gets the size quota of the scheme in bytes. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//quotas/effective_bytes Date: Feb 2024 -Contact: SeongJae Park +Contact: SJ Park Description: Reading from this file gets the effective size quota of the scheme in bytes, which adjusted for the time quota and goals. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//quotas/reset_interval_ms Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Writing to and reading from this file sets and gets the quotas charge reset interval of the scheme in milliseconds. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//quotas/goals/nr_goals Date: Nov 2023 -Contact: SeongJae Park +Contact: SJ Park Description: Writing a number 'N' to this file creates the number of directories for setting automatic tuning of the scheme's aggressiveness named '0' to 'N-1' under the goals/ directory. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//quotas/goals//target_metric Date: Feb 2024 -Contact: SeongJae Park +Contact: SJ Park Description: Writing to and reading from this file sets and gets the quota auto-tuning goal metric. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//quotas/goals//target_value Date: Nov 2023 -Contact: SeongJae Park +Contact: SJ Park Description: Writing to and reading from this file sets and gets the target value of the goal metric. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//quotas/goals//current_value Date: Nov 2023 -Contact: SeongJae Park +Contact: SJ Park Description: Writing to and reading from this file sets and gets the current value of the goal metric. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//quotas/goals//nid Date: Apr 2025 -Contact: SeongJae Park +Contact: SJ Park Description: Writing to and reading from this file sets and gets the nid parameter of the goal. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//quotas/goals//path Date: Oct 2025 -Contact: SeongJae Park +Contact: SJ Park Description: Writing to and reading from this file sets and gets the path parameter of the goal. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//quotas/goal_tuner Date: Mar 2026 -Contact: SeongJae Park +Contact: SJ Park Description: Writing to and reading from this file sets and gets the goal-based effective quota auto-tuning algorithm to use. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//quotas/fail_charge_num Date: Mar 2026 -Contact: SeongJae Park +Contact: SJ Park Description: Writing to and reading from this file sets and gets the action-failed memory quota charging ratio numerator. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//quotas/fail_charge_denom Date: Mar 2026 -Contact: SeongJae Park +Contact: SJ Park Description: Writing to and reading from this file sets and gets the action-failed memory quota charging ratio denominator. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//quotas/weights/sz_permil Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Writing to and reading from this file sets and gets the under-quota limit regions prioritization weight for 'size' in permil. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//quotas/weights/nr_accesses_permil Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Writing to and reading from this file sets and gets the under-quota limit regions prioritization weight for 'nr_accesses' in permil. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//quotas/weights/age_permil Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Writing to and reading from this file sets and gets the under-quota limit regions prioritization weight for 'age' in permil. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//watermarks/metric Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Writing to and reading from this file sets and gets the metric of the watermarks for the scheme. The writable/readable keywords for this file are 'none' for disabling the watermarks @@ -373,44 +373,44 @@ Description: Writing to and reading from this file sets and gets the metric What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//watermarks/interval_us Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Writing to and reading from this file sets and gets the metric check interval of the watermarks for the scheme in microseconds. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//watermarks/high Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Writing to and reading from this file sets and gets the high watermark of the scheme in permil. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//watermarks/mid Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Writing to and reading from this file sets and gets the mid watermark of the scheme in permil. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//watermarks/low Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Writing to and reading from this file sets and gets the low watermark of the scheme in permil. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//core_filters Date: Feb 2025 -Contact: SeongJae Park +Contact: SJ Park Description: Directory for DAMON core layer-handled DAMOS filters. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//core_filters/nr_filters Date: Feb 2025 -Contact: SeongJae Park +Contact: SJ Park Description: Writing a number 'N' to this file creates the number of directories for setting filters of the scheme named '0' to 'N-1' under the core_filters/ directory. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//core_filters//type Date: Feb 2025 -Contact: SeongJae Park +Contact: SJ Park Description: Writing to and reading from this file sets and gets the type of the memory of the interest. 'anon' for anonymous pages, 'memcg' for specific memory cgroup, 'young' for young pages, @@ -419,62 +419,62 @@ Description: Writing to and reading from this file sets and gets the type of What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//core_filters//memcg_path Date: Feb 2025 -Contact: SeongJae Park +Contact: SJ Park Description: If 'memcg' is written to the 'type' file, writing to and reading from this file sets and gets the path to the memory cgroup of the interest. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//core_filters//addr_start Date: Feb 2025 -Contact: SeongJae Park +Contact: SJ Park Description: If 'addr' is written to the 'type' file, writing to or reading from this file sets or gets the start address of the address range for the filter. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//core_filters//addr_end Date: Feb 2025 -Contact: SeongJae Park +Contact: SJ Park Description: If 'addr' is written to the 'type' file, writing to or reading from this file sets or gets the end address of the address range for the filter. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//core_filters//min Date: Feb 2025 -Contact: SeongJae Park +Contact: SJ Park Description: If 'hugepage_size' is written to the 'type' file, writing to or reading from this file sets or gets the minimum size of the hugepage for the filter. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//core_filters//max Date: Feb 2025 -Contact: SeongJae Park +Contact: SJ Park Description: If 'hugepage_size' is written to the 'type' file, writing to or reading from this file sets or gets the maximum size of the hugepage for the filter. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//core_filters//damon_target_idx Date: Feb 2025 -Contact: SeongJae Park +Contact: SJ Park Description: If 'target' is written to the 'type' file, writing to or reading from this file sets or gets the index of the DAMON monitoring target of the interest. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//core_filters//matching Date: Feb 2025 -Contact: SeongJae Park +Contact: SJ Park Description: Writing 'Y' or 'N' to this file sets whether the filter is for the memory of the 'type', or all except the 'type'. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//core_filters//allow Date: Feb 2025 -Contact: SeongJae Park +Contact: SJ Park Description: Writing 'Y' or 'N' to this file sets whether to allow or reject applying the scheme's action to the memory that satisfies the 'type' and the 'matching' of the directory. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//ops_filters Date: Feb 2025 -Contact: SeongJae Park +Contact: SJ Park Description: Directory for DAMON operations set layer-handled DAMOS filters. Files under this directory works same to those of /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//core_filters @@ -482,7 +482,7 @@ Description: Directory for DAMON operations set layer-handled DAMOS filters. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//filters Date: Dec 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Directory for DAMOS filters. Files under this directory works same to those of /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//{core,ops}_filters @@ -491,14 +491,14 @@ Description: Directory for DAMOS filters. Files under this directory works What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//dests/nr_dests Date: Jul 2025 -Contact: SeongJae Park +Contact: SJ Park Description: Writing a number 'N' to this file creates the number of directories for setting action destinations of the scheme named '0' to 'N-1' under the dests/ directory. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//dests//id Date: Jul 2025 -Contact: SeongJae Park +Contact: SJ Park Description: Writing to and reading from this file sets and gets the id of the DAMOS action destination. For DAMOS_MIGRATE_{HOT,COLD} actions, the destination node's node id can be written and @@ -506,98 +506,98 @@ Description: Writing to and reading from this file sets and gets the id of What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//dests//weight Date: Jul 2025 -Contact: SeongJae Park +Contact: SJ Park Description: Writing to and reading from this file sets and gets the weight of the DAMOS action destination to select as the destination of each action among the destinations. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//stats/nr_tried Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Reading this file returns the number of regions that the action of the scheme has tried to be applied. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//stats/sz_tried Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Reading this file returns the total size of regions that the action of the scheme has tried to be applied in bytes. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//stats/nr_applied Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Reading this file returns the number of regions that the action of the scheme has successfully applied. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//stats/sz_applied Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Reading this file returns the total size of regions that the action of the scheme has successfully applied in bytes. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//stats/sz_ops_filter_passed Date: Dec 2024 -Contact: SeongJae Park +Contact: SJ Park Description: Reading this file returns the total size of memory that passed DAMON operations layer-handled filters of the scheme in bytes. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//stats/qt_exceeds Date: Mar 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Reading this file returns the number of the exceed events of the scheme's quotas. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//stats/nr_snapshots Date: Dec 2025 -Contact: SeongJae Park +Contact: SJ Park Description: Reading this file returns the total number of DAMON snapshots that the scheme has tried to be applied. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//stats/max_nr_snapshots Date: Dec 2025 -Contact: SeongJae Park +Contact: SJ Park Description: Writing a number to this file sets the upper limit of nr_snapshots that deactivates the scheme when the limit is reached or exceeded. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//tried_regions/total_bytes Date: Jul 2023 -Contact: SeongJae Park +Contact: SJ Park Description: Reading this file returns the total amount of memory that corresponding DAMON-based Operation Scheme's action has tried to be applied. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//tried_regions//start Date: Oct 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Reading this file returns the start address of a memory region that corresponding DAMON-based Operation Scheme's action has tried to be applied. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//tried_regions//end Date: Oct 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Reading this file returns the end address of a memory region that corresponding DAMON-based Operation Scheme's action has tried to be applied. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//tried_regions//nr_accesses Date: Oct 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Reading this file returns the 'nr_accesses' of a memory region that corresponding DAMON-based Operation Scheme's action has tried to be applied. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//tried_regions//age Date: Oct 2022 -Contact: SeongJae Park +Contact: SJ Park Description: Reading this file returns the 'age' of a memory region that corresponding DAMON-based Operation Scheme's action has tried to be applied. What: /sys/kernel/mm/damon/admin/kdamonds//contexts//schemes//tried_regions//sz_filter_passed Date: Dec 2024 -Contact: SeongJae Park +Contact: SJ Park Description: Reading this file returns the size of the memory in the region that passed DAMON operations layer-handled filters of the scheme in bytes. diff --git a/MAINTAINERS b/MAINTAINERS index c760f7d889e3..272c8475a959 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7124,7 +7124,7 @@ W: https://docs.dasharo.com/ F: drivers/platform/x86/dasharo-acpi.c DAMON -M: SeongJae Park +M: SJ Park L: damon@lists.linux.dev L: linux-mm@kvack.org S: Maintained diff --git a/include/linux/damon.h b/include/linux/damon.h index 02ac34537df9..cfbbf8ba28f6 100644 --- a/include/linux/damon.h +++ b/include/linux/damon.h @@ -1,8 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* * DAMON api - * - * Author: SeongJae Park */ #ifndef _DAMON_H_ diff --git a/mm/damon/core.c b/mm/damon/core.c index 7e4b9affc5b0..d99f7a297fdd 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -1,8 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* * Data Access Monitor - * - * Author: SeongJae Park */ #define pr_fmt(fmt) "damon: " fmt diff --git a/mm/damon/lru_sort.c b/mm/damon/lru_sort.c index 8298c6001fd0..32f41491b726 100644 --- a/mm/damon/lru_sort.c +++ b/mm/damon/lru_sort.c @@ -1,8 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* * DAMON-based LRU-lists Sorting - * - * Author: SeongJae Park */ #define pr_fmt(fmt) "damon-lru-sort: " fmt diff --git a/mm/damon/modules-common.c b/mm/damon/modules-common.c index 86d58f8c4f63..f87fa46a95a0 100644 --- a/mm/damon/modules-common.c +++ b/mm/damon/modules-common.c @@ -1,8 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* * Common Code for DAMON Modules - * - * Author: SeongJae Park */ #include diff --git a/mm/damon/modules-common.h b/mm/damon/modules-common.h index f103ad556368..6fd45490e45b 100644 --- a/mm/damon/modules-common.h +++ b/mm/damon/modules-common.h @@ -1,8 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* * Common Code for DAMON Modules - * - * Author: SeongJae Park */ #include diff --git a/mm/damon/ops-common.c b/mm/damon/ops-common.c index d1842e2b00ef..6bdd1cfd3863 100644 --- a/mm/damon/ops-common.c +++ b/mm/damon/ops-common.c @@ -1,8 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* * Common Code for Data Access Monitoring - * - * Author: SeongJae Park */ #include diff --git a/mm/damon/ops-common.h b/mm/damon/ops-common.h index 5efa5b5970de..38d295488fa1 100644 --- a/mm/damon/ops-common.h +++ b/mm/damon/ops-common.h @@ -1,8 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* * Common Code for Data Access Monitoring - * - * Author: SeongJae Park */ #include diff --git a/mm/damon/paddr.c b/mm/damon/paddr.c index d0598f5f2688..5c2da45f988c 100644 --- a/mm/damon/paddr.c +++ b/mm/damon/paddr.c @@ -1,8 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* * DAMON Code for The Physical Address Space - * - * Author: SeongJae Park */ #define pr_fmt(fmt) "damon-pa: " fmt diff --git a/mm/damon/reclaim.c b/mm/damon/reclaim.c index ce4499cf4b8b..11b70d0a9a6f 100644 --- a/mm/damon/reclaim.c +++ b/mm/damon/reclaim.c @@ -1,8 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* * DAMON-based page reclamation - * - * Author: SeongJae Park */ #define pr_fmt(fmt) "damon-reclaim: " fmt diff --git a/mm/damon/sysfs-common.c b/mm/damon/sysfs-common.c index bdc6ae2639e4..c59d7bf7a73a 100644 --- a/mm/damon/sysfs-common.c +++ b/mm/damon/sysfs-common.c @@ -1,8 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* * Common Code for DAMON Sysfs Interface - * - * Author: SeongJae Park */ #include diff --git a/mm/damon/sysfs-common.h b/mm/damon/sysfs-common.h index 3079306966a9..733764716e8d 100644 --- a/mm/damon/sysfs-common.h +++ b/mm/damon/sysfs-common.h @@ -1,8 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* * Common Code for DAMON Sysfs Interface - * - * Author: SeongJae Park */ #include diff --git a/mm/damon/sysfs-schemes.c b/mm/damon/sysfs-schemes.c index 0134111c3c1f..3cbeccd436e4 100644 --- a/mm/damon/sysfs-schemes.c +++ b/mm/damon/sysfs-schemes.c @@ -1,8 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* * DAMON sysfs Interface - * - * Copyright (c) 2022 SeongJae Park */ #include diff --git a/mm/damon/sysfs.c b/mm/damon/sysfs.c index 2e95e3bac774..a9e187158067 100644 --- a/mm/damon/sysfs.c +++ b/mm/damon/sysfs.c @@ -1,8 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* * DAMON sysfs Interface - * - * Copyright (c) 2022 SeongJae Park */ #include diff --git a/mm/damon/tests/core-kunit.h b/mm/damon/tests/core-kunit.h index 1cfb8c176b87..fcf7c7fadb5f 100644 --- a/mm/damon/tests/core-kunit.h +++ b/mm/damon/tests/core-kunit.h @@ -1,10 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* * Data Access Monitor Unit Tests - * - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Author: SeongJae Park */ #ifdef CONFIG_DAMON_KUNIT_TEST diff --git a/mm/damon/tests/sysfs-kunit.h b/mm/damon/tests/sysfs-kunit.h index f9ec5e795b34..138a4b8d14e7 100644 --- a/mm/damon/tests/sysfs-kunit.h +++ b/mm/damon/tests/sysfs-kunit.h @@ -1,8 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* * Data Access Monitor Unit Tests - * - * Author: SeongJae Park */ #ifdef CONFIG_DAMON_SYSFS_KUNIT_TEST diff --git a/mm/damon/tests/vaddr-kunit.h b/mm/damon/tests/vaddr-kunit.h index 563fbc7e3f44..61f844336ffb 100644 --- a/mm/damon/tests/vaddr-kunit.h +++ b/mm/damon/tests/vaddr-kunit.h @@ -1,10 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* * Data Access Monitor Unit Tests - * - * Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. - * - * Author: SeongJae Park */ #ifdef CONFIG_DAMON_VADDR_KUNIT_TEST diff --git a/mm/damon/vaddr.c b/mm/damon/vaddr.c index d27147603564..e73ec1ce016e 100644 --- a/mm/damon/vaddr.c +++ b/mm/damon/vaddr.c @@ -1,8 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* * DAMON Code for Virtual Address Spaces - * - * Author: SeongJae Park */ #define pr_fmt(fmt) "damon-va: " fmt From 4fcbfdaf4b76f6af41adc5abe0f2558fbbca7fa8 Mon Sep 17 00:00:00 2001 From: Wei Yang Date: Tue, 30 Jun 2026 02:15:40 +0000 Subject: [PATCH 115/562] mm/page_vma_mapped: fix device-private PMD handling Commit 65edfda6f3f2 ("mm/rmap: extend rmap and migration support device-private entries") introduced the concept of device-private PMD entries, but did not correctly update the rmap walk code to account for them. As a result, when page_vma_mapped_walk() encounters device-private PMD entries, it takes no action other than to acquire the PMD lock and exit. However this is highly problematic for two reasons - firstly, device private entries possess a PFN so check_pmd() needs to be called to ensure an overlapping PFN range. Secondly, and more importantly, if PVMW_MIGRATION is set the caller assumes the returned entry is a migration entry, resulting in memory corruption when the caller tries to interpret the device private entry as such. In addition, commit 146287290023 ("mm/huge_memory: implement device-private THP splitting") allowed device private PMDs to be split like THP mappings, but again did not update this code path. As a result, we might race a PMD split prior to acquiring the PMD lock. This patch addresses all of these issues by invoking check_pmd(), ensuring PMVW_MIGRATION is not set and checks whether a split raced us we do for PMD THP and migration entries. Instead of checking for a subset of the cases after taking the pmd_lock(), put device-private along with pmd_trans_huge() and pmd_is_migration_entry(). Also remove thp_migration_supported() as it is already guarded by pmd_is_migration_entry(). Link: https://lore.kernel.org/20260630021540.17297-1-richard.weiyang@gmail.com Fixes: 65edfda6f3f2 ("mm/rmap: extend rmap and migration support device-private entries") Signed-off-by: Wei Yang Suggested-by: David Hildenbrand Reviewed-by: Lance Yang Acked-by: Balbir Singh Cc: SeongJae Park Cc: Zi Yan Cc: Lorenzo Stoakes Cc: Signed-off-by: Andrew Morton --- mm/page_vma_mapped.c | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/mm/page_vma_mapped.c b/mm/page_vma_mapped.c index 2ccbabfb2cc1..2d6c58488e3a 100644 --- a/mm/page_vma_mapped.c +++ b/mm/page_vma_mapped.c @@ -243,21 +243,30 @@ bool page_vma_mapped_walk(struct page_vma_mapped_walk *pvmw) */ pmde = pmdp_get_lockless(pvmw->pmd); - if (pmd_trans_huge(pmde) || pmd_is_migration_entry(pmde)) { + if (pmd_trans_huge(pmde) || pmd_is_migration_entry(pmde) || + pmd_is_device_private_entry(pmde)) { pvmw->ptl = pmd_lock(mm, pvmw->pmd); pmde = *pvmw->pmd; - if (!pmd_present(pmde)) { + if (pmd_is_migration_entry(pmde)) { softleaf_t entry; - if (!thp_migration_supported() || - !(pvmw->flags & PVMW_MIGRATION)) + if (!(pvmw->flags & PVMW_MIGRATION)) return not_found(pvmw); entry = softleaf_from_pmd(pmde); + if (!check_pmd(softleaf_to_pfn(entry), pvmw)) + return not_found(pvmw); + return true; + } else if (pmd_is_device_private_entry(pmde)) { + softleaf_t entry; - if (!softleaf_is_migration(entry) || - !check_pmd(softleaf_to_pfn(entry), pvmw)) + if (pvmw->flags & PVMW_MIGRATION) + return not_found(pvmw); + entry = softleaf_from_pmd(pmde); + if (!check_pmd(softleaf_to_pfn(entry), pvmw)) return not_found(pvmw); return true; + } else if (!pmd_present(pmde)) { + return not_found(pvmw); } if (likely(pmd_trans_huge(pmde))) { if (pvmw->flags & PVMW_MIGRATION) @@ -266,17 +275,10 @@ bool page_vma_mapped_walk(struct page_vma_mapped_walk *pvmw) return not_found(pvmw); return true; } - /* THP pmd was split under us: handle on pte level */ + /* THP/device-private pmd was split under us: handle on pte level */ spin_unlock(pvmw->ptl); pvmw->ptl = NULL; } else if (!pmd_present(pmde)) { - const softleaf_t entry = softleaf_from_pmd(pmde); - - if (softleaf_is_device_private(entry)) { - pvmw->ptl = pmd_lock(mm, pvmw->pmd); - return true; - } - if ((pvmw->flags & PVMW_SYNC) && thp_vma_suitable_order(vma, pvmw->address, PMD_ORDER) && From 49d0d996e4d4fc3419717dd452ebfa2999e6c2e6 Mon Sep 17 00:00:00 2001 From: Andrew Morton Date: Wed, 1 Jul 2026 12:00:04 -0700 Subject: [PATCH 116/562] mm-page_vma_mapped-fix-device-private-pmd-handling-fix fix Raspberry Pi 1 build, per David Reported-by: Klara Modin Tested-by: Klara Modin Cc: Balbir Singh Cc: David Hildenbrand Cc: Lance Yang Cc: Lorenzo Stoakes Cc: SeongJae Park Cc: Wei Yang Cc: Zi Yan Signed-off-by: Andrew Morton --- mm/page_vma_mapped.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mm/page_vma_mapped.c b/mm/page_vma_mapped.c index 2d6c58488e3a..bac2eb5de63d 100644 --- a/mm/page_vma_mapped.c +++ b/mm/page_vma_mapped.c @@ -243,8 +243,9 @@ bool page_vma_mapped_walk(struct page_vma_mapped_walk *pvmw) */ pmde = pmdp_get_lockless(pvmw->pmd); - if (pmd_trans_huge(pmde) || pmd_is_migration_entry(pmde) || - pmd_is_device_private_entry(pmde)) { + if (IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE) && + (pmd_trans_huge(pmde) || pmd_is_migration_entry(pmde) || + pmd_is_device_private_entry(pmde))) { pvmw->ptl = pmd_lock(mm, pvmw->pmd); pmde = *pvmw->pmd; if (pmd_is_migration_entry(pmde)) { From cf6064810c6f063331eac3c60d6a40c25f2cdbd6 Mon Sep 17 00:00:00 2001 From: Gregory Price Date: Sat, 27 Jun 2026 16:22:43 -0400 Subject: [PATCH 117/562] mm/vmstat: fold stranded per-cpu node stats when a node comes online A per-node vmstat counter is pgdat->vm_stat[] plus per-cpu deltas. A balanced counter can sit split as global=+N / per-cpu=-N. The folds reconciling the split only walk online nodes, so when try_offline_node() marks a node offline the per-cpu deltas are stranded. A subsequent online resets the per-cpu area but not pgdat->vm_stat[], orphaning the +N permanently. All NR_VM_NODE_STAT_ITEMS are affected. The existing code zeroes the per-cpu counters and causes a permanent skew. Fold the stranded deltas instead, before the node rejoins the online set. The node is not online yet and the hotplug lock is held, so the remote access to per-cpu values is safe. Discovered when node compaction hung for a nearly empty node, as the math to determine throttling broke. Reproduced by repeated memory hotplug/unplug cycles on a node under pressure: NR_ISOLATED_ANON ratchets up and never returns to zero. Link: https://lore.kernel.org/20260627202243.758289-1-gourry@gourry.net Fixes: 75ef71840539 ("mm, vmstat: add infrastructure for per-node vmstats") Signed-off-by: Gregory Price Cc: Johannes Weiner Cc: Mel Gorman Cc: Mike Rapoport Cc: Vlastimil Babka Cc: Signed-off-by: Andrew Morton --- mm/mm_init.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/mm/mm_init.c b/mm/mm_init.c index 0f64909e8d20..498d62c4ece3 100644 --- a/mm/mm_init.c +++ b/mm/mm_init.c @@ -1540,7 +1540,7 @@ void __ref free_area_init_core_hotplug(struct pglist_data *pgdat) { int nid = pgdat->node_id; enum zone_type z; - int cpu; + int cpu, i; pgdat_init_internals(pgdat); @@ -1558,10 +1558,17 @@ void __ref free_area_init_core_hotplug(struct pglist_data *pgdat) pgdat->node_start_pfn = 0; pgdat->node_present_pages = 0; - for_each_online_cpu(cpu) { - struct per_cpu_nodestat *p; + /* + * Hot-unplug can leave per-cpu vmstat deltas unfolded (folders skip + * offline nodes) - reconcile this at online. Foreign access to counters + * is safe: the node is not online yet and we hold the hotplug lock. + */ + for_each_possible_cpu(cpu) { + struct per_cpu_nodestat *p = per_cpu_ptr(pgdat->per_cpu_nodestats, cpu); - p = per_cpu_ptr(pgdat->per_cpu_nodestats, cpu); + for (i = 0; i < NR_VM_NODE_STAT_ITEMS; i++) + if (p->vm_node_stat_diff[i]) + node_page_state_add(p->vm_node_stat_diff[i], pgdat, i); memset(p, 0, sizeof(*p)); } From a985794cfba7ba3c71509b3b22025e8d096007f9 Mon Sep 17 00:00:00 2001 From: Rik van Riel Date: Wed, 1 Jul 2026 13:42:34 -0400 Subject: [PATCH 118/562] mm/huge_memory: set PG_has_hwpoisoned only after new folio head is established __split_folio_to_order() copies the hwpoison state onto each new sub-folio while splitting a folio to a non-zero order. It does so via if (handle_hwpoison && page_range_has_hwpoisoned(new_head, new_nr_pages)) folio_set_has_hwpoisoned(new_folio); *before* clear_compound_head(new_head)/prep_compound_page(new_head, ...) turns @new_head from a tail page into a proper folio head. PG_has_hwpoisoned is a FOLIO_SECOND_PAGE flag, so folio_set_has_hwpoisoned() resolves to folio_flags(folio, 1). With the new compound_info-based page-flags layout, folio_flags() asserts the page is not a tail: VM_BUG_ON_PGFLAGS(page->compound_info & 1, page); VM_BUG_ON_PGFLAGS(n > 0 && !test_bit(PG_head, &page->flags.f), page); At the current call site @new_head still has the tail marker (compound_info bit 0 set, PG_head clear), so on CONFIG_DEBUG_VM kernels this hits: kernel BUG at include/linux/page-flags.h:354 folio_flags+0x82 folio_set_has_hwpoisoned __split_folio_to_order __split_unmapped_folio __folio_split truncate_inode_partial_folio (shmem hole-punch / MADV_REMOVE) Reproduced by syzkaller: hwpoison-inject a few subpages of a large shmem folio, then MADV_REMOVE (fallocate punch hole) on the same range, which splits the partial folio to a non-zero order. memory_failure() tries to split the poisoned folio to order 0 first, but that split is best-effort; when it fails the folio is left large with PG_has_hwpoisoned set, the case fa5a06170036 added this hwpoison copying for. Move the folio_set_has_hwpoisoned() call to after clear_compound_head()/prep_compound_page(), where @new_folio is a real order-new_order head folio (handle_hwpoison implies new_order != 0, so a second page always exists). The flag still lands on the same struct page (page[1] of the new folio); only the ordering relative to compound-head setup changes, satisfying the FOLIO_SECOND_PAGE precondition. Link: https://lore.kernel.org/20260701174235.3173401-1-riel@surriel.com Fixes: fa5a06170036 ("mm/huge_memory: preserve PG_has_hwpoisoned if a folio is split to >0 order") Signed-off-by: Rik van Riel Assisted-by: Claude:claude-opus-4-8 Reviewed-by: Zi Yan Acked-by: David Hildenbrand (Arm) Tested-by: Lance Yang Reviewed-by: Lorenzo Stoakes Reviewed-by: Baolin Wang Cc: Barry Song Cc: Dev Jain Cc: Lance Yang Cc: Liam R. Howlett Cc: Nico Pache Cc: Ryan Roberts Cc: Yang Shi Cc: Signed-off-by: Andrew Morton --- mm/huge_memory.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/mm/huge_memory.c b/mm/huge_memory.c index 2bccb0a53a0a..b5d1e9d4463d 100644 --- a/mm/huge_memory.c +++ b/mm/huge_memory.c @@ -3587,10 +3587,6 @@ static void __split_folio_to_order(struct folio *folio, int old_order, (1L << PG_dropbehind) | LRU_GEN_MASK | LRU_REFS_MASK)); - if (handle_hwpoison && - page_range_has_hwpoisoned(new_head, new_nr_pages)) - folio_set_has_hwpoisoned(new_folio); - new_folio->mapping = folio->mapping; new_folio->index = folio->index + i; @@ -3612,6 +3608,14 @@ static void __split_folio_to_order(struct folio *folio, int old_order, folio_set_large_rmappable(new_folio); } + /* + * PG_has_hwpoisoned is on the 2nd page, so set it after + * the compound head is prepped. + */ + if (handle_hwpoison && + page_range_has_hwpoisoned(new_head, new_nr_pages)) + folio_set_has_hwpoisoned(new_folio); + if (folio_test_young(folio)) folio_set_young(new_folio); if (folio_test_idle(folio)) From b00a288038d3e11799765d353bdddafd2ee6ff1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Mon, 25 May 2026 10:33:52 +0200 Subject: [PATCH 119/562] m68k: avoid -Wunused-but-set-parameter in clear_user_page() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The loop in clear_user_pages() iterates over all pages and calls clear_user_page() for each of them. During the loop "vaddr" is modified. However on m68k clear_user() is a macro which does not use "vaddr". The compiler sees a variable which is modified but never used and emits a warning for that: include/linux/highmem.h: In function 'clear_user_pages': include/linux/highmem.h:234:63: warning: parameter 'vaddr' set but not used [-Wunused-but-set-parameter=] static inline void clear_user_pages(void *addr, unsigned long vaddr, Other architectures use an inline function for clear_user_page() which avoids the warning. This is not possible on m68k, as dlush_dcache_page() is another macro which is not yet defined where clear_user_page() is defined. Including cacheflush_mm.h will trigger recursive and lots of other issues. So hide the warning with a cast to (void) instead. While we are here, do the same for copy_user_page(). Link: https://lore.kernel.org/20260525-m68k-clear_user_page-v2-1-0c8981c6eca1@weissschuh.net Fixes: 62a9f5a85b98 ("mm: introduce clear_pages() and clear_user_pages()") Signed-off-by: Thomas Weißschuh Acked-by: Geert Uytterhoeven Cc: Andreas Schwab Cc: Ankur Arora Cc: David Hildenbrand Cc: Signed-off-by: Andrew Morton --- arch/m68k/include/asm/page_mm.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/m68k/include/asm/page_mm.h b/arch/m68k/include/asm/page_mm.h index ed782609ca41..0971a0651d49 100644 --- a/arch/m68k/include/asm/page_mm.h +++ b/arch/m68k/include/asm/page_mm.h @@ -55,10 +55,12 @@ static inline void clear_page(void *page) #define clear_user_page(addr, vaddr, page) \ do { clear_page(addr); \ flush_dcache_page(page); \ + (void)(vaddr); \ } while (0) #define copy_user_page(to, from, vaddr, page) \ do { copy_page(to, from); \ flush_dcache_page(page); \ + (void)(vaddr); \ } while (0) extern unsigned long m68k_memoffset; From 7aada911797232fc703525091626abdb847bef2e Mon Sep 17 00:00:00 2001 From: SJ Park Date: Mon, 29 Jun 2026 20:52:19 -0700 Subject: [PATCH 120/562] mm/damon/core: validate ranges in damon_set_regions() DAMON core logic assumes zero length regions don't exist. However, a few DAMON API callers including DAMON_SYSFS, DAMON_RECLAIM and DAMON_LRU_SORT allow users to set empty monitoring target regions. This could result in WARN_ONCE() on CONFIG_DAMON_DEBUG_SANITY enabled kernel, and divide-by-zero from damon_merge_two_regions(). For example, the WANR_ONCE() can be triggered like below. # grep DAMON_DEBUG_SANITY /boot/config-$(uname -r) # CONFIG_DAMON_DEBUG_SANITY=y # damo start # cd /sys/kernel/mm/damon/admin/kdamonds/0 # echo 0 > contexts/0/targets/0/regions/0/start # echo 0 > contexts/0/targets/0/regions/0/end # echo commit > state # dmesg [....] [ 73.705780] ------------[ cut here ]------------ [ 73.707552] start 0 >= end 0 [ 73.708452] WARNING: mm/damon/core.c:359 at damon_new_region+0x6e/0x80, CPU#1: kdamond.0/758 [...] All DAMON API callers eventually use damon_set_regions() to setup the regions. Add the validation logic in the function. Link: https://lore.kernel.org/20260630035221.146458-1-sj@kernel.org Fixes: 43b0536cb471 ("mm/damon: introduce DAMON-based Reclamation (DAMON_RECLAIM)") Signed-off-by: SJ Park Cc: Yang yingliang Cc: # 5.16.x Signed-off-by: Andrew Morton --- mm/damon/core.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mm/damon/core.c b/mm/damon/core.c index d99f7a297fdd..949d5309d54d 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -356,6 +356,12 @@ int damon_set_regions(struct damon_target *t, struct damon_addr_range *ranges, unsigned int i; int err; + for (i = 0; i < nr_ranges; i++) { + if (ALIGN_DOWN(ranges[i].start, min_region_sz) >= + ALIGN(ranges[i].end, min_region_sz)) + return -EINVAL; + } + /* Remove regions which are not in the new ranges */ damon_for_each_region_safe(r, next, t) { for (i = 0; i < nr_ranges; i++) { From fcb6c9ec932bcec5f112ca8fec110ec6b457abf7 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 15 May 2026 22:44:46 +0200 Subject: [PATCH 121/562] fat: avoid stack overflow warning Building the fat kunit tests on with -fsanitize=alignment reveals some rather excessive stack usage: fs/fat/fat_test.c: In function 'fat_clus_to_blknr_test': fs/fat/fat_test.c:33:1: error: the frame size of 4736 bytes is larger than 1536 bytes [-Werror=frame-larger-than=] 33 | } | ^ fs/fat/fat_test.c: In function 'fat_get_blknr_offset_test': fs/fat/fat_test.c:52:1: error: the frame size of 4800 bytes is larger than 1536 bytes [-Werror=frame-larger-than=] The problem is clearly related to the on-stack copy of a local msdos_sb_info structure. Avoid this by making that copy 'static const' and changing the called functions to accept a constant input. Link: https://lore.kernel.org/20260515204456.2692208-1-arnd@kernel.org Fixes: 410002f8139c ("kunit: fat: test cluster and directory i_pos layout helpers") Signed-off-by: Arnd Bergmann Acked-by: OGAWA Hirofumi Cc: Christian Brauner Cc: Jan Kara Cc: Adi Nata Cc: David Laight Signed-off-by: Andrew Morton --- fs/fat/fat.h | 4 ++-- fs/fat/fat_test.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/fat/fat.h b/fs/fat/fat.h index 99ed9228a677..2772675bd35a 100644 --- a/fs/fat/fat.h +++ b/fs/fat/fat.h @@ -249,13 +249,13 @@ static inline unsigned char fat_checksum(const __u8 *name) return s; } -static inline sector_t fat_clus_to_blknr(struct msdos_sb_info *sbi, int clus) +static inline sector_t fat_clus_to_blknr(const struct msdos_sb_info *sbi, int clus) { return ((sector_t)clus - FAT_START_ENT) * sbi->sec_per_clus + sbi->data_start; } -static inline void fat_get_blknr_offset(struct msdos_sb_info *sbi, +static inline void fat_get_blknr_offset(const struct msdos_sb_info *sbi, loff_t i_pos, sector_t *blknr, int *offset) { *blknr = i_pos >> sbi->dir_per_block_bits; diff --git a/fs/fat/fat_test.c b/fs/fat/fat_test.c index 4eeed9dca549..9583ce66dca3 100644 --- a/fs/fat/fat_test.c +++ b/fs/fat/fat_test.c @@ -22,7 +22,7 @@ static void fat_checksum_test(struct kunit *test) static void fat_clus_to_blknr_test(struct kunit *test) { - struct msdos_sb_info sbi = { + static const struct msdos_sb_info sbi = { .sec_per_clus = 4, .data_start = 100, }; @@ -34,7 +34,7 @@ static void fat_clus_to_blknr_test(struct kunit *test) static void fat_get_blknr_offset_test(struct kunit *test) { - struct msdos_sb_info sbi = { + static const struct msdos_sb_info sbi = { .dir_per_block = 16, .dir_per_block_bits = 4, }; From db9ea51f3f76c7dcb37d1cf3fe4b73cb2e751fc9 Mon Sep 17 00:00:00 2001 From: Nico Pache Date: Thu, 2 Jul 2026 11:25:46 -0600 Subject: [PATCH 122/562] mm: decrement MTHP_STAT_NR_ANON in free_zone_device_folio() Patch series "mm: fix PMD level mTHP accounting bugs". While running selftests I noticed the PMD level per-mTHP stats (nr_anon) remained elevated after each run. After further investigation I noticed this accounting error occurs for both the migration.private_anon_htlb_test and the HMM tests. In the HMM case this is due to folio_add_new_anon_rmap() incrementing the mTHP stats, but never containing a corresponding decrement in free_zone_device_folio(). We solve this by making sure to decrement the counter when freeing device memory. In the migration case, we are incrementing this counter without first checking whether this folio is a hugetlb folio, which relies on a separate accounting system. We solve this by adding the proper hugetlb check before incrementing this counter. With these changes in place, the two tests no longer cause elevated PMD level accounting issues. This patch (of 2): When a zone device folio is mapped as anonymous, folio_add_new_anon_rmap() increments MTHP_STAT_NR_ANON. The corresponding decrement lives in __free_pages_prepare() in page_alloc.c, but zone device folios are freed via free_zone_device_folio() which never calls __free_pages_prepare(). This causes nr_anon to remain permanently elevated after zone device folios are freed. Add the missing mod_mthp_stat() decrement to free_zone_device_folio() so that the counter is properly balanced. Link: https://lore.kernel.org/20260702172548.37075-1-npache@redhat.com Link: https://lore.kernel.org/20260702172548.37075-2-npache@redhat.com Fixes: 5d65c8d758f2 ("mm: count the number of anonymous THPs per size") Co-developed-by: David Hildenbrand Signed-off-by: David Hildenbrand Signed-off-by: Nico Pache Reviewed-by: Zi Yan Cc: Alistair Popple Cc: Barry Song Cc: Byungchul Park Cc: Gregory Price Cc: "Huang, Ying" Cc: Joshua Hahn Cc: Lorenzo Stoakes Cc: Matthew Brost Cc: Matthew Wilcox (Oracle) Cc: Oscar Salvador Cc: Rakie Kim Cc: Signed-off-by: Andrew Morton --- mm/memremap.c | 1 + 1 file changed, 1 insertion(+) diff --git a/mm/memremap.c b/mm/memremap.c index 81766d822400..accba23aef28 100644 --- a/mm/memremap.c +++ b/mm/memremap.c @@ -425,6 +425,7 @@ void free_zone_device_folio(struct folio *folio) mem_cgroup_uncharge(folio); if (folio_test_anon(folio)) { + mod_mthp_stat(folio_order(folio), MTHP_STAT_NR_ANON, -1); for (i = 0; i < nr; i++) __ClearPageAnonExclusive(folio_page(folio, i)); } From e5a63b5c5a767124e49454c261cdd44ac4015462 Mon Sep 17 00:00:00 2001 From: Nico Pache Date: Thu, 2 Jul 2026 11:25:47 -0600 Subject: [PATCH 123/562] mm/migrate: exclude hugetlb folios from MTHP_STAT_NR_ANON accounting __folio_migrate_mapping() increments MTHP_STAT_NR_ANON for the destination folio when `folio_test_anon(folio) && folio_test_large(folio)` is true. However, hugetlb folios satisfy both conditions despite having a completely separate accounting system; they use hugetlb_add_anon_rmap() which does not touch mTHP stats, and their free path also bypasses the mTHP decrement in __free_pages_prepare(). This causes MTHP_STAT_NR_ANON to be incremented on each hugetlb migration without a corresponding decrement, permanently inflating the nr_anon counter. Add a !folio_test_hugetlb() check to both places in __folio_migrate_mapping() so that only actual mTHP folios are counted. Link: https://lore.kernel.org/20260702172548.37075-3-npache@redhat.com Fixes: 5d65c8d758f2 ("mm: count the number of anonymous THPs per size") Co-developed-by: David Hildenbrand Signed-off-by: David Hildenbrand Signed-off-by: Nico Pache Reviewed-by: Zi Yan Cc: Alistair Popple Cc: Barry Song Cc: Byungchul Park Cc: Gregory Price Cc: "Huang, Ying" Cc: Joshua Hahn Cc: Lorenzo Stoakes Cc: Matthew Brost Cc: Matthew Wilcox (Oracle) Cc: Oscar Salvador Cc: Rakie Kim Cc: Signed-off-by: Andrew Morton --- mm/migrate.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mm/migrate.c b/mm/migrate.c index d9b23909d716..9fd50ea25d2d 100644 --- a/mm/migrate.c +++ b/mm/migrate.c @@ -590,7 +590,8 @@ static int __folio_migrate_mapping(struct address_space *mapping, /* No turning back from here */ newfolio->index = folio->index; newfolio->mapping = folio->mapping; - if (folio_test_anon(folio) && folio_test_large(folio)) + if (folio_test_anon(folio) && folio_test_large(folio) && + !folio_test_hugetlb(folio)) mod_mthp_stat(folio_order(folio), MTHP_STAT_NR_ANON, 1); if (folio_test_swapbacked(folio)) __folio_set_swapbacked(newfolio); @@ -623,7 +624,8 @@ static int __folio_migrate_mapping(struct address_space *mapping, */ newfolio->index = folio->index; newfolio->mapping = folio->mapping; - if (folio_test_anon(folio) && folio_test_large(folio)) + if (folio_test_anon(folio) && folio_test_large(folio) && + !folio_test_hugetlb(folio)) mod_mthp_stat(folio_order(folio), MTHP_STAT_NR_ANON, 1); folio_ref_add(newfolio, nr); /* add cache reference */ if (folio_test_swapbacked(folio)) From ce8883c1ad6f8e322aea10f8a27de8d16bed11e4 Mon Sep 17 00:00:00 2001 From: Lance Yang Date: Thu, 2 Jul 2026 22:02:57 +0800 Subject: [PATCH 124/562] MAINTAINERS: add Usama as a THP reviewer Usama has been active around THP, really enjoys working on THPs, and tries to review all the patches that come in. Let's invite him to the THP party as a reviewer to help with the ongoing THP review load. Link: https://lore.kernel.org/20260702140257.44780-1-lance.yang@linux.dev Signed-off-by: Lance Yang Acked-by: David Hildenbrand (Arm) Acked-by: Lorenzo Stoakes Acked-by: Zi Yan Acked-by: Barry Song Acked-by: SJ Park Cc: Baolin Wang Cc: Dev Jain Cc: Liam R. Howlett Cc: Nico Pache Cc: Ryan Roberts Cc: Zi Yan Signed-off-by: Andrew Morton --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 272c8475a959..92e76defe957 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17256,6 +17256,7 @@ R: Ryan Roberts R: Dev Jain R: Barry Song R: Lance Yang +R: Usama Arif L: linux-mm@kvack.org S: Maintained W: http://www.linux-mm.org From 840086f1659a919ca46887a26d3d911838654707 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Fri, 3 Jul 2026 09:56:08 -0700 Subject: [PATCH 125/562] mm/damon/core: disallow overlapping input ranges for damon_set_regions() damon_set_regions() assumes the input ranges are sorted by the address and don't overlap each other. Hence the assumption was initially to be explicitly validated. But commit 97d482f4592f ("mm/damon/sysfs: reuse damon_set_regions() for regions setting") has mistakenly removed the validation. This can make DAMON behave in unexpected ways. At the best, the monitoring results snapshot will just look weird since there will be overlapping regions. DAMOS will also work weirdly, applying the same action multiple times for overlapping regions, and make DAMOS quota weird. More seriously, depending on the setup and regions updates sequence, negative size regions can be made. It will trigger WARN_ONCE() if the kernel is built with CONFIG_DAMON_DEBUG_SANITY=y. Depending on the monitoring results, the negative size region can further trigger division by zero in damon_merge_two_regions(). Note that some of the consequences including the WARN_ONCE() and the divide by zero depend on commits that were introduced after the root cause commit 97d482f4592f ("mm/damon/sysfs: reuse damon_set_regions() for regions setting"). Fix the problems by checking the assumption and returning an error if the input ranges don't meet the assumption. The issue was discovered [1] by Sashiko. Link: https://lore.kernel.org/20260703165610.92894-1-sj@kernel.org Link: https://lore.kernel.org/20260630041806.151124-1-sj@kernel.org [1] Fixes: 97d482f4592f ("mm/damon/sysfs: reuse damon_set_regions() for regions setting") Signed-off-by: SJ Park Cc: # 5.19.x Signed-off-by: Andrew Morton --- mm/damon/core.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/mm/damon/core.c b/mm/damon/core.c index 949d5309d54d..cff932b3317d 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -354,12 +354,19 @@ int damon_set_regions(struct damon_target *t, struct damon_addr_range *ranges, { struct damon_region *r, *next; unsigned int i; + unsigned long last_end; int err; for (i = 0; i < nr_ranges; i++) { - if (ALIGN_DOWN(ranges[i].start, min_region_sz) >= - ALIGN(ranges[i].end, min_region_sz)) + unsigned long start, end; + + start = ALIGN_DOWN(ranges[i].start, min_region_sz); + end = ALIGN(ranges[i].end, min_region_sz); + if (start >= end) + return -EINVAL; + if (i > 0 && last_end > start) return -EINVAL; + last_end = end; } /* Remove regions which are not in the new ranges */ From c3b2a6478152eb7401ca8e65e3151f6486ba76d0 Mon Sep 17 00:00:00 2001 From: "Kiryl Shutsemau (Meta)" Date: Fri, 3 Jul 2026 17:18:33 +0100 Subject: [PATCH 126/562] mm/hugetlb: fix swap entry corruption when clearing uffd-wp at fork() copy_hugetlb_page_range() clears the uffd-wp bit of hwpoison and migration entries with huge_pte_clear_uffd_wp(), which operates on the present-PTE bit position. Swap entries keep the uffd-wp state elsewhere -- the same branches read and set it with pte_swp_uffd_wp() and pte_swp_mkuffd_wp() -- and the present-PTE position falls into the swap payload. On x86-64 it lands in the inverted swap offset, where a naturally-aligned hugetlb PFN always has the affected bit set, so the clear advances the encoded PFN by two pages. No userfaultfd needs to be involved: the clear is guarded only by the child VMA not being uffd-wp registered, so a plain fork() with an in-flight hugetlb migration entry (or a poisoned hugetlb page) corrupts the entry copied into the child. Instrumenting the hwpoison branch and forking after MADV_HWPOISON on a 2MB anon hugetlb page shows: offset before=120e00 offset after =120e02 The fallout is mostly latent: rmap walks match migration entries by folio range and remove_migration_pte() rebuilds the PTE from the folio, so a within-folio PFN skew heals once migration completes. But any path that re-encodes the corrupted offset -- e.g. hugetlb_change_protection() rewriting a writable migration entry via make_readable_migration_entry(swp_offset(entry)) -- propagates it, and an hwpoison entry misidentifies which page is poisoned. Use pte_swp_clear_uffd_wp(), matching copy_nonpresent_pte() and move_huge_pte(). Link: https://lore.kernel.org/20260703161833.57416-1-kirill@shutemov.name Fixes: bc70fbf269fd ("mm/hugetlb: handle uffd-wp during fork()") Signed-off-by: Kiryl Shutsemau Assisted-by: Claude:claude-fable-5 Reported-by: Sashiko AI review Closes: https://lore.kernel.org/all/20260703140011.99E601F000E9@smtp.kernel.org/ Cc: David Hildenbrand Cc: Muchun Song Cc: Oscar Salvador Cc: Peter Xu Cc: Signed-off-by: Andrew Morton --- mm/hugetlb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mm/hugetlb.c b/mm/hugetlb.c index 571212b80835..a4e6dd3a82f4 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -4918,7 +4918,7 @@ int copy_hugetlb_page_range(struct mm_struct *dst, struct mm_struct *src, softleaf = softleaf_from_pte(entry); if (unlikely(softleaf_is_hwpoison(softleaf))) { if (!userfaultfd_wp(dst_vma)) - entry = huge_pte_clear_uffd_wp(entry); + entry = pte_swp_clear_uffd_wp(entry); set_huge_pte_at(dst, addr, dst_pte, entry, sz); } else if (unlikely(softleaf_is_migration(softleaf))) { bool uffd_wp = pte_swp_uffd_wp(entry); @@ -4936,7 +4936,7 @@ int copy_hugetlb_page_range(struct mm_struct *dst, struct mm_struct *src, set_huge_pte_at(src, addr, src_pte, entry, sz); } if (!userfaultfd_wp(dst_vma)) - entry = huge_pte_clear_uffd_wp(entry); + entry = pte_swp_clear_uffd_wp(entry); set_huge_pte_at(dst, addr, dst_pte, entry, sz); } else if (unlikely(pte_is_marker(entry))) { const pte_marker marker = copy_pte_marker(softleaf, dst_vma); From 64408e63bc10290882b5ae9d646d985294ba38f8 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Fri, 3 Jul 2026 09:17:24 -0700 Subject: [PATCH 127/562] mm/kmemleak: fix checksum computation for per-cpu objects The per-cpu object checksum folds each CPU's CRC together with XOR and seeds every CRC with 0. Both choices make update_checksum() miss content changes: - XOR is self-cancelling, so equal contents on two CPUs cancel out and simultaneous identical changes leave the checksum unchanged. - crc32(0, ...) over all-zero content is 0, so a freshly allocated, zeroed per-cpu area checksums to 0, matching the initial value, and the object is never seen to change. See discussions at [0]. When update_checksum() wrongly reports an actively modified object as unchanged, kmemleak stops greying it for an extra scan and can report a live per-cpu object as a leak. Fold the per-cpu CRC as a single rolling checksum across all CPUs and initialise the object checksum to ~0 so the first computed value always registers as a change, even for content that hashes to 0. reset_checksum() is seeded the same way. Link: https://lore.kernel.org/all/akfYImSNDh3OjIfR@gmail.com [0] Link: https://lore.kernel.org/20260703-kmemleak_checksum-v1-1-5e0ab7d6966f@debian.org Fixes: 6c99d4eb7c5e ("kmemleak: enable tracking for percpu pointers") Signed-off-by: Breno Leitao Co-developed-by: Catalin Marinas Signed-off-by: Catalin Marinas Reviewed-by: Pavel Tikhomirov Cc: Signed-off-by: Andrew Morton --- mm/kmemleak.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mm/kmemleak.c b/mm/kmemleak.c index 7c7ba17ce7af..e196f53f9b46 100644 --- a/mm/kmemleak.c +++ b/mm/kmemleak.c @@ -687,7 +687,7 @@ static struct kmemleak_object *__alloc_object(gfp_t gfp) atomic_set(&object->use_count, 1); object->excess_ref = 0; object->count = 0; /* white color initially */ - object->checksum = 0; + object->checksum = ~0; object->del_state = 0; /* task information */ @@ -981,7 +981,7 @@ static void reset_checksum(unsigned long ptr) } raw_spin_lock_irqsave(&object->lock, flags); - object->checksum = 0; + object->checksum = ~0; raw_spin_unlock_irqrestore(&object->lock, flags); put_object(object); } @@ -1410,7 +1410,8 @@ static bool update_checksum(struct kmemleak_object *object) for_each_possible_cpu(cpu) { void *ptr = per_cpu_ptr((void __percpu *)object->pointer, cpu); - object->checksum ^= crc32(0, kasan_reset_tag((void *)ptr), object->size); + object->checksum = crc32(object->checksum, + kasan_reset_tag((void *)ptr), object->size); } } else { object->checksum = crc32(0, kasan_reset_tag((void *)object->pointer), object->size); From d5b9d8c557eda0ee3b5c0eb103504988b7c8e030 Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Sun, 5 Jul 2026 02:25:13 -0400 Subject: [PATCH 128/562] mm: page_reporting: allow driver to set batch capacity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit At the moment, if a virtio balloon device has a page reporting vq but its size is < PAGE_REPORTING_CAPACITY (32), the balloon driver fails probe. But, there's no way for host to know this value, so it can easily create a smaller vq and suddenly adding the reporting capability to the device makes all of the driver fail. Not pretty. Add a capacity field to page_reporting_dev_info so drivers can control the maximum number of pages per report batch. In virtio-balloon, set the capacity to the reporting virtqueue size, letting page_reporting adapt to whatever the device provides. Capacity need not be a power of two. Code previously called out division by PAGE_REPORTING_CAPACITY as cheap since it was a power of 2, but no performance difference was observed with non-power-of-2 values. If capacity is 0 or exceeds PAGE_REPORTING_CAPACITY, it defaults to PAGE_REPORTING_CAPACITY. The 0 check and the clamping is done in page_reporting_register(), before the reporting work is scheduled, so we never get division by 0. Link: https://lore.kernel.org/444c24cf39f3f3620fc90ef4695bd6b0979f4c4b.1783232420.git.mst@redhat.com Fixes: b0c504f15471 ("virtio-balloon: add support for providing free page reports to host") Signed-off-by: Michael S. Tsirkin Assisted-by: Claude:claude-opus-4-6 Acked-by: David Hildenbrand (Arm) Reviewed-by: Gregory Price Acked-by: Zi Yan Reviewed-by: Pankaj Gupta Cc: Alexander Duyck Cc: Brendan Jackman Cc: Eugenio Pérez Cc: Jason Wang Cc: Johannes Weiner Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Xuan Zhuo Signed-off-by: Andrew Morton --- drivers/virtio/virtio_balloon.c | 5 +---- include/linux/page_reporting.h | 4 +++- mm/page_reporting.c | 24 ++++++++++++------------ 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/drivers/virtio/virtio_balloon.c b/drivers/virtio/virtio_balloon.c index 088b3a0e6ce6..581ac799d974 100644 --- a/drivers/virtio/virtio_balloon.c +++ b/drivers/virtio/virtio_balloon.c @@ -1017,10 +1017,6 @@ static int virtballoon_probe(struct virtio_device *vdev) unsigned int capacity; capacity = virtqueue_get_vring_size(vb->reporting_vq); - if (capacity < PAGE_REPORTING_CAPACITY) { - err = -ENOSPC; - goto out_unregister_oom; - } vb->pr_dev_info.order = PAGE_REPORTING_ORDER_UNSPECIFIED; @@ -1041,6 +1037,7 @@ static int virtballoon_probe(struct virtio_device *vdev) vb->pr_dev_info.order = 5; #endif + vb->pr_dev_info.capacity = capacity; err = page_reporting_register(&vb->pr_dev_info); if (err) goto out_unregister_oom; diff --git a/include/linux/page_reporting.h b/include/linux/page_reporting.h index 9d4ca5c218a0..272b1274efdc 100644 --- a/include/linux/page_reporting.h +++ b/include/linux/page_reporting.h @@ -5,7 +5,6 @@ #include #include -/* This value should always be a power of 2, see page_reporting_cycle() */ #define PAGE_REPORTING_CAPACITY 32 #define PAGE_REPORTING_ORDER_UNSPECIFIED -1 @@ -22,6 +21,9 @@ struct page_reporting_dev_info { /* Minimal order of page reporting */ unsigned int order; + + /* Max pages per report batch; 0 (default) means PAGE_REPORTING_CAPACITY */ + unsigned int capacity; }; /* Tear-down and bring-up for page reporting devices */ diff --git a/mm/page_reporting.c b/mm/page_reporting.c index 7418f2e500bb..942e84b6908a 100644 --- a/mm/page_reporting.c +++ b/mm/page_reporting.c @@ -173,11 +173,8 @@ page_reporting_cycle(struct page_reporting_dev_info *prdev, struct zone *zone, * any pages that may have already been present from the previous * list processed. This should result in us reporting all pages on * an idle system in about 30 seconds. - * - * The division here should be cheap since PAGE_REPORTING_CAPACITY - * should always be a power of 2. */ - budget = DIV_ROUND_UP(area->nr_free, PAGE_REPORTING_CAPACITY * 16); + budget = DIV_ROUND_UP(area->nr_free, prdev->capacity * 16); /* loop through free list adding unreported pages to sg list */ list_for_each_entry_safe(page, next, list, lru) { @@ -222,10 +219,10 @@ page_reporting_cycle(struct page_reporting_dev_info *prdev, struct zone *zone, spin_unlock_irq(&zone->lock); /* begin processing pages in local list */ - err = prdev->report(prdev, sgl, PAGE_REPORTING_CAPACITY); + err = prdev->report(prdev, sgl, prdev->capacity); /* reset offset since the full list was reported */ - *offset = PAGE_REPORTING_CAPACITY; + *offset = prdev->capacity; /* update budget to reflect call to report function */ budget--; @@ -234,7 +231,7 @@ page_reporting_cycle(struct page_reporting_dev_info *prdev, struct zone *zone, spin_lock_irq(&zone->lock); /* flush reported pages from the sg list */ - page_reporting_drain(prdev, sgl, PAGE_REPORTING_CAPACITY, !err); + page_reporting_drain(prdev, sgl, prdev->capacity, !err); /* * Reset next to first entry, the old next isn't valid @@ -260,13 +257,13 @@ static int page_reporting_process_zone(struct page_reporting_dev_info *prdev, struct scatterlist *sgl, struct zone *zone) { - unsigned int order, mt, leftover, offset = PAGE_REPORTING_CAPACITY; + unsigned int order, mt, leftover, offset = prdev->capacity; unsigned long watermark; int err = 0; /* Generate minimum watermark to be able to guarantee progress */ watermark = low_wmark_pages(zone) + - (PAGE_REPORTING_CAPACITY << page_reporting_order); + (prdev->capacity << page_reporting_order); /* * Cancel request if insufficient free memory or if we failed @@ -290,7 +287,7 @@ page_reporting_process_zone(struct page_reporting_dev_info *prdev, } /* report the leftover pages before going idle */ - leftover = PAGE_REPORTING_CAPACITY - offset; + leftover = prdev->capacity - offset; if (leftover) { sgl = &sgl[offset]; err = prdev->report(prdev, sgl, leftover); @@ -322,11 +319,11 @@ static void page_reporting_process(struct work_struct *work) atomic_set(&prdev->state, state); /* allocate scatterlist to store pages being reported on */ - sgl = kmalloc_objs(*sgl, PAGE_REPORTING_CAPACITY); + sgl = kmalloc_objs(*sgl, prdev->capacity); if (!sgl) goto err_out; - sg_init_table(sgl, PAGE_REPORTING_CAPACITY); + sg_init_table(sgl, prdev->capacity); for_each_zone(zone) { err = page_reporting_process_zone(prdev, sgl, zone); @@ -377,6 +374,9 @@ int page_reporting_register(struct page_reporting_dev_info *prdev) page_reporting_order = pageblock_order; } + if (!prdev->capacity || prdev->capacity > PAGE_REPORTING_CAPACITY) + prdev->capacity = PAGE_REPORTING_CAPACITY; + /* initialize state and work structures */ atomic_set(&prdev->state, PAGE_REPORTING_IDLE); INIT_DELAYED_WORK(&prdev->work, &page_reporting_process); From 75fe872f128cc98b60f221829c5e443925051ef4 Mon Sep 17 00:00:00 2001 From: Sourav Panda Date: Sun, 5 Jul 2026 17:51:19 +0000 Subject: [PATCH 129/562] mm/hugetlb: fix null nodemask in alloc_fresh_hugetlb_folio alloc_buddy_hugetlb_folio_with_mpol() can pass a NULL nodemask to alloc_fresh_hugetlb_folio() as a fallback to allocate from all nodes. If order is gigantic, alloc_fresh_hugetlb_folio() propagates the NULL nodemask down to hugetlb_cma_alloc_frozen_folio() which blindly dereferences it in for_each_node_mask(), leading to a null pointer dereference. Similarly, if the CMA allocation fails, the fallback alloc_contig_frozen_pages() is also called with a NULL nodemask, which may cause issues. Fix this by explicitly checking if nodemask is NULL in alloc_fresh_hugetlb_folio() and defaulting to cpuset_current_mems_allowed. This ensures that both the CMA and contiguous allocators receive a valid nodemask safely using a seqcount loop to prevent torn reads. From a userspace perspective, this bug allows an unprivileged user to crash the kernel (trigger a panic) by requesting a gigantic hugepage allocation with MPOL_PREFERRED_MANY on a system where CMA is only configured on a subset of NUMA nodes. This can be reproduced by booting a VM with two NUMA nodes, restricting CMA to Node 1 (e.g., hugetlb_cma=1:1G default_hugepagesz=1G hugepagesz=1G hugepages=0), and running a program that allocates a 1GB hugepage area without reserving, restricts allocation to Node 0 using mbind() with MPOL_PREFERRED_MANY, and triggers a page fault: void *ptr = mmap(NULL, 1UL << 30, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB | MAP_HUGE_1GB | MAP_NORESERVE, -1, 0); unsigned long nodemask = 1; /* Node 0 */ mbind(ptr, 1UL << 30, MPOL_PREFERRED_MANY, &nodemask, sizeof(nodemask) * 8, 0); memset(ptr, 0, 1UL << 30); /* Trigger fault */ This results in a NULL pointer dereference: BUG: kernel NULL pointer dereference, address: 0000000000000000 #PF: supervisor read access in kernel mode #PF: error_code(0x0000) - not-present page Oops: Oops: 0000 [#1] SMP NOPTI RIP: 0010:hugetlb_cma_alloc_frozen_folio+0x75/0x120 Call Trace: only_alloc_fresh_hugetlb_folio.isra.0+0x2c/0x160 alloc_surplus_hugetlb_folio+0x6d/0x100 alloc_hugetlb_folio+0x3c5/0x660 hugetlb_no_page+0x3d9/0x650 Additionally, this patch adds a missing node_isset(nid, *nodemask) check in hugetlb_cma_alloc_frozen_folio() to ensure the initial node allocation attempt respects the memory policy. Link: https://lore.kernel.org/20260705175119.440599-1-souravpanda@google.com Fixes: eb02f14c4a2b ("mm/hugetlb: allow overcommitting gigantic hugepages") Signed-off-by: Sourav Panda Cc: David Hildenbrand Cc: Frank van der Linden Cc: Greg Thelen Cc: Muchun Song Cc: Oscar Salvador Cc: Suren Baghdasaryan Cc: Usama Arif Cc: Signed-off-by: Andrew Morton --- mm/hugetlb.c | 12 ++++++++++++ mm/hugetlb_cma.c | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/mm/hugetlb.c b/mm/hugetlb.c index a4e6dd3a82f4..2de726ff89da 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -1864,6 +1864,18 @@ static struct folio *alloc_fresh_hugetlb_folio(struct hstate *h, gfp_t gfp_mask, int nid, nodemask_t *nmask) { struct folio *folio; + nodemask_t local_node_mask; + + if (!nmask) { + unsigned int cpuset_mems_cookie; + + do { + cpuset_mems_cookie = read_mems_allowed_begin(); + local_node_mask = cpuset_current_mems_allowed; + } while (read_mems_allowed_retry(cpuset_mems_cookie)); + + nmask = &local_node_mask; + } folio = only_alloc_fresh_hugetlb_folio(h, gfp_mask, nid, nmask, NULL); if (folio) diff --git a/mm/hugetlb_cma.c b/mm/hugetlb_cma.c index 39344d6c78d8..79dbd0baafa3 100644 --- a/mm/hugetlb_cma.c +++ b/mm/hugetlb_cma.c @@ -34,7 +34,7 @@ struct folio *hugetlb_cma_alloc_frozen_folio(int order, gfp_t gfp_mask, if (!hugetlb_cma_size) return NULL; - if (hugetlb_cma[nid]) + if (hugetlb_cma[nid] && node_isset(nid, *nodemask)) page = cma_alloc_frozen_compound(hugetlb_cma[nid], order); if (!page && !(gfp_mask & __GFP_THISNODE)) { From 79973aa4f4d1060e2e5a201175babb03312642f4 Mon Sep 17 00:00:00 2001 From: Usama Arif Date: Sun, 5 Jul 2026 06:12:31 -0700 Subject: [PATCH 130/562] userfaultfd: wait on source PMD during UFFDIO_MOVE move_pages_huge_pmd() snapshots src_pmdval under src_ptl, drops the lock, and, for migration entries, waits with pmd_migration_entry_wait(). Passing &src_pmdval is wrong. pmd_migration_entry_wait() must lock and re-read the real page-table PMD; on split-PMD-lock kernels, a stack address also resolves to the wrong lock. softleaf_entry_wait_on_locked() then waits without a folio reference, which is safe only while serialized against migration-entry removal by the real PT lock. Pass src_pmd, matching __handle_mm_fault() and hmm_vma_walk_pmd(). Link: https://lore.kernel.org/20260705131231.1499198-1-usama.arif@linux.dev Fixes: adef440691ba ("userfaultfd: UFFDIO_MOVE uABI") Reported-by: sashiko-bot Link: https://sashiko.dev/#/patchset/20260703173903.3789516-1-usama.arif%40linux.dev?part=8 Signed-off-by: Usama Arif Reviewed-by: Rik van Riel Cc: Andrea Arcangeli Cc: Baolin Wang Cc: Barry Song Cc: David Hildenbrand Cc: Dev Jain Cc: Johannes Weiner Cc: Lance Yang Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Nico Pache Cc: Ryan Roberts Cc: Shakeel Butt Cc: Zi Yan Cc: Signed-off-by: Andrew Morton --- mm/huge_memory.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/huge_memory.c b/mm/huge_memory.c index b5d1e9d4463d..032702a4637b 100644 --- a/mm/huge_memory.c +++ b/mm/huge_memory.c @@ -2774,7 +2774,7 @@ int move_pages_huge_pmd(struct mm_struct *mm, pmd_t *dst_pmd, pmd_t *src_pmd, pm if (!pmd_trans_huge(src_pmdval)) { spin_unlock(src_ptl); if (pmd_is_migration_entry(src_pmdval)) { - pmd_migration_entry_wait(mm, &src_pmdval); + pmd_migration_entry_wait(mm, src_pmd); return -EAGAIN; } return -ENOENT; From 9efed4fcd8713ac82a137db3cc2816c394895aea Mon Sep 17 00:00:00 2001 From: Joshua Hahn Date: Wed, 24 Jun 2026 11:36:59 -0700 Subject: [PATCH 131/562] mm/memcontrol: remove unused for_each_mem_cgroup macro and cleanup Commit 7e1c0d6f58207 ("memcg: switch lruvec stats to rstat") removed the last caller of for_each_mem_cgroup back in 2021, and there have not been any new callers since. Remove the macro. A comment in mem_cgroup_css_online has also been out of date since 2021, when 2bfd36374edd9 ("mm: vmscan: consolidate shrinker_maps handling code") open-coded the for_each_mem_cgroup iterator. Update the comment. Finally, 99430ab8b804c ("mm: introduce BPF kfuncs to access memcg statistics and events") added a second declaration for memcg_events to include/linux/memcontrol.h, duplicating the one in mm/memcontrol-v1.h. Let's clean that up too. No functional changes intended. Link: https://lore.kernel.org/20260624183700.1152742-1-joshua.hahnjy@gmail.com Signed-off-by: Joshua Hahn Acked-by: Shakeel Butt Reviewed-by: SeongJae Park Acked-by: Johannes Weiner Cc: Michal Hocko Cc: Muchun Song Cc: Roman Gushchin Signed-off-by: Andrew Morton --- mm/memcontrol-v1.h | 6 ------ mm/memcontrol.c | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/mm/memcontrol-v1.h b/mm/memcontrol-v1.h index f92f81108d5e..d3ed5b93290f 100644 --- a/mm/memcontrol-v1.h +++ b/mm/memcontrol-v1.h @@ -17,14 +17,8 @@ iter != NULL; \ iter = mem_cgroup_iter(root, iter, NULL)) -#define for_each_mem_cgroup(iter) \ - for (iter = mem_cgroup_iter(NULL, NULL, NULL); \ - iter != NULL; \ - iter = mem_cgroup_iter(NULL, iter, NULL)) - void drain_all_stock(struct mem_cgroup *root_memcg); -unsigned long memcg_events(struct mem_cgroup *memcg, int event); int memory_stat_show(struct seq_file *m, void *v); struct mem_cgroup *mem_cgroup_private_id_get_online(struct mem_cgroup *memcg, diff --git a/mm/memcontrol.c b/mm/memcontrol.c index 6dc4888a90f3..5e06109f1f66 100644 --- a/mm/memcontrol.c +++ b/mm/memcontrol.c @@ -4217,7 +4217,7 @@ static int mem_cgroup_css_online(struct cgroup_subsys_state *css) /* * A memcg must be visible for expand_shrinker_info() * by the time the maps are allocated. So, we allocate maps - * here, when for_each_mem_cgroup() can't skip it. + * here, when mem_cgroup_iter() can't skip it. */ if (alloc_shrinker_info(memcg)) goto offline_kmem; From 787c3324146c4edbc4a4db7095021d7bf84a81c4 Mon Sep 17 00:00:00 2001 From: Zenghui Yu Date: Wed, 24 Jun 2026 23:06:42 +0800 Subject: [PATCH 132/562] tools/mm: add thp_swap_allocator_test binary to .gitignore Tell git to ignore the generated binary for thp_swap_allocator_test.c. Link: https://lore.kernel.org/20260624150642.19749-1-zenghui.yu@linux.dev Signed-off-by: Zenghui Yu Reviewed-by: SeongJae Park Signed-off-by: Andrew Morton --- tools/mm/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/mm/.gitignore b/tools/mm/.gitignore index 922879f93fc8..1446a659e540 100644 --- a/tools/mm/.gitignore +++ b/tools/mm/.gitignore @@ -2,3 +2,4 @@ slabinfo page-types page_owner_sort +thp_swap_allocator_test From bc1826b411a3ec68f766d5a7e9781e8d1c498641 Mon Sep 17 00:00:00 2001 From: Dev Jain Date: Tue, 23 Jun 2026 12:57:21 +0000 Subject: [PATCH 133/562] mm/swap: rename subpage->page in folio_dup_swap/folio_put_swap Patch series "mm: drop "sub" prefix from various places". Patch 1 converts subpage->page : folios have pages, not subpages. Patch 2 drops "sub" from a function and a variable because the context is clear enough. This patch (of 2): Folios have pages, not subpages. Rename 'subpage' parameters to 'page'. Link: https://lore.kernel.org/20260623125723.2503832-1-dev.jain@arm.com Link: https://lore.kernel.org/20260623125723.2503832-2-dev.jain@arm.com Signed-off-by: Dev Jain Acked-by: David Hildenbrand (Arm) Reviewed-by: Lance Yang Acked-by: Pedro Falcato Reviewed-by: Lorenzo Stoakes Reviewed-by: Nhat Pham Reviewed-by: Kairui Song Reviewed-by: Barry Song Cc: Anshuman Khandual Cc: Baoquan He Cc: Chris Li Cc: Jann Horn Cc: Kemeng Shi Cc: Liam R. Howlett Cc: Ryan Roberts Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/swap.h | 4 ++-- mm/swapfile.c | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/mm/swap.h b/mm/swap.h index 77d2d14eda42..44ab8e1e595b 100644 --- a/mm/swap.h +++ b/mm/swap.h @@ -230,8 +230,8 @@ extern int swap_retry_table_alloc(swp_entry_t entry, gfp_t gfp); * folio_put_swap(): does the opposite thing of folio_dup_swap(). */ int folio_alloc_swap(struct folio *folio); -int folio_dup_swap(struct folio *folio, struct page *subpage); -void folio_put_swap(struct folio *folio, struct page *subpage); +int folio_dup_swap(struct folio *folio, struct page *page); +void folio_put_swap(struct folio *folio, struct page *page); /* For internal use */ extern void __swap_cluster_free_entries(struct swap_info_struct *si, diff --git a/mm/swapfile.c b/mm/swapfile.c index 78b49b0658ad..a602e5820513 100644 --- a/mm/swapfile.c +++ b/mm/swapfile.c @@ -1781,7 +1781,7 @@ int folio_alloc_swap(struct folio *folio) /** * folio_dup_swap() - Increase swap count of swap entries of a folio. * @folio: folio with swap entries bounded. - * @subpage: if not NULL, only increase the swap count of this subpage. + * @page: if not NULL, only increase the swap count of this page. * * Typically called when the folio is unmapped and have its swap entry to * take its place: Swap entries allocated to a folio has count == 0 and pinned @@ -1795,7 +1795,7 @@ int folio_alloc_swap(struct folio *folio) * swap_put_entries_direct on its swap entry before this helper returns, or * the swap count may underflow. */ -int folio_dup_swap(struct folio *folio, struct page *subpage) +int folio_dup_swap(struct folio *folio, struct page *page) { swp_entry_t entry = folio->swap; unsigned long nr_pages = folio_nr_pages(folio); @@ -1803,8 +1803,8 @@ int folio_dup_swap(struct folio *folio, struct page *subpage) VM_WARN_ON_FOLIO(!folio_test_locked(folio), folio); VM_WARN_ON_FOLIO(!folio_test_swapcache(folio), folio); - if (subpage) { - entry.val += folio_page_idx(folio, subpage); + if (page) { + entry.val += folio_page_idx(folio, page); nr_pages = 1; } @@ -1815,13 +1815,13 @@ int folio_dup_swap(struct folio *folio, struct page *subpage) /** * folio_put_swap() - Decrease swap count of swap entries of a folio. * @folio: folio with swap entries bounded, must be in swap cache and locked. - * @subpage: if not NULL, only decrease the swap count of this subpage. + * @page: if not NULL, only decrease the swap count of this page. * * This won't free the swap slots even if swap count drops to zero, they are * still pinned by the swap cache. User may call folio_free_swap to free them. * Context: Caller must ensure the folio is locked and in the swap cache. */ -void folio_put_swap(struct folio *folio, struct page *subpage) +void folio_put_swap(struct folio *folio, struct page *page) { swp_entry_t entry = folio->swap; unsigned long nr_pages = folio_nr_pages(folio); @@ -1830,8 +1830,8 @@ void folio_put_swap(struct folio *folio, struct page *subpage) VM_WARN_ON_FOLIO(!folio_test_locked(folio), folio); VM_WARN_ON_FOLIO(!folio_test_swapcache(folio), folio); - if (subpage) { - entry.val += folio_page_idx(folio, subpage); + if (page) { + entry.val += folio_page_idx(folio, page); nr_pages = 1; } From 1340c9077df82b9eeaa81d8465cf15e5be1b3b6d Mon Sep 17 00:00:00 2001 From: Dev Jain Date: Tue, 23 Jun 2026 12:57:22 +0000 Subject: [PATCH 134/562] mm/mprotect: drop 'sub' from batching context Shorten the name of page_anon_exclusive_sub_batch by dropping the "sub-batch" context - the function itself doesn't need this context. Similarly, drop "sub" from sub_batch_idx, it is unnecessary and the usage is clear enough. Link: https://lore.kernel.org/20260623125723.2503832-3-dev.jain@arm.com Signed-off-by: Dev Jain Reviewed-by: Lance Yang Reviewed-by: Pedro Falcato Reviewed-by: Lorenzo Stoakes Acked-by: David Hildenbrand (Arm) Reviewed-by: Barry Song Cc: Anshuman Khandual Cc: Baoquan He Cc: Chris Li Cc: Jann Horn Cc: Kairui Song Cc: Kemeng Shi Cc: Liam R. Howlett Cc: Nhat Pham Cc: Ryan Roberts Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/mprotect.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mm/mprotect.c b/mm/mprotect.c index 9cbf932b028c..c0f5ab74bee2 100644 --- a/mm/mprotect.c +++ b/mm/mprotect.c @@ -143,7 +143,7 @@ static __always_inline void prot_commit_flush_ptes(struct vm_area_struct *vma, * !PageAnonExclusive() pages, starting from start_idx. Caller must enforce * that the ptes point to consecutive pages of the same anon large folio. */ -static __always_inline int page_anon_exclusive_sub_batch(int start_idx, int max_len, +static __always_inline int page_anon_exclusive_batch(int start_idx, int max_len, struct page *first_page, bool expected_anon_exclusive) { int idx; @@ -174,16 +174,16 @@ static __always_inline void commit_anon_folio_batch(struct vm_area_struct *vma, pte_t oldpte, pte_t ptent, int nr_ptes, struct mmu_gather *tlb) { bool expected_anon_exclusive; - int sub_batch_idx = 0; + int batch_idx = 0; int len; while (nr_ptes) { - expected_anon_exclusive = PageAnonExclusive(first_page + sub_batch_idx); - len = page_anon_exclusive_sub_batch(sub_batch_idx, nr_ptes, + expected_anon_exclusive = PageAnonExclusive(first_page + batch_idx); + len = page_anon_exclusive_batch(batch_idx, nr_ptes, first_page, expected_anon_exclusive); prot_commit_flush_ptes(vma, addr, ptep, oldpte, ptent, len, - sub_batch_idx, expected_anon_exclusive, tlb); - sub_batch_idx += len; + batch_idx, expected_anon_exclusive, tlb); + batch_idx += len; nr_ptes -= len; } } From 0bdfbe3940178abafad1998d5e4c300a995cd8d1 Mon Sep 17 00:00:00 2001 From: Igor Putko Date: Tue, 23 Jun 2026 14:47:42 +0300 Subject: [PATCH 135/562] mm/kasan: remove redundant initialization for kasan_flag_write_only Patch series "mm: remove redundant static variable initializations". This series removes explicit initializations of static bool variables to false within the mm/ subsystem. In C, static variables without explicit initialization are implicitly placed in the .bss section and initialized to zero/false by default. Removing these explicit initializations follows the Linux kernel coding style and avoids cluttering the data section. This patch (of 2): The static variable 'kasan_flag_write_only' is implicitly initialized to false. Remove the explicit initialization to follow the Linux kernel coding style. Link: https://lore.kernel.org/20260623114743.4565-1-igorpetindev@gmail.com Link: https://lore.kernel.org/20260623114743.4565-2-igorpetindev@gmail.com Signed-off-by: Igor Putko Reviewed-by: SeongJae Park Reviewed-by: Lance Yang Cc: Alexander Potapenko Cc: Andrey Konovalov Cc: Andrey Ryabinin Cc: Dmitry Vyukov Cc: Miaohe Lin Cc: Naoya Horiguchi Cc: Vincenzo Frascino Signed-off-by: Andrew Morton --- mm/kasan/hw_tags.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/kasan/hw_tags.c b/mm/kasan/hw_tags.c index cbef5e450954..a848eb2f9910 100644 --- a/mm/kasan/hw_tags.c +++ b/mm/kasan/hw_tags.c @@ -61,7 +61,7 @@ DEFINE_STATIC_KEY_FALSE(kasan_flag_vmalloc); EXPORT_SYMBOL_GPL(kasan_flag_vmalloc); /* Whether to check write accesses only. */ -static bool kasan_flag_write_only = false; +static bool kasan_flag_write_only; #define PAGE_ALLOC_SAMPLE_DEFAULT 1 #define PAGE_ALLOC_SAMPLE_ORDER_DEFAULT 3 From 068e51da06e6a6b0d1e21aa308a4a2c4f1165093 Mon Sep 17 00:00:00 2001 From: Igor Putko Date: Tue, 23 Jun 2026 14:47:43 +0300 Subject: [PATCH 136/562] mm/memory-failure: remove redundant initialization for hw_memory_failure The static variable 'hw_memory_failure' is implicitly initialized to false. Remove the explicit initialization to follow the Linux kernel coding style. Link: https://lore.kernel.org/20260623114743.4565-3-igorpetindev@gmail.com Signed-off-by: Igor Putko Reviewed-by: SeongJae Park Reviewed-by: Lance Yang Acked-by: Miaohe Lin Cc: Alexander Potapenko Cc: Andrey Konovalov Cc: Andrey Ryabinin Cc: Dmitry Vyukov Cc: Naoya Horiguchi Cc: Vincenzo Frascino Signed-off-by: Andrew Morton --- mm/memory-failure.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/memory-failure.c b/mm/memory-failure.c index 51508a55c405..4963ea9f6ec6 100644 --- a/mm/memory-failure.c +++ b/mm/memory-failure.c @@ -76,7 +76,7 @@ static int sysctl_enable_soft_offline __read_mostly = 1; atomic_long_t num_poisoned_pages __read_mostly = ATOMIC_LONG_INIT(0); -static bool hw_memory_failure __read_mostly = false; +static bool hw_memory_failure __read_mostly; static DEFINE_MUTEX(mf_mutex); From fcd4642bfd2d3c2b2df59cd8a168516298549cce Mon Sep 17 00:00:00 2001 From: Guopeng Zhang Date: Tue, 23 Jun 2026 16:26:14 +0800 Subject: [PATCH 137/562] mm: memcg: remove stray text from obj_stock_pcp comment A patch filename was accidentally inserted into the comment describing the nr_bytes field of struct obj_stock_pcp. Remove it. No functional change. Link: https://lore.kernel.org/20260623082614.81621-1-guopeng.zhang@linux.dev Signed-off-by: Guopeng Zhang Acked-by: Harry Yoo (Oracle) Signed-off-by: Andrew Morton --- mm/memcontrol.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/memcontrol.c b/mm/memcontrol.c index 5e06109f1f66..d20ffc827306 100644 --- a/mm/memcontrol.c +++ b/mm/memcontrol.c @@ -2039,7 +2039,7 @@ struct obj_stock_pcp { /* * On rare archs with 256KiB base page size (hexagon and powerpc 44x) * keep nr_bytes to unsigned int as uint16_t cannot represent the full -e patches/memcg-uint16_t-for-nr_bytes-in-obj_stock_pcp.patch * sub-page remainder. Such archs are not cacheline optimization target. + * sub-page remainder. Such archs are not cacheline optimization targets. */ unsigned int nr_bytes[NR_OBJ_STOCK]; #else From 91d86621eef54037b765c63ccdf41ff099bf57d8 Mon Sep 17 00:00:00 2001 From: JP Kobryn Date: Mon, 22 Jun 2026 11:51:27 -0700 Subject: [PATCH 138/562] mm/lruvec: trace LRU add drains and drain-all requests LRU add batches can be drained before they reach capacity. This can be a source of LRU lock contention, but it is not currently possible to attribute these drains to callers with existing tracepoints. Add mm_lru_add_drain to report the CPU and lru_add batch count when an lru_add batch is drained. This allows tracing to distinguish full drains from partial drains and attribute them to the calling stack. Add mm_lru_add_drain_all to capture callers of __lru_add_drain_all and whether they set the force flag for all CPUs. The tracepoint resembles the signature of the enclosing function, but is needed because of potential inlining. Note that DECLARE_TRACE() is used for these new trace hooks to avoid creating a new trace event ABI. Link: https://lore.kernel.org/20260622185127.24579-1-jp.kobryn@linux.dev Signed-off-by: JP Kobryn Reviewed-by: Barry Song Acked-by: Shakeel Butt Cc: Axel Rasmussen Cc: Baoquan He Cc: Chris Li Cc: Kairui Song Cc: Kemeng Shi Cc: "Masami Hiramatsu (Google)" Cc: Mathieu Desnoyers Cc: Matthew Wilcox (Oracle) Cc: Michal Hocko Cc: Nhat Pham Cc: Steven Rostedt Cc: Vlastimil Babka Cc: Wei Xu Cc: Yuanchu Xie Signed-off-by: Andrew Morton --- include/trace/events/pagemap.h | 8 ++++++++ mm/swap.c | 7 ++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/include/trace/events/pagemap.h b/include/trace/events/pagemap.h index 171524d3526d..36c3a90f0acc 100644 --- a/include/trace/events/pagemap.h +++ b/include/trace/events/pagemap.h @@ -77,6 +77,14 @@ TRACE_EVENT(mm_lru_activate, TP_printk("folio=%p pfn=0x%lx", __entry->folio, __entry->pfn) ); +DECLARE_TRACE(mm_lru_add_drain, + TP_PROTO(int cpu, unsigned int nr_folios), + TP_ARGS(cpu, nr_folios)); + +DECLARE_TRACE(mm_lru_add_drain_all, + TP_PROTO(bool force_all_cpus), + TP_ARGS(force_all_cpus)); + #endif /* _TRACE_PAGEMAP_H */ /* This part must be outside protection */ diff --git a/mm/swap.c b/mm/swap.c index 588f50d8f1a8..460e56370b3c 100644 --- a/mm/swap.c +++ b/mm/swap.c @@ -694,9 +694,12 @@ void lru_add_drain_cpu(int cpu) { struct cpu_fbatches *fbatches = &per_cpu(cpu_fbatches, cpu); struct folio_batch *fbatch = &fbatches->lru_add; + unsigned int nr_folios = folio_batch_count(fbatch); - if (folio_batch_count(fbatch)) + if (nr_folios) { folio_batch_move_lru(fbatch, lru_add); + trace_mm_lru_add_drain_tp(cpu, nr_folios); + } fbatch = &fbatches->lru_move_tail; /* Disabling interrupts below acts as a compiler barrier. */ @@ -869,6 +872,8 @@ static inline void __lru_add_drain_all(bool force_all_cpus) if (WARN_ON(!mm_percpu_wq)) return; + trace_mm_lru_add_drain_all_tp(force_all_cpus); + /* * Guarantee folio_batch counter stores visible by this CPU * are visible to other CPUs before loading the current drain From bfd952807fa3693c7d704d877c2cf5805b261045 Mon Sep 17 00:00:00 2001 From: Chi Zhiling Date: Sat, 20 Jun 2026 14:24:45 +0800 Subject: [PATCH 139/562] mm/filemap: reduce unnecessary xarray lookups when read cached pages Patch series "mm/filemap: reduce unnecessary xarray lookups". This series optimizes xarray lookups in filemap by avoiding redundant iterations after obtaining the last needed folio. The boundary check is moved to before advancing the xarray iterator, eliminating unnecessary lookups and branches in the fast path. This reduces the overhead of filemap_get_read_batch() from 2.91% to 2.53% in 4k read tests. This patch (of 2): When reading small amounts of data from the page cache, only a single folio is typically returned from filemap_read_get_batch(). In this case, calling xas_advance() or xas_next() after adding the folio to the batch is unnecessary and only introduces extra branches. The same issue exists for large reads, where one additional xarray walk is always performed before termination. Quit the loop once we get the last folio in the range, so the final redundant xarray advancement can be avoided. The xas_next() does not update xa_index when xas->xa_node is set to XAS_RESTART, so the put and retry path would not update xa_index, hence the warning should therefore never trigger. During the 4k reads test, the overhead of this function dropped from 2.91% to 2.53%. Link: https://lore.kernel.org/20260620062446.351475-2-chizhiling@163.com Signed-off-by: Chi Zhiling Suggested-by: Matthew Wilcox (Oracle) Reviewed-by: Jan Kara Cc: Chi Zhiling Cc: Matthew Wilcox (Oracle) Signed-off-by: Andrew Morton --- mm/filemap.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mm/filemap.c b/mm/filemap.c index 58eb9d240643..dfc22df1031a 100644 --- a/mm/filemap.c +++ b/mm/filemap.c @@ -2467,11 +2467,14 @@ static void filemap_get_read_batch(struct address_space *mapping, XA_STATE(xas, &mapping->i_pages, index); struct folio *folio; + if (index > max) + return; + rcu_read_lock(); for (folio = xas_load(&xas); folio; folio = xas_next(&xas)) { if (xas_retry(&xas, folio)) continue; - if (xas.xa_index > max || xa_is_value(folio)) + if (xa_is_value(folio)) break; if (xa_is_sibling(folio)) break; @@ -2488,6 +2491,8 @@ static void filemap_get_read_batch(struct address_space *mapping, if (folio_test_readahead(folio)) break; xas_advance(&xas, folio_next_index(folio) - 1); + if (xas.xa_index >= max) + break; continue; put_folio: folio_put(folio); From 94d0a5ba69e4e3b88197b8ad4ccb24374385c120 Mon Sep 17 00:00:00 2001 From: Chi Zhiling Date: Sat, 20 Jun 2026 14:24:46 +0800 Subject: [PATCH 140/562] mm/filemap: reduce unnecessary xarray lookups in filemap_get_folios_contig() Apply the same optimization used in filemap_get_read_batch() by moving the boundary check from the loop condition to before xas_next(), avoiding an unnecessary xarray lookup and reducing branches in the fast path. Link: https://lore.kernel.org/20260620062446.351475-3-chizhiling@163.com Signed-off-by: Chi Zhiling Reviewed-by: Jan Kara Cc: Matthew Wilcox (Oracle) Signed-off-by: Andrew Morton --- mm/filemap.c | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/mm/filemap.c b/mm/filemap.c index dfc22df1031a..6e40f36c2bff 100644 --- a/mm/filemap.c +++ b/mm/filemap.c @@ -2270,10 +2270,11 @@ unsigned filemap_get_folios_contig(struct address_space *mapping, unsigned long nr; struct folio *folio; - rcu_read_lock(); + if (*start > end) + return 0; - for (folio = xas_load(&xas); folio && xas.xa_index <= end; - folio = xas_next(&xas)) { + rcu_read_lock(); + for (folio = xas_load(&xas); folio; folio = xas_next(&xas)) { if (xas_retry(&xas, folio)) continue; /* @@ -2281,11 +2282,11 @@ unsigned filemap_get_folios_contig(struct address_space *mapping, * No current caller is looking for DAX entries. */ if (xa_is_value(folio)) - goto update_start; + break; /* If we landed in the middle of a THP, continue at its end. */ if (xa_is_sibling(folio)) - goto update_start; + break; if (!folio_try_get(folio)) goto retry; @@ -2293,29 +2294,27 @@ unsigned filemap_get_folios_contig(struct address_space *mapping, if (unlikely(folio != xas_reload(&xas))) goto put_folio; - if (!folio_batch_add(fbatch, folio)) { - *start = folio_next_index(folio); - goto out; - } + if (!folio_batch_add(fbatch, folio)) + break; + xas_advance(&xas, folio_next_index(folio) - 1); + if (xas.xa_index >= end) + break; continue; + put_folio: folio_put(folio); - retry: xas_reset(&xas); } + rcu_read_unlock(); -update_start: nr = folio_batch_count(fbatch); - if (nr) { folio = fbatch->folios[nr - 1]; *start = folio_next_index(folio); } -out: - rcu_read_unlock(); - return folio_batch_count(fbatch); + return nr; } EXPORT_SYMBOL(filemap_get_folios_contig); From f5ddd9166be15b6517f6263b8f26256dc24d6471 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Fri, 19 Jun 2026 15:18:30 +0200 Subject: [PATCH 141/562] mm: replace __ASSEMBLY__ with __ASSEMBLER__ in memory management header files While the GCC and Clang compilers already define __ASSEMBLER__ automatically when compiling assembly code, __ASSEMBLY__ is a macro that only gets defined by the Makefiles in the kernel. This can be very confusing when switching between userspace and kernelspace coding, or when dealing with uapi headers that rather should use __ASSEMBLER__ instead. So let's standardize now on the __ASSEMBLER__ macro that is provided by the compilers. This is a completely mechanical patch (done with a simple "sed -i" statement). Link: https://lore.kernel.org/20260619131830.229804-1-thuth@redhat.com Signed-off-by: Thomas Huth Cc: Arnd Bergmann Cc: Axel Rasmussen Cc: Barry Song Cc: David Hildenbrand Cc: Kairui Song Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Shakeel Butt Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Wei Xu Cc: Yuanchu Xie Signed-off-by: Andrew Morton --- include/asm-generic/fixmap.h | 4 ++-- include/asm-generic/getorder.h | 4 ++-- include/asm-generic/memory_model.h | 4 ++-- include/asm-generic/mmu.h | 2 +- include/asm-generic/pgtable-nop4d.h | 4 ++-- include/asm-generic/pgtable-nopmd.h | 4 ++-- include/asm-generic/pgtable-nopud.h | 4 ++-- include/linux/mmzone.h | 4 ++-- include/linux/pfn.h | 2 +- include/linux/pgtable.h | 4 ++-- 10 files changed, 18 insertions(+), 18 deletions(-) diff --git a/include/asm-generic/fixmap.h b/include/asm-generic/fixmap.h index 29cab7947980..3ff832ebcea5 100644 --- a/include/asm-generic/fixmap.h +++ b/include/asm-generic/fixmap.h @@ -21,7 +21,7 @@ #define __fix_to_virt(x) (FIXADDR_TOP - ((x) << PAGE_SHIFT)) #define __virt_to_fix(x) ((FIXADDR_TOP - ((x)&PAGE_MASK)) >> PAGE_SHIFT) -#ifndef __ASSEMBLY__ +#ifndef __ASSEMBLER__ /* * 'index to address' translation. If anyone tries to use the idx * directly without translation, we catch the bug with a NULL-deference @@ -97,5 +97,5 @@ static inline unsigned long virt_to_fix(const unsigned long vaddr) #define set_fixmap_io(idx, phys) \ __set_fixmap(idx, phys, FIXMAP_PAGE_IO) -#endif /* __ASSEMBLY__ */ +#endif /* __ASSEMBLER__ */ #endif /* __ASM_GENERIC_FIXMAP_H */ diff --git a/include/asm-generic/getorder.h b/include/asm-generic/getorder.h index f2979e3a96b6..875ccae19683 100644 --- a/include/asm-generic/getorder.h +++ b/include/asm-generic/getorder.h @@ -2,7 +2,7 @@ #ifndef __ASM_GENERIC_GETORDER_H #define __ASM_GENERIC_GETORDER_H -#ifndef __ASSEMBLY__ +#ifndef __ASSEMBLER__ #include #include @@ -47,6 +47,6 @@ static __always_inline __attribute_const__ int get_order(unsigned long size) #endif } -#endif /* __ASSEMBLY__ */ +#endif /* __ASSEMBLER__ */ #endif /* __ASM_GENERIC_GETORDER_H */ diff --git a/include/asm-generic/memory_model.h b/include/asm-generic/memory_model.h index efa6610acbc7..fd74de50b054 100644 --- a/include/asm-generic/memory_model.h +++ b/include/asm-generic/memory_model.h @@ -4,7 +4,7 @@ #include -#ifndef __ASSEMBLY__ +#ifndef __ASSEMBLER__ /* * supports 3 memory models. @@ -86,6 +86,6 @@ static inline int pfn_valid(unsigned long pfn) #endif /* CONFIG_DEBUG_VIRTUAL */ #define phys_to_page(phys) pfn_to_page(PHYS_PFN(phys)) -#endif /* __ASSEMBLY__ */ +#endif /* __ASSEMBLER__ */ #endif diff --git a/include/asm-generic/mmu.h b/include/asm-generic/mmu.h index 061838037542..5f78971e3ac2 100644 --- a/include/asm-generic/mmu.h +++ b/include/asm-generic/mmu.h @@ -6,7 +6,7 @@ * This is the mmu.h header for nommu implementations. * Architectures with an MMU need something more complex. */ -#ifndef __ASSEMBLY__ +#ifndef __ASSEMBLER__ typedef struct { unsigned long end_brk; diff --git a/include/asm-generic/pgtable-nop4d.h b/include/asm-generic/pgtable-nop4d.h index 03b7dae47dd4..89c21f84cffb 100644 --- a/include/asm-generic/pgtable-nop4d.h +++ b/include/asm-generic/pgtable-nop4d.h @@ -2,7 +2,7 @@ #ifndef _PGTABLE_NOP4D_H #define _PGTABLE_NOP4D_H -#ifndef __ASSEMBLY__ +#ifndef __ASSEMBLER__ #define __PAGETABLE_P4D_FOLDED 1 @@ -54,5 +54,5 @@ static inline p4d_t *p4d_offset(pgd_t *pgd, unsigned long address) #undef p4d_addr_end #define p4d_addr_end(addr, end) (end) -#endif /* __ASSEMBLY__ */ +#endif /* __ASSEMBLER__ */ #endif /* _PGTABLE_NOP4D_H */ diff --git a/include/asm-generic/pgtable-nopmd.h b/include/asm-generic/pgtable-nopmd.h index 8ffd64e7a24c..36b6490ed180 100644 --- a/include/asm-generic/pgtable-nopmd.h +++ b/include/asm-generic/pgtable-nopmd.h @@ -2,7 +2,7 @@ #ifndef _PGTABLE_NOPMD_H #define _PGTABLE_NOPMD_H -#ifndef __ASSEMBLY__ +#ifndef __ASSEMBLER__ #include @@ -68,6 +68,6 @@ static inline void pmd_free(struct mm_struct *mm, pmd_t *pmd) #undef pmd_addr_end #define pmd_addr_end(addr, end) (end) -#endif /* __ASSEMBLY__ */ +#endif /* __ASSEMBLER__ */ #endif /* _PGTABLE_NOPMD_H */ diff --git a/include/asm-generic/pgtable-nopud.h b/include/asm-generic/pgtable-nopud.h index eb70c6d7ceff..356cbfbaab24 100644 --- a/include/asm-generic/pgtable-nopud.h +++ b/include/asm-generic/pgtable-nopud.h @@ -2,7 +2,7 @@ #ifndef _PGTABLE_NOPUD_H #define _PGTABLE_NOPUD_H -#ifndef __ASSEMBLY__ +#ifndef __ASSEMBLER__ #include @@ -62,5 +62,5 @@ static inline pud_t *pud_offset(p4d_t *p4d, unsigned long address) #undef pud_addr_end #define pud_addr_end(addr, end) (end) -#endif /* __ASSEMBLY__ */ +#endif /* __ASSEMBLER__ */ #endif /* _PGTABLE_NOPUD_H */ diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h index ca2712187147..e26b3d38fa25 100644 --- a/include/linux/mmzone.h +++ b/include/linux/mmzone.h @@ -2,7 +2,7 @@ #ifndef _LINUX_MMZONE_H #define _LINUX_MMZONE_H -#ifndef __ASSEMBLY__ +#ifndef __ASSEMBLER__ #ifndef __GENERATING_BOUNDS_H #include @@ -2389,5 +2389,5 @@ static inline unsigned long next_present_section_nr(unsigned long section_nr) #endif #endif /* !__GENERATING_BOUNDS.H */ -#endif /* !__ASSEMBLY__ */ +#endif /* !__ASSEMBLER__ */ #endif /* _LINUX_MMZONE_H */ diff --git a/include/linux/pfn.h b/include/linux/pfn.h index b90ca0b6c331..cfedf0f61bb3 100644 --- a/include/linux/pfn.h +++ b/include/linux/pfn.h @@ -2,7 +2,7 @@ #ifndef _LINUX_PFN_H_ #define _LINUX_PFN_H_ -#ifndef __ASSEMBLY__ +#ifndef __ASSEMBLER__ #include #endif diff --git a/include/linux/pgtable.h b/include/linux/pgtable.h index 2981e386da7b..dc804296d78f 100644 --- a/include/linux/pgtable.h +++ b/include/linux/pgtable.h @@ -8,7 +8,7 @@ #define PMD_ORDER (PMD_SHIFT - PAGE_SHIFT) #define PUD_ORDER (PUD_SHIFT - PAGE_SHIFT) -#ifndef __ASSEMBLY__ +#ifndef __ASSEMBLER__ #ifdef CONFIG_MMU #include @@ -2320,7 +2320,7 @@ static inline const char *pgtable_level_to_str(enum pgtable_level level) } } -#endif /* !__ASSEMBLY__ */ +#endif /* !__ASSEMBLER__ */ #if !defined(MAX_POSSIBLE_PHYSMEM_BITS) && !defined(CONFIG_64BIT) #ifdef CONFIG_PHYS_ADDR_T_64BIT From 0882168d05311cae5c929a579ed33a62b3b0dcfd Mon Sep 17 00:00:00 2001 From: Kaitao Cheng Date: Thu, 18 Jun 2026 21:04:11 +0800 Subject: [PATCH 142/562] mm/vmalloc: honor GFP constraints in pcpu_get_vm_areas() Patch series "mm/percpu: Fix possible NOFS/NOIO reclaim recursion", v4. Commit 9a5b183941b5 ("mm, percpu: do not consider sleepable allocations atomic") allowed GFP_NOFS and GFP_NOIO percpu allocations to use pcpu_alloc_mutex and the chunk creation slow path. This restored the allocation capability that was lost when those constrained allocations were treated as atomic, but it also makes the percpu slow path visible to callers from constrained reclaim contexts. There are two related problems. First, the create and populate slow paths do not fully preserve the caller's allocation constraints. pcpu_alloc_noprof() derives pcpu_gfp from the caller supplied GFP mask and passes it down to the percpu backing page allocator. However, chunk creation calls pcpu_get_vm_areas(), and chunk population can allocate temporary metadata or vmalloc page tables while mapping backing pages. Those internal allocations can still use GFP_KERNEL, so a caller using GFP_NOFS or GFP_NOIO can enter unconstrained FS or IO reclaim while holding pcpu_alloc_mutex. One possible case is blk-cgroup after commit 5d726c4dbeed ("blk-cgroup: fix possible deadlock while configuring policy"). blkg_conf_prep() now serializes against blkcg_deactivate_policy() with q->blkcg_mutex, and blkg_alloc() uses GFP_NOIO because queue freeze and IO reclaim dependencies can otherwise deadlock. If the percpu slow path loses that GFP_NOIO context, direct reclaim or writeback can issue IO to a frozen queue while q->blkcg_mutex is held. Second, allowing sleepable GFP_NOFS/GFP_NOIO allocations to take pcpu_alloc_mutex means that unconstrained backing allocations made under the mutex can create an FS/IO reclaim dependency against a constrained caller which already holds an FS or IO lock and then waits for pcpu_alloc_mutex. This series fixes those issues in three steps: - pass the caller supplied GFP mask into pcpu_get_vm_areas() and use it for vmalloc metadata and KASAN shadow allocations; - pass the GFP mask through the chunk population path, including the temporary pages array and vmalloc page table allocation scope; - restrict percpu backing allocations performed while holding pcpu_alloc_mutex to GFP_NOIO, so they cannot recurse into IO or FS reclaim. This keeps sleepable GFP_NOFS/GFP_NOIO percpu allocations working, while avoiding the reclaim recursion risks introduced by making those allocations eligible for the mutex-protected slow path. This patch (of 4): pcpu_alloc_noprof() derives pcpu_gfp from the caller supplied GFP mask and passes it down to the backing percpu allocator. However, when the percpu vmalloc allocator has to create a new chunk, pcpu_create_chunk() calls pcpu_get_vm_areas() to allocate the corresponding vmalloc areas. pcpu_get_vm_areas() currently performs its internal allocations with GFP_KERNEL, including vmap area metadata, vm_struct metadata and KASAN vmalloc shadow population. This means that a caller which deliberately uses GFP_NOFS or GFP_NOIO can still enter FS or IO reclaim while creating the vmalloc areas for a new percpu chunk. One possible case is blk-cgroup after commit 5d726c4dbeed ("blk-cgroup: fix possible deadlock while configuring policy"). blkg_conf_prep() now serializes against blkcg_deactivate_policy() with q->blkcg_mutex, and blkg_alloc() was changed to GFP_NOIO for that reason: CPU0: blkg_conf_prep() mutex_lock(q->blkcg_mutex) blkg_alloc(..., GFP_NOIO) alloc_percpu_gfp(..., GFP_NOIO) pcpu_alloc_noprof(..., GFP_NOIO) pcpu_create_chunk(GFP_NOIO) pcpu_get_vm_areas() -> if percpu chunks are exhausted, chunk create may do internal GFP_KERNEL allocations -> direct reclaim / writeback can issue IO to this queue -> IO waits because the queue is frozen CPU1: blkcg_deactivate_policy() blk_mq_freeze_queue(q) mutex_lock(q->blkcg_mutex) -> waits for CPU0 ... unfreeze only happens after q->blkcg_mutex is acquired/released So the concern is that the caller deliberately uses GFP_NOIO because it may hold a lock which can be acquired after queue freeze, but the percpu slow path can temporarily lose that allocation context. Pass the caller supplied GFP mask from pcpu_create_chunk() to pcpu_get_vm_areas(), and use it for the internal vmalloc metadata and KASAN shadow allocations. Link: https://lore.kernel.org/20260618130414.96383-1-kaitao.cheng@linux.dev Link: https://lore.kernel.org/20260618130414.96383-2-kaitao.cheng@linux.dev Fixes: 9a5b183941b5 ("mm, percpu: do not consider sleepable allocations atomic") Signed-off-by: Kaitao Cheng Reviewed-by: Uladzislau Rezki (Sony) Reviewed-by: Shivam Kalra Acked-by: Dennis Zhou Acked-by: Michal Hocko Cc: Christoph Lameter Cc: Pedro Falcato Cc: Tejun Heo Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- include/linux/vmalloc.h | 4 ++-- mm/percpu-vm.c | 2 +- mm/vmalloc.c | 23 ++++++++++++----------- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/include/linux/vmalloc.h b/include/linux/vmalloc.h index d87dc7f77f4e..e4d8d0a9f30f 100644 --- a/include/linux/vmalloc.h +++ b/include/linux/vmalloc.h @@ -310,14 +310,14 @@ static inline void set_vm_flush_reset_perms(void *addr) {} #if defined(CONFIG_MMU) && defined(CONFIG_SMP) struct vm_struct **pcpu_get_vm_areas(const unsigned long *offsets, const size_t *sizes, int nr_vms, - size_t align); + size_t align, gfp_t gfp); void pcpu_free_vm_areas(struct vm_struct **vms, int nr_vms); # else static inline struct vm_struct ** pcpu_get_vm_areas(const unsigned long *offsets, const size_t *sizes, int nr_vms, - size_t align) + size_t align, gfp_t gfp) { return NULL; } diff --git a/mm/percpu-vm.c b/mm/percpu-vm.c index 4f5937090590..69b00741dc68 100644 --- a/mm/percpu-vm.c +++ b/mm/percpu-vm.c @@ -340,7 +340,7 @@ static struct pcpu_chunk *pcpu_create_chunk(gfp_t gfp) return NULL; vms = pcpu_get_vm_areas(pcpu_group_offsets, pcpu_group_sizes, - pcpu_nr_groups, pcpu_atom_size); + pcpu_nr_groups, pcpu_atom_size, gfp); if (!vms) { pcpu_free_chunk(chunk); return NULL; diff --git a/mm/vmalloc.c b/mm/vmalloc.c index 1afca3568b9b..08f468135e4d 100644 --- a/mm/vmalloc.c +++ b/mm/vmalloc.c @@ -4946,16 +4946,17 @@ pvm_determine_end_from_reverse(struct vmap_area **va, unsigned long align) * @sizes: array containing size of each area * @nr_vms: the number of areas to allocate * @align: alignment, all entries in @offsets and @sizes must be aligned to this + * @gfp: allocation flags passed to the underlying memory allocator * * Returns: kmalloc'd vm_struct pointer array pointing to allocated * vm_structs on success, %NULL on failure * * Percpu allocator wants to use congruent vm areas so that it can * maintain the offsets among percpu areas. This function allocates - * congruent vmalloc areas for it with GFP_KERNEL. These areas tend to - * be scattered pretty far, distance between two areas easily going up - * to gigabytes. To avoid interacting with regular vmallocs, these - * areas are allocated from top. + * congruent vmalloc areas for it. These areas tend to be scattered + * pretty far, distance between two areas easily going up to gigabytes. + * To avoid interacting with regular vmallocs, these areas are allocated + * from top. * * Despite its complicated look, this allocator is rather simple. It * does everything top-down and scans free blocks from the end looking @@ -4966,7 +4967,7 @@ pvm_determine_end_from_reverse(struct vmap_area **va, unsigned long align) */ struct vm_struct **pcpu_get_vm_areas(const unsigned long *offsets, const size_t *sizes, int nr_vms, - size_t align) + size_t align, gfp_t gfp) { const unsigned long vmalloc_start = ALIGN(VMALLOC_START, align); const unsigned long vmalloc_end = VMALLOC_END & ~(align - 1); @@ -5004,14 +5005,14 @@ struct vm_struct **pcpu_get_vm_areas(const unsigned long *offsets, return NULL; } - vms = kzalloc_objs(vms[0], nr_vms); - vas = kzalloc_objs(vas[0], nr_vms); + vms = kzalloc_objs(vms[0], nr_vms, gfp); + vas = kzalloc_objs(vas[0], nr_vms, gfp); if (!vas || !vms) goto err_free2; for (area = 0; area < nr_vms; area++) { - vas[area] = kmem_cache_zalloc(vmap_area_cachep, GFP_KERNEL); - vms[area] = kzalloc_obj(struct vm_struct); + vas[area] = kmem_cache_zalloc(vmap_area_cachep, gfp); + vms[area] = kzalloc_obj(struct vm_struct, gfp); if (!vas[area] || !vms[area]) goto err_free; } @@ -5101,7 +5102,7 @@ struct vm_struct **pcpu_get_vm_areas(const unsigned long *offsets, /* populate the kasan shadow space */ for (area = 0; area < nr_vms; area++) { - if (kasan_populate_vmalloc(vas[area]->va_start, sizes[area], GFP_KERNEL)) + if (kasan_populate_vmalloc(vas[area]->va_start, sizes[area], gfp)) goto err_free_shadow; } @@ -5158,7 +5159,7 @@ struct vm_struct **pcpu_get_vm_areas(const unsigned long *offsets, continue; vas[area] = kmem_cache_zalloc( - vmap_area_cachep, GFP_KERNEL); + vmap_area_cachep, gfp); if (!vas[area]) goto err_free; } From 0f3c7176600ce165deaf5b1d4c77deaf8a8fac90 Mon Sep 17 00:00:00 2001 From: Kaitao Cheng Date: Thu, 18 Jun 2026 21:04:12 +0800 Subject: [PATCH 143/562] mm/percpu: honor GFP constraints when populating chunks pcpu_alloc_noprof() derives pcpu_gfp from the caller supplied GFP mask and passes it down to pcpu_populate_chunk(). pcpu_alloc_pages() already uses that mask for backing page allocation. However, the populate slow path still has internal allocations and page table allocations which can lose the caller's allocation context. The temporary pages array is allocated by pcpu_get_pages() with GFP_KERNEL, and pcpu_map_pages() maps the backing pages through vmap_pages_range_noflush() using GFP_KERNEL. The latter can allocate vmalloc page tables implicitly, so a caller which deliberately uses GFP_NOFS or GFP_NOIO can still enter FS or IO reclaim while populating a percpu chunk. This has the same concern as chunk creation: callers such as blk-cgroup may use GFP_NOIO because they hold locks which can be involved in queue freeze or IO reclaim dependencies. If an allocation reaches the percpu slow path and needs to populate previously unbacked pages, the internal GFP_KERNEL allocations can defeat that context. One possible case is blk-cgroup after commit 5d726c4dbeed ("blk-cgroup: fix possible deadlock while configuring policy"). blkg_conf_prep() now serializes against blkcg_deactivate_policy() with q->blkcg_mutex, and blkg_alloc() was changed to GFP_NOIO for that reason: CPU0: blkg_conf_prep() mutex_lock(q->blkcg_mutex) blkg_alloc(..., GFP_NOIO) alloc_percpu_gfp(..., GFP_NOIO) pcpu_alloc_noprof(..., GFP_NOIO) pcpu_populate_chunk(GFP_NOIO) pcpu_get_pages() pcpu_map_pages() -> if the selected percpu chunk has unpopulated pages, chunk population may do internal GFP_KERNEL allocations -> direct reclaim / writeback can issue IO to this queue -> IO waits because the queue is frozen CPU1: blkcg_deactivate_policy() blk_mq_freeze_queue(q) mutex_lock(q->blkcg_mutex) -> waits for CPU0 ... unfreeze only happens after q->blkcg_mutex is acquired/released So the concern is that the caller deliberately uses GFP_NOIO because it may hold a lock which can be acquired after queue freeze, but the percpu slow path can temporarily lose that allocation context. Pass pcpu_gfp through pcpu_get_pages(), pcpu_map_pages() and __pcpu_map_pages(). Apply the corresponding memalloc scope around vmap_pages_range_noflush(), because vmalloc page table allocation does not pass the GFP mask down explicitly. Keep the first chunk setup path using GFP_KERNEL, matching the previous early-init behavior. Link: https://lore.kernel.org/20260618130414.96383-3-kaitao.cheng@linux.dev Fixes: 9a5b183941b5 ("mm, percpu: do not consider sleepable allocations atomic") Signed-off-by: Kaitao Cheng Acked-by: Dennis Zhou Acked-by: Michal Hocko Cc: Christoph Lameter Cc: Pedro Falcato Cc: Shivam Kalra Cc: Tejun Heo Cc: Uladzislau Rezki (Sony) Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/percpu-vm.c | 38 ++++++++++++++++++++++++++------------ mm/percpu.c | 2 +- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/mm/percpu-vm.c b/mm/percpu-vm.c index 69b00741dc68..ccd03cc152d4 100644 --- a/mm/percpu-vm.c +++ b/mm/percpu-vm.c @@ -21,6 +21,7 @@ static struct page *pcpu_chunk_page(struct pcpu_chunk *chunk, /** * pcpu_get_pages - get temp pages array + * @gfp: allocation flags passed to the underlying allocator * * Returns pointer to array of pointers to struct page which can be indexed * with pcpu_page_idx(). Note that there is only one array and accesses @@ -29,7 +30,7 @@ static struct page *pcpu_chunk_page(struct pcpu_chunk *chunk, * RETURNS: * Pointer to temp pages array on success. */ -static struct page **pcpu_get_pages(void) +static struct page **pcpu_get_pages(gfp_t gfp) { static struct page **pages; size_t pages_size = pcpu_nr_units * pcpu_unit_pages * sizeof(pages[0]); @@ -37,7 +38,7 @@ static struct page **pcpu_get_pages(void) lockdep_assert_held(&pcpu_alloc_mutex); if (!pages) - pages = pcpu_mem_zalloc(pages_size, GFP_KERNEL); + pages = pcpu_mem_zalloc(pages_size, gfp); return pages; } @@ -191,10 +192,22 @@ static void pcpu_post_unmap_tlb_flush(struct pcpu_chunk *chunk, } static int __pcpu_map_pages(unsigned long addr, struct page **pages, - int nr_pages) + int nr_pages, gfp_t gfp) { - return vmap_pages_range_noflush(addr, addr + (nr_pages << PAGE_SHIFT), - PAGE_KERNEL, pages, PAGE_SHIFT, GFP_KERNEL); + unsigned int flags; + int ret; + + /* + * The vmalloc page table allocation path does not pass @gfp down + * explicitly. Apply the corresponding memalloc scope so implicit + * page table allocations preserve NOFS/NOIO constraints. + */ + flags = memalloc_apply_gfp_scope(gfp); + ret = vmap_pages_range_noflush(addr, addr + (nr_pages << PAGE_SHIFT), + PAGE_KERNEL, pages, PAGE_SHIFT, gfp); + memalloc_restore_scope(flags); + + return ret; } /** @@ -203,6 +216,7 @@ static int __pcpu_map_pages(unsigned long addr, struct page **pages, * @pages: pages array containing pages to be mapped * @page_start: page index of the first page to map * @page_end: page index of the last page to map + 1 + * @gfp: allocation flags passed to the underlying allocator * * For each cpu, map pages [@page_start,@page_end) into @chunk. The * caller is responsible for calling pcpu_post_map_flush() after all @@ -211,8 +225,8 @@ static int __pcpu_map_pages(unsigned long addr, struct page **pages, * This function is responsible for setting up whatever is necessary for * reverse lookup (addr -> chunk). */ -static int pcpu_map_pages(struct pcpu_chunk *chunk, - struct page **pages, int page_start, int page_end) +static int pcpu_map_pages(struct pcpu_chunk *chunk, struct page **pages, + int page_start, int page_end, gfp_t gfp) { unsigned int cpu, tcpu; int i, err; @@ -220,7 +234,7 @@ static int pcpu_map_pages(struct pcpu_chunk *chunk, for_each_possible_cpu(cpu) { err = __pcpu_map_pages(pcpu_chunk_addr(chunk, cpu, page_start), &pages[pcpu_page_idx(cpu, page_start)], - page_end - page_start); + page_end - page_start, gfp); if (err < 0) goto err; @@ -271,21 +285,21 @@ static void pcpu_post_map_flush(struct pcpu_chunk *chunk, * @chunk. * * CONTEXT: - * pcpu_alloc_mutex, does GFP_KERNEL allocation. + * pcpu_alloc_mutex, does @gfp allocation. */ static int pcpu_populate_chunk(struct pcpu_chunk *chunk, int page_start, int page_end, gfp_t gfp) { struct page **pages; - pages = pcpu_get_pages(); + pages = pcpu_get_pages(gfp); if (!pages) return -ENOMEM; if (pcpu_alloc_pages(chunk, pages, page_start, page_end, gfp)) return -ENOMEM; - if (pcpu_map_pages(chunk, pages, page_start, page_end)) { + if (pcpu_map_pages(chunk, pages, page_start, page_end, gfp)) { pcpu_free_pages(chunk, pages, page_start, page_end); return -ENOMEM; } @@ -319,7 +333,7 @@ static void pcpu_depopulate_chunk(struct pcpu_chunk *chunk, * successful population attempt so the temp pages array must * be available now. */ - pages = pcpu_get_pages(); + pages = pcpu_get_pages(GFP_KERNEL); BUG_ON(!pages); /* unmap and free */ diff --git a/mm/percpu.c b/mm/percpu.c index b0676b8054ed..4d89965cba16 100644 --- a/mm/percpu.c +++ b/mm/percpu.c @@ -3256,7 +3256,7 @@ int __init pcpu_page_first_chunk(size_t reserved_size, pcpu_fc_cpu_to_node_fn_t /* pte already populated, the following shouldn't fail */ rc = __pcpu_map_pages(unit_addr, &pages[unit * unit_pages], - unit_pages); + unit_pages, GFP_KERNEL); if (rc < 0) panic("failed to map percpu area, err=%d\n", rc); From 8d84110f80cd94f4df3732aca0744362190f1e91 Mon Sep 17 00:00:00 2001 From: Kaitao Cheng Date: Thu, 18 Jun 2026 21:04:13 +0800 Subject: [PATCH 144/562] mm/percpu: make cached pages lookup explicit pcpu_depopulate_chunk() only needs the temporary pages array that was already allocated by an earlier successful population attempt. Passing GFP_KERNEL to pcpu_get_pages() in this path is misleading because the depopulation path is not expected to allocate the array. Teach pcpu_get_pages() to treat a zero gfp mask as a cached-only lookup and add pcpu_get_pages_cached() for that use case. This keeps allocation on the populate path tied to the caller supplied GFP mask while making the depopulate path's dependency on the cached array explicit. Link: https://lore.kernel.org/20260618130414.96383-4-kaitao.cheng@linux.dev Signed-off-by: Kaitao Cheng Suggested-by: Dennis Zhou Acked-by: Michal Hocko Cc: Christoph Lameter Cc: Pedro Falcato Cc: Shivam Kalra Cc: Tejun Heo Cc: Uladzislau Rezki (Sony) Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/percpu-vm.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/mm/percpu-vm.c b/mm/percpu-vm.c index ccd03cc152d4..7ed216192fc0 100644 --- a/mm/percpu-vm.c +++ b/mm/percpu-vm.c @@ -21,7 +21,8 @@ static struct page *pcpu_chunk_page(struct pcpu_chunk *chunk, /** * pcpu_get_pages - get temp pages array - * @gfp: allocation flags passed to the underlying allocator + * @gfp: allocation flags passed to the underlying allocator, 0 to only + * return the cached array * * Returns pointer to array of pointers to struct page which can be indexed * with pcpu_page_idx(). Note that there is only one array and accesses @@ -37,11 +38,16 @@ static struct page **pcpu_get_pages(gfp_t gfp) lockdep_assert_held(&pcpu_alloc_mutex); - if (!pages) + if (!pages && gfp) pages = pcpu_mem_zalloc(pages_size, gfp); return pages; } +static struct page **pcpu_get_pages_cached(void) +{ + return pcpu_get_pages(0); +} + /** * pcpu_free_pages - free pages which were allocated for @chunk * @chunk: chunk pages were allocated for @@ -333,7 +339,7 @@ static void pcpu_depopulate_chunk(struct pcpu_chunk *chunk, * successful population attempt so the temp pages array must * be available now. */ - pages = pcpu_get_pages(GFP_KERNEL); + pages = pcpu_get_pages_cached(); BUG_ON(!pages); /* unmap and free */ From d04d9780e9d13d74f23accc357f3a5e5124f05ac Mon Sep 17 00:00:00 2001 From: Kaitao Cheng Date: Thu, 18 Jun 2026 21:04:14 +0800 Subject: [PATCH 145/562] mm/percpu: avoid IO/FS reclaim in backing allocations Commit 9a5b183941b5 ("mm, percpu: do not consider sleepable allocations atomic") allows sleepable GFP_NOIO and GFP_NOFS percpu allocations to take pcpu_alloc_mutex. This avoids premature allocation failures, but it also makes the mutex visible to callers from constrained IO/FS contexts. Thread A calls pcpu_alloc_noprof() with GFP_KERNEL and takes pcpu_alloc_mutex. Since the internal allocation is not constrained by NOFS, it may enter FS reclaim while still holding pcpu_alloc_mutex, creating a dependency like: pcpu_alloc_mutex -> fs_reclaim -> FS lock At the same time, Thread B may already hold an FS lock and then call pcpu_alloc_noprof() with GFP_NOFS. It will try to acquire pcpu_alloc_mutex and block, creating the reverse dependency: FS lock -> pcpu_alloc_mutex This can still form a potential deadlock cycle. Avoid the dependency by restricting percpu backing allocations to GFP_NOIO. The public allocation still uses the caller's GFP context to decide whether it may block, but the internal memory allocations performed while pcpu_alloc_mutex is held cannot recurse into IO or FS reclaim. Link: https://lore.kernel.org/20260618130414.96383-5-kaitao.cheng@linux.dev Fixes: 9a5b183941b5 ("mm, percpu: do not consider sleepable allocations atomic") Signed-off-by: Kaitao Cheng Cc: Christoph Lameter Cc: Dennis Zhou Cc: Michal Hocko Cc: Pedro Falcato Cc: Shivam Kalra Cc: Tejun Heo Cc: Uladzislau Rezki (Sony) Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/percpu.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/mm/percpu.c b/mm/percpu.c index 4d89965cba16..a802d72c116f 100644 --- a/mm/percpu.c +++ b/mm/percpu.c @@ -1726,9 +1726,8 @@ static void pcpu_alloc_tag_free_hook(struct pcpu_chunk *chunk, int off, size_t s * @gfp: allocation flags * * Allocate percpu area of @size bytes aligned at @align. If @gfp doesn't - * contain %GFP_KERNEL, the allocation is atomic. If @gfp has __GFP_NOWARN - * then no warning will be triggered on invalid or failed allocation - * requests. + * allow blocking, the allocation is atomic. If @gfp has __GFP_NOWARN then no + * warning will be triggered on invalid or failed allocation requests. * * RETURNS: * Percpu pointer to the allocated area on success, NULL on failure. @@ -1749,8 +1748,17 @@ void __percpu *pcpu_alloc_noprof(size_t size, size_t align, bool reserved, size_t bits, bit_align; gfp = current_gfp_context(gfp); - /* whitelisted flags that can be passed to the backing allocators */ - pcpu_gfp = gfp & (GFP_KERNEL | __GFP_NORETRY | __GFP_NOWARN); + /* + * Allowlisted flags that can be passed to the backing allocators. + * Backing allocations under pcpu_alloc_mutex must not recurse into + * IO/FS reclaim. Otherwise a GFP_KERNEL caller holding the mutex can + * block on reclaim while a GFP_NOIO/NOFS caller holding an IO/FS lock + * waits for the same mutex. + * + * Do not pass __GFP_NOFAIL. A small percpu allocation may need many + * backing pages, making nofail reclaim too costly under NOIO/NOFS. + */ + pcpu_gfp = gfp & (GFP_NOIO | __GFP_NORETRY | __GFP_NOWARN); is_atomic = !gfpflags_allow_blocking(gfp); do_warn = !(gfp & __GFP_NOWARN); From 01d80d33299cd38ee171ea23dd111e7e3dd39a29 Mon Sep 17 00:00:00 2001 From: Kefeng Wang Date: Thu, 18 Jun 2026 19:35:23 +0800 Subject: [PATCH 146/562] mm: remove PageTransCompound() Remove the last user of PageTransCompound() in ksm and get rid of PageTransCompound(). Link: https://lore.kernel.org/20260618113523.3913307-1-wangkefeng.wang@huawei.com Signed-off-by: Kefeng Wang Acked-by: David Hildenbrand (Arm) Reviewed-by: Xu Xin Tested-by: Xu Xin Acked-by: Zi Yan Reviewed-by: SeongJae Park Cc: Chengming Zhou Signed-off-by: Andrew Morton --- include/linux/page-flags.h | 14 -------------- mm/ksm.c | 17 +++++++++-------- 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/include/linux/page-flags.h b/include/linux/page-flags.h index 7223f6f4e2b4..7a863572adce 100644 --- a/include/linux/page-flags.h +++ b/include/linux/page-flags.h @@ -879,20 +879,6 @@ FOLIO_FLAG_FALSE(partially_mapped) #define PG_head_mask ((1UL << PG_head)) -#ifdef CONFIG_TRANSPARENT_HUGEPAGE -/* - * PageTransCompound returns true for both transparent huge pages - * and hugetlbfs pages, so it should only be called when it's known - * that hugetlbfs pages aren't involved. - */ -static inline int PageTransCompound(const struct page *page) -{ - return PageCompound(page); -} -#else -TESTPAGEFLAG_FALSE(TransCompound, transcompound) -#endif - #if defined(CONFIG_MEMORY_FAILURE) && defined(CONFIG_TRANSPARENT_HUGEPAGE) /* * PageHasHWPoisoned indicates that at least one subpage is hwpoisoned in the diff --git a/mm/ksm.c b/mm/ksm.c index 7d5b76478f0b..41ab25aa2a82 100644 --- a/mm/ksm.c +++ b/mm/ksm.c @@ -2327,23 +2327,24 @@ static void cmp_and_merge_page(struct page *page, struct ksm_rmap_item *rmap_ite tree_rmap_item = unstable_tree_search_insert(rmap_item, page, &tree_page); if (tree_rmap_item) { + struct folio *tree_folio; bool split; kfolio = try_to_merge_two_pages(rmap_item, page, tree_rmap_item, tree_page); + tree_folio = page_folio(tree_page); /* - * If both pages we tried to merge belong to the same compound - * page, then we actually ended up increasing the reference - * count of the same compound page twice, and split_huge_page - * failed. + * If both pages we tried to merge belong to the same (large) + * folio, then we actually ended up increasing the reference + * count of the same folio twice, and split_huge_page failed. + * * Here we set a flag if that happened, and we use it later to - * try split_huge_page again. Since we call put_page right + * try split_huge_page again. Since we call folio_put() right * afterwards, the reference count will be correct and * split_huge_page should succeed. */ - split = PageTransCompound(page) - && compound_head(page) == compound_head(tree_page); - put_page(tree_page); + split = folio == tree_folio; + folio_put(tree_folio); if (kfolio) { /* * The pages were successfully merged: insert new From b0abfc3189d27d1343b0adf649509d3c6f198920 Mon Sep 17 00:00:00 2001 From: Ben Dooks Date: Thu, 18 Jun 2026 11:06:14 +0100 Subject: [PATCH 147/562] mm/page_alloc: don't build vm_numa_stat_key if CONFIG_NUMA=n vm_numa_stat_key is only exported if CONFIG_NUMA is set, so avoid the following warning by guarding it in an #ifdef on CONFIG_NUMA: mm/page_alloc.c:165:1: warning: symbol 'vm_numa_stat_key' was not declared. Should it be static? Link: https://lore.kernel.org/20260618100614.1321950-1-ben.dooks@codethink.co.uk Signed-off-by: Ben Dooks Acked-by: Johannes Weiner Reviewed-by: Zi Yan Reviewed-by: SeongJae Park Cc: Brendan Jackman Cc: Michal Hocko Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/page_alloc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mm/page_alloc.c b/mm/page_alloc.c index ee902a468c2f..dbe632f6300d 100644 --- a/mm/page_alloc.c +++ b/mm/page_alloc.c @@ -165,7 +165,9 @@ DEFINE_PER_CPU(int, numa_node); EXPORT_PER_CPU_SYMBOL(numa_node); #endif +#ifdef CONFIG_NUMA DEFINE_STATIC_KEY_TRUE(vm_numa_stat_key); +#endif #ifdef CONFIG_HAVE_MEMORYLESS_NODES /* From 56059b6a172a896535370393c9bb30e98f602697 Mon Sep 17 00:00:00 2001 From: Kefeng Wang Date: Thu, 18 Jun 2026 17:28:42 +0800 Subject: [PATCH 148/562] mm: mincore: use walk_page_range_vma() in do_mincore() Patch series "mm: convert to walk_page_range_vma() to eliminate find_vma()", v2. walk_page_range() performs a find_vma() lookup on each page table walk. For callers that already hold a valid VMA and operate on a known single-VMA range, this lookup is redundant. Replace walk_page_range() with walk_page_range_vma() where the caller guarantees single-VMA semantics. This patch (of 4): do_mincore() uses walk_page_range() to walk the page table. Fortunately, the caller always passes start/end that falls within a single VMA, so it's safe to use the walk_page_range_vma() in do_mincore() to eliminate an unnecessary find_vma() lookup. Unlike walk_page_range(), walk_page_range_vma() does not call walk_page_test(), which handles VM_PFNMAP by invoking ->pte_hole() to skip the page table walk. Without this check, PFNMAP PTEs would be treated as present by mincore_pte_range(), changing the returned residency status. Handle VM_PFNMAP explicitly in do_mincore() to preserve the original behavior. Link: https://lore.kernel.org/20260618092845.3905740-1-wangkefeng.wang@huawei.com Link: https://lore.kernel.org/20260618092845.3905740-2-wangkefeng.wang@huawei.com Signed-off-by: Kefeng Wang Acked-by: Zi Yan Acked-by: David Hildenbrand (Arm) Reviewed-by: Pedro Falcato Cc: Alistair Popple Cc: Byungchul Park Cc: Gregory Price Cc: Joshua Hahn Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Matthew Brost Cc: Rakie Kim Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Ying Huang Signed-off-by: Andrew Morton --- mm/mincore.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/mm/mincore.c b/mm/mincore.c index c8757c5085bf..ab0a8b100950 100644 --- a/mm/mincore.c +++ b/mm/mincore.c @@ -258,7 +258,21 @@ static long do_mincore(unsigned long addr, unsigned long pages, unsigned char *v memset(vec, 1, pages); return pages; } - err = walk_page_range(vma->vm_mm, addr, end, &mincore_walk_ops, vec); + + /* + * walk_page_range_vma() does not call walk_page_test(), which + * handles VM_PFNMAP VMA by invoking ->pte_hole() to skip the + * page table walk. Without this check, PFNMAP PTEs would be + * treated as present by mincore_pte_range(), changing the returned + * residency status from the historical "not resident" to "resident". + * Handle VM_PFNMAP explicitly to preserve the original behavior. + */ + if (vma->vm_flags & VM_PFNMAP) { + __mincore_unmapped_range(addr, end, vma, vec); + return (end - addr) >> PAGE_SHIFT; + } + + err = walk_page_range_vma(vma, addr, end, &mincore_walk_ops, vec); if (err < 0) return err; return (end - addr) >> PAGE_SHIFT; From 319037551a87d31b0fc23bbe432533125e38ff46 Mon Sep 17 00:00:00 2001 From: Andrew Morton Date: Wed, 24 Jun 2026 19:35:43 -0700 Subject: [PATCH 149/562] mm-mincore-use-walk_page_range_vma-in-do_mincore-fix simplify comment, per Pedro Link: https://lore.kernel.org/ajP9bQhmvR9OX0VE@pedro-suse Cc: Alistair Popple Cc: Byungchul Park Cc: David Hildenbrand (Arm) Cc: Gregory Price Cc: Joshua Hahn Cc: Kefeng Wang Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Matthew Brost Cc: Pedro Falcato Cc: Rakie Kim Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Ying Huang Cc: Zi Yan Signed-off-by: Andrew Morton --- mm/mincore.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/mm/mincore.c b/mm/mincore.c index ab0a8b100950..53b982803771 100644 --- a/mm/mincore.c +++ b/mm/mincore.c @@ -260,12 +260,7 @@ static long do_mincore(unsigned long addr, unsigned long pages, unsigned char *v } /* - * walk_page_range_vma() does not call walk_page_test(), which - * handles VM_PFNMAP VMA by invoking ->pte_hole() to skip the - * page table walk. Without this check, PFNMAP PTEs would be - * treated as present by mincore_pte_range(), changing the returned - * residency status from the historical "not resident" to "resident". - * Handle VM_PFNMAP explicitly to preserve the original behavior. + * mincore historically reports PFNMAP mappings as non-resident. */ if (vma->vm_flags & VM_PFNMAP) { __mincore_unmapped_range(addr, end, vma, vec); From e040f4e175a8482287482c37332f1ef17cb5b3b5 Mon Sep 17 00:00:00 2001 From: Kefeng Wang Date: Thu, 18 Jun 2026 17:28:43 +0800 Subject: [PATCH 150/562] mm: mprotect: use walk_page_range_vma() in mprotect_fixup() In mprotect_fixup(), the PROT_NONE PFN permission check uses walk_page_range() to walk the page table. Fortunately, the caller always passes start/end that falls within a single VMA, the do_mprotect_pkey() iterates per-VMA via for_each_vma_range(), and setup_arg_pages() passes the whole VMA. Note, walk_page_test() isn't called in walk_page_range_vma(), however, prot_none_test() in prot_none_walk_ops always return 0, so it's safe to replace walk_page_range() with walk_page_range_vma() to eliminate an unnecessary find_vma() lookup, also remove unneeded prot_none_test() too. Link: https://lore.kernel.org/20260618092845.3905740-3-wangkefeng.wang@huawei.com Signed-off-by: Kefeng Wang Reviewed-by: Zi Yan Reviewed-by: Pedro Falcato Cc: Alistair Popple Cc: Byungchul Park Cc: David Hildenbrand (Arm) Cc: Gregory Price Cc: Joshua Hahn Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Matthew Brost Cc: Rakie Kim Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Ying Huang Signed-off-by: Andrew Morton --- mm/mprotect.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/mm/mprotect.c b/mm/mprotect.c index c0f5ab74bee2..8665a23f38d3 100644 --- a/mm/mprotect.c +++ b/mm/mprotect.c @@ -708,16 +708,9 @@ static int prot_none_hugetlb_entry(pte_t *pte, unsigned long hmask, 0 : -EACCES; } -static int prot_none_test(unsigned long addr, unsigned long next, - struct mm_walk *walk) -{ - return 0; -} - static const struct mm_walk_ops prot_none_walk_ops = { .pte_entry = prot_none_pte_entry, .hugetlb_entry = prot_none_hugetlb_entry, - .test_walk = prot_none_test, .walk_lock = PGWALK_WRLOCK, }; @@ -753,7 +746,7 @@ mprotect_fixup(struct vma_iterator *vmi, struct mmu_gather *tlb, !vma_flags_test_any_mask(&new_vma_flags, VMA_ACCESS_FLAGS)) { pgprot_t new_pgprot = vm_get_page_prot(newflags); - error = walk_page_range(current->mm, start, end, + error = walk_page_range_vma(vma, start, end, &prot_none_walk_ops, &new_pgprot); if (error) return error; From 0bea61c1cc4a9ac2da96581f0ed500148cab08f4 Mon Sep 17 00:00:00 2001 From: Kefeng Wang Date: Thu, 18 Jun 2026 17:28:44 +0800 Subject: [PATCH 151/562] mm: mlock: use walk_page_range_vma() in mlock_vma_pages_range() The mlock_vma_pages_range() uses walk_page_range() to walk the page table. Fortunately, the caller always passes start/end that falls within a single VMA, apply_vma_lock_flags() iterates per-VMA, and apply_mlockall_flags() passes the whole VMA. Since there is no .test_walk in mlock_walk_ops and VM_PFNMAP was filtered by vma_supports_mlock(), it's safe to replace walk_page_range() with walk_page_range_vma() to eliminate an unnecessary find_vma() lookup. Link: https://lore.kernel.org/20260618092845.3905740-4-wangkefeng.wang@huawei.com Signed-off-by: Kefeng Wang Reviewed-by: Zi Yan Acked-by: David Hildenbrand (Arm) Reviewed-by: Pedro Falcato Cc: Alistair Popple Cc: Byungchul Park Cc: Gregory Price Cc: Joshua Hahn Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Matthew Brost Cc: Rakie Kim Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Ying Huang Signed-off-by: Andrew Morton --- mm/mlock.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/mlock.c b/mm/mlock.c index 8c227fefa2df..97e49038d8d3 100644 --- a/mm/mlock.c +++ b/mm/mlock.c @@ -446,7 +446,7 @@ static void mlock_vma_pages_range(struct vm_area_struct *vma, vma_flags_reset_once(vma, new_vma_flags); lru_add_drain(); - walk_page_range(vma->vm_mm, start, end, &mlock_walk_ops, NULL); + walk_page_range_vma(vma, start, end, &mlock_walk_ops, NULL); lru_add_drain(); if (vma_flags_test(new_vma_flags, VMA_IO_BIT)) { From cef54e3760b403e6a1dd1e96ae20114ea92b55a4 Mon Sep 17 00:00:00 2001 From: Kefeng Wang Date: Thu, 18 Jun 2026 17:28:45 +0800 Subject: [PATCH 152/562] mm: migrate_device: use walk_page_range_vma() in migrate_vma_collect() migrate_vma_collect() uses walk_page_range() to walk the page table. Fortunately, migrate_vma_setup() already validates that the entire range falls within a single VMA. Since there is no .test_walk in migrate_vma_walk_ops and VM_PFNMAP was filtered by migrate_vma_setup(), it's safe to replace walk_page_range() with walk_page_range_vma() to eliminate an unnecessary find_vma() lookup. Link: https://lore.kernel.org/20260618092845.3905740-5-wangkefeng.wang@huawei.com Signed-off-by: Kefeng Wang Reviewed-by: Zi Yan Acked-by: David Hildenbrand (Arm) Acked-by: Pedro Falcato Cc: Alistair Popple Cc: Byungchul Park Cc: Gregory Price Cc: Joshua Hahn Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Matthew Brost Cc: Rakie Kim Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Ying Huang Signed-off-by: Andrew Morton --- mm/migrate_device.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/migrate_device.c b/mm/migrate_device.c index 554754eb26ff..ae39173d6a0e 100644 --- a/mm/migrate_device.c +++ b/mm/migrate_device.c @@ -513,7 +513,7 @@ static void migrate_vma_collect(struct migrate_vma *migrate) migrate->pgmap_owner); mmu_notifier_invalidate_range_start(&range); - walk_page_range(migrate->vma->vm_mm, migrate->start, migrate->end, + walk_page_range_vma(migrate->vma, migrate->start, migrate->end, &migrate_vma_walk_ops, migrate); mmu_notifier_invalidate_range_end(&range); From a6a84aaf5b3e2b3777f86bfaf8ca1f15ccc0c393 Mon Sep 17 00:00:00 2001 From: Andrew Morton Date: Mon, 29 Jun 2026 17:07:51 -0700 Subject: [PATCH 153/562] csky: implement flush_cache_vmap() in C To avoid getting an unused-var warning from unsigned long start = something; ... flush_cache_vmap(start, ...); Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202606291606.9h8aGniQ-lkp@intel.com/ Reviewed-by: Guo Ren Reviewed-by: Barry Song Cc: Andrew Donnellan Cc: Anshuman Khandual Cc: Catalin Marinas Cc: David Hildenbrand Cc: Dev Jain Cc: Leo Yan Cc: Mike Rapoport Cc: Ryan Roberts Cc: Uladzislau Rezki Cc: Wen Jiang Cc: Wen Jiang Cc: Will Deacon Cc: Xueyuan Chen Signed-off-by: Andrew Morton --- arch/csky/abiv1/inc/abi/cacheflush.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/arch/csky/abiv1/inc/abi/cacheflush.h b/arch/csky/abiv1/inc/abi/cacheflush.h index d011a81575d2..21cc7965c53d 100644 --- a/arch/csky/abiv1/inc/abi/cacheflush.h +++ b/arch/csky/abiv1/inc/abi/cacheflush.h @@ -42,7 +42,12 @@ static inline void flush_anon_page(struct vm_area_struct *vma, * Use cache_wbinv_all() here and need to be improved in future. */ extern void flush_cache_range(struct vm_area_struct *vma, unsigned long start, unsigned long end); -#define flush_cache_vmap(start, end) cache_wbinv_all() + +static inline void flush_cache_vmap(unsigned long start, unsigned long end) +{ + cache_wbinv_all(); +} + #define flush_cache_vmap_early(start, end) do { } while (0) #define flush_cache_vunmap(start, end) cache_wbinv_all() From 3c8130995d94eedf742bfc37d1584fda29905612 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Thu, 18 Jun 2026 01:39:19 +0900 Subject: [PATCH 154/562] arch_numa: remove redundant nodemask clears in numa_init() numa_init() clears numa_nodes_parsed, node_possible_map and node_online_map, then calls numa_memblks_init(), which clears the same nodemasks. Nothing uses them in between. These clears have been redundant since commit 767507654c22 ("arch_numa: switch over to numa_memblks") made numa_init() use numa_memblks_init(). No functional change. Link: https://lore.kernel.org/20260617163919.2544899-1-ekffu200098@gmail.com Signed-off-by: Sang-Heon Jeon Reviewed-by: Mike Rapoport (Microsoft) Cc: Danilo Krummrich Cc: Greg Kroah-Hartman Cc: "Rafael J. Wysocki" Signed-off-by: Andrew Morton --- drivers/base/arch_numa.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/base/arch_numa.c b/drivers/base/arch_numa.c index c99f2ab105e5..442ea239bba7 100644 --- a/drivers/base/arch_numa.c +++ b/drivers/base/arch_numa.c @@ -231,10 +231,6 @@ static int __init numa_init(int (*init_func)(void)) { int ret; - nodes_clear(numa_nodes_parsed); - nodes_clear(node_possible_map); - nodes_clear(node_online_map); - ret = numa_memblks_init(init_func, /* memblock_force_top_down */ false); if (ret < 0) goto out_free_distance; From 4c03043920f9354addfdc6c634f42eca34ef6b1b Mon Sep 17 00:00:00 2001 From: Markus Elfring Date: Tue, 16 Jun 2026 18:14:52 +0200 Subject: [PATCH 155/562] mm/vmalloc: use more common error handling code in pcpu_get_vm_areas() Use an existing label once more so that a bit of exception handling can be better reused at the end of this function implementation. This issue was detected by using the Coccinelle software. Link: https://lore.kernel.org/453375c4-c3ca-4e6f-8880-0e6ff3c74ee3@web.de Signed-off-by: Markus Elfring Reviewed-by: Uladzislau Rezki (Sony) Cc: Alexander Potapenko Cc: Daniel Axtens Cc: Dmitry Vyukov Signed-off-by: Andrew Morton --- mm/vmalloc.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mm/vmalloc.c b/mm/vmalloc.c index 08f468135e4d..12f4a39fdd0b 100644 --- a/mm/vmalloc.c +++ b/mm/vmalloc.c @@ -5199,9 +5199,7 @@ struct vm_struct **pcpu_get_vm_areas(const unsigned long *offsets, kfree(vms[area]); } spin_unlock(&free_vmap_area_lock); - kfree(vas); - kfree(vms); - return NULL; + goto err_free2; } /** From c04dc6d0636dfd4bc6f4dbe9e612aef7299d9aa7 Mon Sep 17 00:00:00 2001 From: Ethan Nelson-Moore Date: Mon, 15 Jun 2026 17:01:31 -0700 Subject: [PATCH 156/562] mm: hugetlb: correct CONFIG_CGROUP_HUGETLB macro name in comment A comment in incorrectly refers to CONFIG_MEM_RES_CTLR_HUGETLB, which has never existed in the kernel, instead of CONFIG_CGROUP_HUGETLB. Correct it. Discovered while searching for CONFIG_* symbols referenced in code but not defined in any Kconfig file. Link: https://lore.kernel.org/20260616000135.62815-1-enelsonmoore@gmail.com Signed-off-by: Ethan Nelson-Moore Reviewed-by: David Hildenbrand (Arm) Reviewed-by: Lorenzo Stoakes Cc: Anthony Yznaga Cc: Muchun Song Cc: Oscar Salvador Cc: Pedro Falcato Signed-off-by: Andrew Morton --- include/linux/hugetlb_cgroup.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/hugetlb_cgroup.h b/include/linux/hugetlb_cgroup.h index e5d64b8b59c2..16d72c8c71f6 100644 --- a/include/linux/hugetlb_cgroup.h +++ b/include/linux/hugetlb_cgroup.h @@ -267,5 +267,5 @@ static inline void hugetlb_cgroup_migrate(struct folio *old_folio, { } -#endif /* CONFIG_MEM_RES_CTLR_HUGETLB */ +#endif /* CONFIG_CGROUP_HUGETLB */ #endif From 0fc18625b9572b25443a62b6cd3c3569503c344e Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 15 Jun 2026 10:49:06 -0700 Subject: [PATCH 157/562] mm/kmemleak: avoid soft lockup when scanning task stacks Patch series "mm/kmemleak: avoid soft lockup when scanning task", v3. kmemleak_scan() scans every task stack under one rcu_read_lock() with no reschedule point, which can trip the soft lockup watchdog on hosts with very many threads. That prints the following message, depending on the workload+host configuration: watchdog: BUG: soft lockup - CPU#35 stuck for 22s! [kmemleak:537] scan_block kmemleak_scan kmemleak_scan_thread kthread Patch 1 walks the tasks with find_ge_pid() so the scan reschedules between tasks Patches 2-3 let the scan loops stop early once a scan is interrupted. This patch (of 3): kmemleak_scan() walks every thread and scans its kernel stack under a single rcu_read_lock() with no reschedule point. On a host with very many threads -- amplified by KASAN/lockdep in debug builds -- this loop can hog a CPU long enough to trip the soft lockup watchdog: watchdog: BUG: soft lockup - CPU#35 stuck for 22s! [kmemleak:537] scan_block kmemleak_scan kmemleak_scan_thread kthread A cond_resched() cannot be added directly: the loop runs inside an RCU read-side critical section. Walk the tasks one PID at a time with find_ge_pid(), taking the RCU read lock only to look up and pin each task. The stack is then scanned with no lock held, so cond_resched() runs between tasks and the scan stops early on scan_should_stop(). This follows the next_tgid()/task_seq_get_next() iteration pattern and keeps each RCU critical section short. Link: https://lore.kernel.org/20260615-kmemleak-stack-resched-v3-0-acecd7d7fd92@debian.org Link: https://lore.kernel.org/20260615-kmemleak-stack-resched-v3-1-acecd7d7fd92@debian.org Fixes: c4b28963fd79 ("mm/kmemleak: rely on rcu for task stack scanning") Signed-off-by: Breno Leitao Reviewed-by: Catalin Marinas Reviewed-by: Davidlohr Bueso Reviewed-by: Lance Yang Reviewed-by: Oleg Nesterov Cc: Qian Cai Cc: SeongJae Park Cc: Signed-off-by: Andrew Morton --- mm/kmemleak.c | 51 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/mm/kmemleak.c b/mm/kmemleak.c index e196f53f9b46..16b72cead07d 100644 --- a/mm/kmemleak.c +++ b/mm/kmemleak.c @@ -1696,6 +1696,42 @@ static void kmemleak_cond_resched(struct kmemleak_object *object) put_object(object); } +/* + * Scan all task kernel stacks, rescheduling between tasks. Each task is looked + * up and pinned within its own RCU read-side section, so no lock is held across + * the scan and the walk cannot trip the soft lockup watchdog. + */ +static void kmemleak_scan_task_stacks(void) +{ + struct pid *pid; + int nr = 1; + + do { + struct task_struct *p = NULL; + + rcu_read_lock(); + pid = find_ge_pid(nr, &init_pid_ns); + if (pid) { + nr = pid_nr(pid) + 1; + p = pid_task(pid, PIDTYPE_PID); + if (p) + get_task_struct(p); + } + rcu_read_unlock(); + + if (p) { + void *stack = try_get_task_stack(p); + + if (stack) { + scan_block(stack, stack + THREAD_SIZE, NULL); + put_task_stack(p); + } + put_task_struct(p); + } + cond_resched(); + } while (pid && !scan_should_stop()); +} + /* * Print one leak inline. The hex dump is gated on OBJECT_ALLOCATED so it * does not touch user memory that was freed concurrently; the rest of the @@ -1885,19 +1921,8 @@ static void kmemleak_scan(void) /* * Scanning the task stacks (may introduce false negatives). */ - if (kmemleak_stack_scan) { - struct task_struct *p, *g; - - rcu_read_lock(); - for_each_process_thread(g, p) { - void *stack = try_get_task_stack(p); - if (stack) { - scan_block(stack, stack + THREAD_SIZE, NULL); - put_task_stack(p); - } - } - rcu_read_unlock(); - } + if (kmemleak_stack_scan) + kmemleak_scan_task_stacks(); /* * Scan the objects already referenced from the sections scanned From 95ed6d78ea3520d9698bf6619507a79c99ea557f Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 15 Jun 2026 10:49:07 -0700 Subject: [PATCH 158/562] mm/kmemleak: stop the task stack scan early when interrupted scan_block() already checks scan_should_stop() for every pointer and bails out of the current block, but the task stack walk cannot tell and keeps issuing a separate scan_should_stop() between every task. Return that status from scan_block() and use it as the task stack loop condition, so the walk stops as soon as a scan is interrupted. Link: https://lore.kernel.org/20260615-kmemleak-stack-resched-v3-2-acecd7d7fd92@debian.org Signed-off-by: Breno Leitao Suggested-by: Catalin Marinas Reviewed-by: Catalin Marinas Reviewed-by: Oleg Nesterov Cc: Davidlohr Bueso Cc: Lance Yang Cc: Qian Cai Cc: SeongJae Park Signed-off-by: Andrew Morton --- mm/kmemleak.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/mm/kmemleak.c b/mm/kmemleak.c index 16b72cead07d..bc79e293531b 100644 --- a/mm/kmemleak.c +++ b/mm/kmemleak.c @@ -1525,22 +1525,25 @@ static int scan_should_stop(void) /* * Scan a memory block (exclusive range) for valid pointers and add those - * found to the gray list. + * found to the gray list. Return non-zero if the scan was interrupted. */ -static void scan_block(void *_start, void *_end, - struct kmemleak_object *scanned) +static int scan_block(void *_start, void *_end, + struct kmemleak_object *scanned) { unsigned long *ptr; unsigned long *start = PTR_ALIGN(_start, BYTES_PER_POINTER); unsigned long *end = _end - (BYTES_PER_POINTER - 1); unsigned long flags; + int stop = 0; raw_spin_lock_irqsave(&kmemleak_lock, flags); for (ptr = start; ptr < end; ptr++) { unsigned long pointer; - if (scan_should_stop()) + if (scan_should_stop()) { + stop = 1; break; + } kasan_disable_current(); pointer = *(unsigned long *)kasan_reset_tag((void *)ptr); @@ -1550,6 +1553,8 @@ static void scan_block(void *_start, void *_end, pointer_update_refs(scanned, pointer, OBJECT_PERCPU); } raw_spin_unlock_irqrestore(&kmemleak_lock, flags); + + return stop; } /* @@ -1705,6 +1710,7 @@ static void kmemleak_scan_task_stacks(void) { struct pid *pid; int nr = 1; + int stop = 0; do { struct task_struct *p = NULL; @@ -1723,13 +1729,13 @@ static void kmemleak_scan_task_stacks(void) void *stack = try_get_task_stack(p); if (stack) { - scan_block(stack, stack + THREAD_SIZE, NULL); + stop = scan_block(stack, stack + THREAD_SIZE, NULL); put_task_stack(p); } put_task_struct(p); } cond_resched(); - } while (pid && !scan_should_stop()); + } while (pid && !stop); } /* From 6b218df63a526130babc228be8be6545e33c3a15 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 15 Jun 2026 10:49:08 -0700 Subject: [PATCH 159/562] mm/kmemleak: stop the per-cpu and struct page scans early too The per-cpu and struct page scan loops have no reschedule-stop check of their own: once a scan is interrupted they keep calling scan_block() for every remaining block, which scans nothing useful. Propagate scan_block()'s interrupted status through scan_large_block() and break both loops as soon as it is set. Link: https://lore.kernel.org/20260615-kmemleak-stack-resched-v3-3-acecd7d7fd92@debian.org Signed-off-by: Breno Leitao Suggested-by: Catalin Marinas Reviewed-by: Catalin Marinas Reviewed-by: Oleg Nesterov Cc: Davidlohr Bueso Cc: Lance Yang Cc: Qian Cai Cc: SeongJae Park Signed-off-by: Andrew Morton --- mm/kmemleak.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/mm/kmemleak.c b/mm/kmemleak.c index bc79e293531b..ac2a44a1c4a5 100644 --- a/mm/kmemleak.c +++ b/mm/kmemleak.c @@ -1559,18 +1559,22 @@ static int scan_block(void *_start, void *_end, /* * Scan a large memory block in MAX_SCAN_SIZE chunks to reduce the latency. + * Return non-zero if the scan was interrupted. */ #ifdef CONFIG_SMP -static void scan_large_block(void *start, void *end) +static int scan_large_block(void *start, void *end) { void *next; while (start < end) { next = min(start + MAX_SCAN_SIZE, end); - scan_block(start, next, NULL); + if (scan_block(start, next, NULL)) + return 1; start = next; cond_resched(); } + + return 0; } #endif @@ -1890,9 +1894,11 @@ static void kmemleak_scan(void) #ifdef CONFIG_SMP /* per-cpu sections scanning */ - for_each_possible_cpu(i) - scan_large_block(__per_cpu_start + per_cpu_offset(i), - __per_cpu_end + per_cpu_offset(i)); + for_each_possible_cpu(i) { + if (scan_large_block(__per_cpu_start + per_cpu_offset(i), + __per_cpu_end + per_cpu_offset(i))) + break; + } #endif /* @@ -1903,6 +1909,7 @@ static void kmemleak_scan(void) unsigned long start_pfn = zone->zone_start_pfn; unsigned long end_pfn = zone_end_pfn(zone); unsigned long pfn; + int stop = 0; for (pfn = start_pfn; pfn < end_pfn; pfn++) { struct page *page = pfn_to_online_page(pfn); @@ -1919,8 +1926,12 @@ static void kmemleak_scan(void) /* only scan if page is in use */ if (page_count(page) == 0) continue; - scan_block(page, page + 1, NULL); + stop = scan_block(page, page + 1, NULL); + if (stop) + break; } + if (stop) + break; } put_online_mems(); From bc573f9ec4757284ecb038f80f90035c34a588ac Mon Sep 17 00:00:00 2001 From: Ye Liu Date: Wed, 1 Jul 2026 14:10:44 +0800 Subject: [PATCH 160/562] mm/page_owner: extract skip_buddy_pages() helper to unify buddy page skipping Patch series "mm/page_owner: misc cleanups", v5. This series collects a few cleanups for mm/page_owner.c that have been accumulated while reading through the file. There is no functional change -- the goal is to make the code easier to read and maintain. Patch 1 consolidates three identical PageBuddy skip blocks into a single skip_buddy_pages() helper, eliminating the duplication and keeping the lockless-read comment in one place. Patch 2 replaces the -1 magic number used for "never migrated" with a proper MR_NEVER member in enum migrate_reason, adds the corresponding "never_migrated" string in the MIGRATE_REASON trace macro, and updates the GDB page_owner script to use MR_NEVER so that lx-dump-page-owner correctly detects unmigrated pages. Patch 3 follows up by converting the remaining 'int reason' parameters throughout the migration and hugetlb callchains to 'enum migrate_reason', making the type explicit and gaining compiler checking. The 'short last_migrate_reason' struct field in page_owner is intentionally left as 'short' since it is per-page metadata where size matters. Patch 4 hoists the CONFIG_MEMCG guard out of print_page_owner_memcg()'s body so that the real implementation and the empty stub are two clearly separate definitions, the common kernel idiom. Patch 5 adds a missing \n to the count_threshold debugfs attribute format string so that cat(1) output is properly terminated. Patch 6 moves free_ts_nsec from the allocation summary line to the free section in __dump_page_owner(), grouping it with free_pid and free_tgid where it logically belongs. This also makes the dump output consistent with print_page_owner(). Patch 7 drops the redundant page_owner_ prefix from file-scoped static symbols (stack_fops, threshold_fops, etc.). Since they cannot collide across translation units, the prefix carries no information. Patch 8 clamps the PFN advance in skip_buddy_pages() at the next MAX_ORDER_NR_PAGES boundary. The lockless buddy_order_unsafe() read can return a garbage order value if the page is concurrently allocated between the PageBuddy check and the private read, potentially causing the PFN to advance past the next bounadry whose pfn_valid() check would have caught an offline memory section. In read_page_owner(), which relies solely on boundary-aligned pfn_valid() to guard pfn_to_page(), this could lead to an unmapped mem_section access. Patch 9 avoids a TOCTOU VM_BUG_ON in print_page_owner_memcg() by reusing the page->memcg_data snapshot already taken via READ_ONCE at the top of the function, instead of calling PageMemcgKmem() which re-reads folio->memcg_data and page->compound_head locklessly with VM_BUG_ON assertions. If the page is concurrently freed and reallocated as a THP tail or slab page between the initial guards and this final call, those assertions can fire on CONFIG_DEBUG_VM=y builds. This patch (of 9) Three places in page_owner.c duplicate the same pattern: check if a page is PageBuddy, read its order via buddy_order_unsafe(), advance the pfn past the buddy block if the order is valid, and continue. Consolidate them into a single inline helper skip_buddy_pages(). The function returns true (skip) for any buddy page and advances @pfn past the block when the order is valid; returns false if the page is not a buddy page and should be processed normally. The old init_pages_in_zone() variant used "order > 0" as an extra guard before advancing pfn, but the continue was unconditional and (1UL << 0) - 1 == 0, so the behaviour is identical. The comment about zone->lock is preserved in the helper's kernel-doc. No functional change. Link: https://lore.kernel.org/20260701061101.344679-1-ye.liu@linux.dev Link: https://lore.kernel.org/20260701061101.344679-2-ye.liu@linux.dev Signed-off-by: Ye Liu Reviewed-by: Zi Yan Reviewed-by: Vlastimil Babka (SUSE) Cc: Brendan Jackman Cc: Johannes Weiner Cc: Michal Hocko Cc: Suren Baghdasaryan Cc: Lorenzo Stoakes Signed-off-by: Andrew Morton --- mm/page_owner.c | 52 ++++++++++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/mm/page_owner.c b/mm/page_owner.c index 2dddcb6510aa..342549891a8d 100644 --- a/mm/page_owner.c +++ b/mm/page_owner.c @@ -422,6 +422,29 @@ void __folio_copy_owner(struct folio *newfolio, struct folio *old) rcu_read_unlock(); } +/* + * Check if a page is a buddy page and advance @pfn past the entire buddy block. + * This safely reads the buddy order without the zone lock, which may cause us + * to skip less than the full buddy block, but that is acceptable for page owner + * iteration purposes. + * + * Return: true if the page was skipped (caller should continue its loop), + * false if the page is not a buddy page and should be processed normally. + */ +static inline bool skip_buddy_pages(unsigned long *pfn, struct page *page) +{ + unsigned long order; + + if (!PageBuddy(page)) + return false; + + order = buddy_order_unsafe(page); + if (order <= MAX_PAGE_ORDER) + *pfn += (1UL << order) - 1; + + return true; +} + void pagetypeinfo_showmixedcount_print(struct seq_file *m, pg_data_t *pgdat, struct zone *zone) { @@ -461,14 +484,8 @@ void pagetypeinfo_showmixedcount_print(struct seq_file *m, if (page_zone(page) != zone) continue; - if (PageBuddy(page)) { - unsigned long freepage_order; - - freepage_order = buddy_order_unsafe(page); - if (freepage_order <= MAX_PAGE_ORDER) - pfn += (1UL << freepage_order) - 1; + if (skip_buddy_pages(&pfn, page)) continue; - } if (PageReserved(page)) continue; @@ -697,13 +714,8 @@ read_page_owner(struct file *file, char __user *buf, size_t count, loff_t *ppos) } page = pfn_to_page(pfn); - if (PageBuddy(page)) { - unsigned long freepage_order = buddy_order_unsafe(page); - - if (freepage_order <= MAX_PAGE_ORDER) - pfn += (1UL << freepage_order) - 1; + if (skip_buddy_pages(&pfn, page)) continue; - } page_ext = page_ext_get(page); if (unlikely(!page_ext)) @@ -798,20 +810,8 @@ static void init_pages_in_zone(struct zone *zone) if (page_zone(page) != zone) continue; - /* - * To avoid having to grab zone->lock, be a little - * careful when reading buddy page order. The only - * danger is that we skip too much and potentially miss - * some early allocated pages, which is better than - * heavy lock contention. - */ - if (PageBuddy(page)) { - unsigned long order = buddy_order_unsafe(page); - - if (order > 0 && order <= MAX_PAGE_ORDER) - pfn += (1UL << order) - 1; + if (skip_buddy_pages(&pfn, page)) continue; - } if (PageReserved(page)) continue; From 76cb547ce005e68204fa9dbd7112ae161b453580 Mon Sep 17 00:00:00 2001 From: Ye Liu Date: Wed, 1 Jul 2026 14:10:45 +0800 Subject: [PATCH 161/562] mm/page_owner: add MR_NEVER to enum migrate_reason and use it for last_migrate_reason The last_migrate_reason field uses -1 as a sentinel value to mean "no migration has happened". Replace the four bare -1 occurrences by adding a proper MR_NEVER member to enum migrate_reason, defining a corresponding "never_migrated" string in the MIGRATE_REASON trace macro, and updating the GDB page_owner script to use MR_NEVER instead of the hardcoded -1 so that lx-dump-page-owner does not incorrectly report unmigrated pages as migrated. No functional change. Link: https://lore.kernel.org/20260701061101.344679-3-ye.liu@linux.dev Signed-off-by: Ye Liu Reviewed-by: Zi Yan Reviewed-by: Vlastimil Babka (SUSE) Cc: Brendan Jackman Cc: Johannes Weiner Cc: Michal Hocko Cc: Suren Baghdasaryan Cc: Lorenzo Stoakes Signed-off-by: Andrew Morton --- include/linux/migrate_mode.h | 1 + include/trace/events/migrate.h | 3 ++- mm/page_owner.c | 8 ++++---- scripts/gdb/linux/page_owner.py | 4 +++- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/include/linux/migrate_mode.h b/include/linux/migrate_mode.h index 265c4328b36a..05102d4d2490 100644 --- a/include/linux/migrate_mode.h +++ b/include/linux/migrate_mode.h @@ -25,6 +25,7 @@ enum migrate_reason { MR_LONGTERM_PIN, MR_DEMOTION, MR_DAMON, + MR_NEVER, /* page has never been migrated */ MR_TYPES }; diff --git a/include/trace/events/migrate.h b/include/trace/events/migrate.h index cd01dd7b3640..11bc0aa14c7e 100644 --- a/include/trace/events/migrate.h +++ b/include/trace/events/migrate.h @@ -23,7 +23,8 @@ EM( MR_CONTIG_RANGE, "contig_range") \ EM( MR_LONGTERM_PIN, "longterm_pin") \ EM( MR_DEMOTION, "demotion") \ - EMe(MR_DAMON, "damon") + EM( MR_DAMON, "damon") \ + EMe(MR_NEVER, "never_migrated") /* * First define the enums in the above macros to be exported to userspace diff --git a/mm/page_owner.c b/mm/page_owner.c index 342549891a8d..c2f43ab860eb 100644 --- a/mm/page_owner.c +++ b/mm/page_owner.c @@ -339,7 +339,7 @@ noinline void __set_page_owner(struct page *page, unsigned short order, depot_stack_handle_t handle; handle = save_stack(gfp_mask); - __update_page_owner_handle(page, handle, order, gfp_mask, -1, + __update_page_owner_handle(page, handle, order, gfp_mask, MR_NEVER, ts_nsec, current->pid, current->tgid, current->comm); inc_stack_record_count(handle, gfp_mask, 1 << order); @@ -596,7 +596,7 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn, if (ret >= count) goto err; - if (page_owner->last_migrate_reason != -1) { + if (page_owner->last_migrate_reason != MR_NEVER) { ret += scnprintf(kbuf + ret, count - ret, "Page has been migrated, last migrate reason: %s\n", migrate_reason_names[page_owner->last_migrate_reason]); @@ -667,7 +667,7 @@ void __dump_page_owner(const struct page *page) stack_depot_print(handle); } - if (page_owner->last_migrate_reason != -1) + if (page_owner->last_migrate_reason != MR_NEVER) pr_alert("page has been migrated, last migrate reason: %s\n", migrate_reason_names[page_owner->last_migrate_reason]); page_ext_put(page_ext); @@ -826,7 +826,7 @@ static void init_pages_in_zone(struct zone *zone) /* Found early allocated page */ __update_page_owner_handle(page, early_handle, 0, 0, - -1, local_clock(), current->pid, + MR_NEVER, local_clock(), current->pid, current->tgid, current->comm); count++; ext_put_continue: diff --git a/scripts/gdb/linux/page_owner.py b/scripts/gdb/linux/page_owner.py index 8e713a09cfe7..eeabaeed438b 100644 --- a/scripts/gdb/linux/page_owner.py +++ b/scripts/gdb/linux/page_owner.py @@ -34,6 +34,7 @@ class DumpPageOwner(gdb.Command): max_pfn = None p_ops = None migrate_reason_names = None + mr_never = None def __init__(self): super(DumpPageOwner, self).__init__("lx-dump-page-owner", gdb.COMMAND_SUPPORT) @@ -65,6 +66,7 @@ def get_page_owner_info(self): self.max_pfn = int(gdb.parse_and_eval("max_pfn")) self.page_ext_size = int(gdb.parse_and_eval("page_ext_size")) self.migrate_reason_names = gdb.parse_and_eval('migrate_reason_names') + self.mr_never = int(gdb.parse_and_eval('MR_NEVER')) def page_ext_invalid(self, page_ext): if page_ext == gdb.Value(0): @@ -138,7 +140,7 @@ def read_page_owner_by_addr(self, struct_page_addr): else: gdb.write('page last free stack trace:\n') stackdepot.stack_depot_print(page_owner["free_handle"]) - if page_owner['last_migrate_reason'] != -1: + if page_owner['last_migrate_reason'] != self.mr_never: gdb.write('page has been migrated, last migrate reason: %s\n' % self.migrate_reason_names[page_owner['last_migrate_reason']]) def read_page_owner(self): From 8eb79123c241a1fc55e607fa9737b1d55d6a7710 Mon Sep 17 00:00:00 2001 From: Ye Liu Date: Wed, 1 Jul 2026 14:10:46 +0800 Subject: [PATCH 162/562] mm: use enum migrate_reason instead of int for migration reason parameters Replace all 'int reason' function parameters that carry migrate_reason values with the proper 'enum migrate_reason' type. This makes the intent explicit and leverages compiler type checking. The affected subsystems are: - page_owner: __folio_set_owner_migrate_reason(), folio_set_owner_migrate_reason() - migrate: migrate_pages(), migrate_pages_sync(), migrate_pages_batch(), migrate_folios_move(), migrate_hugetlbs(), unmap_and_move_huge_page() - hugetlb: move_hugetlb_state(), htlb_allow_alloc_fallback() - trace: mm_migrate_pages and mm_migrate_pages_start events The 'short last_migrate_reason' struct field and internal helper parameter in page_owner are intentionally left as 'short' since they store per-page metadata where size matters. No functional change. Link: https://lore.kernel.org/20260701061101.344679-4-ye.liu@linux.dev Signed-off-by: Ye Liu Reviewed-by: Zi Yan Reviewed-by: Vlastimil Babka (SUSE) Reviewed-by: Lorenzo Stoakes Cc: Brendan Jackman Cc: Johannes Weiner Cc: Michal Hocko Cc: Suren Baghdasaryan Signed-off-by: Andrew Morton --- include/linux/hugetlb.h | 9 +++++---- include/linux/migrate.h | 6 ++++-- include/linux/page_owner.h | 7 ++++--- include/trace/events/migrate.h | 8 ++++---- mm/hugetlb.c | 3 ++- mm/migrate.c | 12 ++++++------ mm/page_owner.c | 2 +- 7 files changed, 26 insertions(+), 21 deletions(-) diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h index 2abaf99321e9..fa828232dfcc 100644 --- a/include/linux/hugetlb.h +++ b/include/linux/hugetlb.h @@ -154,7 +154,8 @@ long hugetlb_unreserve_pages(struct inode *inode, long start, long end, bool folio_isolate_hugetlb(struct folio *folio, struct list_head *list); int get_hwpoison_hugetlb_folio(struct folio *folio, bool *hugetlb, bool unpoison); void folio_putback_hugetlb(struct folio *folio); -void move_hugetlb_state(struct folio *old_folio, struct folio *new_folio, int reason); +void move_hugetlb_state(struct folio *old_folio, struct folio *new_folio, + enum migrate_reason reason); void hugetlb_fix_reserve_counts(struct inode *inode); extern struct mutex *hugetlb_fault_mutex_table; u32 hugetlb_fault_mutex_hash(struct address_space *mapping, pgoff_t idx); @@ -424,7 +425,7 @@ static inline void folio_putback_hugetlb(struct folio *folio) } static inline void move_hugetlb_state(struct folio *old_folio, - struct folio *new_folio, int reason) + struct folio *new_folio, enum migrate_reason reason) { } @@ -956,7 +957,7 @@ static inline gfp_t htlb_modify_alloc_mask(struct hstate *h, gfp_t gfp_mask) return modified_mask; } -static inline bool htlb_allow_alloc_fallback(int reason) +static inline bool htlb_allow_alloc_fallback(enum migrate_reason reason) { bool allowed_fallback = false; @@ -1238,7 +1239,7 @@ static inline gfp_t htlb_modify_alloc_mask(struct hstate *h, gfp_t gfp_mask) return 0; } -static inline bool htlb_allow_alloc_fallback(int reason) +static inline bool htlb_allow_alloc_fallback(enum migrate_reason reason) { return false; } diff --git a/include/linux/migrate.h b/include/linux/migrate.h index d5af2b7f577b..1f83924615d6 100644 --- a/include/linux/migrate.h +++ b/include/linux/migrate.h @@ -57,7 +57,8 @@ void putback_movable_pages(struct list_head *l); int migrate_folio(struct address_space *mapping, struct folio *dst, struct folio *src, enum migrate_mode mode); int migrate_pages(struct list_head *l, new_folio_t new, free_folio_t free, - unsigned long private, enum migrate_mode mode, int reason, + unsigned long private, enum migrate_mode mode, + enum migrate_reason reason, unsigned int *ret_succeeded); struct folio *alloc_migration_target(struct folio *src, unsigned long private); bool isolate_movable_ops_page(struct page *page, isolate_mode_t mode); @@ -77,7 +78,8 @@ int set_movable_ops(const struct movable_operations *ops, enum pagetype type); static inline void putback_movable_pages(struct list_head *l) {} static inline int migrate_pages(struct list_head *l, new_folio_t new, free_folio_t free, unsigned long private, - enum migrate_mode mode, int reason, unsigned int *ret_succeeded) + enum migrate_mode mode, enum migrate_reason reason, + unsigned int *ret_succeeded) { return -ENOSYS; } static inline struct folio *alloc_migration_target(struct folio *src, unsigned long private) diff --git a/include/linux/page_owner.h b/include/linux/page_owner.h index 3328357f6dba..9fe51dfccf26 100644 --- a/include/linux/page_owner.h +++ b/include/linux/page_owner.h @@ -3,6 +3,7 @@ #define __LINUX_PAGE_OWNER_H #include +#include #ifdef CONFIG_PAGE_OWNER extern struct static_key_false page_owner_inited; @@ -14,7 +15,7 @@ extern void __set_page_owner(struct page *page, extern void __split_page_owner(struct page *page, int old_order, int new_order); extern void __folio_copy_owner(struct folio *newfolio, struct folio *old); -extern void __folio_set_owner_migrate_reason(struct folio *folio, int reason); +extern void __folio_set_owner_migrate_reason(struct folio *folio, enum migrate_reason reason); extern void __dump_page_owner(const struct page *page); extern void pagetypeinfo_showmixedcount_print(struct seq_file *m, pg_data_t *pgdat, struct zone *zone); @@ -43,7 +44,7 @@ static inline void folio_copy_owner(struct folio *newfolio, struct folio *old) if (static_branch_unlikely(&page_owner_inited)) __folio_copy_owner(newfolio, old); } -static inline void folio_set_owner_migrate_reason(struct folio *folio, int reason) +static inline void folio_set_owner_migrate_reason(struct folio *folio, enum migrate_reason reason) { if (static_branch_unlikely(&page_owner_inited)) __folio_set_owner_migrate_reason(folio, reason); @@ -68,7 +69,7 @@ static inline void split_page_owner(struct page *page, int old_order, static inline void folio_copy_owner(struct folio *newfolio, struct folio *folio) { } -static inline void folio_set_owner_migrate_reason(struct folio *folio, int reason) +static inline void folio_set_owner_migrate_reason(struct folio *folio, enum migrate_reason reason) { } static inline void dump_page_owner(const struct page *page) diff --git a/include/trace/events/migrate.h b/include/trace/events/migrate.h index 11bc0aa14c7e..15ee2ef201b5 100644 --- a/include/trace/events/migrate.h +++ b/include/trace/events/migrate.h @@ -52,7 +52,7 @@ TRACE_EVENT(mm_migrate_pages, TP_PROTO(unsigned long succeeded, unsigned long failed, unsigned long thp_succeeded, unsigned long thp_failed, unsigned long thp_split, unsigned long large_folio_split, - enum migrate_mode mode, int reason), + enum migrate_mode mode, enum migrate_reason reason), TP_ARGS(succeeded, failed, thp_succeeded, thp_failed, thp_split, large_folio_split, mode, reason), @@ -65,7 +65,7 @@ TRACE_EVENT(mm_migrate_pages, __field( unsigned long, thp_split) __field( unsigned long, large_folio_split) __field( enum migrate_mode, mode) - __field( int, reason) + __field( enum migrate_reason, reason) ), TP_fast_assign( @@ -92,13 +92,13 @@ TRACE_EVENT(mm_migrate_pages, TRACE_EVENT(mm_migrate_pages_start, - TP_PROTO(enum migrate_mode mode, int reason), + TP_PROTO(enum migrate_mode mode, enum migrate_reason reason), TP_ARGS(mode, reason), TP_STRUCT__entry( __field(enum migrate_mode, mode) - __field(int, reason) + __field(enum migrate_reason, reason) ), TP_fast_assign( diff --git a/mm/hugetlb.c b/mm/hugetlb.c index 2de726ff89da..3a0fcb84cc39 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -7194,7 +7194,8 @@ void folio_putback_hugetlb(struct folio *folio) folio_put(folio); } -void move_hugetlb_state(struct folio *old_folio, struct folio *new_folio, int reason) +void move_hugetlb_state(struct folio *old_folio, struct folio *new_folio, + enum migrate_reason reason) { struct hstate *h = folio_hstate(old_folio); diff --git a/mm/migrate.c b/mm/migrate.c index 9fd50ea25d2d..c13241cc0c40 100644 --- a/mm/migrate.c +++ b/mm/migrate.c @@ -1471,7 +1471,7 @@ static int migrate_folio_move(free_folio_t put_new_folio, unsigned long private, static int unmap_and_move_huge_page(new_folio_t get_new_folio, free_folio_t put_new_folio, unsigned long private, struct folio *src, int force, enum migrate_mode mode, - int reason, struct list_head *ret) + enum migrate_reason reason, struct list_head *ret) { struct folio *dst; int rc = -EAGAIN; @@ -1628,7 +1628,7 @@ struct migrate_pages_stats { */ static int migrate_hugetlbs(struct list_head *from, new_folio_t get_new_folio, free_folio_t put_new_folio, unsigned long private, - enum migrate_mode mode, int reason, + enum migrate_mode mode, enum migrate_reason reason, struct migrate_pages_stats *stats, struct list_head *ret_folios) { @@ -1718,7 +1718,7 @@ static int migrate_hugetlbs(struct list_head *from, new_folio_t get_new_folio, static void migrate_folios_move(struct list_head *src_folios, struct list_head *dst_folios, free_folio_t put_new_folio, unsigned long private, - enum migrate_mode mode, int reason, + enum migrate_mode mode, enum migrate_reason reason, struct list_head *ret_folios, struct migrate_pages_stats *stats, int *retry, int *thp_retry, int *nr_failed, @@ -1801,7 +1801,7 @@ static void migrate_folios_undo(struct list_head *src_folios, */ static int migrate_pages_batch(struct list_head *from, new_folio_t get_new_folio, free_folio_t put_new_folio, - unsigned long private, enum migrate_mode mode, int reason, + unsigned long private, enum migrate_mode mode, enum migrate_reason reason, struct list_head *ret_folios, struct list_head *split_folios, struct migrate_pages_stats *stats, int nr_pass) { @@ -2013,7 +2013,7 @@ static int migrate_pages_batch(struct list_head *from, static int migrate_pages_sync(struct list_head *from, new_folio_t get_new_folio, free_folio_t put_new_folio, unsigned long private, - enum migrate_mode mode, int reason, + enum migrate_mode mode, enum migrate_reason reason, struct list_head *ret_folios, struct list_head *split_folios, struct migrate_pages_stats *stats) { @@ -2090,7 +2090,7 @@ static int migrate_pages_sync(struct list_head *from, new_folio_t get_new_folio, */ int migrate_pages(struct list_head *from, new_folio_t get_new_folio, free_folio_t put_new_folio, unsigned long private, - enum migrate_mode mode, int reason, unsigned int *ret_succeeded) + enum migrate_mode mode, enum migrate_reason reason, unsigned int *ret_succeeded) { int rc, rc_gather; int nr_pages; diff --git a/mm/page_owner.c b/mm/page_owner.c index c2f43ab860eb..4e352941a6e2 100644 --- a/mm/page_owner.c +++ b/mm/page_owner.c @@ -345,7 +345,7 @@ noinline void __set_page_owner(struct page *page, unsigned short order, inc_stack_record_count(handle, gfp_mask, 1 << order); } -void __folio_set_owner_migrate_reason(struct folio *folio, int reason) +void __folio_set_owner_migrate_reason(struct folio *folio, enum migrate_reason reason) { struct page_ext *page_ext = page_ext_get(&folio->page); struct page_owner *page_owner; From fcf27e8279ac92d23e27821c0baff656c20db8ae Mon Sep 17 00:00:00 2001 From: Ye Liu Date: Wed, 1 Jul 2026 14:10:47 +0800 Subject: [PATCH 163/562] mm/page_owner: hoist CONFIG_MEMCG to function level for print_page_owner_memcg() The print_page_owner_memcg() function has CONFIG_MEMCG guarding its entire body via #ifdef inside the function, which leaves a no-op { return ret; } when the config is disabled. Hoist the #ifdef to the top level so the real implementation and the empty stub are two clearly separated definitions. No functional change. Link: https://lore.kernel.org/20260701061101.344679-5-ye.liu@linux.dev Signed-off-by: Ye Liu Reviewed-by: Zi Yan Reviewed-by: Vlastimil Babka (SUSE) Cc: Brendan Jackman Cc: Johannes Weiner Cc: Michal Hocko Cc: Suren Baghdasaryan Cc: Lorenzo Stoakes Signed-off-by: Andrew Morton --- mm/page_owner.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/mm/page_owner.c b/mm/page_owner.c index 4e352941a6e2..fe2bf2274d8a 100644 --- a/mm/page_owner.c +++ b/mm/page_owner.c @@ -522,13 +522,13 @@ void pagetypeinfo_showmixedcount_print(struct seq_file *m, seq_putc(m, '\n'); } +#ifdef CONFIG_MEMCG /* * Looking for memcg information and print it out */ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret, struct page *page) { -#ifdef CONFIG_MEMCG unsigned long memcg_data; struct mem_cgroup *memcg; bool online; @@ -556,10 +556,16 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret, name); out_unlock: rcu_read_unlock(); -#endif /* CONFIG_MEMCG */ return ret; } +#else +static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret, + struct page *page) +{ + return ret; +} +#endif static ssize_t print_page_owner(char __user *buf, size_t count, unsigned long pfn, From 4c1744bec09c814ecc486c75275af9c4e3208713 Mon Sep 17 00:00:00 2001 From: Ye Liu Date: Wed, 1 Jul 2026 14:10:48 +0800 Subject: [PATCH 164/562] mm/page_owner: add missing newline to count_threshold format string The DEFINE_SIMPLE_ATTRIBUTE format string for page_owner_threshold_fops is missing a trailing \n. simple_attr_read() uses scnprintf() with the format string, which does not append a newline, so reading /sys/kernel/debug/page_owner_stacks/count_threshold produces output without a terminating newline. Add the missing \n to match the standard debugfs attribute convention. Link: https://lore.kernel.org/20260701061101.344679-6-ye.liu@linux.dev Signed-off-by: Ye Liu Reviewed-by: Zi Yan Reviewed-by: Vlastimil Babka (SUSE) Cc: Brendan Jackman Cc: Johannes Weiner Cc: Michal Hocko Cc: Suren Baghdasaryan Cc: Lorenzo Stoakes Signed-off-by: Andrew Morton --- mm/page_owner.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/page_owner.c b/mm/page_owner.c index fe2bf2274d8a..7520718f63f1 100644 --- a/mm/page_owner.c +++ b/mm/page_owner.c @@ -974,7 +974,7 @@ static int page_owner_threshold_set(void *data, u64 val) } DEFINE_SIMPLE_ATTRIBUTE(page_owner_threshold_fops, &page_owner_threshold_get, - &page_owner_threshold_set, "%llu"); + &page_owner_threshold_set, "%llu\n"); static int __init pageowner_init(void) From fc98cd0e7026922b18a94b5325e4efd543b2f345 Mon Sep 17 00:00:00 2001 From: Ye Liu Date: Wed, 1 Jul 2026 14:10:49 +0800 Subject: [PATCH 165/562] mm/page_owner: move free_ts_nsec output to free section in __dump_page_owner() The free_ts_nsec field is a free-event timestamp, but it was printed in the allocation summary line alongside ts_nsec (allocation time). Move it to the free section where it logically belongs, together with free_pid and free_tgid. This also makes __dump_page_owner() consistent with print_page_owner(), which only prints ts_nsec in the allocation summary. The output now groups all free-related information (pid, tgid, timestamp, stack trace) in one place. No functional change except output formatting. Link: https://lore.kernel.org/20260701061101.344679-7-ye.liu@linux.dev Signed-off-by: Ye Liu Acked-by: Zi Yan Reviewed-by: Vlastimil Babka (SUSE) Cc: Brendan Jackman Cc: Johannes Weiner Cc: Michal Hocko Cc: Suren Baghdasaryan Cc: Lorenzo Stoakes Signed-off-by: Andrew Morton --- mm/page_owner.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mm/page_owner.c b/mm/page_owner.c index 7520718f63f1..84eb44459478 100644 --- a/mm/page_owner.c +++ b/mm/page_owner.c @@ -653,10 +653,10 @@ void __dump_page_owner(const struct page *page) else pr_alert("page_owner tracks the page as freed\n"); - pr_alert("page last allocated via order %u, migratetype %s, gfp_mask %#x(%pGg), pid %d, tgid %d (%s), ts %llu, free_ts %llu\n", + pr_alert("page last allocated via order %u, migratetype %s, gfp_mask %#x(%pGg), pid %d, tgid %d (%s), ts %llu\n", page_owner->order, migratetype_names[mt], gfp_mask, &gfp_mask, page_owner->pid, page_owner->tgid, page_owner->comm, - page_owner->ts_nsec, page_owner->free_ts_nsec); + page_owner->ts_nsec); handle = READ_ONCE(page_owner->handle); if (!handle) @@ -668,8 +668,9 @@ void __dump_page_owner(const struct page *page) if (!handle) { pr_alert("page_owner free stack trace missing\n"); } else { - pr_alert("page last free pid %d tgid %d stack trace:\n", - page_owner->free_pid, page_owner->free_tgid); + pr_alert("page last free pid %d tgid %d ts %llu stack trace:\n", + page_owner->free_pid, page_owner->free_tgid, + page_owner->free_ts_nsec); stack_depot_print(handle); } From b3476b27a455833e1e800c4f15af9336099b0a7f Mon Sep 17 00:00:00 2001 From: Ye Liu Date: Wed, 1 Jul 2026 14:10:50 +0800 Subject: [PATCH 166/562] mm/page_owner: drop redundant page_owner prefix from static symbols All of these symbols are file-scoped (static) in page_owner.c, so the page_owner_ prefix is pure noise. Rename them to shorter, still-clear names: page_owner_stack_op -> stack_op page_owner_stack_open -> stack_open page_owner_stack_fops -> stack_fops page_owner_pages_threshold -> pages_threshold page_owner_threshold_get -> threshold_get page_owner_threshold_set -> threshold_set page_owner_threshold_fops -> threshold_fops No functional change. Link: https://lore.kernel.org/20260701061101.344679-8-ye.liu@linux.dev Signed-off-by: Ye Liu Acked-by: Zi Yan Reviewed-by: Vlastimil Babka (SUSE) Cc: Brendan Jackman Cc: Johannes Weiner Cc: Michal Hocko Cc: Suren Baghdasaryan Cc: Lorenzo Stoakes Signed-off-by: Andrew Morton --- mm/page_owner.c | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/mm/page_owner.c b/mm/page_owner.c index 84eb44459478..46a933f9c229 100644 --- a/mm/page_owner.c +++ b/mm/page_owner.c @@ -894,7 +894,7 @@ static void *stack_next(struct seq_file *m, void *v, loff_t *ppos) return stack; } -static unsigned long page_owner_pages_threshold; +static unsigned long pages_threshold; static int stack_print(struct seq_file *m, void *v) { @@ -911,7 +911,7 @@ static int stack_print(struct seq_file *m, void *v) nr_base_pages = refcount_read(&stack_record->count) - 1; if (ctx->flags & STACK_PRINT_FLAG_PAGES && - (nr_base_pages < 1 || nr_base_pages < page_owner_pages_threshold)) + (nr_base_pages < 1 || nr_base_pages < pages_threshold)) return 0; if (ctx->flags & STACK_PRINT_FLAG_STACK) { @@ -933,16 +933,16 @@ static void stack_stop(struct seq_file *m, void *v) { } -static const struct seq_operations page_owner_stack_op = { +static const struct seq_operations stack_op = { .start = stack_start, .next = stack_next, .stop = stack_stop, .show = stack_print }; -static int page_owner_stack_open(struct inode *inode, struct file *file) +static int stack_open(struct inode *inode, struct file *file) { - int ret = seq_open_private(file, &page_owner_stack_op, + int ret = seq_open_private(file, &stack_op, sizeof(struct stack_print_ctx)); if (!ret) { @@ -955,28 +955,26 @@ static int page_owner_stack_open(struct inode *inode, struct file *file) return ret; } -static const struct file_operations page_owner_stack_fops = { - .open = page_owner_stack_open, +static const struct file_operations stack_fops = { + .open = stack_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release_private, }; -static int page_owner_threshold_get(void *data, u64 *val) +static int threshold_get(void *data, u64 *val) { - *val = READ_ONCE(page_owner_pages_threshold); + *val = READ_ONCE(pages_threshold); return 0; } -static int page_owner_threshold_set(void *data, u64 val) +static int threshold_set(void *data, u64 val) { - WRITE_ONCE(page_owner_pages_threshold, val); + WRITE_ONCE(pages_threshold, val); return 0; } -DEFINE_SIMPLE_ATTRIBUTE(page_owner_threshold_fops, &page_owner_threshold_get, - &page_owner_threshold_set, "%llu\n"); - +DEFINE_SIMPLE_ATTRIBUTE(threshold_fops, &threshold_get, &threshold_set, "%llu\n"); static int __init pageowner_init(void) { @@ -992,17 +990,17 @@ static int __init pageowner_init(void) debugfs_create_file("show_stacks", 0400, dir, (void *)(STACK_PRINT_FLAG_STACK | STACK_PRINT_FLAG_PAGES), - &page_owner_stack_fops); + &stack_fops); debugfs_create_file("show_handles", 0400, dir, (void *)(STACK_PRINT_FLAG_HANDLE | STACK_PRINT_FLAG_PAGES), - &page_owner_stack_fops); + &stack_fops); debugfs_create_file("show_stacks_handles", 0400, dir, (void *)(STACK_PRINT_FLAG_STACK | STACK_PRINT_FLAG_HANDLE), - &page_owner_stack_fops); + &stack_fops); debugfs_create_file("count_threshold", 0600, dir, NULL, - &page_owner_threshold_fops); + &threshold_fops); return 0; } late_initcall(pageowner_init) From 1e5e9fb35b60a8f5db22ae735b37244b410b3fa0 Mon Sep 17 00:00:00 2001 From: Ye Liu Date: Wed, 1 Jul 2026 14:10:51 +0800 Subject: [PATCH 167/562] mm/page_owner: clamp skip_buddy_pages() PFN advance at MAX_ORDER_NR_PAGES boundary The lockless buddy_order_unsafe() read can return a garbage order value if the page is concurrently allocated between the PageBuddy check and the private read. If this bogus order is <= MAX_PAGE_ORDER, skip_buddy_pages() would arbitrarily advance the PFN, potentially jumping past a MAX_ORDER_NR_PAGES boundary whose pfn_valid() check would have caught an offline memory section. In read_page_owner(), which relies solely on boundary-aligned pfn_valid() to guard pfn_to_page(), skipping the boundary could cause pfn_to_page() to access an unmapped mem_section. Clamp the advance so it never crosses the next MAX_ORDER_NR_PAGES boundary. This is safe for all three callers: the pageblock-iterating ones already handle boundary transitions in their outer loops, and for read_page_owner() the worst case is one extra PageBuddy check per 1024 pages for a huge buddy block straddling the boundary. Link: https://lore.kernel.org/20260701061101.344679-9-ye.liu@linux.dev Signed-off-by: Ye Liu Reviewed-by: Vlastimil Babka (SUSE) Reviewed-by: Zi Yan Cc: Brendan Jackman Cc: Johannes Weiner Cc: Michal Hocko Cc: Suren Baghdasaryan Cc: Lorenzo Stoakes Signed-off-by: Andrew Morton --- mm/page_owner.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/mm/page_owner.c b/mm/page_owner.c index 46a933f9c229..2e3880053a34 100644 --- a/mm/page_owner.c +++ b/mm/page_owner.c @@ -428,6 +428,12 @@ void __folio_copy_owner(struct folio *newfolio, struct folio *old) * to skip less than the full buddy block, but that is acceptable for page owner * iteration purposes. * + * The lockless read of buddy_order_unsafe() can also return a garbage order if + * the page is concurrently allocated and PageBuddy is cleared between the check + * and the read. Clamp the advance at the next MAX_ORDER_NR_PAGES boundary so + * that a bogus order cannot carry @pfn into an unvalidated memory section, + * which would break callers that rely on boundary-aligned pfn_valid() checks. + * * Return: true if the page was skipped (caller should continue its loop), * false if the page is not a buddy page and should be processed normally. */ @@ -439,8 +445,12 @@ static inline bool skip_buddy_pages(unsigned long *pfn, struct page *page) return false; order = buddy_order_unsafe(page); - if (order <= MAX_PAGE_ORDER) - *pfn += (1UL << order) - 1; + if (order <= MAX_PAGE_ORDER) { + unsigned long new_pfn = *pfn + (1UL << order); + unsigned long boundary = ALIGN(*pfn + 1, MAX_ORDER_NR_PAGES); + + *pfn = min(new_pfn, boundary) - 1; + } return true; } From c00fe10201c10ec02ac86d685d0a4bc23c0ffb78 Mon Sep 17 00:00:00 2001 From: Ye Liu Date: Wed, 1 Jul 2026 14:10:52 +0800 Subject: [PATCH 168/562] mm/page_owner: use memcg_data snapshot instead of PageMemcgKmem() to avoid TOCTOU VM_BUG_ON MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit print_page_owner_memcg() takes a snapshot of page->memcg_data via READ_ONCE at the top of the function and guards against tail pages and NULL memcg_data. However, at the end it calls PageMemcgKmem(page) which internally calls folio_memcg_kmem() — and that function re-reads folio->memcg_data and page->compound_head locklessly, wrapping both in VM_BUG_ON assertions: VM_BUG_ON_PGFLAGS(PageTail(&folio->page), &folio->page); VM_BUG_ON_FOLIO(folio->memcg_data & MEMCG_DATA_OBJEXTS, folio); If the page is concurrently freed and reallocated as a THP tail page or a slab page between the initial guards and this final call, the VM_BUG_ON assertions can fire on debug builds (CONFIG_DEBUG_VM=y), causing a kernel panic. Fix by reusing the memcg_data snapshot already taken at function entry instead of calling PageMemcgKmem(), which is semantically equivalent: PageMemcgKmem()->folio_memcg_kmem()->folio->memcg_data & MEMCG_DATA_KMEM. This avoids both the TOCTOU window and the assertions entirely. Link: https://lore.kernel.org/20260701061101.344679-10-ye.liu@linux.dev Signed-off-by: Ye Liu Reviewed-by: Vlastimil Babka (SUSE) Reviewed-by: Zi Yan Cc: Brendan Jackman Cc: Johannes Weiner Cc: Michal Hocko Cc: Suren Baghdasaryan Cc: Lorenzo Stoakes Signed-off-by: Andrew Morton --- mm/page_owner.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/page_owner.c b/mm/page_owner.c index 2e3880053a34..efbf67d54ee2 100644 --- a/mm/page_owner.c +++ b/mm/page_owner.c @@ -561,7 +561,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret, cgroup_name(memcg->css.cgroup, name, sizeof(name)); ret += scnprintf(kbuf + ret, count - ret, "Charged %sto %smemcg %s\n", - PageMemcgKmem(page) ? "(via objcg) " : "", + (memcg_data & MEMCG_DATA_KMEM) ? "(via objcg) " : "", online ? "" : "offline ", name); out_unlock: From 48cfb01579e411ef1cf8a0cc3748ae862b92d81b Mon Sep 17 00:00:00 2001 From: Shivank Garg Date: Wed, 1 Jul 2026 05:17:20 +0000 Subject: [PATCH 169/562] mm/migrate: rename page to folio leftovers Patch series "mm/migrate: preparatory cleanups for batch copy and offload", v2. This is a small set of mm/migrate cleanups split out of the batch-copy and offload RFC [1], so they can be reviewed and merged independently ahead of that larger series. No functional change intended. This patch (of 3): Rename migrate_folio_undo_src()'s page_was_mapped parameter to was_mapped, unmap_and_move_huge_page() to unmap_and_move_hugetlb_folio(), its page_was_mapped variable to was_mapped and fix stale "page" wording in its comments. Also fix migrate_folio() kerneldoc to say "folio" instead of "page". Link: https://lore.kernel.org/20260701-migrate-cleanups-prep-v2-0-d9e8f17130b1@amd.com Link: https://lore.kernel.org/20260701-migrate-cleanups-prep-v2-1-d9e8f17130b1@amd.com Link: https://lore.kernel.org/all/20260428155043.39251-2-shivankg@amd.com [1] Signed-off-by: Shivank Garg Suggested-by: Dev Jain Suggested-by: David Hildenbrand (Arm) Reviewed-by: Zi Yan Reviewed-by: SJ Park Cc: Alistair Popple Cc: Byungchul Park Cc: Gregory Price Cc: "Huang, Ying" Cc: Joshua Hahn Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Matthew Brost Cc: Michal Hocko Cc: Mike Rapoport Cc: Rakie Kim Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Jonathan Cameron Signed-off-by: Andrew Morton --- mm/migrate.c | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/mm/migrate.c b/mm/migrate.c index c13241cc0c40..e915bddfea06 100644 --- a/mm/migrate.c +++ b/mm/migrate.c @@ -886,7 +886,7 @@ static int __migrate_folio(struct address_space *mapping, struct folio *dst, * @mapping: The address_space containing the folio. * @dst: The folio to migrate the data to. * @src: The folio containing the current data. - * @mode: How to migrate the page. + * @mode: How to migrate the folio. * * Common logic to directly migrate a single LRU folio suitable for * folios that do not have private data. @@ -1159,13 +1159,10 @@ static void __migrate_folio_extract(struct folio *dst, } /* Restore the source folio to the original state upon failure */ -static void migrate_folio_undo_src(struct folio *src, - int page_was_mapped, - struct anon_vma *anon_vma, - bool locked, - struct list_head *ret) +static void migrate_folio_undo_src(struct folio *src, int was_mapped, + struct anon_vma *anon_vma, bool locked, struct list_head *ret) { - if (page_was_mapped) + if (was_mapped) remove_migration_ptes(src, src, 0); /* Drop an anon_vma reference if we took one */ if (anon_vma) @@ -1451,7 +1448,8 @@ static int migrate_folio_move(free_folio_t put_new_folio, unsigned long private, } /* - * Counterpart of unmap_and_move_page() for hugepage migration. + * Counterpart of migrate_folio_unmap() and migrate_folio_move() for hugetlb + * folio migration. * * This function doesn't wait the completion of hugepage I/O * because there is no race between I/O and migration for hugepage. @@ -1468,20 +1466,20 @@ static int migrate_folio_move(free_folio_t put_new_folio, unsigned long private, * because then pte is replaced with migration swap entry and direct I/O code * will wait in the page fault for migration to complete. */ -static int unmap_and_move_huge_page(new_folio_t get_new_folio, +static int unmap_and_move_hugetlb_folio(new_folio_t get_new_folio, free_folio_t put_new_folio, unsigned long private, struct folio *src, int force, enum migrate_mode mode, enum migrate_reason reason, struct list_head *ret) { struct folio *dst; int rc = -EAGAIN; - int page_was_mapped = 0; + int was_mapped = 0; struct anon_vma *anon_vma = NULL; struct address_space *mapping = NULL; enum ttu_flags ttu = 0; if (folio_ref_count(src) == 1) { - /* page was freed from under us. So we are done. */ + /* folio was freed from under us. So we are done. */ folio_putback_hugetlb(src); return 0; } @@ -1503,8 +1501,8 @@ static int unmap_and_move_huge_page(new_folio_t get_new_folio, } /* - * Check for pages which are in the process of being freed. Without - * folio_mapping() set, hugetlbfs specific move page routine will not + * Check for folios which are in the process of being freed. Without + * folio_mapping() set, hugetlbfs specific move folio routine will not * be called and we could leak usage counts for subpools. */ if (hugetlb_folio_subpool(src) && !folio_mapping(src)) { @@ -1534,13 +1532,13 @@ static int unmap_and_move_huge_page(new_folio_t get_new_folio, } try_to_migrate(src, ttu); - page_was_mapped = 1; + was_mapped = 1; } if (!folio_mapped(src)) rc = move_to_new_folio(dst, src, mode); - if (page_was_mapped) + if (was_mapped) remove_migration_ptes(src, !rc ? dst : src, ttu); if (ttu & TTU_RMAP_LOCKED) @@ -1665,10 +1663,10 @@ static int migrate_hugetlbs(struct list_head *from, new_folio_t get_new_folio, continue; } - rc = unmap_and_move_huge_page(get_new_folio, - put_new_folio, private, - folio, pass > 2, mode, - reason, ret_folios); + rc = unmap_and_move_hugetlb_folio(get_new_folio, + put_new_folio, private, + folio, pass > 2, mode, + reason, ret_folios); /* * The rules are: * 0: hugetlb folio will be put back From 38b4e076e0511390679b9fa7a9828de9898153d9 Mon Sep 17 00:00:00 2001 From: Shivank Garg Date: Wed, 1 Jul 2026 05:17:21 +0000 Subject: [PATCH 170/562] mm/migrate: fix stale list name in migrate_folios_move() comment The return-value description in migrate_folios_move() still refers to unmap_folios, but that list no longer exists. Update this name to src_folios. Link: https://lore.kernel.org/20260701-migrate-cleanups-prep-v2-2-d9e8f17130b1@amd.com Signed-off-by: Shivank Garg Reviewed-by: Zi Yan Acked-by: David Hildenbrand (Arm) Cc: Alistair Popple Cc: Byungchul Park Cc: Dev Jain Cc: Gregory Price Cc: "Huang, Ying" Cc: Jonathan Cameron Cc: Joshua Hahn Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Matthew Brost Cc: Michal Hocko Cc: Mike Rapoport Cc: Rakie Kim Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/migrate.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/migrate.c b/mm/migrate.c index e915bddfea06..f8bbc32325d4 100644 --- a/mm/migrate.c +++ b/mm/migrate.c @@ -1741,7 +1741,7 @@ static void migrate_folios_move(struct list_head *src_folios, /* * The rules are: * 0: folio will be freed - * -EAGAIN: stay on the unmap_folios list + * -EAGAIN: stay on the src_folios list * Other errno: put on ret_folios list */ switch (rc) { From c6232f640ac76c79f25bdfa5b3631a100eda7846 Mon Sep 17 00:00:00 2001 From: Shivank Garg Date: Wed, 1 Jul 2026 05:17:22 +0000 Subject: [PATCH 171/562] mm/migrate: use migrate_info field instead of private Add an unsigned long migrate_info member to the struct folio union and use it to store migration state (anon_vma pointer and FOLIO_WAS_* markers) instead of using folio->private. While at it, switch to bitwise OR. No functional change. Link: https://lore.kernel.org/20260701-migrate-cleanups-prep-v2-3-d9e8f17130b1@amd.com Signed-off-by: Shivank Garg Suggested-by: David Hildenbrand (Arm) Reviewed-by: Jonathan Cameron Acked-by: David Hildenbrand (Arm) Reviewed-by: Huang Ying Acked-by: Zi Yan Reviewed-by: SJ Park Cc: Alistair Popple Cc: Byungchul Park Cc: Dev Jain Cc: Gregory Price Cc: Joshua Hahn Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Matthew Brost Cc: Michal Hocko Cc: Mike Rapoport Cc: Rakie Kim Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- include/linux/mm_types.h | 1 + mm/migrate.c | 14 +++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h index b18c2b2e7d2c..ae9bca4eda5c 100644 --- a/include/linux/mm_types.h +++ b/include/linux/mm_types.h @@ -427,6 +427,7 @@ struct folio { union { void *private; swp_entry_t swap; + unsigned long migrate_info; }; atomic_t _mapcount; atomic_t _refcount; diff --git a/mm/migrate.c b/mm/migrate.c index f8bbc32325d4..806508be3dec 100644 --- a/mm/migrate.c +++ b/mm/migrate.c @@ -1132,7 +1132,7 @@ static int move_to_new_folio(struct folio *dst, struct folio *src, } /* - * To record some information during migration, we use unused private + * To record some information during migration, we use the migrate_info * field of struct folio of the newly allocated destination folio. * This is safe because nobody is using it except us. */ @@ -1145,17 +1145,17 @@ enum { static void __migrate_folio_record(struct folio *dst, int old_folio_state, struct anon_vma *anon_vma) { - dst->private = (void *)anon_vma + old_folio_state; + dst->migrate_info = (unsigned long)anon_vma | old_folio_state; } static void __migrate_folio_extract(struct folio *dst, int *old_folio_state, struct anon_vma **anon_vmap) { - unsigned long private = (unsigned long)dst->private; + unsigned long info = dst->migrate_info; - *anon_vmap = (struct anon_vma *)(private & ~FOLIO_OLD_STATES); - *old_folio_state = private & FOLIO_OLD_STATES; - dst->private = NULL; + *anon_vmap = (struct anon_vma *)(info & ~FOLIO_OLD_STATES); + *old_folio_state = info & FOLIO_OLD_STATES; + dst->migrate_info = 0; } /* Restore the source folio to the original state upon failure */ @@ -1216,7 +1216,7 @@ static int migrate_folio_unmap(new_folio_t get_new_folio, return -ENOMEM; *dstp = dst; - dst->private = NULL; + dst->migrate_info = 0; if (!folio_trylock(src)) { if (mode == MIGRATE_ASYNC) From 8bf7b4f7cf17abca9dd44e246c831c73050033e4 Mon Sep 17 00:00:00 2001 From: Zhen Ni Date: Thu, 25 Jun 2026 12:30:58 +0800 Subject: [PATCH 172/562] mm/page_owner: add print_mode filter Patch series "mm/page_owner: add per-fd filter infrastructure for print_mode and NUMA filtering", v11. This patch series introduces per-file-descriptor filtering capabilities to the page_owner feature. This patch (of 4): Add a print_mode filter to page_owner that allows users to choose between printing stack traces, stack handles, or both, providing flexibility for different debugging and analysis scenarios. The filter provides three modes via page_owner: - Writing "mode=stack" prints stack traces for each page (default) - Writing "mode=handle" prints only the handle number - Writing "mode=stack_handle" prints both stack traces and handles The default stack mode maintains backward compatibility with existing usage, displaying complete stack traces for each page allocation. The handle mode dramatically reduces log size and improves performance by showing only the handle number instead of the full stack trace. Testing shows handle mode reduces output size by ~66% (84MB vs 244MB) and improves read performance by ~4.4x compared to full stack output. The mapping from handles to actual stack traces can be obtained via the show_stacks_handles interface. The stack_handle mode prints both stack traces and handles, making it easier to identify pages with the same allocation pattern by comparing handle numbers instead of comparing large stack traces. Example usage: # Using the page_owner_filter tool (recommended) ./page_owner_filter -m stack # Print only stack traces (default) ./page_owner_filter -m handle # Print only handles ./page_owner_filter -m stack_handle # Print both stack and handles Sample output (handle mode): Page allocated via order 0, migratetype Unmovable, gfp_mask 0x1100ca, pid 1, tgid 1 (systemd), ts 123456789 ns PFN 0x1000 type Unmovable Block 1 type Unmovable Flags 0x3fffe800000084(referenced|lru|active|private|node=0|zone=1) handle: 17432583 ... This implementation uses per-file-descriptor filter state stored in file->private_data, allowing each opener to have independent filter configuration. Link: https://lore.kernel.org/20260625043101.338794-1-zhen.ni@easystack.cn Link: https://lore.kernel.org/20260625043101.338794-2-zhen.ni@easystack.cn Signed-off-by: Zhen Ni Tested-by: Zi Yan Acked-by: Zi Yan Cc: Brendan Jackman Cc: David Hildenbrand Cc: Johannes Weiner Cc: Jonathan Corbet Cc: Liam Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Yichong Chen Cc: Ye Liu Signed-off-by: Andrew Morton --- mm/page_owner.c | 129 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 123 insertions(+), 6 deletions(-) diff --git a/mm/page_owner.c b/mm/page_owner.c index efbf67d54ee2..9e602b581743 100644 --- a/mm/page_owner.c +++ b/mm/page_owner.c @@ -54,6 +54,23 @@ struct stack_print_ctx { u8 flags; }; +enum page_owner_print_mode { + PAGE_OWNER_PRINT_STACK, + PAGE_OWNER_PRINT_HANDLE, + PAGE_OWNER_PRINT_STACK_HANDLE, +}; + +static const char * const page_owner_print_mode_strings[] = { + [PAGE_OWNER_PRINT_STACK] = "stack", + [PAGE_OWNER_PRINT_HANDLE] = "handle", + [PAGE_OWNER_PRINT_STACK_HANDLE] = "stack_handle", +}; + +struct page_owner_filter_state { + enum page_owner_print_mode print_mode; + spinlock_t lock; +}; + static bool page_owner_enabled __initdata; DEFINE_STATIC_KEY_FALSE(page_owner_inited); @@ -580,16 +597,23 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret, static ssize_t print_page_owner(char __user *buf, size_t count, unsigned long pfn, struct page *page, struct page_owner *page_owner, - depot_stack_handle_t handle) + depot_stack_handle_t handle, + struct page_owner_filter_state *state) { int ret, pageblock_mt, page_mt; char *kbuf; + enum page_owner_print_mode print_mode; + unsigned long flags; count = min_t(size_t, count, PAGE_SIZE); kbuf = kmalloc(count, GFP_KERNEL); if (!kbuf) return -ENOMEM; + spin_lock_irqsave(&state->lock, flags); + print_mode = state->print_mode; + spin_unlock_irqrestore(&state->lock, flags); + ret = scnprintf(kbuf, count, "Page allocated via order %u, mask %#x(%pGg), pid %d, tgid %d (%s), ts %llu ns\n", page_owner->order, page_owner->gfp_mask, @@ -608,9 +632,18 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn, migratetype_names[pageblock_mt], &page->flags.f); - ret += stack_depot_snprint(handle, kbuf + ret, count - ret, 0); - if (ret >= count) - goto err; + if (print_mode != PAGE_OWNER_PRINT_HANDLE) { + ret += stack_depot_snprint(handle, kbuf + ret, count - ret, 0); + if (ret >= count) + goto err; + } + + if (print_mode != PAGE_OWNER_PRINT_STACK) { + ret += scnprintf(kbuf + ret, count - ret, "handle: %u\n", + handle); + if (ret >= count) + goto err; + } if (page_owner->last_migrate_reason != MR_NEVER) { ret += scnprintf(kbuf + ret, count - ret, @@ -698,6 +731,7 @@ read_page_owner(struct file *file, char __user *buf, size_t count, loff_t *ppos) struct page_ext *page_ext; struct page_owner *page_owner; depot_stack_handle_t handle; + struct page_owner_filter_state *state = file->private_data; if (!static_branch_unlikely(&page_owner_inited)) return -EINVAL; @@ -775,7 +809,7 @@ read_page_owner(struct file *file, char __user *buf, size_t count, loff_t *ppos) page_owner_tmp = *page_owner; page_ext_put(page_ext); return print_page_owner(buf, count, pfn, page, - &page_owner_tmp, handle); + &page_owner_tmp, handle, state); ext_put_continue: page_ext_put(page_ext); } @@ -864,7 +898,90 @@ static void init_early_allocated_pages(void) init_pages_in_zone(zone); } +static int page_owner_open(struct inode *inode, struct file *file) +{ + struct page_owner_filter_state *state; + + state = kzalloc_obj(*state); + if (!state) + return -ENOMEM; + + spin_lock_init(&state->lock); + state->print_mode = PAGE_OWNER_PRINT_STACK; + file->private_data = state; + return 0; +} + +static int page_owner_release(struct inode *inode, struct file *file) +{ + kfree(file->private_data); + return 0; +} + +static ssize_t page_owner_write(struct file *file, + const char __user *buf, + size_t count, loff_t *ppos) +{ + char *kbuf; + char *orig; + char *token; + int ret; + size_t max_input_len; + struct page_owner_filter_state *state = file->private_data; + enum page_owner_print_mode new_print_mode; + unsigned long flags; + + /* + * Maximum input length for filter commands: + * 32: print_mode command max length is 17 ("mode=stack_handle"). + */ + max_input_len = 32; + + if (count > max_input_len) + return -EINVAL; + + kbuf = memdup_user_nul(buf, count); + if (IS_ERR(kbuf)) + return PTR_ERR(kbuf); + + orig = kbuf; + + spin_lock_irqsave(&state->lock, flags); + new_print_mode = state->print_mode; + spin_unlock_irqrestore(&state->lock, flags); + + while ((token = strsep(&kbuf, " \t\n")) != NULL) { + if (*token == '\0') + continue; + + if (!strncmp(token, "mode=", 5)) { + ret = sysfs_match_string(page_owner_print_mode_strings, + token + 5); + if (ret < 0) + goto out_free; + new_print_mode = ret; + } else { + ret = -EINVAL; + goto out_free; + } + } + + spin_lock_irqsave(&state->lock, flags); + state->print_mode = new_print_mode; + spin_unlock_irqrestore(&state->lock, flags); + + ret = count; + +out_free: + kfree(orig); + return ret; +} + static const struct file_operations page_owner_fops = { + .owner = THIS_MODULE, + .open = page_owner_open, + .release = page_owner_release, + .write = page_owner_write, .read = read_page_owner, .llseek = lseek_page_owner, }; @@ -995,7 +1112,7 @@ static int __init pageowner_init(void) return 0; } - debugfs_create_file("page_owner", 0400, NULL, NULL, &page_owner_fops); + debugfs_create_file("page_owner", 0600, NULL, NULL, &page_owner_fops); dir = debugfs_create_dir("page_owner_stacks", NULL); debugfs_create_file("show_stacks", 0400, dir, (void *)(STACK_PRINT_FLAG_STACK | From ef69581d58976f542f4c18a6a5d80f01c0162f0a Mon Sep 17 00:00:00 2001 From: Andrew Morton Date: Thu, 25 Jun 2026 12:18:25 -0700 Subject: [PATCH 173/562] mm-page_owner-add-print_mode-filter-fix remove local max_input_len. per Zi Yan Cc: Brendan Jackman Cc: David Hildenbrand Cc: Johannes Weiner Cc: Jonathan Corbet Cc: Liam Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Ye Liu Cc: Yichong Chen Cc: Zhen Ni Cc: Zi Yan Signed-off-by: Andrew Morton --- mm/page_owner.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/mm/page_owner.c b/mm/page_owner.c index 9e602b581743..828a0da5e27b 100644 --- a/mm/page_owner.c +++ b/mm/page_owner.c @@ -926,7 +926,6 @@ static ssize_t page_owner_write(struct file *file, char *orig; char *token; int ret; - size_t max_input_len; struct page_owner_filter_state *state = file->private_data; enum page_owner_print_mode new_print_mode; unsigned long flags; @@ -935,9 +934,7 @@ static ssize_t page_owner_write(struct file *file, * Maximum input length for filter commands: * 32: print_mode command max length is 17 ("mode=stack_handle"). */ - max_input_len = 32; - - if (count > max_input_len) + if (count > 32) return -EINVAL; kbuf = memdup_user_nul(buf, count); From 7a03fe13b359e822385005d02f68f25fa8dc53ba Mon Sep 17 00:00:00 2001 From: Zhen Ni Date: Thu, 25 Jun 2026 12:30:59 +0800 Subject: [PATCH 174/562] mm/page_owner: add NUMA node filter Add NUMA node filtering functionality to page_owner to allow filtering pages by specific NUMA node(s). This is useful for NUMA-aware memory allocation analysis and debugging. The filter supports flexible input formats: - Single node: nid=0 - Multiple nodes: nid=0,2,3 - Node range: nid=0-3 - Mixed format: nid=0,2-4,7 Example usage: # Using the page_owner_filter tool (recommended) ./page_owner_filter -n 0-3 ./page_owner_filter -m stack_handle -n 0,2-4,7 The implementation uses per-file-descriptor filter state stored in file->private_data, allowing each opener to have independent filter configuration. It uses nodemask_t for efficient multi-node filtering and nodelist_parse() for flexible input parsing. Node validity is verified using nodes_subset() to reject nodes without memory. Link: https://lore.kernel.org/20260625043101.338794-3-zhen.ni@easystack.cn Signed-off-by: Zhen Ni Tested-by: Zi Yan Acked-by: Zi Yan Cc: Brendan Jackman Cc: David Hildenbrand Cc: Johannes Weiner Cc: Jonathan Corbet Cc: Liam Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Ye Liu Cc: Yichong Chen Signed-off-by: Andrew Morton --- mm/page_owner.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/mm/page_owner.c b/mm/page_owner.c index 828a0da5e27b..fe92affbef90 100644 --- a/mm/page_owner.c +++ b/mm/page_owner.c @@ -68,6 +68,8 @@ static const char * const page_owner_print_mode_strings[] = { struct page_owner_filter_state { enum page_owner_print_mode print_mode; + nodemask_t nid_filter; + bool nid_filter_enabled; spinlock_t lock; }; @@ -732,6 +734,7 @@ read_page_owner(struct file *file, char __user *buf, size_t count, loff_t *ppos) struct page_owner *page_owner; depot_stack_handle_t handle; struct page_owner_filter_state *state = file->private_data; + unsigned long flags; if (!static_branch_unlikely(&page_owner_inited)) return -EINVAL; @@ -803,6 +806,27 @@ read_page_owner(struct file *file, char __user *buf, size_t count, loff_t *ppos) if (!handle) goto ext_put_continue; + spin_lock_irqsave(&state->lock, flags); + if (state->nid_filter_enabled) { + int nid; + memdesc_flags_t page_flags = READ_ONCE(page->flags); + + /* + * Bypass PF_POISONED_CHECK() in page_to_nid() to avoid + * VM_BUG_ON when accessing poisoned pages. + */ + if (page_flags.f == PAGE_POISON_PATTERN) { + spin_unlock_irqrestore(&state->lock, flags); + goto ext_put_continue; + } + nid = memdesc_nid(page_flags); + if (!node_isset(nid, state->nid_filter)) { + spin_unlock_irqrestore(&state->lock, flags); + goto ext_put_continue; + } + } + spin_unlock_irqrestore(&state->lock, flags); + /* Record the next PFN to read in the file offset */ *ppos = pfn + 1; @@ -812,6 +836,7 @@ read_page_owner(struct file *file, char __user *buf, size_t count, loff_t *ppos) &page_owner_tmp, handle, state); ext_put_continue: page_ext_put(page_ext); + cond_resched(); } return 0; @@ -908,6 +933,8 @@ static int page_owner_open(struct inode *inode, struct file *file) spin_lock_init(&state->lock); state->print_mode = PAGE_OWNER_PRINT_STACK; + nodes_clear(state->nid_filter); + state->nid_filter_enabled = false; file->private_data = state; return 0; } @@ -927,14 +954,20 @@ static ssize_t page_owner_write(struct file *file, char *token; int ret; struct page_owner_filter_state *state = file->private_data; + nodemask_t new_nid_filter; + bool new_nid_filter_enabled; enum page_owner_print_mode new_print_mode; unsigned long flags; /* * Maximum input length for filter commands: - * 32: print_mode command max length is 17 ("mode=stack_handle"). + * - 32: print_mode command max length is 17 ("mode=stack_handle") + * with sufficient buffer + * - 6 * MAX_NUMNODES: worst case for nid list + * Worst case per node: ",NNNNN" (comma + 5-digit node number) = 6 + * bytes */ - if (count > 32) + if (count > 32 + 6 * MAX_NUMNODES) return -EINVAL; kbuf = memdup_user_nul(buf, count); @@ -945,6 +978,8 @@ static ssize_t page_owner_write(struct file *file, spin_lock_irqsave(&state->lock, flags); new_print_mode = state->print_mode; + new_nid_filter = state->nid_filter; + new_nid_filter_enabled = state->nid_filter_enabled; spin_unlock_irqrestore(&state->lock, flags); while ((token = strsep(&kbuf, " \t\n")) != NULL) { @@ -957,14 +992,37 @@ static ssize_t page_owner_write(struct file *file, if (ret < 0) goto out_free; new_print_mode = ret; + } else if (!strncmp(token, "nid=", 4)) { + ret = nodelist_parse(token + 4, new_nid_filter); + if (ret < 0) + goto out_free; + + if (nodes_empty(new_nid_filter)) { + ret = -EINVAL; + goto out_free; + } + + /* + * We want to filter memory allocations by numa nodes, so make sure + * that the specified nodes have memory. + */ + if (!nodes_subset(new_nid_filter, node_states[N_MEMORY])) { + ret = -EINVAL; + goto out_free; + } + + new_nid_filter_enabled = true; } else { ret = -EINVAL; goto out_free; } } + /* Commit all filter changes */ spin_lock_irqsave(&state->lock, flags); state->print_mode = new_print_mode; + state->nid_filter = new_nid_filter; + state->nid_filter_enabled = new_nid_filter_enabled; spin_unlock_irqrestore(&state->lock, flags); ret = count; From 0823e197a576eab55dbb07d3283acb2758604f70 Mon Sep 17 00:00:00 2001 From: Zhen Ni Date: Thu, 25 Jun 2026 12:31:00 +0800 Subject: [PATCH 175/562] tools/mm: add page_owner_filter userspace tool Add a userspace filtering tool for page_owner that supports per-fd filtering with print_mode and NUMA node filters. Features: - Three print modes: stack (default), handle, stack_handle - NUMA node filtering with flexible formats (single: 0, multiple: 0,1,2, range: 0-3, mixed: 0,2-3) - Per-file-descriptor filter state for independent filtering Usage examples: # Filter by print mode ./page_owner_filter -m handle ./page_owner_filter -m stack_handle # Filter by NUMA node ./page_owner_filter -n 0 ./page_owner_filter -n 0-3 # Combined filters ./page_owner_filter -m stack -n 0,1,2 ./page_owner_filter -m handle -n 0,2-3 The tool validates inputs before sending commands to the kernel and provides clear error messages when the kernel does not support per-fd filtering. Link: https://lore.kernel.org/20260625043101.338794-4-zhen.ni@easystack.cn Signed-off-by: Zhen Ni Tested-by: Zi Yan Cc: Brendan Jackman Cc: David Hildenbrand Cc: Johannes Weiner Cc: Jonathan Corbet Cc: Liam Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Ye Liu Cc: Yichong Chen Signed-off-by: Andrew Morton --- tools/mm/Makefile | 4 +- tools/mm/page_owner_filter.c | 310 +++++++++++++++++++++++++++++++++++ 2 files changed, 312 insertions(+), 2 deletions(-) create mode 100644 tools/mm/page_owner_filter.c diff --git a/tools/mm/Makefile b/tools/mm/Makefile index f5725b5c23aa..858186a6eefd 100644 --- a/tools/mm/Makefile +++ b/tools/mm/Makefile @@ -3,7 +3,7 @@ # include ../scripts/Makefile.include -BUILD_TARGETS=page-types slabinfo page_owner_sort thp_swap_allocator_test +BUILD_TARGETS=page-types slabinfo page_owner_sort page_owner_filter thp_swap_allocator_test INSTALL_TARGETS = $(BUILD_TARGETS) thpmaps LIB_DIR = ../lib/api @@ -23,7 +23,7 @@ $(LIBS): $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) clean: - $(RM) page-types slabinfo page_owner_sort thp_swap_allocator_test + $(RM) page-types slabinfo page_owner_sort page_owner_filter thp_swap_allocator_test make -C $(LIB_DIR) clean sbindir ?= /usr/sbin diff --git a/tools/mm/page_owner_filter.c b/tools/mm/page_owner_filter.c new file mode 100644 index 000000000000..1d1f0a38678a --- /dev/null +++ b/tools/mm/page_owner_filter.c @@ -0,0 +1,310 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * User-space helper to filter page_owner output per-fd + * + * Example use: + * ./page_owner_filter -m handle + * ./page_owner_filter -m stack_handle + * ./page_owner_filter -n 0,1,2 + * + * See Documentation/mm/page_owner.rst + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_CMD_LEN 512 + +static void usage(const char *prog) +{ + fprintf(stderr, "Usage: %s [OPTIONS]\n", prog); + fprintf(stderr, "\nOptions:\n"); + fprintf(stderr, " -m, --mode MODE : print_mode (stack, handle, or stack_handle)\n"); + fprintf(stderr, " -n, --nid NID_LIST : NUMA node IDs (comma-separated or ranges)\n"); + fprintf(stderr, " -o, --output FILE : output file (default: stdout)\n"); + fprintf(stderr, " -h, --help : show this help message\n"); + fprintf(stderr, "\nExamples:\n"); + fprintf(stderr, " %s -m stack\n", prog); + fprintf(stderr, " %s -m handle\n", prog); + fprintf(stderr, " %s -m stack_handle\n", prog); + fprintf(stderr, " %s -m stack -o output.txt\n", prog); + fprintf(stderr, " %s -n 0,1,2\n", prog); + fprintf(stderr, " %s -m stack -n 0\n", prog); +} + +static int validate_mode(const char *mode) +{ + if (strcmp(mode, "stack") == 0 || + strcmp(mode, "handle") == 0 || + strcmp(mode, "stack_handle") == 0) + return 0; + + fprintf(stderr, "Error: Invalid mode '%s'\n", mode); + fprintf(stderr, "Valid modes: stack, handle, stack_handle\n"); + return -1; +} + +static int validate_nid_list(const char *nid_list) +{ + const char *p; + int i = 0; + int has_digit = 0; + int in_range = 0; + int prev_num = 0; + int curr_num = 0; + + if (!nid_list || strlen(nid_list) == 0) + return 0; + + for (p = nid_list; *p; p++) { + if (*p == ',') { + if (!has_digit) { + fprintf(stderr, "Error: Invalid nid_list format\n"); + return -1; + } + if (in_range && prev_num > curr_num) { + fprintf(stderr, + "Error: Invalid range %d-%d (start must be <= end)\n", + prev_num, curr_num); + return -1; + } + i = 0; + has_digit = 0; + in_range = 0; + prev_num = 0; + curr_num = 0; + continue; + } + + if (*p == '-') { + if (!has_digit) { + fprintf(stderr, + "Error: Invalid nid_list format "); + fprintf(stderr, + "(dash without preceding number)\n"); + return -1; + } + if (in_range) { + fprintf(stderr, "Error: Multiple dashes in nid_list\n"); + return -1; + } + prev_num = curr_num; + curr_num = 0; + i = 0; + has_digit = 0; + in_range = 1; + continue; + } + + if (!isdigit((unsigned char)*p)) { + fprintf(stderr, "Error: Invalid character '%c' in nid_list\n", *p); + return -1; + } + + if (i > 5) { + fprintf(stderr, "Error: NID too long (max 65536)\n"); + return -1; + } + curr_num = curr_num * 10 + (*p - '0'); + i++; + has_digit = 1; + } + + if (!has_digit) { + fprintf(stderr, "Error: Invalid nid_list format\n"); + return -1; + } + + if (in_range && prev_num > curr_num) { + fprintf(stderr, + "Error: Invalid range %d-%d (start must be <= end)\n", + prev_num, curr_num); + return -1; + } + + return 0; +} + +int main(int argc, char *argv[]) +{ + const char *output_file = NULL; + char filter_cmd[MAX_CMD_LEN]; + FILE *output = NULL; + int fd = -1; + ssize_t ret; + char buf[4096]; + int opt; + size_t cmd_len = 0; + + signal(SIGPIPE, SIG_IGN); + + static struct option long_options[] = { + {"mode", required_argument, 0, 'm'}, + {"nid", required_argument, 0, 'n'}, + {"output", required_argument, 0, 'o'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; + + filter_cmd[0] = '\0'; + + if (argc > 1) { + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { + usage(argv[0]); + return 0; + } + } + } + + /* Check if page_owner exists and is readable */ + if (access("/sys/kernel/debug/page_owner", F_OK) != 0) { + if (errno == ENOENT) + fprintf(stderr, "Error: /sys/kernel/debug/page_owner does not exist\n"); + else + perror("Error accessing /sys/kernel/debug/page_owner"); + fprintf(stderr, "Make sure page_owner is enabled in kernel\n"); + return 1; + } + + while ((opt = getopt_long(argc, argv, "m:n:o:h", long_options, NULL)) != -1) { + int len; + + switch (opt) { + case 'm': { + const char *mode = optarg; + + if (validate_mode(mode) < 0) + return 1; + len = snprintf(filter_cmd + cmd_len, MAX_CMD_LEN - cmd_len, + "%smode=%s", cmd_len > 0 ? " " : "", mode); + if (len < 0 || cmd_len + len >= MAX_CMD_LEN) { + fprintf(stderr, "Error: Command too long\n"); + return 1; + } + cmd_len += len; + break; + } + case 'n': { + const char *nid_list = optarg; + + if (validate_nid_list(nid_list) < 0) + return 1; + len = snprintf(filter_cmd + cmd_len, MAX_CMD_LEN - cmd_len, + "%snid=%s", cmd_len > 0 ? " " : "", nid_list); + if (len < 0 || cmd_len + len >= MAX_CMD_LEN) { + fprintf(stderr, "Error: Command too long\n"); + return 1; + } + cmd_len += len; + break; + } + case 'o': + output_file = optarg; + break; + case 'h': + /* Already handled above */ + break; + default: + usage(argv[0]); + return 1; + } + } + + /* At least one filter must be specified */ + if (cmd_len == 0) { + fprintf(stderr, "Error: At least one filter (-m or -n) must be specified\n\n"); + usage(argv[0]); + return 1; + } + + /* Open page_owner for read-write - this will fail if kernel doesn't support write */ + fd = open("/sys/kernel/debug/page_owner", O_RDWR); + if (fd < 0) { + if (errno == EACCES || errno == EPERM) { + fprintf(stderr, "Error: /sys/kernel/debug/page_owner "); + fprintf(stderr, "does not support write access\n"); + fprintf(stderr, "This kernel does not support "); + fprintf(stderr, "per-fd filtering.\n"); + fprintf(stderr, "Please ensure you have a kernel with "); + fprintf(stderr, "per-fd filtering support.\n"); + } else { + perror("Error opening /sys/kernel/debug/page_owner"); + } + return 1; + } + + if (output_file) { + output = fopen(output_file, "w"); + if (!output) { + perror("open output file"); + close(fd); + return 1; + } + } else { + output = stdout; + } + + ret = write(fd, filter_cmd, strlen(filter_cmd)); + + if (ret < 0) { + if (errno == EINVAL) { + fprintf(stderr, "Error: Kernel rejected the filter command.\n"); + fprintf(stderr, "Possible causes:\n"); + fprintf(stderr, " - Kernel does not support per-fd filtering\n"); + fprintf(stderr, " - NUMA node has no memory\n"); + fprintf(stderr, " - Unknown reason\n"); + } else { + perror("write filter command"); + } + goto out; + } + + if ((size_t)ret != strlen(filter_cmd)) + fprintf(stderr, "Warning: Partial write (%zd/%zu)\n", ret, strlen(filter_cmd)); + + /* Read and display filtered output */ + ret = 0; + while ((ret = read(fd, buf, sizeof(buf))) > 0) { + size_t written = fwrite(buf, 1, ret, output); + + if (written != (size_t)ret) { + if (errno == EPIPE) { + /* Pipe closed, treat as success */ + ret = 0; + goto out; + } + perror("write output"); + ret = -1; + goto out; + } + } + + if (ret < 0) { + perror("read page_owner"); + goto out; + } + + if (fflush(output)) { + if (errno == EPIPE) { + /* Pipe closed, treat as success */ + ret = 0; + } else { + perror("flush output"); + ret = -1; + } + } + +out: + close(fd); + if (output != stdout) + fclose(output); + return ret < 0 ? 1 : 0; +} From 4582f1cfeeeace8f95e2c97751e10a91caf029ec Mon Sep 17 00:00:00 2001 From: Zhen Ni Date: Thu, 25 Jun 2026 12:31:01 +0800 Subject: [PATCH 176/562] mm/page_owner: document page_owner filter Add documentation for the page_owner_filter userspace tool and kernel-level filtering features. Link: https://lore.kernel.org/20260625043101.338794-5-zhen.ni@easystack.cn Signed-off-by: Zhen Ni Tested-by: Zi Yan Cc: Brendan Jackman Cc: David Hildenbrand Cc: Johannes Weiner Cc: Jonathan Corbet Cc: Liam Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Ye Liu Cc: Yichong Chen Signed-off-by: Andrew Morton --- Documentation/mm/page_owner.rst | 77 ++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 2 deletions(-) diff --git a/Documentation/mm/page_owner.rst b/Documentation/mm/page_owner.rst index 6b12f3b007ec..383e59c42743 100644 --- a/Documentation/mm/page_owner.rst +++ b/Documentation/mm/page_owner.rst @@ -65,7 +65,14 @@ un-tracking state. Usage ===== -1) Build user-space helper:: +1) Build user-space helpers:: + +To filter page_owner output: + + cd tools/mm + make page_owner_filter + +To sort and analyze page_owner output: cd tools/mm make page_owner_sort @@ -74,7 +81,11 @@ Usage 3) Do the job that you want to debug. -4) Analyze information from page owner:: +4) (Optional) Filter page_owner output:: + + ./page_owner_filter -m handle -n 0,1,2 > filtered_page_owner.txt + +5) Analyze information from page owner:: cat /sys/kernel/debug/page_owner_stacks/show_stacks > stacks.txt cat stacks.txt @@ -263,3 +274,65 @@ STANDARD FORMAT SPECIFIERS f free whether the page has been released or not st stacktrace stack trace of the page allocation ator allocator memory allocator for pages + +Filtering page_owner output +============================ + +page_owner supports filtering output at the kernel level before reading, +which reduces the amount of data that needs to be processed in userspace. + +The page_owner_filter tool provides a convenient interface for this filtering +capability. It supports two types of filters: + +1. **print_mode filter**: Control what information is printed for each page + - ``stack``: Print full stack traces (default, compatible with existing usage) + - ``handle``: Print only stack handle numbers (much faster, smaller output) + - ``stack_handle``: Print both stack traces and handle numbers + + The ``handle`` mode uses numeric identifiers instead of full stack traces. + The mapping from handles to actual stack traces can be obtained via the + show_stacks_handles interface. + +2. **NUMA node filter**: Filter pages by NUMA node ID + - Supports single node: ``-n 0`` + - Multiple nodes: ``-n 0,1,2`` + - Ranges: ``-n 0-3`` + - Mixed format: ``-n 0,2-3,5`` + +Usage examples:: + + # Filter by print mode + ./page_owner_filter -m handle + ./page_owner_filter -m stack_handle + + # Filter by NUMA node + ./page_owner_filter -n 0 + ./page_owner_filter -n 0-3 + + # Combined filters + ./page_owner_filter -m stack -n 0,1,2 + ./page_owner_filter -m handle -n 0,2-3 + + # Save to file + ./page_owner_filter -m handle -o filtered_output.txt + +The handle mode is particularly useful for monitoring and performance-critical +scenarios as it dramatically reduces output size. Testing shows handle mode can +reduce output size by ~66% (84MB vs 244MB) and improve read performance by ~4.4x +compared to full stack output. + +The NUMA node filter is useful for NUMA-aware memory allocation analysis and debugging. + +Behind the scenes, page_owner_filter opens /sys/kernel/debug/page_owner and +writes filter commands before reading the filtered output. The filtering uses +per-file-descriptor state, allowing each open() to have independent filter settings. + +Each file descriptor maintains its own filter state, so you can have multiple +independent filtering operations running concurrently. For example, in different +terminals you can run different filters simultaneously:: + + # Terminal 1: Filter node 0 + ./page_owner_filter -n 0 > node0_output.txt + + # Terminal 2: Filter node 1 (runs concurrently) + ./page_owner_filter -n 1 > node1_output.txt From c0d1a601ae1da4979bc4dcb2135d53a0dc2745f6 Mon Sep 17 00:00:00 2001 From: "Matthew Wilcox (Oracle)" Date: Mon, 15 Jun 2026 17:22:41 +0100 Subject: [PATCH 177/562] mm: add writeback.h to docs build There's four functions in this header file with kernel-doc; add them to the htmldocs. Link: https://lore.kernel.org/20260615162244.2170866-1-willy@infradead.org Signed-off-by: Matthew Wilcox (Oracle) Cc: Andreas Gruenbacher Signed-off-by: Andrew Morton --- Documentation/core-api/mm-api.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/core-api/mm-api.rst b/Documentation/core-api/mm-api.rst index aabdd3cba58e..4df7d5edbee5 100644 --- a/Documentation/core-api/mm-api.rst +++ b/Documentation/core-api/mm-api.rst @@ -73,6 +73,7 @@ Readahead Writeback --------- +.. kernel-doc:: include/linux/writeback.h .. kernel-doc:: mm/page-writeback.c :export: From 92d820d9e7f98d9291ea2d47fbe965cae7e87ee3 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Mon, 15 Jun 2026 17:22:42 +0100 Subject: [PATCH 178/562] writeback.h: fix a typo in the wbc_init_bio() description initializtion -> initialization (missing "a") Link: https://lore.kernel.org/20260615162244.2170866-2-willy@infradead.org Signed-off-by: Andreas Gruenbacher Signed-off-by: Matthew Wilcox (Oracle) Signed-off-by: Andrew Morton --- include/linux/writeback.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/writeback.h b/include/linux/writeback.h index 62552a2ce5b9..b749a9a5a5ee 100644 --- a/include/linux/writeback.h +++ b/include/linux/writeback.h @@ -244,7 +244,7 @@ void wbc_attach_fdatawrite_inode(struct writeback_control *wbc, struct inode *inode); /** - * wbc_init_bio - writeback specific initializtion of bio + * wbc_init_bio - writeback specific initialization of bio * @wbc: writeback_control for the writeback in progress * @bio: bio to be initialized * From 68f6ac7502c20a876eea5eb1f21a24f06c5bcd18 Mon Sep 17 00:00:00 2001 From: Brendan Jackman Date: Mon, 15 Jun 2026 10:54:34 +0000 Subject: [PATCH 179/562] mm/page_alloc: drop flag-conversion "optimisation" This code uses flag equivalences to try to optimise conversion from GFP_ to ALLOC_ but there's no clear reason to believe it makes things faster. Even if it gets rid of conditional branches, it just trades them for a data dependency. CPUs are pretty good at conditional branches. But, in my GCC x86 build it doesn't look like there are any branches anyway, the compiler found some conditional instruction tricks. (Caveat: This was extracted & annotated by Gemini AI, I did not actually read the disasm myself) Old code: ae50: 8b 04 24 mov (%rsp),%eax # Load gfp_mask ... ae5d: 41 89 c4 mov %eax,%r12d ae64: 41 81 e4 20 08 00 00 and $0x820,%r12d # Mask both flags at once ... ae6f: 44 89 e1 mov %r12d,%ecx ae77: 83 c9 40 or $0x40,%ecx # OR with ALLOC_CPUSET (0x40) ae7a: 89 4c 24 60 mov %ecx,0x60(%rsp) # Store to alloc_flags New code: For __GFP_HIGH ( 0x20 ): It uses the Carry Flag (via sbb ) to conditionally add 0x20 to the base 0x40 ( ALLOC_CPUSET ) flag: ae63: 83 e0 20 and $0x20,%eax # Test __GFP_HIGH ... ae6a: 83 f8 01 cmp $0x1,%eax # Set carry flag if 0 ae6f: 45 19 e4 sbb %r12d,%r12d # %r12d = (gfp & 0x20) ? 0 : -1 ae80: 41 83 e4 e0 and $0xffffffe0,%r12d # %r12d = (gfp & 0x20) ? 0 : -32 ae87: 41 83 c4 60 add $0x60,%r12d # %r12d = (gfp & 0x20) ? 0x60 : 0x40 For __GFP_KSWAPD_RECLAIM ( 0x800 ): It uses a conditional move ( cmov ) later in the function to set the ALLOC_KSWAPD ( 0x800 ) bit: ae72: 25 00 08 00 00 and $0x800,%eax # Test __GFP_KSWAPD_RECLAIM ae77: 89 44 24 30 mov %eax,0x30(%rsp) # Store result ... af2c: 80 cf 08 or $0x8,%bh # Set ALLOC_KSWAPD (0x800) in temp reg af2f: 45 85 c9 test %r9d,%r9d # Check if __GFP_KSWAPD_RECLAIM was set af32: 0f 44 d8 cmove %eax,%ebx # If not, revert to flags without it Testing with a modified version[0] of lib/free_pages_test.c (adding printks with timing)... Old results from a Sapphire Rapids consumer CPU: [ 67.157118] page_alloc_test: Testing with GFP_KERNEL [ 67.157122] page_alloc_test: Starting 1,000,000 allocations... [ 70.704446] page_alloc_test: Completed. Time: 3543002 us (Avg: 3543.00 ns per alloc+free loop) [ 70.704456] page_alloc_test: Testing with GFP_KERNEL | __GFP_COMP [ 70.704460] page_alloc_test: Starting 1,000,000 allocations... [ 70.944672] page_alloc_test: Completed. Time: 239980 us (Avg: 239.98 ns per alloc+free loop) [ 70.944675] page_alloc_test: Test completed New results: [ 70.079015] page_alloc_test: Testing with GFP_KERNEL [ 70.079020] page_alloc_test: Starting 1,000,000 allocations... [ 73.669396] page_alloc_test: Completed. Time: 3586954 us (Avg: 3586.95 ns per alloc+free loop) [ 73.669402] page_alloc_test: Testing with GFP_KERNEL | __GFP_COMP [ 73.669405] page_alloc_test: Starting 1,000,000 allocations... [ 73.905084] page_alloc_test: Completed. Time: 235496 us (Avg: 235.49 ns per alloc+free loop) [ 73.905086] page_alloc_test: Test completed Seems like a wash. So, drop the flag value coupling here and let the compiler and CPU do their job. Superscalar CPUs are pretty neat after all. (Used AI for the disasm but the rest is all manual). Link: https://lore.kernel.org/20260629-gfp-pessimisation-v2-1-311ece6a8637@google.com Link: https://lore.kernel.org/20260615-gfp-pessimisation-v2-1-65f1319e6818@google.com Link: https://github.com/bjackman/aethelred/blob/2ccdc84ef087c2a631914f58e106e99e19bd3b98/page-alloc-test/page-alloc-test.c [1] Signed-off-by: Brendan Jackman Reviewed-by: Zi Yan Reviewed-by: Vlastimil Babka (SUSE) Reviewed-by: Gregory Price Acked-by: Johannes Weiner Acked-by: Harry Yoo (Oracle) Signed-off-by: Andrew Morton --- mm/page_alloc.c | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/mm/page_alloc.c b/mm/page_alloc.c index dbe632f6300d..a460811eaa4d 100644 --- a/mm/page_alloc.c +++ b/mm/page_alloc.c @@ -3741,13 +3741,10 @@ static bool zone_allows_reclaim(struct zone *local_zone, struct zone *zone) static inline unsigned int alloc_flags_nofragment(struct zone *zone, gfp_t gfp_mask) { - unsigned int alloc_flags; + unsigned int alloc_flags = 0; - /* - * __GFP_KSWAPD_RECLAIM is assumed to be the same as ALLOC_KSWAPD - * to save a branch. - */ - alloc_flags = (__force int) (gfp_mask & __GFP_KSWAPD_RECLAIM); + if (gfp_mask & __GFP_KSWAPD_RECLAIM) + alloc_flags |= ALLOC_KSWAPD; if (defrag_mode) { alloc_flags |= ALLOC_NOFRAGMENT; @@ -4480,22 +4477,16 @@ gfp_to_alloc_flags(gfp_t gfp_mask, unsigned int order) { unsigned int alloc_flags = ALLOC_WMARK_MIN | ALLOC_CPUSET; - /* - * __GFP_HIGH is assumed to be the same as ALLOC_MIN_RESERVE - * and __GFP_KSWAPD_RECLAIM is assumed to be the same as ALLOC_KSWAPD - * to save two branches. - */ - BUILD_BUG_ON(__GFP_HIGH != (__force gfp_t) ALLOC_MIN_RESERVE); - BUILD_BUG_ON(__GFP_KSWAPD_RECLAIM != (__force gfp_t) ALLOC_KSWAPD); - /* * The caller may dip into page reserves a bit more if the caller * cannot run direct reclaim, or if the caller has realtime scheduling * policy or is asking for __GFP_HIGH memory. GFP_ATOMIC requests will * set both ALLOC_NON_BLOCK and ALLOC_MIN_RESERVE(__GFP_HIGH). */ - alloc_flags |= (__force int) - (gfp_mask & (__GFP_HIGH | __GFP_KSWAPD_RECLAIM)); + if (gfp_mask & __GFP_HIGH) + alloc_flags |= ALLOC_MIN_RESERVE; + if (gfp_mask & __GFP_KSWAPD_RECLAIM) + alloc_flags |= ALLOC_KSWAPD; if (!(gfp_mask & __GFP_DIRECT_RECLAIM)) { /* From b75c9d9cb82362b50406eb3c6481be7465687c22 Mon Sep 17 00:00:00 2001 From: "Matthew Wilcox (Oracle)" Date: Wed, 11 Dec 2024 20:40:14 +0000 Subject: [PATCH 180/562] percpu_ref: fix documentation of maximum value Tejun changd percpu_ref to use long instead of int back in 2014 but missed updating this bit of the documentation. Also add the documentation to the htmldocs. Link: https://lore.kernel.org/20241211204017.184512-1-willy@infradead.org Signed-off-by: Matthew Wilcox (Oracle) Acked-by: Tejun Heo Signed-off-by: Andrew Morton --- Documentation/driver-api/basics.rst | 3 +++ include/linux/percpu-refcount.h | 5 +++-- lib/percpu-refcount.c | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Documentation/driver-api/basics.rst b/Documentation/driver-api/basics.rst index 8b6a5888cb11..3b182cfdf135 100644 --- a/Documentation/driver-api/basics.rst +++ b/Documentation/driver-api/basics.rst @@ -90,6 +90,9 @@ Reference counting .. kernel-doc:: lib/refcount.c :export: +.. kernel-doc:: include/linux/percpu-refcount.h +.. kernel-doc:: lib/percpu-refcount.c + Atomics ------- diff --git a/include/linux/percpu-refcount.h b/include/linux/percpu-refcount.h index d73a1c08c3e3..1e3212e2c827 100644 --- a/include/linux/percpu-refcount.h +++ b/include/linux/percpu-refcount.h @@ -12,8 +12,8 @@ * start shutting down you call percpu_ref_kill() _before_ dropping the initial * refcount. * - * The refcount will have a range of 0 to ((1U << 31) - 1), i.e. one bit less - * than an atomic_t - this is because of the way shutdown works, see + * The refcount will have a range of 0 to LONG_MAX, i.e. one bit less + * than an atomic_long_t - this is because of the way shutdown works, see * percpu_ref_kill()/PERCPU_COUNT_BIAS. * * Before you call percpu_ref_kill(), percpu_ref_put() does not check for the @@ -269,6 +269,7 @@ static inline bool percpu_ref_tryget(struct percpu_ref *ref) /** * percpu_ref_tryget_live_rcu - same as percpu_ref_tryget_live() but the * caller is responsible for taking RCU. + * @ref: percpu_ref to try-get * * This function is safe to call as long as @ref is between init and exit. */ diff --git a/lib/percpu-refcount.c b/lib/percpu-refcount.c index 97772e42b9b2..f8d90689af9f 100644 --- a/lib/percpu-refcount.c +++ b/lib/percpu-refcount.c @@ -289,7 +289,7 @@ static void __percpu_ref_switch_mode(struct percpu_ref *ref, * @confirm_switch: optional confirmation callback * * There's no reason to use this function for the usual reference counting. - * Use percpu_ref_kill[_and_confirm](). + * Use percpu_ref_kill() or percpu_ref_kill_and_confirm(). * * Schedule switching of @ref to atomic mode. All its percpu counts will * be collected to the main atomic counter. On completion, when all CPUs From 8332f363411a1d73e41568e068ad5c6e6055f68c Mon Sep 17 00:00:00 2001 From: Muchun Song Date: Fri, 12 Jun 2026 11:58:45 +0800 Subject: [PATCH 181/562] mm/hugetlb: fix boot panic with CONFIG_DEBUG_VM and HVO bootmem pages Patch series "mm: Refactor bootmem gigantic hugepage allocation", v4. This series is split out from the earlier larger series "mm: Generalize HVO for HugeTLB and device DAX" [1]. It collects the first 19 patches of that series as a standalone set of fixes and preparatory cleanups around bootmem HugeTLB handling, sparse initialization ordering, and related vmemmap setup. The first patches fix a few bugs found while reviewing the existing code, including incorrect bootmem HVO handling, wrong vmemmap registration arguments, a powerpc compound-vmemmap tracking bug, and too-late initialization of gigantic bootmem HugeTLB struct pages. The rest of the series reorders early memory initialization so the relevant zone state is available before sparse and HugeTLB boot-time setup runs, then simplifies the remaining bootmem gigantic hugepage allocation path and removes code made obsolete by that rework. At a high level: - patches [1-4] fix boot-time and arch-specific bugs - patches [5-12] reorder and simplify sparse/mm/hugetlb early init - patches [13-19] refactor bootmem gigantic hugepage allocation and remove obsolete helpers and state This patch (of 19): Commit 622026e87c40 ("mm/hugetlb: remove fake head pages") switched HVO to reuse per-zone shared tail pages from zone->vmemmap_tails[]. Those shared tail pages were initialized in hugetlb_vmemmap_init(), but bootmem HugeTLB folios are prepared earlier from gather_bootmem_prealloc(). With hugetlb_free_vmemmap=on, prep_and_add_bootmem_folios() can access pageblock flags on bootmem HugeTLB pages whose mirrored tail struct pages already point to the shared tail page. On CONFIG_DEBUG_VM kernels, get_pfnblock_bitmap_bitidx() then dereferences the still-uninitialized shared tail page and can panic during boot. Initialize zone->vmemmap_tails[] from gather_bootmem_prealloc(), before bootmem HugeTLB folios are processed, and drop the later initialization from hugetlb_vmemmap_init(). This bug only affects CONFIG_DEBUG_VM kernels, where the relevant assertion is evaluated. Link: https://lore.kernel.org/20260612035903.2468601-1-songmuchun@bytedance.com Link: https://lore.kernel.org/20260612035903.2468601-2-songmuchun@bytedance.com Fixes: 622026e87c40 ("mm/hugetlb: remove fake head pages") Signed-off-by: Muchun Song Acked-by: Oscar Salvador Cc: Michal Clapinski Cc: "Aneesh Kumar K.V" Cc: David Hildenbrand Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Madhavan Srinivasan Cc: Michael Ellerman Cc: Mike Rapoport Cc: Nicholas Piggin Cc: "Ritesh Harjani (IBM)" Cc: Vlastimil Babka Cc: Frank van der Linden Cc: Oscar Salvador (SUSE) Cc: Usama Arif Cc: Signed-off-by: Andrew Morton --- mm/hugetlb.c | 25 +++++++++++++++++++++++++ mm/hugetlb_vmemmap.c | 17 ----------------- mm/sparse-vmemmap.c | 2 +- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/mm/hugetlb.c b/mm/hugetlb.c index 3a0fcb84cc39..e72f36df07e4 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -3377,6 +3377,31 @@ static void __init gather_bootmem_prealloc(void) .max_threads = num_node_state(N_MEMORY), .numa_aware = true, }; +#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP + struct zone *zone; + + for_each_zone(zone) { + for (int i = 0; i < NR_VMEMMAP_TAILS; i++) { + struct page *tail, *p; + unsigned int order; + + tail = zone->vmemmap_tails[i]; + if (!tail) + continue; + + order = i + VMEMMAP_TAIL_MIN_ORDER; + p = page_to_virt(tail); + /* + * prep_and_add_bootmem_folios() can access pageblock + * flags on bootmem HugeTLB pages, so initialize the + * shared tail struct pages here before bootmem folios + * start using them. + */ + for (int j = 0; j < PAGE_SIZE / sizeof(struct page); j++) + init_compound_tail(p + j, NULL, order, zone); + } + } +#endif padata_do_multithreaded(&job); } diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c index 133b46dfb09f..c713c0d2593a 100644 --- a/mm/hugetlb_vmemmap.c +++ b/mm/hugetlb_vmemmap.c @@ -870,27 +870,10 @@ static const struct ctl_table hugetlb_vmemmap_sysctls[] = { static int __init hugetlb_vmemmap_init(void) { const struct hstate *h; - struct zone *zone; /* HUGETLB_VMEMMAP_RESERVE_SIZE should cover all used struct pages */ BUILD_BUG_ON(__NR_USED_SUBPAGE > HUGETLB_VMEMMAP_RESERVE_PAGES); - for_each_zone(zone) { - for (int i = 0; i < NR_VMEMMAP_TAILS; i++) { - struct page *tail, *p; - unsigned int order; - - tail = zone->vmemmap_tails[i]; - if (!tail) - continue; - - order = i + VMEMMAP_TAIL_MIN_ORDER; - p = page_to_virt(tail); - for (int j = 0; j < PAGE_SIZE / sizeof(struct page); j++) - init_compound_tail(p + j, NULL, order, zone); - } - } - for_each_hstate(h) { if (hugetlb_vmemmap_optimizable(h)) { register_sysctl_init("vm", hugetlb_vmemmap_sysctls); diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c index 99e2be39671b..bb23fb3077a3 100644 --- a/mm/sparse-vmemmap.c +++ b/mm/sparse-vmemmap.c @@ -342,7 +342,7 @@ static __meminit struct page *vmemmap_get_tail(unsigned int order, struct zone * * * Any initialization done here will be overwritten by memmap_init(). * - * hugetlb_vmemmap_init() will take care of initialization after + * gather_bootmem_prealloc() will take care of initialization after * memmap_init(). */ From 56a637bdb95bfa37ede6bfba98f1adae49f3d9c1 Mon Sep 17 00:00:00 2001 From: Muchun Song Date: Fri, 12 Jun 2026 11:58:46 +0800 Subject: [PATCH 182/562] mm/hugetlb_vmemmap: fix __hugetlb_vmemmap_optimize_folios() __hugetlb_vmemmap_optimize_folios() uses incorrect arguments when handling bootmem HugeTLB folios. The section number passed to register_page_bootmem_memmap() is derived from the vmemmap virtual address of folio->page instead of the folio PFN, so the bootmem memmap metadata can be registered against the wrong section. The helper is also given HUGETLB_VMEMMAP_RESERVE_SIZE even though it expects a page count, not a size in bytes. In addition, the write-protect range is based on pages_per_huge_page(h), which does not cover the full HugeTLB vmemmap area and can leave part of the shared tail vmemmap mapping writable. Fix the section lookup to use folio_pfn(folio), use HUGETLB_VMEMMAP_RESERVE_PAGES when registering the reserved memmap pages, and use hugetlb_vmemmap_size(h) for the write-protect range. Link: https://lore.kernel.org/20260612035903.2468601-3-songmuchun@bytedance.com Fixes: 752fe17af693 ("mm/hugetlb: add pre-HVO framework") Signed-off-by: Muchun Song Acked-by: Oscar Salvador Reviewed-by: Frank van der Linden Cc: "Aneesh Kumar K.V" Cc: David Hildenbrand Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Madhavan Srinivasan Cc: Michael Ellerman Cc: Mike Rapoport (Microsoft) Cc: Nicholas Piggin Cc: Oscar Salvador (SUSE) Cc: "Ritesh Harjani (IBM)" Cc: Usama Arif Cc: Vlastimil Babka Cc: Signed-off-by: Andrew Morton --- mm/hugetlb_vmemmap.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c index c713c0d2593a..ea6af85bfec1 100644 --- a/mm/hugetlb_vmemmap.c +++ b/mm/hugetlb_vmemmap.c @@ -635,12 +635,12 @@ static void __hugetlb_vmemmap_optimize_folios(struct hstate *h, * mirrored tail page structs RO. */ spfn = (unsigned long)&folio->page; - epfn = spfn + pages_per_huge_page(h); + epfn = spfn + hugetlb_vmemmap_size(h); vmemmap_wrprotect_hvo(spfn, epfn, folio_nid(folio), HUGETLB_VMEMMAP_RESERVE_SIZE); - register_page_bootmem_memmap(pfn_to_section_nr(spfn), + register_page_bootmem_memmap(pfn_to_section_nr(folio_pfn(folio)), &folio->page, - HUGETLB_VMEMMAP_RESERVE_SIZE); + HUGETLB_VMEMMAP_RESERVE_PAGES); continue; } From ba1dbcce0ce3c5026b2ef6e7b80a1659d3c3cbce Mon Sep 17 00:00:00 2001 From: Muchun Song Date: Fri, 12 Jun 2026 11:58:47 +0800 Subject: [PATCH 183/562] powerpc/mm: fix wrong addr_pfn tracking in compound vmemmap population vmemmap_populate_compound_pages() uses addr_pfn to determine the PFN offset within a compound page and to decide whether the current vmemmap slot should be populated as a head page mapping or should reuse a tail page mapping. However, addr_pfn is advanced manually in parallel with addr. The loop itself progresses in vmemmap address space, so each PAGE_SIZE step in addr covers PAGE_SIZE / sizeof(struct page) struct page slots. Since addr_pfn is compared against nr_pages in data-PFN units, it should advance by the same number of PFNs. The existing manual increments do not match that and therefore do not reliably track the PFN corresponding to the current addr. As a result, pfn_offset can be computed from the wrong PFN and the code can make the head/tail decision for the wrong compound-page position. Fix this by deriving addr_pfn directly from the current vmemmap address instead of carrying it as loop state. Link: https://lore.kernel.org/20260612035903.2468601-4-songmuchun@bytedance.com Fixes: f2b79c0d7968 ("powerpc/book3s64/radix: add support for vmemmap optimization for radix") Signed-off-by: Muchun Song Acked-by: Oscar Salvador Reviewed-by: Ritesh Harjani (IBM) Cc: "Aneesh Kumar K.V" Cc: David Hildenbrand Cc: Frank van der Linden Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Madhavan Srinivasan Cc: Michael Ellerman Cc: Mike Rapoport (Microsoft) Cc: Nicholas Piggin Cc: Oscar Salvador (SUSE) Cc: Usama Arif Cc: Vlastimil Babka Cc: Signed-off-by: Andrew Morton --- arch/powerpc/mm/book3s64/radix_pgtable.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/arch/powerpc/mm/book3s64/radix_pgtable.c b/arch/powerpc/mm/book3s64/radix_pgtable.c index 10aced261cff..cf692b2b5f7b 100644 --- a/arch/powerpc/mm/book3s64/radix_pgtable.c +++ b/arch/powerpc/mm/book3s64/radix_pgtable.c @@ -1314,7 +1314,6 @@ int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn, * covering out both edges. */ unsigned long addr; - unsigned long addr_pfn = start_pfn; unsigned long next; pgd_t *pgd; p4d_t *p4d; @@ -1335,7 +1334,6 @@ int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn, if (pmd_leaf(READ_ONCE(*pmd))) { /* existing huge mapping. Skip the range */ - addr_pfn += (PMD_SIZE >> PAGE_SHIFT); next = pmd_addr_end(addr, end); continue; } @@ -1348,11 +1346,11 @@ int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn, * page whose VMEMMAP_RESERVE_NR pages were mapped and * this request fall in those pages. */ - addr_pfn += 1; next = addr + PAGE_SIZE; continue; } else { unsigned long nr_pages = pgmap_vmemmap_nr(pgmap); + unsigned long addr_pfn = page_to_pfn((struct page *)addr); unsigned long pfn_offset = addr_pfn - ALIGN_DOWN(addr_pfn, nr_pages); pte_t *tail_page_pte; @@ -1376,7 +1374,6 @@ int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn, if (!pte) return -ENOMEM; - addr_pfn += 2; next = addr + 2 * PAGE_SIZE; continue; } @@ -1392,7 +1389,6 @@ int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn, return -ENOMEM; vmemmap_verify(pte, node, addr, addr + PAGE_SIZE); - addr_pfn += 1; next = addr + PAGE_SIZE; continue; } @@ -1402,7 +1398,6 @@ int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn, return -ENOMEM; vmemmap_verify(pte, node, addr, addr + PAGE_SIZE); - addr_pfn += 1; next = addr + PAGE_SIZE; continue; } From 6d557f364d26c19491d08305b284c1a38d51781a Mon Sep 17 00:00:00 2001 From: Muchun Song Date: Fri, 12 Jun 2026 11:58:48 +0800 Subject: [PATCH 184/562] mm/hugetlb: initialize gigantic bootmem hugepage struct pages earlier Gigantic bootmem HugeTLB pages are currently initialized from hugetlb_init(), but page_alloc_init_late() runs earlier and walks pageblocks to determine zone contiguity. If a bootmem HugeTLB region is marked noinit, set_zone_contiguous() can observe still-uninitialized struct pages through __pageblock_pfn_to_page(). This may not trigger an immediate failure, but it can make set_zone_contiguous() compute the wrong zone contiguity state. If extra poisoned-page checks are added in this path, such as PF_POISONED_CHECK() in page_zone_id(), it can also trigger an early boot panic. Initialize gigantic bootmem HugeTLB struct pages from page_alloc_init_late(), before zone contiguity is evaluated, so later page allocator setup only sees valid struct page state. This also makes the initialization order more natural, as struct pages should be initialized before later code inspects them. Link: https://lore.kernel.org/20260612035903.2468601-5-songmuchun@bytedance.com Fixes: fde1c4ecf916 ("mm: hugetlb: skip initialization of gigantic tail struct pages if freed by HVO") Signed-off-by: Muchun Song Acked-by: Mike Rapoport (Microsoft) Acked-by: Oscar Salvador Cc: "Aneesh Kumar K.V" Cc: David Hildenbrand Cc: Frank van der Linden Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Madhavan Srinivasan Cc: Michael Ellerman Cc: Nicholas Piggin Cc: Oscar Salvador (SUSE) Cc: "Ritesh Harjani (IBM)" Cc: Usama Arif Cc: Vlastimil Babka Cc: Signed-off-by: Andrew Morton --- include/linux/hugetlb.h | 5 +++++ mm/hugetlb.c | 5 ++--- mm/mm_init.c | 1 + mm/sparse-vmemmap.c | 4 ++-- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h index fa828232dfcc..09778c04fdbc 100644 --- a/include/linux/hugetlb.h +++ b/include/linux/hugetlb.h @@ -172,6 +172,7 @@ extern int movable_gigantic_pages __read_mostly; extern int sysctl_hugetlb_shm_group __read_mostly; extern struct list_head huge_boot_pages[MAX_NUMNODES]; +void hugetlb_bootmem_struct_page_init(void); void hugetlb_bootmem_alloc(void); extern nodemask_t hugetlb_bootmem_nodes; void hugetlb_bootmem_set_nodes(void); @@ -1294,6 +1295,10 @@ static inline bool hugetlbfs_pagecache_present( static inline void hugetlb_bootmem_alloc(void) { } + +static inline void hugetlb_bootmem_struct_page_init(void) +{ +} #endif /* CONFIG_HUGETLB_PAGE */ static inline spinlock_t *huge_pte_lock(struct hstate *h, diff --git a/mm/hugetlb.c b/mm/hugetlb.c index e72f36df07e4..dac8241d5c43 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -3365,7 +3365,7 @@ static void __init gather_bootmem_prealloc_parallel(unsigned long start, gather_bootmem_prealloc_node(nid); } -static void __init gather_bootmem_prealloc(void) +void __init hugetlb_bootmem_struct_page_init(void) { struct padata_mt_job job = { .thread_fn = gather_bootmem_prealloc_parallel, @@ -3594,7 +3594,7 @@ static unsigned long __init hugetlb_pages_alloc_boot(struct hstate *h) * - For gigantic pages, this is called early in the boot process and * pages are allocated from memblock allocated or something similar. * Gigantic pages are actually added to pools later with the routine - * gather_bootmem_prealloc. + * hugetlb_bootmem_struct_page_init. * - For non-gigantic pages, this is called later in the boot process after * all of mm is up and functional. Pages are allocated from buddy and * then added to hugetlb pools. @@ -4164,7 +4164,6 @@ static int __init hugetlb_init(void) } hugetlb_init_hstates(); - gather_bootmem_prealloc(); report_hugepages(); hugetlb_sysfs_init(); diff --git a/mm/mm_init.c b/mm/mm_init.c index 498d62c4ece3..eb7222f133e6 100644 --- a/mm/mm_init.c +++ b/mm/mm_init.c @@ -2330,6 +2330,7 @@ void __init page_alloc_init_late(void) /* Reinit limits that are based on free pages after the kernel is up */ files_maxfiles_init(); #endif + hugetlb_bootmem_struct_page_init(); /* Accounting of total+free memory is stable at this point. */ mem_init_print_info(); diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c index bb23fb3077a3..6e09000ed3e1 100644 --- a/mm/sparse-vmemmap.c +++ b/mm/sparse-vmemmap.c @@ -342,8 +342,8 @@ static __meminit struct page *vmemmap_get_tail(unsigned int order, struct zone * * * Any initialization done here will be overwritten by memmap_init(). * - * gather_bootmem_prealloc() will take care of initialization after - * memmap_init(). + * hugetlb_bootmem_struct_page_init() will take care of initialization + * after memmap_init(). */ p = vmemmap_alloc_block_zero(PAGE_SIZE, node); From a233510a56d34b794a0dc8bcab3eba8495a19fed Mon Sep 17 00:00:00 2001 From: Muchun Song Date: Fri, 12 Jun 2026 11:58:49 +0800 Subject: [PATCH 185/562] mm/mm_init: simplify deferred_free_pages() migratetype init deferred_free_pages() open-codes two loops to initialize the pageblock migratetype for a range of pages. Replace them with pageblock_migratetype_init_range() to remove the duplication and make the code clearer (Note that deferred_free_pages() may be called from atomic context). Link: https://lore.kernel.org/20260612035903.2468601-6-songmuchun@bytedance.com Signed-off-by: Muchun Song Acked-by: Mike Rapoport (Microsoft) Acked-by: Oscar Salvador Cc: "Aneesh Kumar K.V" Cc: David Hildenbrand Cc: Frank van der Linden Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Madhavan Srinivasan Cc: Michael Ellerman Cc: Nicholas Piggin Cc: Oscar Salvador (SUSE) Cc: "Ritesh Harjani (IBM)" Cc: Usama Arif Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/mm_init.c | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/mm/mm_init.c b/mm/mm_init.c index eb7222f133e6..405ecee15714 100644 --- a/mm/mm_init.c +++ b/mm/mm_init.c @@ -674,15 +674,15 @@ static inline void fixup_hashdist(void) static inline void fixup_hashdist(void) {} #endif /* CONFIG_NUMA */ -#ifdef CONFIG_ZONE_DEVICE +#if defined(CONFIG_ZONE_DEVICE) || defined(CONFIG_DEFERRED_STRUCT_PAGE_INIT) static __meminit void pageblock_migratetype_init_range(unsigned long pfn, - unsigned long nr_pages, int migratetype) + unsigned long nr_pages, int migratetype, bool atomic) { const unsigned long end = pfn + nr_pages; for (pfn = pageblock_align(pfn); pfn < end; pfn += pageblock_nr_pages) { init_pageblock_migratetype(pfn_to_page(pfn), migratetype, false); - if (IS_ALIGNED(pfn, PAGES_PER_SECTION)) + if (!atomic && IS_ALIGNED(pfn, PAGES_PER_SECTION)) cond_resched(); } } @@ -1142,7 +1142,7 @@ void __ref memmap_init_zone_device(struct zone *zone, compound_nr_pages(pfn, altmap, pgmap)); } - pageblock_migratetype_init_range(start_pfn, nr_pages, MIGRATE_MOVABLE); + pageblock_migratetype_init_range(start_pfn, nr_pages, MIGRATE_MOVABLE, false); pr_debug("%s initialised %lu pages in %ums\n", __func__, nr_pages, jiffies_to_msecs(jiffies - start)); @@ -1988,12 +1988,12 @@ static void __init deferred_free_pages(unsigned long pfn, if (!nr_pages) return; + pageblock_migratetype_init_range(pfn, nr_pages, mt, true); + page = pfn_to_page(pfn); /* Free a large naturally-aligned chunk if possible */ if (nr_pages == MAX_ORDER_NR_PAGES && IS_MAX_ORDER_ALIGNED(pfn)) { - for (i = 0; i < nr_pages; i += pageblock_nr_pages) - init_pageblock_migratetype(page + i, mt, false); __free_pages_core(page, MAX_PAGE_ORDER, MEMINIT_EARLY); return; } @@ -2001,11 +2001,8 @@ static void __init deferred_free_pages(unsigned long pfn, /* Accept chunks smaller than MAX_PAGE_ORDER upfront */ accept_memory(PFN_PHYS(pfn), nr_pages * PAGE_SIZE); - for (i = 0; i < nr_pages; i++, page++, pfn++) { - if (pageblock_aligned(pfn)) - init_pageblock_migratetype(page, mt, false); - __free_pages_core(page, 0, MEMINIT_EARLY); - } + for (i = 0; i < nr_pages; i++) + __free_pages_core(page + i, 0, MEMINIT_EARLY); } /* Completion tracking for deferred_init_memmap() threads */ From b1ac407c55a767d3fba43c89e29e3cdb61702589 Mon Sep 17 00:00:00 2001 From: Muchun Song Date: Fri, 12 Jun 2026 11:58:50 +0800 Subject: [PATCH 186/562] mm/sparse: panic on memmap and usemap allocation failure When vmemmap or usemap allocation fails, sparse_init_nid() currently marks the section non-present and continues. Later boot-time code can still walk PFNs in that section without checking for this partial setup, which leads to invalid accesses. subsection_map_init() can also touch an unallocated usemap. Auditing and fixing all early PFN walkers for this case is not worth the complexity. These allocation failures are expected to be fatal anyway, and other memory models already treat them that way. Make memmap and usemap allocation failures panic immediately instead of trying to recover and crashing later in less obvious ways. This is also consistent with how other memory model configurations handle memmap allocation failures. Link: https://lore.kernel.org/20260612035903.2468601-7-songmuchun@bytedance.com Signed-off-by: Muchun Song Acked-by: Mike Rapoport (Microsoft) Acked-by: Oscar Salvador Cc: "Aneesh Kumar K.V" Cc: David Hildenbrand Cc: Frank van der Linden Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Madhavan Srinivasan Cc: Michael Ellerman Cc: Nicholas Piggin Cc: Oscar Salvador (SUSE) Cc: "Ritesh Harjani (IBM)" Cc: Usama Arif Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/sparse.c | 44 +++++++++----------------------------------- 1 file changed, 9 insertions(+), 35 deletions(-) diff --git a/mm/sparse.c b/mm/sparse.c index 16ac6df3c89f..c92bbc3f3aa3 100644 --- a/mm/sparse.c +++ b/mm/sparse.c @@ -239,15 +239,8 @@ struct page __init *__populate_section_memmap(unsigned long pfn, struct dev_pagemap *pgmap) { unsigned long size = section_map_size(); - struct page *map; - phys_addr_t addr = __pa(MAX_DMA_ADDRESS); - map = memmap_alloc(size, size, addr, nid, false); - if (!map) - panic("%s: Failed to allocate %lu bytes align=0x%lx nid=%d from=%pa\n", - __func__, size, PAGE_SIZE, nid, &addr); - - return map; + return memmap_alloc(size, size, __pa(MAX_DMA_ADDRESS), nid, false); } #endif /* !CONFIG_SPARSEMEM_VMEMMAP */ @@ -300,17 +293,14 @@ static void __init sparse_init_nid(int nid, unsigned long pnum_begin, unsigned long map_count) { unsigned long pnum; - struct page *map; - struct mem_section *ms; - if (sparse_usage_init(nid, map_count)) { - pr_err("%s: node[%d] usemap allocation failed", __func__, nid); - goto failed; - } + if (sparse_usage_init(nid, map_count)) + panic("Failed to allocate usemap for node %d\n", nid); sparse_vmemmap_init_nid_early(nid); for_each_present_section_nr(pnum_begin, pnum) { + struct mem_section *ms; unsigned long pfn = section_nr_to_pfn(pnum); if (pnum >= pnum_end) @@ -318,34 +308,18 @@ static void __init sparse_init_nid(int nid, unsigned long pnum_begin, ms = __nr_to_section(pnum); if (!preinited_vmemmap_section(ms)) { + struct page *map; + map = __populate_section_memmap(pfn, PAGES_PER_SECTION, - nid, NULL, NULL); - if (!map) { - pr_err("%s: node[%d] memory map backing failed. Some memory will not be available.", - __func__, nid); - pnum_begin = pnum; - sparse_usage_fini(); - goto failed; - } + nid, NULL, NULL); + if (!map) + panic("Failed to allocate memmap for section %lu\n", pnum); memmap_boot_pages_add(DIV_ROUND_UP(PAGES_PER_SECTION * sizeof(struct page), PAGE_SIZE)); sparse_init_early_section(nid, map, pnum, 0); } } sparse_usage_fini(); - return; -failed: - /* - * We failed to allocate, mark all the following pnums as not present, - * except the ones already initialized earlier. - */ - for_each_present_section_nr(pnum_begin, pnum) { - if (pnum >= pnum_end) - break; - ms = __nr_to_section(pnum); - if (!preinited_vmemmap_section(ms)) - ms->section_mem_map = 0; - } } /* From 6983df1fcf2d725bbbd0d66ccdc83d907389f49a Mon Sep 17 00:00:00 2001 From: Muchun Song Date: Fri, 12 Jun 2026 11:58:51 +0800 Subject: [PATCH 187/562] mm/sparse: move subsection_map_init() into sparse_init() subsection_map_init() is part of sparse memory initialization, but it is currently called from free_area_init(). Move it into sparse_init() so the sparse-specific setup stays together instead of being split across the generic free_area_init() path. Link: https://lore.kernel.org/20260612035903.2468601-8-songmuchun@bytedance.com Signed-off-by: Muchun Song Acked-by: Mike Rapoport (Microsoft) Acked-by: Oscar Salvador Cc: "Aneesh Kumar K.V" Cc: David Hildenbrand Cc: Frank van der Linden Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Madhavan Srinivasan Cc: Michael Ellerman Cc: Nicholas Piggin Cc: Oscar Salvador (SUSE) Cc: "Ritesh Harjani (IBM)" Cc: Usama Arif Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/internal.h | 5 ++--- mm/mm_init.c | 10 ++-------- mm/sparse-vmemmap.c | 11 ++++++++++- mm/sparse.c | 1 + 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/mm/internal.h b/mm/internal.h index 181e79f1d6a2..dccd4727de46 100644 --- a/mm/internal.h +++ b/mm/internal.h @@ -994,10 +994,9 @@ static inline void sparse_init(void) {} * mm/sparse-vmemmap.c */ #ifdef CONFIG_SPARSEMEM_VMEMMAP -void sparse_init_subsection_map(unsigned long pfn, unsigned long nr_pages); +void sparse_init_subsection_map(void); #else -static inline void sparse_init_subsection_map(unsigned long pfn, - unsigned long nr_pages) +static inline void sparse_init_subsection_map(void) { } #endif /* CONFIG_SPARSEMEM_VMEMMAP */ diff --git a/mm/mm_init.c b/mm/mm_init.c index 405ecee15714..0394150be24b 100644 --- a/mm/mm_init.c +++ b/mm/mm_init.c @@ -1871,18 +1871,12 @@ static void __init free_area_init(void) (u64)zone_movable_pfn[i] << PAGE_SHIFT); } - /* - * Print out the early node map, and initialize the - * subsection-map relative to active online memory ranges to - * enable future "sub-section" extensions of the memory map. - */ + /* Print out the early node map. */ pr_info("Early memory node ranges\n"); - for_each_mem_pfn_range(i, MAX_NUMNODES, &start_pfn, &end_pfn, &nid) { + for_each_mem_pfn_range(i, MAX_NUMNODES, &start_pfn, &end_pfn, &nid) pr_info(" node %3d: [mem %#018Lx-%#018Lx]\n", nid, (u64)start_pfn << PAGE_SHIFT, ((u64)end_pfn << PAGE_SHIFT) - 1); - sparse_init_subsection_map(start_pfn, end_pfn - start_pfn); - } /* Initialise every node */ mminit_verify_pageflags_layout(); diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c index 6e09000ed3e1..0b4019a93188 100644 --- a/mm/sparse-vmemmap.c +++ b/mm/sparse-vmemmap.c @@ -601,7 +601,7 @@ static void subsection_mask_set(unsigned long *map, unsigned long pfn, bitmap_set(map, idx, end - idx + 1); } -void __init sparse_init_subsection_map(unsigned long pfn, unsigned long nr_pages) +static void __init sparse_init_subsection_map_range(unsigned long pfn, unsigned long nr_pages) { int end_sec_nr = pfn_to_section_nr(pfn + nr_pages - 1); unsigned long nr, start_sec_nr = pfn_to_section_nr(pfn); @@ -624,6 +624,15 @@ void __init sparse_init_subsection_map(unsigned long pfn, unsigned long nr_pages } } +void __init sparse_init_subsection_map(void) +{ + int i, nid; + unsigned long start, end; + + for_each_mem_pfn_range(i, MAX_NUMNODES, &start, &end, &nid) + sparse_init_subsection_map_range(start, end - start); +} + #ifdef CONFIG_MEMORY_HOTPLUG /* Mark all memory sections within the pfn range as online */ diff --git a/mm/sparse.c b/mm/sparse.c index c92bbc3f3aa3..85557ef387c7 100644 --- a/mm/sparse.c +++ b/mm/sparse.c @@ -361,5 +361,6 @@ void __init sparse_init(void) } /* cover the last node */ sparse_init_nid(nid_begin, pnum_begin, pnum_end, map_count); + sparse_init_subsection_map(); vmemmap_populate_print_last(); } From 30c807cffe9e0583ed6978d3c24891e696e0a13a Mon Sep 17 00:00:00 2001 From: Muchun Song Date: Fri, 12 Jun 2026 11:58:52 +0800 Subject: [PATCH 188/562] mm/mm_init: defer sparse_init() until after zone initialization free_area_init() is responsible for initializing pgdat and zone state. Calling sparse_init() from there mixes in later vmemmap and struct page setup, which makes the initialization flow less clear. Defer sparse_init(), sparse_vmemmap_init_nid_late(), and memmap_init() until after free_area_init() completes, when zone initialization is fully done. This keeps free_area_init() focused on zone setup and ensures that sparse_init() runs with the relevant zone state already available. This is also a prerequisite for later hugetlb vmemmap changes that need zone information during early sparse vmemmap setup. Link: https://lore.kernel.org/20260612035903.2468601-9-songmuchun@bytedance.com Signed-off-by: Muchun Song Reviewed-by: Mike Rapoport (Microsoft) Reviewed-by: Oscar Salvador (SUSE) Cc: "Aneesh Kumar K.V" Cc: David Hildenbrand Cc: Frank van der Linden Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Madhavan Srinivasan Cc: Michael Ellerman Cc: Nicholas Piggin Cc: Oscar Salvador Cc: "Ritesh Harjani (IBM)" Cc: Usama Arif Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/mm_init.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/mm/mm_init.c b/mm/mm_init.c index 0394150be24b..bd18862ac30b 100644 --- a/mm/mm_init.c +++ b/mm/mm_init.c @@ -1821,7 +1821,6 @@ static void __init free_area_init(void) bool descending; arch_zone_limits_init(max_zone_pfn); - sparse_init(); start_pfn = PHYS_PFN(memblock_start_of_DRAM()); descending = arch_has_descending_max_zone_pfns(); @@ -1910,11 +1909,7 @@ static void __init free_area_init(void) } } - for_each_node_state(nid, N_MEMORY) - sparse_vmemmap_init_nid_late(nid); - calc_nr_kernel_pages(); - memmap_init(); /* disable hash distribution for systems with a single node */ fixup_hashdist(); @@ -2686,10 +2681,17 @@ void __init __weak mem_init(void) void __init mm_core_init_early(void) { + int nid; + hugetlb_cma_reserve(); hugetlb_bootmem_alloc(); free_area_init(); + + sparse_init(); + for_each_node_state(nid, N_MEMORY) + sparse_vmemmap_init_nid_late(nid); + memmap_init(); } /* From 2b89fe0a268c04778b834252c76144cb3cbdcaa0 Mon Sep 17 00:00:00 2001 From: Muchun Song Date: Fri, 12 Jun 2026 11:58:53 +0800 Subject: [PATCH 189/562] mm/mm_init: defer hugetlb reservation until after zone initialization hugetlb_cma_reserve() and hugetlb_bootmem_alloc() currently run before free_area_init(), so HugeTLB reservation happens before zone state is initialized. Move the reservation step after free_area_init() so the relevant zone information is available before HugeTLB reserves memory. This is needed for later hugetlb changes that validate boot-time HugeTLB reservations against zone boundaries. Link: https://lore.kernel.org/20260612035903.2468601-10-songmuchun@bytedance.com Signed-off-by: Muchun Song Reviewed-by: Mike Rapoport (Microsoft) Reviewed-by: Oscar Salvador (SUSE) Cc: "Aneesh Kumar K.V" Cc: David Hildenbrand Cc: Frank van der Linden Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Madhavan Srinivasan Cc: Michael Ellerman Cc: Nicholas Piggin Cc: Oscar Salvador Cc: "Ritesh Harjani (IBM)" Cc: Usama Arif Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/mm_init.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mm/mm_init.c b/mm/mm_init.c index bd18862ac30b..7cfe7301a107 100644 --- a/mm/mm_init.c +++ b/mm/mm_init.c @@ -2683,11 +2683,11 @@ void __init mm_core_init_early(void) { int nid; + free_area_init(); + hugetlb_cma_reserve(); hugetlb_bootmem_alloc(); - free_area_init(); - sparse_init(); for_each_node_state(nid, N_MEMORY) sparse_vmemmap_init_nid_late(nid); From 61b39b5aefe3f164b8f15c36abb9f47ab5481d0c Mon Sep 17 00:00:00 2001 From: Muchun Song Date: Fri, 12 Jun 2026 11:58:54 +0800 Subject: [PATCH 190/562] mm/mm_init: remove set_pageblock_order() call from sparse_init() free_area_init() already sets pageblock_order before sparse_init() runs for CONFIG_HUGETLB_PAGE_SIZE_VARIABLE, so sparse_init() does not need to call set_pageblock_order() again. With that call removed, set_pageblock_order() is only used in mm/mm_init.c. Make it static. Link: https://lore.kernel.org/20260612035903.2468601-11-songmuchun@bytedance.com Signed-off-by: Muchun Song Reviewed-by: Mike Rapoport (Microsoft) Reviewed-by: Oscar Salvador (SUSE) Cc: "Aneesh Kumar K.V" Cc: David Hildenbrand Cc: Frank van der Linden Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Madhavan Srinivasan Cc: Michael Ellerman Cc: Nicholas Piggin Cc: Oscar Salvador (SUSE) Cc: "Ritesh Harjani (IBM)" Cc: Usama Arif Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/internal.h | 1 - mm/mm_init.c | 4 ++-- mm/sparse.c | 3 --- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/mm/internal.h b/mm/internal.h index dccd4727de46..09efb9f4d126 100644 --- a/mm/internal.h +++ b/mm/internal.h @@ -1435,7 +1435,6 @@ extern unsigned long __must_check vm_mmap_pgoff(struct file *, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long); -extern void set_pageblock_order(void); unsigned long reclaim_pages(struct list_head *folio_list); unsigned int reclaim_clean_pages_from_list(struct zone *zone, struct list_head *folio_list); diff --git a/mm/mm_init.c b/mm/mm_init.c index 7cfe7301a107..6f9b02ad4f2c 100644 --- a/mm/mm_init.c +++ b/mm/mm_init.c @@ -1496,7 +1496,7 @@ static inline void setup_usemap(struct zone *zone) {} #ifdef CONFIG_HUGETLB_PAGE_SIZE_VARIABLE /* Initialise the number of pages represented by NR_PAGEBLOCK_BITS */ -void __init set_pageblock_order(void) +static void __init set_pageblock_order(void) { unsigned int order = PAGE_BLOCK_MAX_ORDER; @@ -1522,7 +1522,7 @@ void __init set_pageblock_order(void) * include/linux/pageblock-flags.h for the values of pageblock_order based on * the kernel config */ -void __init set_pageblock_order(void) +static inline void __init set_pageblock_order(void) { } diff --git a/mm/sparse.c b/mm/sparse.c index 85557ef387c7..324213d8bdcb 100644 --- a/mm/sparse.c +++ b/mm/sparse.c @@ -343,9 +343,6 @@ void __init sparse_init(void) pnum_begin = first_present_section_nr(); nid_begin = sparse_early_nid(__nr_to_section(pnum_begin)); - /* Setup pageblock_order for HUGETLB_PAGE_SIZE_VARIABLE */ - set_pageblock_order(); - for_each_present_section_nr(pnum_begin + 1, pnum_end) { int nid = sparse_early_nid(__nr_to_section(pnum_end)); From 59e685f97cfd6b0abcfe094cf0f693c4125e1b33 Mon Sep 17 00:00:00 2001 From: Muchun Song Date: Fri, 12 Jun 2026 11:58:55 +0800 Subject: [PATCH 191/562] mm/sparse: move sparse_vmemmap_init_nid_late() into sparse_init_nid() sparse_vmemmap_init_nid_late() is still called separately from mm_core_init_early(), away from the rest of the sparse initialization path. Now that sparse_init() runs after zone initialization, call sparse_vmemmap_init_nid_late() from sparse_init_nid() instead. This keeps both sparse_vmemmap_init_nid_early() and sparse_vmemmap_init_nid_late() in the sparse setup path. Link: https://lore.kernel.org/20260612035903.2468601-12-songmuchun@bytedance.com Signed-off-by: Muchun Song Reviewed-by: Mike Rapoport (Microsoft) Reviewed-by: Oscar Salvador (SUSE) Cc: "Aneesh Kumar K.V" Cc: David Hildenbrand Cc: Frank van der Linden Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Madhavan Srinivasan Cc: Michael Ellerman Cc: Nicholas Piggin Cc: Oscar Salvador Cc: "Ritesh Harjani (IBM)" Cc: Usama Arif Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/mm_init.c | 4 ---- mm/sparse.c | 1 + 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/mm/mm_init.c b/mm/mm_init.c index 6f9b02ad4f2c..4ed4591dcdba 100644 --- a/mm/mm_init.c +++ b/mm/mm_init.c @@ -2681,16 +2681,12 @@ void __init __weak mem_init(void) void __init mm_core_init_early(void) { - int nid; - free_area_init(); hugetlb_cma_reserve(); hugetlb_bootmem_alloc(); sparse_init(); - for_each_node_state(nid, N_MEMORY) - sparse_vmemmap_init_nid_late(nid); memmap_init(); } diff --git a/mm/sparse.c b/mm/sparse.c index 324213d8bdcb..3917a47153d8 100644 --- a/mm/sparse.c +++ b/mm/sparse.c @@ -320,6 +320,7 @@ static void __init sparse_init_nid(int nid, unsigned long pnum_begin, } } sparse_usage_fini(); + sparse_vmemmap_init_nid_late(nid); } /* From 9c8ea49e157e91a6a6013dca3a9ff056ce87b6ba Mon Sep 17 00:00:00 2001 From: Muchun Song Date: Fri, 12 Jun 2026 11:58:56 +0800 Subject: [PATCH 192/562] mm/hugetlb_cma: validate hugetlb CMA range by zone at reserve time Hugetlb CMA allocation currently has to cope with CMA areas that span multiple zones. Validate the reserved CMA range up front in hugetlb_cma_reserve() so later hugetlb CMA allocations can assume a zone-consistent area. Also drop the pfn_valid() check from cma_validate_zones(). mem_section is not fully initialized at this point, so the check can trigger false warnings. Keep the sanity check in cma_activate_area() instead. Link: https://lore.kernel.org/20260612035903.2468601-13-songmuchun@bytedance.com Signed-off-by: Muchun Song Acked-by: Mike Rapoport (Microsoft) Reviewed-by: Oscar Salvador (SUSE) Cc: "Aneesh Kumar K.V" Cc: David Hildenbrand Cc: Frank van der Linden Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Madhavan Srinivasan Cc: Michael Ellerman Cc: Nicholas Piggin Cc: Oscar Salvador (SUSE) Cc: "Ritesh Harjani (IBM)" Cc: Usama Arif Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/cma.c | 3 ++- mm/hugetlb_cma.c | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/mm/cma.c b/mm/cma.c index a13ce4999b39..31073738f2ac 100644 --- a/mm/cma.c +++ b/mm/cma.c @@ -126,7 +126,6 @@ bool cma_validate_zones(struct cma *cma) * to be in the same zone. Simplify by forcing the entire * CMA resv range to be in the same zone. */ - WARN_ON_ONCE(!pfn_valid(base_pfn)); if (pfn_range_intersects_zones(cma->nid, base_pfn, cmr->count)) { set_bit(CMA_ZONES_INVALID, &cma->flags); return false; @@ -165,6 +164,8 @@ static void __init cma_activate_area(struct cma *cma) bitmap_set(cmr->bitmap, 0, bitmap_count); } + WARN_ON_ONCE(!pfn_valid(cmr->base_pfn)); + for (pfn = early_pfn[r]; pfn < cmr->base_pfn + cmr->count; pfn += pageblock_nr_pages) init_cma_reserved_pageblock(pfn_to_page(pfn)); diff --git a/mm/hugetlb_cma.c b/mm/hugetlb_cma.c index 79dbd0baafa3..665d910b0f13 100644 --- a/mm/hugetlb_cma.c +++ b/mm/hugetlb_cma.c @@ -231,9 +231,11 @@ void __init hugetlb_cma_reserve(void) res = cma_declare_contiguous_multi(size, gigantic_page_size, HUGETLB_PAGE_ORDER, name, &hugetlb_cma[nid], nid); - if (res) { - pr_warn("hugetlb_cma: reservation failed: err %d, node %d", + if (res || !cma_validate_zones(hugetlb_cma[nid])) { + pr_warn("hugetlb_cma: %s: err %d, node %d\n", + res ? "reservation failed" : "reserved area spans zones", res, nid); + hugetlb_cma[nid] = NULL; continue; } From 8cc1742b6b044c53e210ade128141e9f99bdfe4e Mon Sep 17 00:00:00 2001 From: Muchun Song Date: Fri, 12 Jun 2026 11:58:57 +0800 Subject: [PATCH 193/562] mm/hugetlb: refactor early boot gigantic hugepage allocation The early boot gigantic hugepage allocation helpers currently mix allocation with huge_bootmem_page setup, and leave part of the initialization flow in architecture code. Refactor the interface to return the allocated huge page pointer and move the huge_bootmem_page setup into the generic hugetlb code. This makes the architecture-specific paths focus only on finding memory, while the common code handles node placement and early page metadata setup in one place. This also lets powerpc benefit from memblock_reserved_mark_noinit(), which it did not enable before. In addition, upcoming cross-zone validation for boot-time gigantic hugetlb reservation is common logic. With this refactoring, that logic can stay in the generic code instead of being duplicated in architecture-specific paths. Link: https://lore.kernel.org/20260612035903.2468601-14-songmuchun@bytedance.com Signed-off-by: Muchun Song Reviewed-by: Mike Rapoport (Microsoft) Reviewed-by: Oscar Salvador (SUSE) Cc: "Aneesh Kumar K.V" Cc: David Hildenbrand Cc: Frank van der Linden Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Madhavan Srinivasan Cc: Michael Ellerman Cc: Nicholas Piggin Cc: Oscar Salvador (SUSE) Cc: "Ritesh Harjani (IBM)" Cc: Usama Arif Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- arch/powerpc/mm/hugetlbpage.c | 13 ++--- include/linux/hugetlb.h | 18 ++----- mm/hugetlb.c | 95 ++++++++++++++--------------------- mm/hugetlb_cma.c | 13 ++--- mm/hugetlb_cma.h | 8 ++- mm/internal.h | 9 ++++ 6 files changed, 64 insertions(+), 92 deletions(-) diff --git a/arch/powerpc/mm/hugetlbpage.c b/arch/powerpc/mm/hugetlbpage.c index 558fafb82b8a..a298746dc143 100644 --- a/arch/powerpc/mm/hugetlbpage.c +++ b/arch/powerpc/mm/hugetlbpage.c @@ -104,17 +104,14 @@ void __init pseries_add_gpage(u64 addr, u64 page_size, unsigned long number_of_p } } -static int __init pseries_alloc_bootmem_huge_page(struct hstate *hstate) +static __init void *pseries_alloc_bootmem_huge_page(struct hstate *hstate) { - struct huge_bootmem_page *m; + void *m; if (nr_gpages == 0) - return 0; + return NULL; m = phys_to_virt(gpage_freearray[--nr_gpages]); gpage_freearray[nr_gpages] = 0; - list_add(&m->list, &huge_boot_pages[0]); - m->hstate = hstate; - m->flags = 0; - return 1; + return m; } bool __init hugetlb_node_alloc_supported(void) @@ -124,7 +121,7 @@ bool __init hugetlb_node_alloc_supported(void) #endif -int __init alloc_bootmem_huge_page(struct hstate *h, int nid) +void *__init arch_alloc_bootmem_huge_page(struct hstate *h, int nid) { #ifdef CONFIG_PPC_BOOK3S_64 diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h index 09778c04fdbc..4579271d2aab 100644 --- a/include/linux/hugetlb.h +++ b/include/linux/hugetlb.h @@ -675,19 +675,11 @@ struct hstate { char name[HSTATE_NAME_LEN]; }; -struct cma; - -struct huge_bootmem_page { - struct list_head list; - struct hstate *hstate; - unsigned long flags; - struct cma *cma; -}; - #define HUGE_BOOTMEM_HVO 0x0001 #define HUGE_BOOTMEM_ZONES_VALID 0x0002 #define HUGE_BOOTMEM_CMA 0x0004 +struct huge_bootmem_page; bool hugetlb_bootmem_page_zones_valid(int nid, struct huge_bootmem_page *m); int isolate_or_dissolve_huge_folio(struct folio *folio, struct list_head *list); @@ -707,8 +699,8 @@ void restore_reserve_on_error(struct hstate *h, struct vm_area_struct *vma, unsigned long address, struct folio *folio); /* arch callback */ -int __init __alloc_bootmem_huge_page(struct hstate *h, int nid); -int __init alloc_bootmem_huge_page(struct hstate *h, int nid); +void *__init __alloc_bootmem_huge_page(struct hstate *h, int nid); +void *__init arch_alloc_bootmem_huge_page(struct hstate *h, int nid); bool __init hugetlb_node_alloc_supported(void); void __init hugetlb_add_hstate(unsigned order); @@ -1139,9 +1131,9 @@ alloc_hugetlb_folio_nodemask(struct hstate *h, int preferred_nid, return NULL; } -static inline int __alloc_bootmem_huge_page(struct hstate *h) +static inline void *__alloc_bootmem_huge_page(struct hstate *h, int nid) { - return 0; + return NULL; } static inline struct hstate *hstate_file(struct file *f) diff --git a/mm/hugetlb.c b/mm/hugetlb.c index dac8241d5c43..a4d28bc711ee 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -3039,79 +3039,58 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma, static __init void *alloc_bootmem(struct hstate *h, int nid, bool node_exact) { - struct huge_bootmem_page *m; - int listnode = nid; - if (hugetlb_early_cma(h)) - m = hugetlb_cma_alloc_bootmem(h, &listnode, node_exact); - else { - if (node_exact) - m = memblock_alloc_exact_nid_raw(huge_page_size(h), + return hugetlb_cma_alloc_bootmem(h, nid, node_exact); + + if (node_exact) + return memblock_alloc_exact_nid_raw(huge_page_size(h), huge_page_size(h), 0, MEMBLOCK_ALLOC_ACCESSIBLE, nid); - else { - m = memblock_alloc_try_nid_raw(huge_page_size(h), + + return memblock_alloc_try_nid_raw(huge_page_size(h), huge_page_size(h), 0, MEMBLOCK_ALLOC_ACCESSIBLE, nid); - /* - * For pre-HVO to work correctly, pages need to be on - * the list for the node they were actually allocated - * from. That node may be different in the case of - * fallback by memblock_alloc_try_nid_raw. So, - * extract the actual node first. - */ - if (m) - listnode = early_pfn_to_nid(PHYS_PFN(__pa(m))); - } - - if (m) { - m->flags = 0; - m->cma = NULL; - } - } - - if (m) { - /* - * Use the beginning of the huge page to store the - * huge_bootmem_page struct (until gather_bootmem - * puts them into the mem_map). - * - * Put them into a private list first because mem_map - * is not up yet. - */ - INIT_LIST_HEAD(&m->list); - list_add(&m->list, &huge_boot_pages[listnode]); - m->hstate = h; - } - - return m; } -int alloc_bootmem_huge_page(struct hstate *h, int nid) +void *__init arch_alloc_bootmem_huge_page(struct hstate *h, int nid) __attribute__ ((weak, alias("__alloc_bootmem_huge_page"))); -int __alloc_bootmem_huge_page(struct hstate *h, int nid) +void *__init __alloc_bootmem_huge_page(struct hstate *h, int nid) { - struct huge_bootmem_page *m = NULL; /* initialize for clang */ int nr_nodes, node = nid; /* do node specific alloc */ - if (nid != NUMA_NO_NODE) { - m = alloc_bootmem(h, node, true); - if (!m) - return 0; - goto found; - } + if (nid != NUMA_NO_NODE) + return alloc_bootmem(h, node, true); /* allocate from next node when distributing huge pages */ for_each_node_mask_to_alloc(&h->next_nid_to_alloc, nr_nodes, node, - &hugetlb_bootmem_nodes) { - m = alloc_bootmem(h, node, false); - if (!m) - return 0; - goto found; - } + &hugetlb_bootmem_nodes) + return alloc_bootmem(h, node, false); -found: + return NULL; +} + +static bool __init alloc_bootmem_huge_page(struct hstate *h, int nid) +{ + struct huge_bootmem_page *m = arch_alloc_bootmem_huge_page(h, nid); + + if (!m) + return false; + + nid = early_pfn_to_nid(PHYS_PFN(__pa(m))); + /* + * Use the beginning of the huge page to store the huge_bootmem_page + * struct (until gather_bootmem puts them into the mem_map). + * + * Put them into a private list first because mem_map is not up yet. + */ + INIT_LIST_HEAD(&m->list); + list_add(&m->list, &huge_boot_pages[nid]); + m->hstate = h; + if (!hugetlb_early_cma(h)) { + m->cma = NULL; + m->flags = 0; + } /* * Only initialize the head struct page in memmap_init_reserved_pages, @@ -3123,7 +3102,7 @@ int __alloc_bootmem_huge_page(struct hstate *h, int nid) memblock_reserved_mark_noinit(__pa((void *)m + PAGE_SIZE), huge_page_size(h) - PAGE_SIZE); - return 1; + return true; } /* Initialize [start_page:end_page_number] tail struct pages of a hugepage */ diff --git a/mm/hugetlb_cma.c b/mm/hugetlb_cma.c index 665d910b0f13..ae21b24e45b0 100644 --- a/mm/hugetlb_cma.c +++ b/mm/hugetlb_cma.c @@ -56,14 +56,13 @@ struct folio *hugetlb_cma_alloc_frozen_folio(int order, gfp_t gfp_mask, return folio; } -struct huge_bootmem_page * __init -hugetlb_cma_alloc_bootmem(struct hstate *h, int *nid, bool node_exact) +void * __init hugetlb_cma_alloc_bootmem(struct hstate *h, int nid, bool node_exact) { struct cma *cma; struct huge_bootmem_page *m; - int node = *nid; + int node; - cma = hugetlb_cma[*nid]; + cma = hugetlb_cma[nid]; m = cma_reserve_early(cma, huge_page_size(h)); if (!m) { if (node_exact) @@ -71,13 +70,11 @@ hugetlb_cma_alloc_bootmem(struct hstate *h, int *nid, bool node_exact) for_each_node_mask(node, hugetlb_bootmem_nodes) { cma = hugetlb_cma[node]; - if (!cma || node == *nid) + if (!cma || node == nid) continue; m = cma_reserve_early(cma, huge_page_size(h)); - if (m) { - *nid = node; + if (m) break; - } } } diff --git a/mm/hugetlb_cma.h b/mm/hugetlb_cma.h index c619c394b1ae..3aa483573d17 100644 --- a/mm/hugetlb_cma.h +++ b/mm/hugetlb_cma.h @@ -6,8 +6,7 @@ void hugetlb_cma_free_frozen_folio(struct folio *folio); struct folio *hugetlb_cma_alloc_frozen_folio(int order, gfp_t gfp_mask, int nid, nodemask_t *nodemask); -struct huge_bootmem_page *hugetlb_cma_alloc_bootmem(struct hstate *h, int *nid, - bool node_exact); +void *hugetlb_cma_alloc_bootmem(struct hstate *h, int nid, bool node_exact); bool hugetlb_cma_exclusive_alloc(void); unsigned long hugetlb_cma_total_size(void); void hugetlb_cma_validate_params(void); @@ -23,9 +22,8 @@ static inline struct folio *hugetlb_cma_alloc_frozen_folio(int order, return NULL; } -static inline -struct huge_bootmem_page *hugetlb_cma_alloc_bootmem(struct hstate *h, int *nid, - bool node_exact) +static inline void *hugetlb_cma_alloc_bootmem(struct hstate *h, int nid, + bool node_exact) { return NULL; } diff --git a/mm/internal.h b/mm/internal.h index 09efb9f4d126..3401759924d9 100644 --- a/mm/internal.h +++ b/mm/internal.h @@ -23,6 +23,15 @@ #include "vma.h" struct folio_batch; +struct hstate; +struct cma; + +struct huge_bootmem_page { + struct list_head list; + struct hstate *hstate; + unsigned long flags; + struct cma *cma; +}; /* * Maintains state across a page table move. The operation assumes both source From 505c579b988e9989bede88fbaf8ed9840a224ebb Mon Sep 17 00:00:00 2001 From: Muchun Song Date: Fri, 12 Jun 2026 11:58:58 +0800 Subject: [PATCH 194/562] mm/hugetlb: free cross-zone bootmem gigantic pages after allocation Now that hugetlb reservation runs after zone initialization, bootmem gigantic page allocation can detect pages that span multiple zones. Keep those cross-zone pages separate during allocation and free them after allocation completes, so later hugetlb initialization only sees zone-valid gigantic pages. This chooses to free cross-zone gigantic pages directly instead of retrying allocation. In practice, such cross-zone cases are expected to be very rare, so adding retry logic does not seem justified at this point. Keeping the handling simple also preserves the previous behavior. If similar real-world reports show up later, retry support can be reconsidered then. Link: https://lore.kernel.org/20260612035903.2468601-15-songmuchun@bytedance.com Signed-off-by: Muchun Song Acked-by: Mike Rapoport (Microsoft) Cc: "Aneesh Kumar K.V" Cc: David Hildenbrand Cc: Frank van der Linden Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Madhavan Srinivasan Cc: Michael Ellerman Cc: Nicholas Piggin Cc: Oscar Salvador Cc: Oscar Salvador (SUSE) Cc: "Ritesh Harjani (IBM)" Cc: Usama Arif Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/hugetlb.c | 75 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 64 insertions(+), 11 deletions(-) diff --git a/mm/hugetlb.c b/mm/hugetlb.c index a4d28bc711ee..8603141d2a06 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -3072,12 +3072,15 @@ void *__init __alloc_bootmem_huge_page(struct hstate *h, int nid) static bool __init alloc_bootmem_huge_page(struct hstate *h, int nid) { + unsigned long pfn; + unsigned int nid_request = nid; struct huge_bootmem_page *m = arch_alloc_bootmem_huge_page(h, nid); if (!m) return false; - nid = early_pfn_to_nid(PHYS_PFN(__pa(m))); + pfn = PHYS_PFN(__pa(m)); + nid = early_pfn_to_nid(pfn); /* * Use the beginning of the huge page to store the huge_bootmem_page * struct (until gather_bootmem puts them into the mem_map). @@ -3085,22 +3088,38 @@ static bool __init alloc_bootmem_huge_page(struct hstate *h, int nid) * Put them into a private list first because mem_map is not up yet. */ INIT_LIST_HEAD(&m->list); - list_add(&m->list, &huge_boot_pages[nid]); m->hstate = h; if (!hugetlb_early_cma(h)) { m->cma = NULL; m->flags = 0; } - /* - * Only initialize the head struct page in memmap_init_reserved_pages, - * rest of the struct pages will be initialized by the HugeTLB - * subsystem itself. - * The head struct page is used to get folio information by the HugeTLB - * subsystem like zone id and node id. - */ - memblock_reserved_mark_noinit(__pa((void *)m + PAGE_SIZE), - huge_page_size(h) - PAGE_SIZE); + /* CMA pages: zone-crossing is validated in hugetlb_cma_reserve(). */ + if (!hugetlb_early_cma(h) && + pfn_range_intersects_zones(nid, pfn, pages_per_huge_page(h))) { + /* + * If the allocated page is on a different node than requested + * (e.g., on PowerPC LPARs), put it on the requested node's list, + * because hugetlb_free_cross_zone_pages() only frees cross-zone + * pages belonging to the requested node. + */ + if (WARN_ON_ONCE(nid_request != NUMA_NO_NODE && nid != nid_request)) + list_add(&m->list, &huge_boot_pages[nid_request]); + else + list_add(&m->list, &huge_boot_pages[nid]); + } else { + list_add_tail(&m->list, &huge_boot_pages[nid]); + m->flags |= HUGE_BOOTMEM_ZONES_VALID; + /* + * Only initialize the head struct page in memmap_init_reserved_pages, + * rest of the struct pages will be initialized by the HugeTLB + * subsystem itself. + * The head struct page is used to get folio information by the HugeTLB + * subsystem like zone id and node id. + */ + memblock_reserved_mark_noinit(__pa((void *)m + PAGE_SIZE), + huge_page_size(h) - PAGE_SIZE); + } return true; } @@ -3385,6 +3404,34 @@ void __init hugetlb_bootmem_struct_page_init(void) padata_do_multithreaded(&job); } +static unsigned long __init hugetlb_free_cross_zone_pages(struct hstate *h, int nid) +{ + unsigned long freed = 0; + struct huge_bootmem_page *m, *tmp; + + if (!hstate_is_gigantic(h)) + return freed; + + list_for_each_entry_safe(m, tmp, &huge_boot_pages[nid], list) { + if (m->flags & HUGE_BOOTMEM_ZONES_VALID) + break; + + list_del(&m->list); + memblock_free(m, huge_page_size(h)); + freed++; + } + + if (freed) { + char buf[32]; + + string_get_size(huge_page_size(h), 1, STRING_UNITS_2, buf, sizeof(buf)); + pr_warn("HugeTLB: freed %lu cross-zone hugepages of size %s on node %d.\n", + freed, buf, nid); + } + + return freed; +} + static void __init hugetlb_hstate_alloc_pages_onenode(struct hstate *h, int nid) { unsigned long i; @@ -3415,6 +3462,8 @@ static void __init hugetlb_hstate_alloc_pages_onenode(struct hstate *h, int nid) cond_resched(); } + i -= hugetlb_free_cross_zone_pages(h, nid); + if (!list_empty(&folio_list)) prep_and_add_allocated_folios(h, &folio_list); @@ -3488,6 +3537,7 @@ static void __init hugetlb_pages_alloc_boot_node(unsigned long start, unsigned l static unsigned long __init hugetlb_gigantic_pages_alloc_boot(struct hstate *h) { + int nid; unsigned long i; for (i = 0; i < h->max_huge_pages; ++i) { @@ -3496,6 +3546,9 @@ static unsigned long __init hugetlb_gigantic_pages_alloc_boot(struct hstate *h) cond_resched(); } + for_each_node(nid) + i -= hugetlb_free_cross_zone_pages(h, nid); + return i; } From 2decf10f910f60b235720dca948e86c108dd85d9 Mon Sep 17 00:00:00 2001 From: Muchun Song Date: Fri, 12 Jun 2026 11:58:59 +0800 Subject: [PATCH 195/562] mm/hugetlb_vmemmap: move bootmem HVO setup to early init Bootmem HugeTLB pages currently defer HVO setup to hugetlb_vmemmap_init_late(), because the optimization needs zone information. Now that zone initialization is available earlier, the bootmem HVO setup can be done directly from hugetlb_vmemmap_init_early(). This lets gigantic HugeTLB pages apply HVO as soon as they are allocated. Bootmem gigantic pages that span multiple zones are now filtered out when they are allocated, so the remaining bootmem gigantic pages seen by later hugetlb initialization are already zone-valid. As a result, hugetlb_vmemmap_init_late() no longer needs to handle bootmem HVO setup. Link: https://lore.kernel.org/20260612035903.2468601-16-songmuchun@bytedance.com Signed-off-by: Muchun Song Acked-by: Mike Rapoport (Microsoft) Acked-by: Usama Arif Cc: "Aneesh Kumar K.V" Cc: David Hildenbrand Cc: Frank van der Linden Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Madhavan Srinivasan Cc: Michael Ellerman Cc: Nicholas Piggin Cc: Oscar Salvador Cc: Oscar Salvador (SUSE) Cc: "Ritesh Harjani (IBM)" Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/hugetlb_vmemmap.c | 93 ++++++++++++-------------------------------- 1 file changed, 25 insertions(+), 68 deletions(-) diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c index ea6af85bfec1..ee4fbd5fed0d 100644 --- a/mm/hugetlb_vmemmap.c +++ b/mm/hugetlb_vmemmap.c @@ -745,6 +745,20 @@ static bool vmemmap_should_optimize_bootmem_page(struct huge_bootmem_page *m) return true; } +static struct zone *pfn_to_zone(unsigned nid, unsigned long pfn) +{ + struct zone *zone; + enum zone_type zone_type; + + for (zone_type = 0; zone_type < MAX_NR_ZONES; zone_type++) { + zone = &NODE_DATA(nid)->node_zones[zone_type]; + if (zone_spans_pfn(zone, pfn)) + return zone; + } + + return NULL; +} + /* * Initialize memmap section for a gigantic page, HVO-style. */ @@ -752,6 +766,7 @@ void __init hugetlb_vmemmap_init_early(int nid) { unsigned long psize, paddr, section_size; unsigned long ns, i, pnum, pfn, nr_pages; + unsigned long start, end; struct huge_bootmem_page *m = NULL; void *map; @@ -761,6 +776,8 @@ void __init hugetlb_vmemmap_init_early(int nid) section_size = (1UL << PA_SECTION_SHIFT); list_for_each_entry(m, &huge_boot_pages[nid], list) { + struct zone *zone; + if (!vmemmap_should_optimize_bootmem_page(m)) continue; @@ -769,6 +786,14 @@ void __init hugetlb_vmemmap_init_early(int nid) paddr = virt_to_phys(m); pfn = PHYS_PFN(paddr); map = pfn_to_page(pfn); + start = (unsigned long)map; + end = start + hugetlb_vmemmap_size(m->hstate); + zone = pfn_to_zone(nid, pfn); + + if (vmemmap_populate_hvo(start, end, huge_page_order(m->hstate), + zone, HUGETLB_VMEMMAP_RESERVE_SIZE)) + panic("Failed to allocate memmap for HugeTLB page\n"); + memmap_boot_pages_add(DIV_ROUND_UP(HUGETLB_VMEMMAP_RESERVE_SIZE, PAGE_SIZE)); pnum = pfn_to_section_nr(pfn); ns = psize / section_size; @@ -784,76 +809,8 @@ void __init hugetlb_vmemmap_init_early(int nid) } } -static struct zone *pfn_to_zone(unsigned nid, unsigned long pfn) -{ - struct zone *zone; - enum zone_type zone_type; - - for (zone_type = 0; zone_type < MAX_NR_ZONES; zone_type++) { - zone = &NODE_DATA(nid)->node_zones[zone_type]; - if (zone_spans_pfn(zone, pfn)) - return zone; - } - - return NULL; -} - void __init hugetlb_vmemmap_init_late(int nid) { - struct huge_bootmem_page *m, *tm; - unsigned long phys, nr_pages, start, end; - unsigned long pfn, nr_mmap; - struct zone *zone = NULL; - struct hstate *h; - void *map; - - if (!READ_ONCE(vmemmap_optimize_enabled)) - return; - - list_for_each_entry_safe(m, tm, &huge_boot_pages[nid], list) { - if (!(m->flags & HUGE_BOOTMEM_HVO)) - continue; - - phys = virt_to_phys(m); - h = m->hstate; - pfn = PHYS_PFN(phys); - nr_pages = pages_per_huge_page(h); - map = pfn_to_page(pfn); - start = (unsigned long)map; - end = start + nr_pages * sizeof(struct page); - - if (!hugetlb_bootmem_page_zones_valid(nid, m)) { - /* - * Oops, the hugetlb page spans multiple zones. - * Remove it from the list, and populate it normally. - */ - list_del(&m->list); - - vmemmap_populate(start, end, nid, NULL); - nr_mmap = end - start; - memmap_boot_pages_add(DIV_ROUND_UP(nr_mmap, PAGE_SIZE)); - - memblock_phys_free(phys, huge_page_size(h)); - continue; - } - - if (!zone || !zone_spans_pfn(zone, pfn)) - zone = pfn_to_zone(nid, pfn); - if (WARN_ON_ONCE(!zone)) - continue; - - if (vmemmap_populate_hvo(start, end, huge_page_order(h), zone, - HUGETLB_VMEMMAP_RESERVE_SIZE) < 0) { - /* Fallback if HVO population fails */ - vmemmap_populate(start, end, nid, NULL); - nr_mmap = end - start; - } else { - m->flags |= HUGE_BOOTMEM_ZONES_VALID; - nr_mmap = HUGETLB_VMEMMAP_RESERVE_SIZE; - } - - memmap_boot_pages_add(DIV_ROUND_UP(nr_mmap, PAGE_SIZE)); - } } #endif From 448a0a00baa5ca61b3962beb470cce2c93e1a26b Mon Sep 17 00:00:00 2001 From: Muchun Song Date: Fri, 12 Jun 2026 11:59:00 +0800 Subject: [PATCH 196/562] mm/hugetlb: remove obsolete bootmem cross-zone checks Bootmem gigantic HugeTLB pages used to be validated again during gather_bootmem_prealloc_node() and any cross-zone pages were discarded there. That validation is no longer needed. Cross-zone bootmem gigantic pages are now detected during allocation and freed before they reach the later bootmem gathering path, so the remaining pages are already zone-valid. Remove the obsolete cross-zone validation, invalid-page freeing, and the associated discarded-page accounting. Link: https://lore.kernel.org/20260612035903.2468601-17-songmuchun@bytedance.com Signed-off-by: Muchun Song Acked-by: Mike Rapoport (Microsoft) Cc: "Aneesh Kumar K.V" Cc: David Hildenbrand Cc: Frank van der Linden Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Madhavan Srinivasan Cc: Michael Ellerman Cc: Nicholas Piggin Cc: Oscar Salvador Cc: Oscar Salvador (SUSE) Cc: "Ritesh Harjani (IBM)" Cc: Usama Arif Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- include/linux/hugetlb.h | 3 -- mm/hugetlb.c | 70 ----------------------------------------- 2 files changed, 73 deletions(-) diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h index 4579271d2aab..7bc3cafa7f61 100644 --- a/include/linux/hugetlb.h +++ b/include/linux/hugetlb.h @@ -679,9 +679,6 @@ struct hstate { #define HUGE_BOOTMEM_ZONES_VALID 0x0002 #define HUGE_BOOTMEM_CMA 0x0004 -struct huge_bootmem_page; -bool hugetlb_bootmem_page_zones_valid(int nid, struct huge_bootmem_page *m); - int isolate_or_dissolve_huge_folio(struct folio *folio, struct list_head *list); int replace_free_hugepage_folios(unsigned long start_pfn, unsigned long end_pfn); void wait_for_freed_hugetlb_folios(void); diff --git a/mm/hugetlb.c b/mm/hugetlb.c index 8603141d2a06..c79b27a6b162 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -58,7 +58,6 @@ struct hstate hstates[HUGE_MAX_HSTATE]; __initdata nodemask_t hugetlb_bootmem_nodes; __initdata struct list_head huge_boot_pages[MAX_NUMNODES]; -static unsigned long hstate_boot_nrinvalid[HUGE_MAX_HSTATE] __initdata; /* * Due to ordering constraints across the init code for various @@ -3233,57 +3232,6 @@ static void __init prep_and_add_bootmem_folios(struct hstate *h, } } -bool __init hugetlb_bootmem_page_zones_valid(int nid, - struct huge_bootmem_page *m) -{ - unsigned long start_pfn; - bool valid; - - if (m->flags & HUGE_BOOTMEM_ZONES_VALID) { - /* - * Already validated, skip check. - */ - return true; - } - - if (hugetlb_bootmem_page_earlycma(m)) { - valid = cma_validate_zones(m->cma); - goto out; - } - - start_pfn = virt_to_phys(m) >> PAGE_SHIFT; - - valid = !pfn_range_intersects_zones(nid, start_pfn, - pages_per_huge_page(m->hstate)); -out: - if (!valid) - hstate_boot_nrinvalid[hstate_index(m->hstate)]++; - - return valid; -} - -/* - * Free a bootmem page that was found to be invalid (intersecting with - * multiple zones). - * - * Since it intersects with multiple zones, we can't just do a free - * operation on all pages at once, but instead have to walk all - * pages, freeing them one by one. - */ -static void __init hugetlb_bootmem_free_invalid_page(int nid, struct page *page, - struct hstate *h) -{ - unsigned long npages = pages_per_huge_page(h); - unsigned long pfn; - - while (npages--) { - pfn = page_to_pfn(page); - __init_page_from_nid(pfn, nid); - free_reserved_page(page); - page++; - } -} - /* * Put bootmem huge pages into the standard lists after mem_map is up. * Note: This only applies to gigantic (order > MAX_PAGE_ORDER) pages. @@ -3299,17 +3247,6 @@ static void __init gather_bootmem_prealloc_node(unsigned long nid) struct folio *folio = (void *)page; h = m->hstate; - if (!hugetlb_bootmem_page_zones_valid(nid, m)) { - /* - * Can't use this page. Initialize the - * page structures if that hasn't already - * been done, and give them to the page - * allocator. - */ - hugetlb_bootmem_free_invalid_page(nid, page, h); - continue; - } - /* * It is possible to have multiple huge page sizes (hstates) * in this list. If so, process each size separately. @@ -3704,20 +3641,13 @@ static void __init hugetlb_init_hstates(void) static void __init report_hugepages(void) { struct hstate *h; - unsigned long nrinvalid; for_each_hstate(h) { char buf[32]; - nrinvalid = hstate_boot_nrinvalid[hstate_index(h)]; - h->max_huge_pages -= nrinvalid; - string_get_size(huge_page_size(h), 1, STRING_UNITS_2, buf, 32); pr_info("HugeTLB: registered %s page size, pre-allocated %ld pages\n", buf, h->nr_huge_pages); - if (nrinvalid) - pr_info("HugeTLB: %s page size: %lu invalid page%s discarded\n", - buf, nrinvalid, str_plural(nrinvalid)); pr_info("HugeTLB: %d KiB vmemmap can be freed for a %s page\n", hugetlb_vmemmap_optimizable_size(h) / SZ_1K, buf); } From 762a69bd2d2bd76d062f442fc4408a802dc9c2c0 Mon Sep 17 00:00:00 2001 From: Muchun Song Date: Fri, 12 Jun 2026 11:59:01 +0800 Subject: [PATCH 197/562] mm/sparse-vmemmap: remove sparse_vmemmap_init_nid_late() hugetlb_vmemmap_init_late() no longer has any users, so the remaining late-init path in sparse_vmemmap_init_nid_late() is dead code. Remove sparse_vmemmap_init_nid_late() and its declarations. Link: https://lore.kernel.org/20260612035903.2468601-18-songmuchun@bytedance.com Signed-off-by: Muchun Song Acked-by: Mike Rapoport (Microsoft) Cc: "Aneesh Kumar K.V" Cc: David Hildenbrand Cc: Frank van der Linden Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Madhavan Srinivasan Cc: Michael Ellerman Cc: Nicholas Piggin Cc: Oscar Salvador Cc: Oscar Salvador (SUSE) Cc: "Ritesh Harjani (IBM)" Cc: Usama Arif Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- include/linux/mmzone.h | 7 ------- mm/hugetlb_vmemmap.c | 4 ---- mm/hugetlb_vmemmap.h | 5 ----- mm/sparse-vmemmap.c | 11 ----------- mm/sparse.c | 1 - 5 files changed, 28 deletions(-) diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h index e26b3d38fa25..35d1a7643dc4 100644 --- a/include/linux/mmzone.h +++ b/include/linux/mmzone.h @@ -2156,8 +2156,6 @@ static inline int preinited_vmemmap_section(const struct mem_section *section) } void sparse_vmemmap_init_nid_early(int nid); -void sparse_vmemmap_init_nid_late(int nid); - #else static inline int preinited_vmemmap_section(const struct mem_section *section) { @@ -2166,10 +2164,6 @@ static inline int preinited_vmemmap_section(const struct mem_section *section) static inline void sparse_vmemmap_init_nid_early(int nid) { } - -static inline void sparse_vmemmap_init_nid_late(int nid) -{ -} #endif static inline int online_section_nr(unsigned long nr) @@ -2374,7 +2368,6 @@ static inline unsigned long next_present_section_nr(unsigned long section_nr) #else #define sparse_vmemmap_init_nid_early(_nid) do {} while (0) -#define sparse_vmemmap_init_nid_late(_nid) do {} while (0) #define pfn_in_present_section pfn_valid #endif /* CONFIG_SPARSEMEM */ diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c index ee4fbd5fed0d..eefd6b5f9706 100644 --- a/mm/hugetlb_vmemmap.c +++ b/mm/hugetlb_vmemmap.c @@ -808,10 +808,6 @@ void __init hugetlb_vmemmap_init_early(int nid) m->flags |= HUGE_BOOTMEM_HVO; } } - -void __init hugetlb_vmemmap_init_late(int nid) -{ -} #endif static const struct ctl_table hugetlb_vmemmap_sysctls[] = { diff --git a/mm/hugetlb_vmemmap.h b/mm/hugetlb_vmemmap.h index 18b490825215..7ac49c52457d 100644 --- a/mm/hugetlb_vmemmap.h +++ b/mm/hugetlb_vmemmap.h @@ -29,7 +29,6 @@ void hugetlb_vmemmap_optimize_folios(struct hstate *h, struct list_head *folio_l void hugetlb_vmemmap_optimize_bootmem_folios(struct hstate *h, struct list_head *folio_list); #ifdef CONFIG_SPARSEMEM_VMEMMAP_PREINIT void hugetlb_vmemmap_init_early(int nid); -void hugetlb_vmemmap_init_late(int nid); #endif @@ -81,10 +80,6 @@ static inline void hugetlb_vmemmap_init_early(int nid) { } -static inline void hugetlb_vmemmap_init_late(int nid) -{ -} - static inline unsigned int hugetlb_vmemmap_optimizable_size(const struct hstate *h) { return 0; diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c index 0b4019a93188..515a404b66c0 100644 --- a/mm/sparse-vmemmap.c +++ b/mm/sparse-vmemmap.c @@ -579,17 +579,6 @@ void __init sparse_vmemmap_init_nid_early(int nid) { hugetlb_vmemmap_init_early(nid); } - -/* - * This is called just before the initialization of page structures - * through memmap_init. Zones are now initialized, so any work that - * needs to be done that needs zone information can be done from - * here. - */ -void __init sparse_vmemmap_init_nid_late(int nid) -{ - hugetlb_vmemmap_init_late(nid); -} #endif static void subsection_mask_set(unsigned long *map, unsigned long pfn, diff --git a/mm/sparse.c b/mm/sparse.c index 3917a47153d8..324213d8bdcb 100644 --- a/mm/sparse.c +++ b/mm/sparse.c @@ -320,7 +320,6 @@ static void __init sparse_init_nid(int nid, unsigned long pnum_begin, } } sparse_usage_fini(); - sparse_vmemmap_init_nid_late(nid); } /* From befeb601c6320c98d1602043ddf0138434ddd803 Mon Sep 17 00:00:00 2001 From: Muchun Song Date: Fri, 12 Jun 2026 11:59:02 +0800 Subject: [PATCH 198/562] mm/hugetlb: remove unused bootmem cma field struct huge_bootmem_page no longer needs to keep the CMA pointer. The bootmem path only needs to remember whether a huge page came from CMA, which is already encoded in the flags field. Set HUGE_BOOTMEM_CMA when the page is allocated, drop the unused cma field together with the redundant assignments, and simplify the early CMA bootmem allocation fallback path now that the cma pointer no longer has to be stored in struct huge_bootmem_page. Link: https://lore.kernel.org/20260612035903.2468601-19-songmuchun@bytedance.com Signed-off-by: Muchun Song Cc: "Aneesh Kumar K.V" Cc: David Hildenbrand Cc: Frank van der Linden Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Madhavan Srinivasan Cc: Michael Ellerman Cc: Mike Rapoport (Microsoft) Cc: Nicholas Piggin Cc: Oscar Salvador Cc: Oscar Salvador (SUSE) Cc: "Ritesh Harjani (IBM)" Cc: Usama Arif Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/hugetlb.c | 5 +---- mm/hugetlb_cma.c | 29 +++++++++++------------------ mm/internal.h | 2 -- 3 files changed, 12 insertions(+), 24 deletions(-) diff --git a/mm/hugetlb.c b/mm/hugetlb.c index c79b27a6b162..5d6b15692e3b 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -3088,10 +3088,7 @@ static bool __init alloc_bootmem_huge_page(struct hstate *h, int nid) */ INIT_LIST_HEAD(&m->list); m->hstate = h; - if (!hugetlb_early_cma(h)) { - m->cma = NULL; - m->flags = 0; - } + m->flags = hugetlb_early_cma(h) ? HUGE_BOOTMEM_CMA : 0; /* CMA pages: zone-crossing is validated in hugetlb_cma_reserve(). */ if (!hugetlb_early_cma(h) && diff --git a/mm/hugetlb_cma.c b/mm/hugetlb_cma.c index ae21b24e45b0..d0dc28f08b3c 100644 --- a/mm/hugetlb_cma.c +++ b/mm/hugetlb_cma.c @@ -59,31 +59,24 @@ struct folio *hugetlb_cma_alloc_frozen_folio(int order, gfp_t gfp_mask, void * __init hugetlb_cma_alloc_bootmem(struct hstate *h, int nid, bool node_exact) { struct cma *cma; - struct huge_bootmem_page *m; + void *m; int node; cma = hugetlb_cma[nid]; m = cma_reserve_early(cma, huge_page_size(h)); - if (!m) { - if (node_exact) - return NULL; + if (m || node_exact) + return m; - for_each_node_mask(node, hugetlb_bootmem_nodes) { - cma = hugetlb_cma[node]; - if (!cma || node == nid) - continue; - m = cma_reserve_early(cma, huge_page_size(h)); - if (m) - break; - } - } - - if (m) { - m->flags = HUGE_BOOTMEM_CMA; - m->cma = cma; + for_each_node_mask(node, hugetlb_bootmem_nodes) { + cma = hugetlb_cma[node]; + if (!cma || node == nid) + continue; + m = cma_reserve_early(cma, huge_page_size(h)); + if (m) + return m; } - return m; + return NULL; } static int __init cmdline_parse_hugetlb_cma(char *p) diff --git a/mm/internal.h b/mm/internal.h index 3401759924d9..39c9564fba0e 100644 --- a/mm/internal.h +++ b/mm/internal.h @@ -24,13 +24,11 @@ struct folio_batch; struct hstate; -struct cma; struct huge_bootmem_page { struct list_head list; struct hstate *hstate; unsigned long flags; - struct cma *cma; }; /* From 43513b0079b3b127b0881d7531de54121ea8fb75 Mon Sep 17 00:00:00 2001 From: Muchun Song Date: Fri, 12 Jun 2026 11:59:03 +0800 Subject: [PATCH 199/562] mm/mm_init: fold __init_page_from_nid() into __init_deferred_page() __init_page_from_nid() no longer has external users and is only used locally in mm/mm_init.c under CONFIG_DEFERRED_STRUCT_PAGE_INIT. Fold it into its sole caller __init_deferred_page() and remove the separate helper declaration. Link: https://lore.kernel.org/20260612035903.2468601-20-songmuchun@bytedance.com Signed-off-by: Muchun Song Reviewed-by: Mike Rapoport (Microsoft) Cc: "Aneesh Kumar K.V" Cc: David Hildenbrand Cc: Frank van der Linden Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Madhavan Srinivasan Cc: Michael Ellerman Cc: Nicholas Piggin Cc: Oscar Salvador Cc: Oscar Salvador (SUSE) Cc: "Ritesh Harjani (IBM)" Cc: Usama Arif Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/internal.h | 1 - mm/mm_init.c | 42 ++++++++++++++++-------------------------- 2 files changed, 16 insertions(+), 27 deletions(-) diff --git a/mm/internal.h b/mm/internal.h index 39c9564fba0e..dc10291f14bb 100644 --- a/mm/internal.h +++ b/mm/internal.h @@ -1760,7 +1760,6 @@ static inline bool pte_needs_soft_dirty_wp(struct vm_area_struct *vma, pte_t pte void __meminit __init_single_page(struct page *page, unsigned long pfn, unsigned long zone, int nid); -void __meminit __init_page_from_nid(unsigned long pfn, int nid); /* shrinker related functions */ unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg, diff --git a/mm/mm_init.c b/mm/mm_init.c index 4ed4591dcdba..cfd0b2722d83 100644 --- a/mm/mm_init.c +++ b/mm/mm_init.c @@ -688,31 +688,6 @@ static __meminit void pageblock_migratetype_init_range(unsigned long pfn, } #endif -/* - * Initialize a reserved page unconditionally, finding its zone first. - */ -void __meminit __init_page_from_nid(unsigned long pfn, int nid) -{ - pg_data_t *pgdat; - int zid; - - pgdat = NODE_DATA(nid); - - for (zid = 0; zid < MAX_NR_ZONES; zid++) { - struct zone *zone = &pgdat->node_zones[zid]; - - if (zone_spans_pfn(zone, pfn)) - break; - } - __init_single_page(pfn_to_page(pfn), pfn, zid, nid); - - if (pageblock_aligned(pfn)) { - enum migratetype mt = - kho_scratch_migratetype(pfn, MIGRATE_MOVABLE); - init_pageblock_migratetype(pfn_to_page(pfn), mt, false); - } -} - #ifdef CONFIG_DEFERRED_STRUCT_PAGE_INIT static inline void pgdat_set_deferred_range(pg_data_t *pgdat) { @@ -771,10 +746,25 @@ defer_init(int nid, unsigned long pfn, unsigned long end_pfn) static void __meminit __init_deferred_page(unsigned long pfn, int nid) { + pg_data_t *pgdat = NODE_DATA(nid); + int zid; + if (early_page_initialised(pfn, nid)) return; - __init_page_from_nid(pfn, nid); + for (zid = 0; zid < MAX_NR_ZONES; zid++) { + struct zone *zone = &pgdat->node_zones[zid]; + + if (zone_spans_pfn(zone, pfn)) + break; + } + __init_single_page(pfn_to_page(pfn), pfn, zid, nid); + + if (pageblock_aligned(pfn)) { + enum migratetype mt = + kho_scratch_migratetype(pfn, MIGRATE_MOVABLE); + init_pageblock_migratetype(pfn_to_page(pfn), mt, false); + } } #else static inline void pgdat_set_deferred_range(pg_data_t *pgdat) {} From 220d8c583fc19fc598162617e91c6206823b249f Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Tue, 30 Jun 2026 05:46:04 -0700 Subject: [PATCH 200/562] mm/memory-failure: drop dead error_states[] entry for reserved pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Patch series "mm/memory-failure: add panic option for unrecoverable pages", v10. A multi-bit ECC error on a kernel-owned page that the memory failure handler cannot recover is currently swallowed: PG_hwpoison is set, the event is logged, and the kernel keeps running. The corrupted memory remains accessible to the kernel and either drives silent data corruption or surfaces seconds-to-minutes later as an apparently unrelated crash. In a large fleet that delayed, unattributable crash turns into significant engineering effort to root-cause; in a kdump configuration, by the time the crash happens the original error context (faulting PFN, MCE/GHES record, page state) is long gone. This series adds an opt-in sysctl, vm.panic_on_unrecoverable_memory_failure, that converts an unrecoverable kernel-page hwpoison event into an immediate panic with a clean dmesg/vmcore that still contains the original failure context. The default is disabled so existing workloads see no change. There is a selftest that test different cases, and I tested it using the following variants: ┌─────────┬──────────┬───────────────────────────────────────────────────────────┐ │ Variant │ PFN │ Result │ ├─────────┼──────────┼───────────────────────────────────────────────────────────┤ │ rodata │ 0x2600 │ Panic with "Memory failure: 0x2600: unrecoverable page" │ ├─────────┼──────────┼───────────────────────────────────────────────────────────┤ │ slab │ 0x100032 │ Panic with "Memory failure: 0x100032: unrecoverable page" │ ├─────────┼──────────┼───────────────────────────────────────────────────────────┤ │ pgtable │ 0x100000 │ Panic with "Memory failure: 0x100000: unrecoverable page" │ └─────────┴──────────┴───────────────────────────────────────────────────────────┘ Each one shows the same call trace, exactly the path the series builds: hard_offline_page_store → memory_failure → action_result → panic("Memory failure: %#lx: unrecoverable page") This patch (of 6): The first entry of error_states[], { reserved, reserved, MF_MSG_KERNEL, me_kernel }, is unreachable. identify_page_state() has two callers, and neither one can dispatch a PG_reserved page to me_kernel(): * memory_failure() reaches identify_page_state() only after get_hwpoison_page() returned 1. get_any_page() reaches that return only via __get_hwpoison_page(), which only takes a refcount when the page is HWPoisonHandlable(). HWPoisonHandlable() is an allowlist for LRU, free-buddy, and (for soft-offline) movable_ops pages -- PG_reserved pages do not satisfy any of these, so they fail with -EBUSY/-EIO long before identify_page_state() runs. * try_memory_failure_hugetlb() reaches identify_page_state() only via the MF_HUGETLB_IN_USED branch, where the page is necessarily a hugetlb folio. hugetlb folios don't carry PG_reserved at that point: hugetlb_folio_init_vmemmap() calls __folio_clear_reserved() during init, so the reserved entry would not match even if it were still present. me_kernel() never executes and the entry exists only to be matched against by code that cannot see it. Drop the entry, the me_kernel() helper, and the now-unused "reserved" macro. Leave the MF_MSG_KERNEL enum value in place: it remains part of the tracepoint and pr_err() string tables, and follow-on work to classify unrecoverable kernel pages can reuse it without churning the user-visible enum. No functional change. Link: https://lore.kernel.org/20260630-ecc_panic-v10-0-c6ed5b62eea2@debian.org Link: https://lore.kernel.org/20260630-ecc_panic-v10-1-c6ed5b62eea2@debian.org Signed-off-by: Breno Leitao Suggested-by: David Hildenbrand Acked-by: David Hildenbrand (Arm) Reviewed-by: Lance Yang Acked-by: Miaohe Lin Cc: Jonathan Corbet Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: "Masami Hiramatsu (Google)" Cc: Mathieu Desnoyers Cc: Michal Hocko Cc: Mike Rapoport Cc: Naoya Horiguchi Cc: Shuah Khan Cc: Steven Rostedt Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/memory-failure.c | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/mm/memory-failure.c b/mm/memory-failure.c index 4963ea9f6ec6..c4d7d5ef92f0 100644 --- a/mm/memory-failure.c +++ b/mm/memory-failure.c @@ -980,17 +980,6 @@ static bool has_extra_refcount(struct page_state *ps, struct page *p, return false; } -/* - * Error hit kernel page. - * Do nothing, try to be lucky and not touch this instead. For a few cases we - * could be more sophisticated. - */ -static int me_kernel(struct page_state *ps, struct page *p) -{ - unlock_page(p); - return MF_IGNORED; -} - /* * Page in unknown state. Do nothing. * This is a catch-all in case we fail to make sense of the page state. @@ -1199,10 +1188,8 @@ static int me_huge_page(struct page_state *ps, struct page *p) #define mlock (1UL << PG_mlocked) #define lru (1UL << PG_lru) #define head (1UL << PG_head) -#define reserved (1UL << PG_reserved) static struct page_state error_states[] = { - { reserved, reserved, MF_MSG_KERNEL, me_kernel }, /* * free pages are specially detected outside this table: * PG_buddy pages only make a small fraction of all free pages. @@ -1234,7 +1221,6 @@ static struct page_state error_states[] = { #undef mlock #undef lru #undef head -#undef reserved static void update_per_node_mf_stats(unsigned long pfn, enum mf_result result) From c4f52d80c6d4278a32b75f4d964caee6c9fd8308 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Tue, 30 Jun 2026 05:46:05 -0700 Subject: [PATCH 201/562] mm/memory-failure: surface unhandlable kernel pages as -ENOTRECOVERABLE get_any_page() collapses every HWPoisonHandlable() rejection into a single -EIO via the __get_hwpoison_page() -> -EBUSY -> shake_page() -> retry path. That is correct for the transient case (a userspace folio briefly off LRU during migration or compaction, which a later shake can drag back), but wrong for stable kernel-owned pages: slab, page-table, large-kmalloc and PG_reserved pages will never become HWPoisonHandlable(), so the retry loop is wasted work and the final -EIO loses the "this is structurally unrecoverable" information. memory_failure() then maps -EIO into MF_MSG_GET_HWPOISON, which the panic-on-unrecoverable sysctl deliberately does not act on. Introduce is_kernel_owned_page(), a small predicate that positively identifies pages the hwpoison handler cannot recover from: is_kernel_owned_page(p) := PageReserved(p) || PageSlab(head) || PageTable(head) || PageLargeKmalloc(head) where head = compound_head(p). PG_reserved is a per-page flag (PF_NO_COMPOUND) and is tested on the page directly. The slab, page-table and large-kmalloc page-type bits are only stored on the head page, so those tests resolve the compound head first, then re-read compound_head(page) afterwards: a concurrent split or compound free that moves head invalidates the just-read flags and the loop retries. The lookup still takes no refcount, mirroring the rest of get_any_page(); the recheck closes the common split race, and a residual free->alloc->free in the same window can only mis-tag a genuinely poisoned page, never reclassify a handlable one. No MF_SOFT_OFFLINE / page_has_movable_ops() opt-out is needed: a movable_ops page is always PageOffline or PageZsmalloc, whose page_type is mutually exclusive with slab, page-table and large-kmalloc, and it never carries PG_reserved, so it can never match any of the checks above. The list is intentionally not exhaustive. vmalloc and kernel-stack pages, for example, do not carry a page_type bit and would need a different oracle; they keep going through the existing retry path unchanged. This is the smallest set we can identify with certainty by page type. Wire the helper into the top of get_any_page() to short-circuit those pages before the retry loop runs. On a hit, drop the caller's MF_COUNT_INCREASED reference (if any) and return -ENOTRECOVERABLE straight away. Pages outside the helper's positive list still take the existing retry path and return -EIO, leaving operator-visible behaviour for those cases unchanged. Extend the unhandlable-page pr_err() to fire for either errno and update the get_hwpoison_page() kerneldoc to document the new return. memory_failure() still folds every negative return into MF_MSG_GET_HWPOISON via its existing "else if (res < 0)" branch, so this patch on its own only changes the errno that soft_offline_page() can propagate to its callers. A follow-up wires -ENOTRECOVERABLE through memory_failure() and reports MF_MSG_KERNEL for the unrecoverable cases, which is what the panic_on_unrecoverable_memory_failure sysctl observes. Link: https://lore.kernel.org/20260630-ecc_panic-v10-2-c6ed5b62eea2@debian.org Signed-off-by: Breno Leitao Suggested-by: David Hildenbrand Suggested-by: Lance Yang Cc: Jonathan Corbet Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: "Masami Hiramatsu (Google)" Cc: Mathieu Desnoyers Cc: Miaohe Lin Cc: Michal Hocko Cc: Mike Rapoport Cc: Naoya Horiguchi Cc: Shuah Khan Cc: Steven Rostedt Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/memory-failure.c | 52 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/mm/memory-failure.c b/mm/memory-failure.c index c4d7d5ef92f0..6cb6c635565c 100644 --- a/mm/memory-failure.c +++ b/mm/memory-failure.c @@ -1325,6 +1325,38 @@ static inline bool HWPoisonHandlable(struct page *page, unsigned long flags) return PageLRU(page) || is_free_buddy_page(page); } +/* + * Positive identification of pages the hwpoison handler cannot recover: + * pages owned by kernel internals with no userspace mapping to unmap, no + * file mapping to invalidate, and no migration target. + */ +static inline bool is_kernel_owned_page(struct page *page) +{ + struct page *head; + bool kernel_owned; + + /* PG_reserved is a per-page flag, never set on a compound page. */ + if (PageReserved(page)) + return true; + + /* + * Page-type bits live only on the head page, so resolve any tail + * first. The check takes no refcount; recheck the head afterwards + * so a concurrent split or compound free cannot leave us trusting + * a stale view. A residual free->alloc->free cannot be closed here + * (frozen slab and large-kmalloc pages cannot be pinned), but is + * harmless: where a wrong verdict could panic, memory_failure() has + * already set PageHWPoison, which bars the page from the allocator. + */ +retry: + head = compound_head(page); + kernel_owned = PageSlab(head) || PageTable(head) || + PageLargeKmalloc(head); + if (head != compound_head(page)) + goto retry; + return kernel_owned; +} + static int __get_hwpoison_page(struct page *page, unsigned long flags) { struct folio *folio = page_folio(page); @@ -1371,6 +1403,19 @@ static int get_any_page(struct page *p, unsigned long flags) if (flags & MF_COUNT_INCREASED) count_increased = true; + /* + * Page types we know are kernel-owned and cannot be recovered. + * Short-circuit before the shake_page() / retry loop, which + * cannot turn any of these into something HWPoisonHandlable(). + * Drop the caller's reference if MF_COUNT_INCREASED took one. + */ + if (is_kernel_owned_page(p)) { + if (count_increased) + put_page(p); + ret = -ENOTRECOVERABLE; + goto out; + } + try_again: if (!count_increased) { ret = __get_hwpoison_page(p, flags); @@ -1418,7 +1463,7 @@ static int get_any_page(struct page *p, unsigned long flags) ret = -EIO; } out: - if (ret == -EIO) + if (ret == -EIO || ret == -ENOTRECOVERABLE) pr_err("%#lx: unhandlable page.\n", page_to_pfn(p)); return ret; @@ -1475,7 +1520,10 @@ static int __get_unpoison_page(struct page *page) * -EIO for pages on which we can not handle memory errors, * -EBUSY when get_hwpoison_page() has raced with page lifecycle * operations like allocation and free, - * -EHWPOISON when the page is hwpoisoned and taken off from buddy. + * -EHWPOISON when the page is hwpoisoned and taken off from buddy, + * -ENOTRECOVERABLE for kernel-owned pages identified by + * is_kernel_owned_page() (PG_reserved, slab, + * page-table, large-kmalloc) that the handler cannot recover. */ static int get_hwpoison_page(struct page *p, unsigned long flags) { From f3132c42439c0e6db1c82ad6d3dc82bb6d9c1dc9 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Tue, 30 Jun 2026 05:46:06 -0700 Subject: [PATCH 202/562] mm/memory-failure: report MF_MSG_KERNEL for unrecoverable kernel pages The previous patch teaches get_any_page() to return -ENOTRECOVERABLE for stable unhandlable kernel pages (PG_reserved, slab, page tables, large-kmalloc). memory_failure() still folds every negative return into MF_MSG_GET_HWPOISON, so callers that want to react to the unrecoverable cases (a panic option, smarter logging) cannot tell them apart from transient page-allocator races. Turn the post-call branch into a switch over the get_hwpoison_page() return code: map -ENOTRECOVERABLE to MF_MSG_KERNEL and any other negative return to MF_MSG_GET_HWPOISON. case 0 keeps the existing free-buddy / kernel-high-order handling and case 1 falls through to the rest of memory_failure() unchanged. The MF_MSG_KERNEL label and tracepoint string are kept as "reserved kernel page" to avoid breaking userspace tools that match on those literals; the enum value still adequately tags the failure even though it now also covers slab, page tables and large-kmalloc pages. Link: https://lore.kernel.org/20260630-ecc_panic-v10-3-c6ed5b62eea2@debian.org Signed-off-by: Breno Leitao Suggested-by: David Hildenbrand Acked-by: David Hildenbrand (Arm) Acked-by: Miaohe Lin Cc: Jonathan Corbet Cc: Lance Yang Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: "Masami Hiramatsu (Google)" Cc: Mathieu Desnoyers Cc: Michal Hocko Cc: Mike Rapoport Cc: Naoya Horiguchi Cc: Shuah Khan Cc: Steven Rostedt Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/memory-failure.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/mm/memory-failure.c b/mm/memory-failure.c index 6cb6c635565c..38762a183dfb 100644 --- a/mm/memory-failure.c +++ b/mm/memory-failure.c @@ -2436,7 +2436,8 @@ int memory_failure(unsigned long pfn, int flags) * that may make page_ref_freeze()/page_ref_unfreeze() mismatch. */ res = get_hwpoison_page(p, flags); - if (!res) { + switch (res) { + case 0: if (is_free_buddy_page(p)) { if (take_page_off_buddy(p)) { page_ref_inc(p); @@ -2455,7 +2456,19 @@ int memory_failure(unsigned long pfn, int flags) res = action_result(pfn, MF_MSG_KERNEL_HIGH_ORDER, MF_IGNORED); } goto unlock_mutex; - } else if (res < 0) { + case 1: + /* Got a refcount on a handlable page. */ + break; + case -ENOTRECOVERABLE: + /* + * Stable unhandlable kernel-owned page (PG_reserved, + * slab, page tables, large-kmalloc). + * No recovery possible. + */ + res = action_result(pfn, MF_MSG_KERNEL, MF_IGNORED); + goto unlock_mutex; + default: + /* Transient lifecycle race with the page allocator. */ res = action_result(pfn, MF_MSG_GET_HWPOISON, MF_IGNORED); goto unlock_mutex; } From dc862da46e715c0ad2e56a5286e8661e36d542c0 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Tue, 30 Jun 2026 05:46:07 -0700 Subject: [PATCH 203/562] mm/memory-failure: add panic option for unrecoverable pages Add a sysctl panic_on_unrecoverable_memory_failure (disabled by default) that triggers a kernel panic when memory_failure() encounters pages that cannot be recovered. This provides a clean crash with useful debug information rather than allowing silent data corruption or a delayed crash at an unrelated code path. Panic eligibility is intentionally narrow: only MF_MSG_KERNEL with result == MF_IGNORED panics. After the previous patch, MF_MSG_KERNEL covers PG_reserved pages and the kernel-owned pages promoted from get_hwpoison_page() via -ENOTRECOVERABLE (slab, page tables, large-kmalloc). All other action types are excluded: - MF_MSG_GET_HWPOISON and MF_MSG_KERNEL_HIGH_ORDER can be reached by transient refcount races with the page allocator (an in-flight buddy allocation has refcount 0 and is no longer on the buddy free list, briefly), and panicking on them would risk killing the box for what is actually a recoverable userspace page. - MF_MSG_UNKNOWN means identify_page_state() could not classify the page; that is precisely the wrong basis for a panic decision. Link: https://lore.kernel.org/20260630-ecc_panic-v10-4-c6ed5b62eea2@debian.org Signed-off-by: Breno Leitao Acked-by: Miaohe Lin Cc: David Hildenbrand (Arm) Cc: Jonathan Corbet Cc: Lance Yang Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: "Masami Hiramatsu (Google)" Cc: Mathieu Desnoyers Cc: Michal Hocko Cc: Mike Rapoport Cc: Naoya Horiguchi Cc: Shuah Khan Cc: Steven Rostedt Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/memory-failure.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/mm/memory-failure.c b/mm/memory-failure.c index 38762a183dfb..4916ab145325 100644 --- a/mm/memory-failure.c +++ b/mm/memory-failure.c @@ -74,6 +74,8 @@ static int sysctl_memory_failure_recovery __read_mostly = 1; static int sysctl_enable_soft_offline __read_mostly = 1; +static int sysctl_panic_on_unrecoverable_mf __read_mostly; + atomic_long_t num_poisoned_pages __read_mostly = ATOMIC_LONG_INIT(0); static bool hw_memory_failure __read_mostly; @@ -155,6 +157,15 @@ static const struct ctl_table memory_failure_table[] = { .proc_handler = proc_dointvec_minmax, .extra1 = SYSCTL_ZERO, .extra2 = SYSCTL_ONE, + }, + { + .procname = "panic_on_unrecoverable_memory_failure", + .data = &sysctl_panic_on_unrecoverable_mf, + .maxlen = sizeof(sysctl_panic_on_unrecoverable_mf), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = SYSCTL_ZERO, + .extra2 = SYSCTL_ONE, } }; @@ -1255,6 +1266,15 @@ static void update_per_node_mf_stats(unsigned long pfn, ++mf_stats->total; } +static bool panic_on_unrecoverable_mf(enum mf_action_page_type type, + enum mf_result result) +{ + if (!sysctl_panic_on_unrecoverable_mf) + return false; + + return type == MF_MSG_KERNEL && result == MF_IGNORED; +} + /* * "Dirty/Clean" indication is not 100% accurate due to the possibility of * setting PG_dirty outside page lock. See also comment above set_page_dirty(). @@ -1272,6 +1292,9 @@ static int action_result(unsigned long pfn, enum mf_action_page_type type, pr_err("%#lx: recovery action for %s: %s\n", pfn, action_page_types[type], action_name[result]); + if (panic_on_unrecoverable_mf(type, result)) + panic("Memory failure: %#lx: unrecoverable page", pfn); + return (result == MF_RECOVERED || result == MF_DELAYED) ? 0 : -EBUSY; } From c80153103c447ca0ac25293fe9021555c99dd570 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Tue, 30 Jun 2026 05:46:08 -0700 Subject: [PATCH 204/562] Documentation: document panic_on_unrecoverable_memory_failure sysctl Add documentation for the new vm.panic_on_unrecoverable_memory_failure sysctl, describing which failures trigger a panic (kernel-owned pages the handler cannot recover) and which are intentionally left out (transient allocator races and unclassified pages). Link: https://lore.kernel.org/20260630-ecc_panic-v10-5-c6ed5b62eea2@debian.org Signed-off-by: Breno Leitao Cc: David Hildenbrand (Arm) Cc: Jonathan Corbet Cc: Lance Yang Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: "Masami Hiramatsu (Google)" Cc: Mathieu Desnoyers Cc: Miaohe Lin Cc: Michal Hocko Cc: Mike Rapoport Cc: Naoya Horiguchi Cc: Shuah Khan Cc: Steven Rostedt Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- Documentation/admin-guide/sysctl/vm.rst | 80 +++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/Documentation/admin-guide/sysctl/vm.rst b/Documentation/admin-guide/sysctl/vm.rst index b9b0c218bfb4..22cc54cac3b2 100644 --- a/Documentation/admin-guide/sysctl/vm.rst +++ b/Documentation/admin-guide/sysctl/vm.rst @@ -67,6 +67,7 @@ Currently, these files are in /proc/sys/vm: - page-cluster - page_lock_unfairness - panic_on_oom +- panic_on_unrecoverable_memory_failure - percpu_pagelist_high_fraction - stat_interval - stat_refresh @@ -925,6 +926,85 @@ panic_on_oom=2+kdump gives you very strong tool to investigate why oom happens. You can get snapshot. +panic_on_unrecoverable_memory_failure +====================================== + +When a hardware memory error (e.g. multi-bit ECC) hits a kernel page +that cannot be recovered by the memory failure handler, the default +behaviour is to ignore the error and continue operation. This is +dangerous because the corrupted data remains accessible to the kernel, +risking silent data corruption or a delayed crash when the poisoned +memory is next accessed. + +When enabled, this sysctl triggers a panic on memory failure events +hitting kernel-owned pages that the handler cannot recover: +``PageReserved`` (firmware reservations, kernel image, vDSO, zero +page, and similar memblock-reserved regions), ``PageSlab``, +``PageTable``, and ``PageLargeKmalloc``. These are owned by the +kernel and the memory failure handler cannot reliably evict their +contents. + +Other unrecoverable kernel-owned populations (vmalloc allocations, +kernel stack pages, ...) are not currently covered because the +handler has no page-type signal that distinguishes them from a +userspace folio temporarily off the LRU during migration or +compaction. Such pages still go through the standard +MF_MSG_GET_HWPOISON path: ``PG_hwpoison`` is set on them and a +delayed crash on the next access remains possible. Coverage may +grow as the handler gains stronger kernel-ownership signals. + +Recoverable failure paths are also intentionally left out: in-flight +buddy allocations and other transient races with the page allocator +can reach the same diagnostic, and panicking on them would risk +killing the box for a page destined for userspace where the standard +SIGBUS recovery path applies. Pages whose state could not be +classified at all are not covered either, since an unknown state is +not a sound basis for a panic decision. + +For many environments it is preferable to panic immediately with a clean +crash dump that captures the original error context, rather than to +continue and face a random crash later whose cause is difficult to +diagnose. + +Use cases +--------- + +This option is most useful in environments where unattributed crashes +are expensive to debug or where data integrity must take precedence +over availability: + +* Large fleets, where multi-bit ECC errors on kernel pages are observed + regularly and post-mortem analysis of an unrelated downstream crash + (often seconds to minutes after the original error) consumes + significant engineering effort. + +* Systems configured with kdump, where panicking at the moment of the + hardware error produces a vmcore that still contains the faulting + address, the affected page state, and the originating MCE/GHES + record — context that is typically lost by the time a delayed crash + occurs. + +* High-availability clusters that rely on fast, deterministic node + failure for failover, and prefer an immediate panic over silent data + corruption propagating to replicas or persistent storage. + +* Kernel and platform developers reproducing hwpoison issues with + tools such as ``mce-inject`` or error-injection debugfs interfaces, + where panicking on the unrecoverable path makes regressions + immediately visible instead of surfacing as later, unrelated + failures. + += ===================================================================== +0 Try to continue operation (default). +1 Panic immediately. If the ``panic`` sysctl is also non-zero then the + machine will be rebooted. += ===================================================================== + +Example:: + + echo 1 > /proc/sys/vm/panic_on_unrecoverable_memory_failure + + percpu_pagelist_high_fraction ============================= From c0586ed5b9deedf7ba1f7af505dd513f36f2af8d Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Tue, 30 Jun 2026 05:46:09 -0700 Subject: [PATCH 205/562] selftests/mm: add hwpoison-panic destructive test Add a destructive selftest that verifies vm.panic_on_unrecoverable_memory_failure actually panics when a hwpoison error hits a kernel-owned page. Three "kinds" of kernel-owned page can be targeted, selectable via the script's first positional argument (default: rodata): rodata - a PG_reserved page in the kernel rodata range, sourced from the "Kernel rodata" sub-resource of "System RAM" in /proc/iomem. That entry is reported on every major architecture and guarantees the chosen PFN is backed by struct page (an online System RAM range, not a firmware hole), is PG_reserved, and is read-only -- so even if the panic fails to fire for some reason, the resulting PG_hwpoison marker on rodata does not corrupt writable kernel state. slab - a slab page found by walking /proc/kpageflags for the first PFN with KPF_SLAB set (and KPF_HWPOISON / KPF_NOPAGE / KPF_COMPOUND_TAIL clear). Exercises the get_any_page() path on a non PG_reserved kernel-owned page and so catches regressions where get_any_page() collapses kernel-owned pages into a transient -EIO instead of -ENOTRECOVERABLE. pgtable - same as slab, but the PFN is selected via KPF_PGTABLE. PageLargeKmalloc, the fourth page type matched by is_kernel_owned_page(), is intentionally not covered: it is a PAGE_TYPE_OPS flag with no /proc/kpageflags bit, so selecting such a PFN from userspace is not feasible. The slab and pgtable variants already exercise the same get_any_page() positive-check branch. The script enables the sysctl and writes the selected physical address to /sys/devices/system/memory/hard_offline_page. A successful run crashes the kernel with Memory failure: : unrecoverable page A return from the inject means no panic fired. Before reporting, the script restores the sysctl and best-effort unpoisons the target PFN through the hwpoison debugfs interface (hard_offline_page() injects with MF_SW_SIMULATED, so the page stays unpoisonable), then re-reads /proc/kpageflags: a PFN that is still the kernel-owned type it selected is a genuine failure, while one that raced to a different type before the inject is skipped as inconclusive. Test outcome is therefore observed externally (serial console, kdump) rather than from the script's own exit code. The script is intentionally NOT wired into run_vmtests.sh: every successful run panics the kernel, which is incompatible with the sequential "run each category in the same VM" model that run_vmtests.sh assumes. It is also not registered as a TEST_PROGS / ksft_* wrapper so a default kselftest run does not opt itself into a panic. The script is meant to be executed manually inside a disposable VM (e.g. virtme-ng), one variant per VM boot, and requires RUN_DESTRUCTIVE=1 in the environment as a safety net. Link: https://lore.kernel.org/20260630-ecc_panic-v10-6-c6ed5b62eea2@debian.org Signed-off-by: Breno Leitao Cc: David Hildenbrand (Arm) Cc: Jonathan Corbet Cc: Lance Yang Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: "Masami Hiramatsu (Google)" Cc: Mathieu Desnoyers Cc: Miaohe Lin Cc: Michal Hocko Cc: Mike Rapoport Cc: Naoya Horiguchi Cc: Shuah Khan Cc: Steven Rostedt Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- tools/testing/selftests/mm/Makefile | 4 + tools/testing/selftests/mm/hwpoison-panic.sh | 255 +++++++++++++++++++ 2 files changed, 259 insertions(+) create mode 100755 tools/testing/selftests/mm/hwpoison-panic.sh diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile index e6df968f0971..ed321ae709da 100644 --- a/tools/testing/selftests/mm/Makefile +++ b/tools/testing/selftests/mm/Makefile @@ -174,6 +174,10 @@ TEST_PROGS += ksft_userfaultfd.sh TEST_PROGS += ksft_vma_merge.sh TEST_PROGS += ksft_vmalloc.sh +# Destructive: every successful run panics the kernel. Installed and +# kept executable, but not run from a default kselftest invocation. +TEST_PROGS_EXTENDED += hwpoison-panic.sh + TEST_FILES := test_vmalloc.sh TEST_FILES += test_hmm.sh TEST_FILES += va_high_addr_switch.sh diff --git a/tools/testing/selftests/mm/hwpoison-panic.sh b/tools/testing/selftests/mm/hwpoison-panic.sh new file mode 100755 index 000000000000..d953d1367332 --- /dev/null +++ b/tools/testing/selftests/mm/hwpoison-panic.sh @@ -0,0 +1,255 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Verify vm.panic_on_unrecoverable_memory_failure by injecting a hwpoison +# error on a kernel-owned page and confirming the kernel panics. +# +# Three "kinds" of kernel-owned page can be targeted, selectable via the +# first positional argument (default: rodata): +# +# rodata - a PG_reserved page in the kernel rodata range +# (sourced from /proc/iomem "Kernel rodata"). Exercises +# memory_failure() -> get_any_page() on a PageReserved page. +# +# slab - a slab page found via /proc/kpageflags (KPF_SLAB). +# Exercises memory_failure() -> get_any_page() on a non +# PG_reserved kernel-owned page. This path is what catches +# regressions where get_any_page() collapses kernel-owned +# pages into a transient -EIO instead of -ENOTRECOVERABLE. +# +# pgtable - a page-table page found via /proc/kpageflags (KPF_PGTABLE). +# Same path as slab, different page type. +# +# This test is DESTRUCTIVE: a successful run crashes the kernel. It is +# meant to be executed inside a disposable VM (e.g. virtme-ng) with a +# serial console captured by the harness. It is skipped unless the +# caller opts in via RUN_DESTRUCTIVE=1. +# +# Test passes externally: the kernel must panic with +# "Memory failure: : unrecoverable page" +# A return from the inject means no panic fired: that is a failure, +# unless the target PFN raced to a different page type before injection, +# in which case the run is inconclusive and is skipped. +# +# Author: Breno Leitao + +set -u + +# KTAP output helpers (ktap_print_msg, ktap_skip_all, ktap_exit_fail_msg, ...). +DIR="$(dirname "$(readlink -f "$0")")" +# shellcheck source=../kselftest/ktap_helpers.sh +source "${DIR}"/../kselftest/ktap_helpers.sh + +sysctl_path=/proc/sys/vm/panic_on_unrecoverable_memory_failure +inject_path=/sys/devices/system/memory/hard_offline_page +kpageflags_path=/proc/kpageflags +unpoison_path=/sys/kernel/debug/hwpoison/unpoison-pfn + +# /proc/kpageflags bit positions (see include/uapi/linux/kernel-page-flags.h) +KPF_SLAB=7 +KPF_COMPOUND_TAIL=16 +KPF_HWPOISON=19 +KPF_NOPAGE=20 +KPF_PGTABLE=26 +KPF_RESERVED=32 + +pagesize=$(getconf PAGE_SIZE) + +kind=${1:-rodata} + +if [ "$(id -u)" -ne 0 ]; then + ktap_skip_all "must run as root" + exit "$KSFT_SKIP" +fi + +if [ ! -w "$sysctl_path" ]; then + ktap_skip_all "$sysctl_path not present (kernel without the sysctl?)" + exit "$KSFT_SKIP" +fi + +if [ ! -w "$inject_path" ]; then + ktap_skip_all "$inject_path not present (no MEMORY_HOTPLUG?)" + exit "$KSFT_SKIP" +fi + +if [ "${RUN_DESTRUCTIVE:-0}" != "1" ]; then + ktap_skip_all "destructive test; re-run with RUN_DESTRUCTIVE=1 inside a disposable VM" + exit "$KSFT_SKIP" +fi + +# Pick a PFN inside the kernel image rodata region of /proc/iomem. +# This is preferred over a top-level "Reserved" entry because top-level +# Reserved ranges are often firmware holes that have no backing struct +# page; pfn_to_online_page() returns NULL on those and memory_failure() +# bails out with -ENXIO before reaching the panic path. +# +# "Kernel rodata" is reported as a sub-resource of "System RAM" on every +# major architecture, which guarantees: +# - the PFN is backed by struct page (within an online memory range); +# - PG_reserved is set on the page (kernel image area); +# - the memory is read-only, so setting PG_hwpoison on it does not +# corrupt writable kernel state if the panic somehow does not fire. +# +# /proc/iomem entries look like (indented for sub-resources): +# " 02500000-02ffffff : Kernel rodata" +pick_rodata_phys_addr() { + awk -v pagesize="$(getconf PAGE_SIZE)" ' + # Convert a hex string to a number without relying on the gawk-only + # strtonum(). mawk lacks it and would otherwise spuriously skip + # this test on distros that ship mawk as /usr/bin/awk. + function hex2num(s, n, i, c, v) { + n = 0 + for (i = 1; i <= length(s); i++) { + c = tolower(substr(s, i, 1)) + v = index("0123456789abcdef", c) - 1 + if (v < 0) + return -1 + n = n * 16 + v + } + return n + } + /: Kernel rodata[[:space:]]*$/ { + sub(/^[[:space:]]+/, "") + n = split($0, a, /[- ]/) + start = hex2num(a[1]) + end = hex2num(a[2]) + if (end <= start) + next + # Page-align upward and emit the first byte of that page. + pfn = int((start + pagesize - 1) / pagesize) + printf "0x%x\n", pfn * pagesize + exit 0 + } + ' /proc/iomem +} + +# Walk /proc/kpageflags and return the phys addr of the first PFN that +# has bit $1 set, with KPF_HWPOISON, KPF_NOPAGE and KPF_COMPOUND_TAIL +# all clear (so we attack a real, non-tail, not-already-poisoned page). +# +# We skip the first 16 MiB of PFNs to step past low-memory special +# ranges (BIOS/EFI/ACPI/etc.) that often are PG_reserved and would not +# exhibit the slab/pgtable type we are looking for. +pick_kpageflags_phys_addr() { + local want_bit=$1 + local pagesize skip_pfn + + [ -r "$kpageflags_path" ] || return + + pagesize=$(getconf PAGE_SIZE) + skip_pfn=$(((16 * 1024 * 1024) / pagesize)) + + od -An -tx8 -v -w8 -j "$((skip_pfn * 8))" "$kpageflags_path" 2>/dev/null | \ + awk -v want_bit="$want_bit" \ + -v hwp_bit="$KPF_HWPOISON" \ + -v nopage_bit="$KPF_NOPAGE" \ + -v tail_bit="$KPF_COMPOUND_TAIL" \ + -v base_pfn="$skip_pfn" \ + -v pagesize="$pagesize" ' + # Test whether bit "b" is set in the 16-hex-digit value "hex". + # Done with substring + per-digit lookup so we never rely on awk + # bitwise operators (mawk lacks them), 64-bit FP precision or the + # gawk-only strtonum(). + function bit_set(hex, b, di, bi, c, v) { + di = int(b / 4) + bi = b - di * 4 + c = substr(hex, length(hex) - di, 1) + v = index("0123456789abcdef", tolower(c)) - 1 + if (bi == 0) return (v % 2) == 1 + if (bi == 1) return int(v / 2) % 2 == 1 + if (bi == 2) return int(v / 4) % 2 == 1 + return int(v / 8) % 2 == 1 + } + { + gsub(/^[[:space:]]+/, "") + h = $1 + if (bit_set(h, want_bit) && + !bit_set(h, hwp_bit) && + !bit_set(h, nopage_bit) && + !bit_set(h, tail_bit)) { + pfn = base_pfn + NR - 1 + printf "0x%x\n", pfn * pagesize + exit 0 + } + } + ' +} + +# Return 0 if /proc/kpageflags bit $2 is set for PFN $1, 1 if it is +# clear, or 2 if the word cannot be read. Used to re-confirm the target +# page type after a non-panicking inject. +kpageflags_bit_set() { + local word + + word=$(od -An -tx8 -v -j "$(($1 * 8))" -N 8 "$kpageflags_path" 2>/dev/null | tr -d '[:space:]') + [ -n "$word" ] || return 2 + (( (16#$word >> $2) & 1 )) +} + +# Best-effort: drop the PG_hwpoison marker set by the inject so a failed +# run does not leave a poisoned page behind. hard_offline_page() injects +# with MF_SW_SIMULATED, so the page stays unpoisonable through the +# hwpoison debugfs interface (needs CONFIG_HWPOISON_INJECT + debugfs). +try_unpoison() { + [ -w "$unpoison_path" ] || return 0 + echo "$1" > "$unpoison_path" 2>/dev/null || true +} + +case "$kind" in +rodata) + phys_addr=$(pick_rodata_phys_addr) + recheck_bit=$KPF_RESERVED + missing_msg='no "Kernel rodata" entry in /proc/iomem' + ;; +slab) + phys_addr=$(pick_kpageflags_phys_addr "$KPF_SLAB") + recheck_bit=$KPF_SLAB + missing_msg="no usable slab PFN found in $kpageflags_path" + ;; +pgtable) + phys_addr=$(pick_kpageflags_phys_addr "$KPF_PGTABLE") + recheck_bit=$KPF_PGTABLE + missing_msg="no usable page-table PFN found in $kpageflags_path" + ;; +*) + ktap_exit_fail_msg "unknown kind '$kind' (expected: rodata|slab|pgtable)" + ;; +esac + +if [ -z "$phys_addr" ]; then + ktap_skip_all "$missing_msg" + exit "$KSFT_SKIP" +fi + +ktap_print_msg "enabling $sysctl_path" +prior=$(cat "$sysctl_path") +echo 1 > "$sysctl_path" || ktap_exit_fail_msg "failed to enable sysctl" + +pfn=$((phys_addr / pagesize)) +ktap_print_msg "injecting hwpoison at phys 0x$(printf '%x' "$phys_addr") (pfn 0x$(printf '%x' "$pfn"), kind=$kind)" +ktap_print_msg "expecting kernel panic: 'Memory failure: : unrecoverable page'" + +# A successful run never returns from the inject -- it panics the kernel. +# Reaching the code below therefore means no panic fired. Note whether +# the write itself succeeded, then put the machine back: restore the +# sysctl and best-effort unpoison the page we just marked. +if echo "$phys_addr" > "$inject_path"; then + verdict="inject returned without panic; sysctl ineffective" +else + verdict="inject failed before reaching the panic path" +fi + +echo "$prior" > "$sysctl_path" +try_unpoison "$pfn" + +# The page type can change between selection and injection (e.g. a slab +# or page-table page is freed and reused). Only treat a missing panic as +# a failure if the target PFN is still the kernel-owned type we aimed at; +# if it raced to another type the run is inconclusive, so skip instead. +kpageflags_bit_set "$pfn" "$recheck_bit" +case $? in +0) ktap_exit_fail_msg "$verdict (page still $kind)" ;; +1) ktap_skip_all "target PFN no longer $kind; raced before inject, inconclusive" + exit "$KSFT_SKIP" ;; +*) ktap_exit_fail_msg "$verdict (could not reconfirm page type via $kpageflags_path)" ;; +esac From 21031ca0439047d6eeaeca56ecb445f290407694 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Fri, 26 Jun 2026 10:56:23 -0700 Subject: [PATCH 206/562] mm/kmemleak: skip the remaining scan phases when interrupted kmemleak_scan() scans the per-cpu sections, the struct page ranges and the task stacks in sequence. Each loop now bails out once scan_block() reports the scan was interrupted, but the later phases are still entered and only bail on their first scan_block() call. Jump straight to the gray list scan once a phase reports an interrupted scan, so the remaining scan phases are not entered at all. This does not change the scan results, it only avoids the pointless re-entry. Link: https://lore.kernel.org/20260626-kmemleak_improve-v1-1-d40c7616f64f@debian.org Signed-off-by: Breno Leitao Suggested-by: Oleg Nesterov Reviewed-by: Catalin Marinas Signed-off-by: Andrew Morton --- mm/kmemleak.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mm/kmemleak.c b/mm/kmemleak.c index ac2a44a1c4a5..e96e9efd19b0 100644 --- a/mm/kmemleak.c +++ b/mm/kmemleak.c @@ -1851,6 +1851,7 @@ static void kmemleak_scan(void) int __maybe_unused i; struct xarray dedup; int new_leaks = 0; + int stop = 0; jiffies_last_scan = jiffies; @@ -1897,7 +1898,7 @@ static void kmemleak_scan(void) for_each_possible_cpu(i) { if (scan_large_block(__per_cpu_start + per_cpu_offset(i), __per_cpu_end + per_cpu_offset(i))) - break; + goto scan_gray; } #endif @@ -1909,7 +1910,6 @@ static void kmemleak_scan(void) unsigned long start_pfn = zone->zone_start_pfn; unsigned long end_pfn = zone_end_pfn(zone); unsigned long pfn; - int stop = 0; for (pfn = start_pfn; pfn < end_pfn; pfn++) { struct page *page = pfn_to_online_page(pfn); @@ -1934,6 +1934,8 @@ static void kmemleak_scan(void) break; } put_online_mems(); + if (stop) + goto scan_gray; /* * Scanning the task stacks (may introduce false negatives). @@ -1945,6 +1947,7 @@ static void kmemleak_scan(void) * Scan the objects already referenced from the sections scanned * above. */ +scan_gray: scan_gray_list(); /* From 84f0c4c2b7c0f5bfaf7c6ce7a6a7b6b88551595c Mon Sep 17 00:00:00 2001 From: Yousef Alhouseen Date: Sun, 28 Jun 2026 02:43:14 +0200 Subject: [PATCH 207/562] tmpfs: zero unused folio tail for long symlinks shmem_symlink() marks the entire folio uptodate after copying only the NUL-terminated link target. The remainder of the freshly allocated folio is left uninitialized. Reclaim may pass the whole folio to a swap compressor. KMSAN observed sw842_compress() computing a checksum over the uninitialized tail. If the folio is written to a swap device, those bytes can also leave the kernel. Zero the remainder of the folio before marking it uptodate and dirty. Link: https://lore.kernel.org/20260628004314.27370-1-alhouseenyousef@gmail.com Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: syzbot+bf5586280a66e9ccdfa9@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=bf5586280a66e9ccdfa9 Signed-off-by: Yousef Alhouseen Reviewed-by: Baolin Wang Cc: Baolin Wang Signed-off-by: Andrew Morton --- mm/shmem.c | 1 + 1 file changed, 1 insertion(+) diff --git a/mm/shmem.c b/mm/shmem.c index b51f83c970bb..b06c1ae2f50c 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -4057,6 +4057,7 @@ static int shmem_symlink(struct mnt_idmap *idmap, struct inode *dir, goto out_remove_offset; inode->i_op = &shmem_symlink_inode_operations; memcpy(folio_address(folio), symname, len); + folio_zero_range(folio, len, folio_size(folio) - len); folio_mark_uptodate(folio); folio_mark_dirty(folio); folio_unlock(folio); From c58a824f79b0349ae8252404780b2524a67bb6c1 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sat, 27 Jun 2026 11:58:58 -0700 Subject: [PATCH 208/562] radix-tree: add/correct some kernel-doc Correct some kernel-doc issues in radix-tree.h: - use "DOC:" so that a kernel-doc comment is parsed correctly (or we could just use "/*" for that comment) - add one function parameter description - add one function parameter name inside the prototype to fix these warnings: Warning: include/linux/radix-tree.h:164 Incorrect use of kernel-doc format: * radix_tree_deref_slot - dereference a slot Warning: include/linux/radix-tree.h:177 cannot understand function prototype: '* @slot: slot pointer, returned by radix_tree_lookup_slot Warning: include/linux/radix-tree.h:192 function parameter 'treelock' not described in 'radix_tree_deref_slot_protected' Warning: include/linux/radix-tree.h:309 function parameter '' not described in 'radix_tree_next_chunk' Link: https://lore.kernel.org/20260627185859.1632928-1-rdunlap@infradead.org Signed-off-by: Randy Dunlap Cc: Matthew Wilcox (Oracle) Signed-off-by: Andrew Morton --- include/linux/radix-tree.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/linux/radix-tree.h b/include/linux/radix-tree.h index eae67015ce51..057edc4cbb6e 100644 --- a/include/linux/radix-tree.h +++ b/include/linux/radix-tree.h @@ -111,7 +111,7 @@ struct radix_tree_iter { }; /** - * Radix-tree synchronization + * DOC: Radix-tree synchronization * * The radix-tree API requires that users provide all synchronisation (with * specific exceptions, noted below). @@ -182,6 +182,7 @@ static inline void *radix_tree_deref_slot(void __rcu **slot) /** * radix_tree_deref_slot_protected - dereference a slot with tree lock held * @slot: slot pointer, returned by radix_tree_lookup_slot + * @treelock: caller must hold this spinlock * * Similar to radix_tree_deref_slot. The caller does not hold the RCU read * lock but it must hold the tree lock to prevent parallel updates. @@ -306,7 +307,7 @@ radix_tree_iter_init(struct radix_tree_iter *iter, unsigned long start) * Also it fills @iter with data about chunk: position in the tree (index), * its end (next_index), and constructs a bit mask for tagged iterating (tags). */ -void __rcu **radix_tree_next_chunk(const struct radix_tree_root *, +void __rcu **radix_tree_next_chunk(const struct radix_tree_root *root, struct radix_tree_iter *iter, unsigned flags); /** From fce184196427ae140aa99764f5f80a2ffa362181 Mon Sep 17 00:00:00 2001 From: Xuewen Wang Date: Fri, 26 Jun 2026 13:37:00 +0800 Subject: [PATCH 209/562] mm: annotate data-race in cpu_needs_drain() KCSAN reports a data-race when cpu_needs_drain() reads another CPU's per-cpu folio_batch->nr without locking, while the owning CPU writes to it via folio_batch_add(). Reading a slightly stale value is harmless -- cpu_needs_drain() only decides whether to schedule a drain, and the next iteration of __lru_add_drain_all() will re-check. Use data_race() to annotate the intentional race. Link: https://lore.kernel.org/20260626053700.2036899-1-wangxuewen@kylinos.cn Signed-off-by: Xuewen Wang Acked-by: David Hildenbrand (Arm) Reviewed-by: Pedro Falcato Reviewed-by: Lorenzo Stoakes Cc: Axel Rasmussen Cc: Baoquan He Cc: Barry Song Cc: Chris Li Cc: Jann Horn Cc: Kairui Song Cc: Kemeng Shi Cc: Liam R. Howlett Cc: Nhat Pham Cc: Shakeel Butt Cc: Vlastimil Babka Cc: Wei Xu Cc: Yuanchu Xie Signed-off-by: Andrew Morton --- mm/swap.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mm/swap.c b/mm/swap.c index 460e56370b3c..0132ed0fb76b 100644 --- a/mm/swap.c +++ b/mm/swap.c @@ -831,13 +831,13 @@ static bool cpu_needs_drain(unsigned int cpu) struct cpu_fbatches *fbatches = &per_cpu(cpu_fbatches, cpu); /* Check these in order of likelihood that they're not zero */ - return folio_batch_count(&fbatches->lru_add) || + return data_race(folio_batch_count(&fbatches->lru_add) || folio_batch_count(&fbatches->lru_move_tail) || folio_batch_count(&fbatches->lru_deactivate_file) || folio_batch_count(&fbatches->lru_deactivate) || folio_batch_count(&fbatches->lru_lazyfree) || folio_batch_count(&fbatches->lru_activate) || - need_mlock_drain(cpu) || + need_mlock_drain(cpu)) || has_bh_in_lru(cpu, NULL); } From 06d14352e594d9adb14b78b93e3e2af00c4af847 Mon Sep 17 00:00:00 2001 From: Andrew Morton Date: Mon, 29 Jun 2026 09:26:20 -0700 Subject: [PATCH 210/562] mm-annotate-data-race-in-cpu_needs_drain-fix reindent cpu_needs_drain, per David & Lorenzo Cc: Axel Rasmussen Cc: Baoquan He Cc: Barry Song Cc: Chris Li Cc: David Hildenbrand (Arm) Cc: Jann Horn Cc: Kairui Song Cc: Kemeng Shi Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Nhat Pham Cc: Pedro Falcato Cc: Shakeel Butt Cc: Vlastimil Babka Cc: Wei Xu Cc: Xuewen Wang Cc: Yuanchu Xie Signed-off-by: Andrew Morton --- mm/swap.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mm/swap.c b/mm/swap.c index 0132ed0fb76b..58e4eff698cc 100644 --- a/mm/swap.c +++ b/mm/swap.c @@ -832,12 +832,12 @@ static bool cpu_needs_drain(unsigned int cpu) /* Check these in order of likelihood that they're not zero */ return data_race(folio_batch_count(&fbatches->lru_add) || - folio_batch_count(&fbatches->lru_move_tail) || - folio_batch_count(&fbatches->lru_deactivate_file) || - folio_batch_count(&fbatches->lru_deactivate) || - folio_batch_count(&fbatches->lru_lazyfree) || - folio_batch_count(&fbatches->lru_activate) || - need_mlock_drain(cpu)) || + folio_batch_count(&fbatches->lru_move_tail) || + folio_batch_count(&fbatches->lru_deactivate_file) || + folio_batch_count(&fbatches->lru_deactivate) || + folio_batch_count(&fbatches->lru_lazyfree) || + folio_batch_count(&fbatches->lru_activate) || + need_mlock_drain(cpu)) || has_bh_in_lru(cpu, NULL); } From 618c0ac40e5e256a321ce624a80140c97a2ce0f5 Mon Sep 17 00:00:00 2001 From: Wenchao Hao Date: Fri, 26 Jun 2026 09:50:00 +0800 Subject: [PATCH 211/562] mm/zsmalloc: encode class index in obj value for lockless class lookup Patch series "mm/zsmalloc: reduce lock contention in zs_free()", v6. This series reduces lock contention in zs_free(), which dominates the unmap path under memory pressure on Android (LMK kills) and on x86 servers running zswap-heavy workloads. The current zs_free() takes pool->lock (rwlock, read side) just to look up the size_class for a handle, then takes class->lock and holds it across __free_zspage() which can call into the buddy allocator and acquire zone->lock. Two costs follow: * pool->lock reader-counter cacheline bouncing among concurrent zs_free() callers. * class->lock held across folio_put(), so any zone->lock wait fans out to every other zs_free() on the same class. The series tackles both: Patch 1: encode size_class index into obj alongside PFN and obj_idx, so zs_free() can locate the class without pool->lock. Patch 2: drop pool->lock from zs_free() on 64-bit; 32-bit unchanged. Patch 3: move zspage page-freeing out of class->lock. Patch 4: document the three free_zspage helper variants that result from the split in patch 3. Performance results: Test: each process independently mmap 256MB, write data, madvise MADV_PAGEOUT to swap out via zram (lzo-rle), then concurrent munmap. Raspberry Pi 4B (4-core ARM64 Cortex-A72): mode Base Patched Speedup single 59.0ms 56.0ms 1.05x multi 2p 94.6ms 66.7ms 1.42x multi 4p 202.9ms 110.6ms 1.83x x86 (20-core Intel i7-12700, 16 concurrent processes): mode Base Patched Speedup single 11.7ms 9.8ms 1.19x multi 2p 24.1ms 17.2ms 1.40x multi 4p 63.0ms 45.3ms 1.39x This patch (of 4): Encode the size_class index (class_idx) into the obj value so that zs_free() can determine the correct size_class without dereferencing the handle->obj->PFN->zpdesc->zspage->class chain under pool->lock. class_idx is invariant across page migration (only PFN is rewritten), so a lockless read of obj always yields a valid class_idx. Where obj has more bits below the PFN field than obj_idx alone needs, split that space into class_idx and obj_idx subfields: |<-- _PFN_BITS -->|<-- ZS_OBJ_CLASS_BITS -->|<-- ZS_OBJ_IDX_BITS -->| +-----------------+-------------------------+-----------------------+ | PFN | class_idx | obj_idx | +-----------------+-------------------------+-----------------------+ MSB ^ LSB | +-- ZS_OBJ_PFN_SHIFT The macro layout changes as follows: Before After Meaning ---------------- ------------------ ---------------------------- OBJ_INDEX_BITS ZS_OBJ_IDX_BITS width of obj_idx subfield OBJ_INDEX_MASK ZS_OBJ_IDX_MASK mask of obj_idx subfield (n/a) ZS_OBJ_CLASS_BITS width of class_idx subfield (n/a) ZS_OBJ_CLASS_MASK mask of class_idx subfield (n/a) ZS_OBJ_PFN_SHIFT bit offset of PFN in obj ZS_OBJ_CLASS_BITS folds to 0 (and the layout collapses to [PFN | obj_idx]) when obj has no spare bits, i.e. on 32-bit or on 64-bit fallback paths where MAX_POSSIBLE_PHYSMEM_BITS == BITS_PER_LONG (e.g. UML); zs_free() then falls back to pool->lock. Link: https://lore.kernel.org/20260626015003.2965881-1-haowenchao22@gmail.com Link: https://lore.kernel.org/20260626015003.2965881-2-haowenchao22@gmail.com Signed-off-by: Wenchao Hao Reviewed-by: Nhat Pham Cc: Barry Song Cc: Joshua Hahn Cc: Minchan Kim Cc: Sergey Senozhatsky Cc: Xueyuan Chen Signed-off-by: Andrew Morton --- mm/zsmalloc.c | 105 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 91 insertions(+), 14 deletions(-) diff --git a/mm/zsmalloc.c b/mm/zsmalloc.c index 83f5820c45f9..a1750eabb771 100644 --- a/mm/zsmalloc.c +++ b/mm/zsmalloc.c @@ -67,8 +67,8 @@ #define MAX_POSSIBLE_PHYSMEM_BITS MAX_PHYSMEM_BITS #else /* - * If this definition of MAX_PHYSMEM_BITS is used, OBJ_INDEX_BITS will just - * be PAGE_SHIFT + * If this definition of MAX_PHYSMEM_BITS is used, ZS_OBJ_PFN_SHIFT will + * just be PAGE_SHIFT */ #define MAX_POSSIBLE_PHYSMEM_BITS BITS_PER_LONG #endif @@ -88,8 +88,23 @@ #define OBJ_TAG_BITS 1 #define OBJ_TAG_MASK OBJ_ALLOCATED_TAG -#define OBJ_INDEX_BITS (BITS_PER_LONG - _PFN_BITS) -#define OBJ_INDEX_MASK ((_AC(1, UL) << OBJ_INDEX_BITS) - 1) +/* + * obj is encoded as [PFN | class_idx | obj_idx] within an unsigned long: + * + * |<-- _PFN_BITS -->|<-- ZS_OBJ_CLASS_BITS -->|<-- ZS_OBJ_IDX_BITS -->| + * +-----------------+-------------------------+-----------------------+ + * | PFN | class_idx | obj_idx | + * +-----------------+-------------------------+-----------------------+ + * MSB ^ LSB + * | + * +-- ZS_OBJ_PFN_SHIFT + * + * Encoding class_idx into obj lets zs_free() locate the size_class + * without holding pool->lock; class_idx is invariant across page + * migration (only PFN changes), so a lockless read of the obj value + * always yields a valid class_idx. + */ +#define ZS_OBJ_PFN_SHIFT (BITS_PER_LONG - _PFN_BITS) #define HUGE_BITS 1 #define FULLNESS_BITS 4 @@ -98,9 +113,61 @@ #define ZS_MAX_PAGES_PER_ZSPAGE (_AC(CONFIG_ZSMALLOC_CHAIN_SIZE, UL)) +/* + * Bits to index a page within a zspage = ceil(log2(ZS_MAX_PAGES_PER_ZSPAGE)). + * Computed at preprocessor time, for use in #if below. Kconfig + * restricts ZSMALLOC_CHAIN_SIZE to [4, 16]. + */ +#if ZS_MAX_PAGES_PER_ZSPAGE <= 4 +#define ZS_PAGES_PER_ZSPAGE_BITS 2 +#elif ZS_MAX_PAGES_PER_ZSPAGE <= 8 +#define ZS_PAGES_PER_ZSPAGE_BITS 3 +#elif ZS_MAX_PAGES_PER_ZSPAGE <= 16 +#define ZS_PAGES_PER_ZSPAGE_BITS 4 +#else +#error "ZSMALLOC_CHAIN_SIZE out of expected range [4,16]" +#endif + +/* + * Bits to index an object within a single PAGE_SIZE at the smallest + * possible object size: log2(PAGE_SIZE / 32) = PAGE_SHIFT - 5. + * 32 is the hard floor of ZS_MIN_ALLOC_SIZE. + */ +#define ZS_OBJS_PER_PAGE_BITS (PAGE_SHIFT - 5) + +/* + * Bits to index any object in the densest possible zspage. Below this, + * ZS_MIN_ALLOC_SIZE is auto-raised by the MAX(32, ...) formula -- still + * correct, but objects are coarser. + */ +#define ZS_OBJS_PER_ZSPAGE_BITS \ + (ZS_PAGES_PER_ZSPAGE_BITS + ZS_OBJS_PER_PAGE_BITS) + +/* + * Encode class_idx only when obj has spare bits; otherwise + * ZS_OBJ_CLASS_BITS folds to 0 (32-bit, or 64-bit UML/fallback). + */ +#if BITS_PER_LONG >= 64 && \ + ZS_OBJ_PFN_SHIFT >= (CLASS_BITS + 1) + ZS_OBJS_PER_ZSPAGE_BITS +#define ZS_OBJ_CLASS_BITS (CLASS_BITS + 1) +#else +#define ZS_OBJ_CLASS_BITS 0 +#endif +#define ZS_OBJ_CLASS_MASK ((_AC(1, UL) << ZS_OBJ_CLASS_BITS) - 1) + +#define ZS_OBJ_IDX_BITS (ZS_OBJ_PFN_SHIFT - ZS_OBJ_CLASS_BITS) +#define ZS_OBJ_IDX_MASK ((_AC(1, UL) << ZS_OBJ_IDX_BITS) - 1) + +/* + * Belt-and-suspenders: the #if above already guarantees this when + * class_idx is enabled. Catches future tweaks that bypass it. + */ +static_assert(ZS_OBJ_IDX_BITS >= ZS_PAGES_PER_ZSPAGE_BITS, + "zsmalloc: ZS_MIN_ALLOC_SIZE would exceed ZS_MAX_ALLOC_SIZE"); + /* ZS_MIN_ALLOC_SIZE must be multiple of ZS_ALIGN */ #define ZS_MIN_ALLOC_SIZE \ - MAX(32, (ZS_MAX_PAGES_PER_ZSPAGE << PAGE_SHIFT >> OBJ_INDEX_BITS)) + MAX(32, (ZS_MAX_PAGES_PER_ZSPAGE << PAGE_SHIFT >> ZS_OBJ_IDX_BITS)) /* each chunk includes extra space to keep handle */ #define ZS_MAX_ALLOC_SIZE PAGE_SIZE @@ -720,26 +787,35 @@ static struct zpdesc *get_next_zpdesc(struct zpdesc *zpdesc) static void obj_to_location(unsigned long obj, struct zpdesc **zpdesc, unsigned int *obj_idx) { - *zpdesc = pfn_zpdesc(obj >> OBJ_INDEX_BITS); - *obj_idx = (obj & OBJ_INDEX_MASK); + *zpdesc = pfn_zpdesc(obj >> ZS_OBJ_PFN_SHIFT); + *obj_idx = (obj & ZS_OBJ_IDX_MASK); } static void obj_to_zpdesc(unsigned long obj, struct zpdesc **zpdesc) { - *zpdesc = pfn_zpdesc(obj >> OBJ_INDEX_BITS); + *zpdesc = pfn_zpdesc(obj >> ZS_OBJ_PFN_SHIFT); +} + +/* Folds to 0 when ZS_OBJ_CLASS_BITS == 0; no ifdef needed at callers. */ +static unsigned int obj_to_class_idx(unsigned long obj) +{ + return (obj >> ZS_OBJ_IDX_BITS) & ZS_OBJ_CLASS_MASK; } /** - * location_to_obj - get obj value encoded from (, ) + * location_to_obj - encode (, , ) into obj value * @zpdesc: zpdesc object resides in zspage * @obj_idx: object index + * @class_idx: size class index; ignored when ZS_OBJ_CLASS_BITS == 0 */ -static unsigned long location_to_obj(struct zpdesc *zpdesc, unsigned int obj_idx) +static unsigned long location_to_obj(struct zpdesc *zpdesc, unsigned int obj_idx, + unsigned int class_idx) { unsigned long obj; - obj = zpdesc_pfn(zpdesc) << OBJ_INDEX_BITS; - obj |= obj_idx & OBJ_INDEX_MASK; + obj = zpdesc_pfn(zpdesc) << ZS_OBJ_PFN_SHIFT; + obj |= (unsigned long)(class_idx & ZS_OBJ_CLASS_MASK) << ZS_OBJ_IDX_BITS; + obj |= obj_idx & ZS_OBJ_IDX_MASK; return obj; } @@ -1275,7 +1351,7 @@ static unsigned long obj_malloc(struct zs_pool *pool, kunmap_local(vaddr); mod_zspage_inuse(zspage, 1); - obj = location_to_obj(m_zpdesc, obj); + obj = location_to_obj(m_zpdesc, obj, zspage->class); record_obj(handle, obj); return obj; @@ -1761,7 +1837,8 @@ static int zs_page_migrate(struct page *newpage, struct page *page, old_obj = handle_to_obj(handle); obj_to_location(old_obj, &dummy, &obj_idx); - new_obj = (unsigned long)location_to_obj(newzpdesc, obj_idx); + new_obj = location_to_obj(newzpdesc, obj_idx, + obj_to_class_idx(old_obj)); record_obj(handle, new_obj); } } From cd30a03d5c3f75d8c4c3ee83bc86e20243f269a9 Mon Sep 17 00:00:00 2001 From: Andrew Morton Date: Mon, 29 Jun 2026 09:44:25 -0700 Subject: [PATCH 212/562] mm-zsmalloc-encode-class-index-in-obj-value-for-lockless-class-lookup-fix - fix obj_to_class_idx() defined but not used - remove duplicated #ifdef CONFIG_COMPACTION Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202606291933.Cw5TBs54-lkp@intel.com/ Cc: Barry Song Cc: Joshua Hahn Cc: Minchan Kim Cc: Nhat Pham Cc: Sergey Senozhatsky Cc: Wenchao Hao Cc: Xueyuan Chen Signed-off-by: Andrew Morton --- mm/zsmalloc.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/mm/zsmalloc.c b/mm/zsmalloc.c index a1750eabb771..2c7c27b5b38a 100644 --- a/mm/zsmalloc.c +++ b/mm/zsmalloc.c @@ -796,12 +796,6 @@ static void obj_to_zpdesc(unsigned long obj, struct zpdesc **zpdesc) *zpdesc = pfn_zpdesc(obj >> ZS_OBJ_PFN_SHIFT); } -/* Folds to 0 when ZS_OBJ_CLASS_BITS == 0; no ifdef needed at callers. */ -static unsigned int obj_to_class_idx(unsigned long obj) -{ - return (obj >> ZS_OBJ_IDX_BITS) & ZS_OBJ_CLASS_MASK; -} - /** * location_to_obj - encode (, , ) into obj value * @zpdesc: zpdesc object resides in zspage @@ -1719,9 +1713,12 @@ static void lock_zspage(struct zspage *zspage) } zspage_read_unlock(zspage); } -#endif /* CONFIG_COMPACTION */ -#ifdef CONFIG_COMPACTION +/* Folds to 0 when ZS_OBJ_CLASS_BITS == 0; no ifdef needed at callers. */ +static unsigned int obj_to_class_idx(unsigned long obj) +{ + return (obj >> ZS_OBJ_IDX_BITS) & ZS_OBJ_CLASS_MASK; +} static void replace_sub_page(struct size_class *class, struct zspage *zspage, struct zpdesc *newzpdesc, struct zpdesc *oldzpdesc) From 04d0daddcb8cfff409f31eb8460d1227091489c4 Mon Sep 17 00:00:00 2001 From: Wenchao Hao Date: Fri, 26 Jun 2026 09:50:01 +0800 Subject: [PATCH 213/562] mm/zsmalloc: drop pool->lock from zs_free on 64-bit systems With class_idx encoded in obj, zs_free() can locate the size_class without holding pool->lock on 64-bit systems. Page migration also takes class->lock and only rewrites the PFN field of obj, so: 1. read obj locklessly, 2. lock the size_class derived from obj's class_idx, 3. re-read obj under class->lock to get a stable PFN. This eliminates the rwlock read-side cacheline bouncing between zs_free() and migration/compaction on multi-core systems. Annotate handle_to_obj()/record_obj() with READ_ONCE()/WRITE_ONCE() to prevent load/store tearing on the lockless read path and silence KCSAN data race reports. When ZS_OBJ_CLASS_BITS == 0 (32-bit, or 64-bit with obj too narrow to hold class_idx), zs_free() keeps pool->lock. Link: https://lore.kernel.org/20260626015003.2965881-3-haowenchao22@gmail.com Signed-off-by: Wenchao Hao Reviewed-by: Nhat Pham Reviewed-by: Barry Song Cc: Joshua Hahn Cc: Minchan Kim Cc: Sergey Senozhatsky Cc: Xueyuan Chen Signed-off-by: Andrew Morton --- mm/zsmalloc.c | 75 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 60 insertions(+), 15 deletions(-) diff --git a/mm/zsmalloc.c b/mm/zsmalloc.c index 2c7c27b5b38a..a69e96e18170 100644 --- a/mm/zsmalloc.c +++ b/mm/zsmalloc.c @@ -21,6 +21,10 @@ * pool->lock * class->lock * zspage->lock + * + * When ZS_OBJ_CLASS_BITS > 0, zs_free() skips pool->lock; it picks + * the size_class from obj's encoded class_idx and serializes against + * page migration via class->lock. */ #include @@ -463,10 +467,13 @@ static void cache_free_zspage(struct zspage *zspage) kmem_cache_free(zspage_cachep, zspage); } -/* class->lock(which owns the handle) synchronizes races */ +/* + * Pairs with READ_ONCE() in handle_to_obj(): zs_free() may read the + * handle locklessly, so prevent store tearing here. + */ static void record_obj(unsigned long handle, unsigned long obj) { - *(unsigned long *)handle = obj; + WRITE_ONCE(*(unsigned long *)handle, obj); } static inline bool __maybe_unused is_first_zpdesc(struct zpdesc *zpdesc) @@ -816,7 +823,7 @@ static unsigned long location_to_obj(struct zpdesc *zpdesc, unsigned int obj_idx static unsigned long handle_to_obj(unsigned long handle) { - return *(unsigned long *)handle; + return READ_ONCE(*(unsigned long *)handle); } static inline bool obj_allocated(struct zpdesc *zpdesc, void *obj, @@ -1450,10 +1457,58 @@ static void obj_free(int class_size, unsigned long obj) mod_zspage_inuse(zspage, -1); } +/* + * Resolve @handle to its zspage / size_class and acquire class->lock. + * + * When class_idx is encoded in obj (ZS_OBJ_CLASS_BITS > 0), it is + * invariant under page migration, so the handle can be read locklessly + * to pick the size_class. Once class->lock is held migration is + * blocked and the handle is re-read to obtain a stable PFN. + * + * Otherwise (32-bit, or 64-bit fallback paths like UML where the + * encoding is disabled), fall back to pool->lock for the lookup. + */ +#if ZS_OBJ_CLASS_BITS > 0 +static inline void obj_class_get_and_lock(struct zs_pool *pool, unsigned long handle, + unsigned long *objp, struct zspage **zspagep, + struct size_class **classp) + __acquires(&(*classp)->lock) +{ + struct zpdesc *f_zpdesc; + unsigned long obj; + + obj = handle_to_obj(handle); + *classp = pool->size_class[obj_to_class_idx(obj)]; + spin_lock(&(*classp)->lock); + /* Re-read under class->lock: PFN is now stable vs migration. */ + obj = handle_to_obj(handle); + obj_to_zpdesc(obj, &f_zpdesc); + *zspagep = get_zspage(f_zpdesc); + *objp = obj; +} +#else +static inline void obj_class_get_and_lock(struct zs_pool *pool, unsigned long handle, + unsigned long *objp, struct zspage **zspagep, + struct size_class **classp) + __acquires(&(*classp)->lock) +{ + struct zpdesc *f_zpdesc; + unsigned long obj; + + read_lock(&pool->lock); + obj = handle_to_obj(handle); + obj_to_zpdesc(obj, &f_zpdesc); + *zspagep = get_zspage(f_zpdesc); + *classp = zspage_class(pool, *zspagep); + spin_lock(&(*classp)->lock); + read_unlock(&pool->lock); + *objp = obj; +} +#endif + void zs_free(struct zs_pool *pool, unsigned long handle) { struct zspage *zspage; - struct zpdesc *f_zpdesc; unsigned long obj; struct size_class *class; int fullness; @@ -1461,17 +1516,7 @@ void zs_free(struct zs_pool *pool, unsigned long handle) if (IS_ERR_OR_NULL((void *)handle)) return; - /* - * The pool->lock protects the race with zpage's migration - * so it's safe to get the page from handle. - */ - read_lock(&pool->lock); - obj = handle_to_obj(handle); - obj_to_zpdesc(obj, &f_zpdesc); - zspage = get_zspage(f_zpdesc); - class = zspage_class(pool, zspage); - spin_lock(&class->lock); - read_unlock(&pool->lock); + obj_class_get_and_lock(pool, handle, &obj, &zspage, &class); class_stat_sub(class, ZS_OBJS_INUSE, 1); obj_free(class->size, obj); From 682dad4dfc6a8f2ec578cd269bf27d2efd2412a1 Mon Sep 17 00:00:00 2001 From: Andrew Morton Date: Mon, 29 Jun 2026 19:48:04 -0700 Subject: [PATCH 214/562] mm-zsmalloc-drop-pool-lock-from-zs_free-on-64-bit-systems-fix build fix mm/zsmalloc.c: In function 'obj_class_get_and_lock': mm/zsmalloc.c:1481:36: error: implicit declaration of function 'obj_to_class_idx' [-Wimplicit-function-declaration] 1481 | *classp = pool->size_class[obj_to_class_idx(obj)]; | ^~~~~~~~~~~~~~~~ Cc: Barry Song Cc: Joshua Hahn Cc: Minchan Kim Cc: Nhat Pham Cc: Sergey Senozhatsky Cc: Wenchao Hao Cc: Xueyuan Chen Signed-off-by: Andrew Morton --- mm/zsmalloc.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mm/zsmalloc.c b/mm/zsmalloc.c index a69e96e18170..496126c5b638 100644 --- a/mm/zsmalloc.c +++ b/mm/zsmalloc.c @@ -1457,6 +1457,12 @@ static void obj_free(int class_size, unsigned long obj) mod_zspage_inuse(zspage, -1); } +/* Folds to 0 when ZS_OBJ_CLASS_BITS == 0; no ifdef needed at callers. */ +static unsigned int obj_to_class_idx(unsigned long obj) +{ + return (obj >> ZS_OBJ_IDX_BITS) & ZS_OBJ_CLASS_MASK; +} + /* * Resolve @handle to its zspage / size_class and acquire class->lock. * @@ -1759,12 +1765,6 @@ static void lock_zspage(struct zspage *zspage) zspage_read_unlock(zspage); } -/* Folds to 0 when ZS_OBJ_CLASS_BITS == 0; no ifdef needed at callers. */ -static unsigned int obj_to_class_idx(unsigned long obj) -{ - return (obj >> ZS_OBJ_IDX_BITS) & ZS_OBJ_CLASS_MASK; -} - static void replace_sub_page(struct size_class *class, struct zspage *zspage, struct zpdesc *newzpdesc, struct zpdesc *oldzpdesc) { From 38c05ae2cc4c2ec5aa91116af6286526792da469 Mon Sep 17 00:00:00 2001 From: Andrew Morton Date: Wed, 1 Jul 2026 15:52:15 -0700 Subject: [PATCH 215/562] mm-zsmalloc-drop-pool-lock-from-zs_free-on-64-bit-systems-fix-fix fix obj_to_class_idx() warning yet again Reported-by: kernel test robot Link: https://lore.kernel.org/202607020359.FMDmPwjF-lkp@intel.com Cc: Barry Song Cc: Joshua Hahn Cc: Minchan Kim Cc: Nhat Pham Cc: Sergey Senozhatsky Cc: Wenchao Hao Cc: Xueyuan Chen Signed-off-by: Andrew Morton --- mm/zsmalloc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mm/zsmalloc.c b/mm/zsmalloc.c index 496126c5b638..165063a0db55 100644 --- a/mm/zsmalloc.c +++ b/mm/zsmalloc.c @@ -1457,11 +1457,13 @@ static void obj_free(int class_size, unsigned long obj) mod_zspage_inuse(zspage, -1); } +#if (ZS_OBJ_CLASS_BITS > 0) || defined(CONFIG_COMPACTION) /* Folds to 0 when ZS_OBJ_CLASS_BITS == 0; no ifdef needed at callers. */ static unsigned int obj_to_class_idx(unsigned long obj) { return (obj >> ZS_OBJ_IDX_BITS) & ZS_OBJ_CLASS_MASK; } +#endif /* * Resolve @handle to its zspage / size_class and acquire class->lock. From 6d43b4bbafe077f68c6abe35ac867c8c02260bb4 Mon Sep 17 00:00:00 2001 From: Xueyuan Chen Date: Fri, 26 Jun 2026 09:50:02 +0800 Subject: [PATCH 216/562] mm/zsmalloc: drop class lock before freeing zspage Currently in zs_free(), the class->lock is held until the zspage is completely freed and the counters are updated. However, freeing pages back to the buddy allocator requires acquiring the zone lock. Under heavy memory pressure, zone lock contention can be severe. When this happens, the CPU holding the class->lock will stall waiting for the zone lock, thereby blocking all other CPUs attempting to acquire the same class->lock. This patch shrinks the critical section of the class->lock to reduce lock contention. By moving the actual page freeing process outside the class->lock, we can improve the concurrency performance of zs_free(). Testing on the RADXA O6 platform shows that with 12 CPUs concurrently performing zs_free() operations, the execution time is reduced by 20%. Link: https://lore.kernel.org/20260626015003.2965881-4-haowenchao22@gmail.com Signed-off-by: Xueyuan Chen Signed-off-by: Wenchao Hao Reviewed-by: Nhat Pham Reviewed-by: Joshua Hahn Reviewed-by: Barry Song Cc: Minchan Kim Cc: Sergey Senozhatsky Signed-off-by: Andrew Morton --- mm/zsmalloc.c | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/mm/zsmalloc.c b/mm/zsmalloc.c index 165063a0db55..e13d8dc1d2ee 100644 --- a/mm/zsmalloc.c +++ b/mm/zsmalloc.c @@ -877,13 +877,10 @@ static int trylock_zspage(struct zspage *zspage) return 0; } -static void __free_zspage(struct zs_pool *pool, struct size_class *class, - struct zspage *zspage) +static inline void __free_zspage_lockless(struct zspage *zspage) { struct zpdesc *zpdesc, *next; - assert_spin_locked(&class->lock); - VM_BUG_ON(get_zspage_inuse(zspage)); VM_BUG_ON(zspage->fullness != ZS_INUSE_RATIO_0); @@ -899,7 +896,13 @@ static void __free_zspage(struct zs_pool *pool, struct size_class *class, } while (zpdesc != NULL); cache_free_zspage(zspage); +} +static void __free_zspage(struct zs_pool *pool, struct size_class *class, + struct zspage *zspage) +{ + assert_spin_locked(&class->lock); + __free_zspage_lockless(zspage); class_stat_sub(class, ZS_OBJS_ALLOCATED, class->objs_per_zspage); atomic_long_sub(class->pages_per_zspage, &pool->pages_allocated); } @@ -1520,6 +1523,7 @@ void zs_free(struct zs_pool *pool, unsigned long handle) unsigned long obj; struct size_class *class; int fullness; + struct zspage *zspage_to_free = NULL; if (IS_ERR_OR_NULL((void *)handle)) return; @@ -1530,10 +1534,23 @@ void zs_free(struct zs_pool *pool, unsigned long handle) obj_free(class->size, obj); fullness = fix_fullness_group(class, zspage); - if (fullness == ZS_INUSE_RATIO_0) - free_zspage(pool, class, zspage); + if (fullness == ZS_INUSE_RATIO_0) { + if (trylock_zspage(zspage)) { + remove_zspage(class, zspage); + class_stat_sub(class, ZS_OBJS_ALLOCATED, + class->objs_per_zspage); + zspage_to_free = zspage; + } else { + kick_deferred_free(pool); + } + } spin_unlock(&class->lock); + + if (zspage_to_free) { + __free_zspage_lockless(zspage_to_free); + atomic_long_sub(class->pages_per_zspage, &pool->pages_allocated); + } cache_free_handle(handle); } EXPORT_SYMBOL_GPL(zs_free); From 743deb8e17a8b38db519bef914f3bd209f9aa097 Mon Sep 17 00:00:00 2001 From: Wenchao Hao Date: Fri, 26 Jun 2026 09:50:03 +0800 Subject: [PATCH 217/562] mm/zsmalloc: document free_zspage helper variants After splitting __free_zspage() into a lockless core and a wrapper that does the class-stat bookkeeping, three similarly-named helpers coexist: free_zspage / __free_zspage / __free_zspage_lockless. Add a comment block above them describing what each does and where it is used, so the names are not easy to confuse. No functional change. Link: https://lore.kernel.org/20260626015003.2965881-5-haowenchao22@gmail.com Signed-off-by: Wenchao Hao Suggested-by: Nhat Pham Reviewed-by: Nhat Pham Reviewed-by: Barry Song Cc: Joshua Hahn Cc: Minchan Kim Cc: Sergey Senozhatsky Cc: Xueyuan Chen Signed-off-by: Andrew Morton --- mm/zsmalloc.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/mm/zsmalloc.c b/mm/zsmalloc.c index e13d8dc1d2ee..3d566a3ee96f 100644 --- a/mm/zsmalloc.c +++ b/mm/zsmalloc.c @@ -877,6 +877,22 @@ static int trylock_zspage(struct zspage *zspage) return 0; } +/* + * Three free helpers, kept apart here: + * + * __free_zspage_lockless(): bare core; walks zpdescs and returns pages + * to the buddy allocator. Caller owns all zpdesc locks and has + * removed the zspage from its class list. Used by zs_free() outside + * class->lock so the buddy-side work does not stall the class. + * + * __free_zspage(): __free_zspage_lockless() + per-class accounting, + * under class->lock. Used by async_free_zspage(), the worker for + * zspages whose trylock_zspage() failed. + * + * free_zspage(): full wrapper - trylock zpdescs, remove from class + * list, call __free_zspage(); kicks deferred free on contention. + * Used by compaction. + */ static inline void __free_zspage_lockless(struct zspage *zspage) { struct zpdesc *zpdesc, *next; From bfb8498f8092f0c9b59c98a30df17f33ee7b6b52 Mon Sep 17 00:00:00 2001 From: Suren Baghdasaryan Date: Thu, 25 Jun 2026 11:48:56 -0700 Subject: [PATCH 218/562] MAINTAINERS: move inactive maintainer to CREDITS Patch series "move alloc_tag.c file under mm/". Memory allocation profiling is ultimately an mm feature and now that we need to use some internal mm definitions in it [1], the time is right to move its implementation under mm/. The move is straight-forward, involving just alloc_tag.c file. Update config, makefiles and maintainers as well. This patch (of 2): Move Kent Overstreet from maintainers for Memory Allocation Profiling to CREDITS in recognition of his co-authorship and contributions to this feature. Link: https://lore.kernel.org/20260625184857.2193482-2-surenb@google.com Link: https://lore.kernel.org/all/20260622-alloc-trylock-v2-13-31f31367d420@google.com/ [1] Signed-off-by: Suren Baghdasaryan Acked-by: Lorenzo Stoakes Cc: Kent Overstreet Cc: Brendan Jackman Cc: David Hildenbrand Cc: Liam R. Howlett Cc: Mike Rapoport Cc: Vlastimil Babka Cc: Hao Ge Cc: Harry Yoo (Oracle) Cc: SeongJae Park Signed-off-by: Andrew Morton --- CREDITS | 4 ++++ MAINTAINERS | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CREDITS b/CREDITS index 84793a967a0b..8b229514ec8e 100644 --- a/CREDITS +++ b/CREDITS @@ -3096,6 +3096,10 @@ N: Jens Osterkamp E: jens@de.ibm.com D: Maintainer of Spidernet network driver for Cell +N: Kent Overstreet +E: kent.overstreet@linux.dev +D: Co-authored and contributed to Memory Allocation Profiling + N: Gadi Oxman E: gadio@netvision.net.il D: Original author and maintainer of IDE/ATAPI floppy/tape drivers diff --git a/MAINTAINERS b/MAINTAINERS index 92e76defe957..c435d4303d98 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16887,7 +16887,6 @@ F: tools/testing/memblock/ MEMORY ALLOCATION PROFILING M: Suren Baghdasaryan -M: Kent Overstreet R: Hao Ge L: linux-mm@kvack.org S: Maintained From e4b1210829f816ee2e94c84638b1f02ecd3aa515 Mon Sep 17 00:00:00 2001 From: Lorenzo Stoakes Date: Thu, 25 Jun 2026 11:48:57 -0700 Subject: [PATCH 219/562] mm: move alloc tag to mm The alloc tagging work is really mm-specific, so move alloc_tag.c to mm/ and additionally update the MAINTAINERS entry to place it within memory management and port over the Kconfig and Makefile code to mm. Link: https://lore.kernel.org/20260625184857.2193482-3-surenb@google.com Signed-off-by: Lorenzo Stoakes Signed-off-by: Suren Baghdasaryan Reviewed-by: SeongJae Park Tested-by: Hao Ge Acked-by: Hao Ge Acked-by: Vlastimil Babka (SUSE) Acked-by: David Hildenbrand (Arm) Acked-by: Mike Rapoport (Microsoft) Acked-by: Harry Yoo (Oracle) Reviewed-by: Lorenzo Stoakes Cc: Brendan Jackman Cc: Kent Overstreet Cc: Liam R. Howlett Signed-off-by: Andrew Morton --- MAINTAINERS | 20 ++++++++++---------- lib/Kconfig.debug | 28 ---------------------------- lib/Makefile | 1 - mm/Kconfig.debug | 28 ++++++++++++++++++++++++++++ mm/Makefile | 1 + {lib => mm}/alloc_tag.c | 0 6 files changed, 39 insertions(+), 39 deletions(-) rename {lib => mm}/alloc_tag.c (100%) diff --git a/MAINTAINERS b/MAINTAINERS index c435d4303d98..29c302e9c17b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16885,16 +16885,6 @@ F: mm/mm_init.c F: mm/rodata_test.c F: tools/testing/memblock/ -MEMORY ALLOCATION PROFILING -M: Suren Baghdasaryan -R: Hao Ge -L: linux-mm@kvack.org -S: Maintained -F: Documentation/mm/allocation-profiling.rst -F: include/linux/alloc_tag.h -F: include/linux/pgalloc_tag.h -F: lib/alloc_tag.c - MEMORY CONTROLLER DRIVERS M: Krzysztof Kozlowski L: linux-kernel@vger.kernel.org @@ -16939,6 +16929,16 @@ T: quilt git://git.kernel.org/pub/scm/linux/kernel/git/akpm/25-new F: mm/ F: tools/mm/ +MEMORY MANAGEMENT - ALLOCATION PROFILING (ALLOC TAG) +M: Suren Baghdasaryan +R: Hao Ge +L: linux-mm@kvack.org +S: Maintained +F: Documentation/mm/allocation-profiling.rst +F: include/linux/alloc_tag.h +F: include/linux/pgalloc_tag.h +F: mm/alloc_tag.c + MEMORY MANAGEMENT - BALLOON M: Andrew Morton M: David Hildenbrand diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 1244dcac2294..b82515cde538 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -1048,34 +1048,6 @@ config CODE_TAGGING bool select KALLSYMS -config MEM_ALLOC_PROFILING - bool "Enable memory allocation profiling" - default n - depends on MMU - depends on PROC_FS - depends on !DEBUG_FORCE_WEAK_PER_CPU - select CODE_TAGGING - select PAGE_EXTENSION - select SLAB_OBJ_EXT - help - Track allocation source code and record total allocation size - initiated at that code location. The mechanism can be used to track - memory leaks with a low performance and memory impact. - -config MEM_ALLOC_PROFILING_ENABLED_BY_DEFAULT - bool "Enable memory allocation profiling by default" - default y - depends on MEM_ALLOC_PROFILING - -config MEM_ALLOC_PROFILING_DEBUG - bool "Memory allocation profiler debugging" - default n - depends on MEM_ALLOC_PROFILING - select MEM_ALLOC_PROFILING_ENABLED_BY_DEFAULT - help - Adds warnings with helpful error messages for memory allocation - profiling. - source "lib/Kconfig.kasan" source "lib/Kconfig.kfence" source "lib/Kconfig.kmsan" diff --git a/lib/Makefile b/lib/Makefile index 7f75cc6edf94..531a0be88062 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -196,7 +196,6 @@ obj-$(CONFIG_OF_RECONFIG_NOTIFIER_ERROR_INJECT) += \ obj-$(CONFIG_FUNCTION_ERROR_INJECTION) += error-inject.o obj-$(CONFIG_CODE_TAGGING) += codetag.o -obj-$(CONFIG_MEM_ALLOC_PROFILING) += alloc_tag.o lib-$(CONFIG_GENERIC_BUG) += bug.o diff --git a/mm/Kconfig.debug b/mm/Kconfig.debug index 91b3e027b753..5737a504efbb 100644 --- a/mm/Kconfig.debug +++ b/mm/Kconfig.debug @@ -320,3 +320,31 @@ config PER_VMA_LOCK_STATS overhead in the page fault path. If in doubt, say N. + +config MEM_ALLOC_PROFILING + bool "Enable memory allocation profiling" + default n + depends on MMU + depends on PROC_FS + depends on !DEBUG_FORCE_WEAK_PER_CPU + select CODE_TAGGING + select PAGE_EXTENSION + select SLAB_OBJ_EXT + help + Track allocation source code and record total allocation size + initiated at that code location. The mechanism can be used to track + memory leaks with a low performance and memory impact. + +config MEM_ALLOC_PROFILING_ENABLED_BY_DEFAULT + bool "Enable memory allocation profiling by default" + default y + depends on MEM_ALLOC_PROFILING + +config MEM_ALLOC_PROFILING_DEBUG + bool "Memory allocation profiler debugging" + default n + depends on MEM_ALLOC_PROFILING + select MEM_ALLOC_PROFILING_ENABLED_BY_DEFAULT + help + Adds warnings with helpful error messages for memory allocation + profiling. diff --git a/mm/Makefile b/mm/Makefile index eff9f9e7e061..4fc713867b9b 100644 --- a/mm/Makefile +++ b/mm/Makefile @@ -147,3 +147,4 @@ obj-$(CONFIG_SHRINKER_DEBUG) += shrinker_debug.o obj-$(CONFIG_EXECMEM) += execmem.o obj-$(CONFIG_TMPFS_QUOTA) += shmem_quota.o obj-$(CONFIG_LAZY_MMU_MODE_KUNIT_TEST) += tests/lazy_mmu_mode_kunit.o +obj-$(CONFIG_MEM_ALLOC_PROFILING) += alloc_tag.o diff --git a/lib/alloc_tag.c b/mm/alloc_tag.c similarity index 100% rename from lib/alloc_tag.c rename to mm/alloc_tag.c From 628bb87cbe011c3c6b7f4aa8e5cd8151328c99b8 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 11 Jun 2026 14:56:57 +0200 Subject: [PATCH 220/562] mm/damon/core: reduce kernel stack usage The main thread function has recently grown to the point of exceeding stack frame size warning limits in some configurations. This is what I hit on s390 with clang and CONFIG_KASAN: mm/damon/core.c:3440:31: error: stack frame size (1352) exceeds limit (1280) in 'kdamond_fn' [-Werror,-Wframe-larger-than] 3440 | static int kdamond_fn(struct damon_ctx *ctx) The largest stack usage here is inside of the kdamond_tune_intervals(), so by marking that one as noinline_for_stack, the functions individually stay below the warning limit, though kdamond_fn() itself still uses hundreds of kilobytes for some reason. Link: https://lore.kernel.org/20260611125704.3386176-1-arnd@kernel.org Signed-off-by: Arnd Bergmann Reviewed-by: SeongJae Park Cc: Bill Wendling Cc: Justin Stitt Cc: Nathan Chancellor Cc: Quanmin Yan Signed-off-by: Andrew Morton --- mm/damon/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/damon/core.c b/mm/damon/core.c index cff932b3317d..eab553fcb0b5 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -2052,7 +2052,7 @@ static unsigned long damon_get_intervals_adaptation_bp(struct damon_ctx *c) return adaptation_bp; } -static void kdamond_tune_intervals(struct damon_ctx *c) +static noinline_for_stack void kdamond_tune_intervals(struct damon_ctx *c) { unsigned long adaptation_bp; struct damon_attrs new_attrs; From c6b0b9701fcef71ed7cabc0358bb18e15e8e8329 Mon Sep 17 00:00:00 2001 From: "Ritesh Harjani (IBM)" Date: Thu, 11 Jun 2026 08:39:32 +0530 Subject: [PATCH 221/562] include/linux/swap.h: remove unused leftovers This removed unused leftovers, most of them are forward structure declarations. Also removes SWAP_BATCH macro which isn't used any where in the code. Found these during manual code review. Link: https://lore.kernel.org/68591daf0d679e5a0072d63751f187d14613e2b0.1781146877.git.ritesh.list@gmail.com Signed-off-by: Ritesh Harjani (IBM) Reviewed-by: Barry Song Acked-by: Chris Li Acked-by: David Hildenbrand (Arm) Cc: Baoquan He Cc: Kairui Song Cc: Kemeng Shi Cc: Nhat Pham Signed-off-by: Andrew Morton --- include/linux/swap.h | 7 ------- 1 file changed, 7 deletions(-) diff --git a/include/linux/swap.h b/include/linux/swap.h index 8f0f68e245ba..46c25523d7b8 100644 --- a/include/linux/swap.h +++ b/include/linux/swap.h @@ -16,10 +16,6 @@ #include #include -struct notifier_block; - -struct bio; - #define SWAP_FLAG_PREFER 0x8000 /* set if swap priority specified */ #define SWAP_FLAG_PRIO_MASK 0x7fff #define SWAP_FLAG_DISCARD 0x10000 /* enable discard for swap */ @@ -29,7 +25,6 @@ struct bio; #define SWAP_FLAGS_VALID (SWAP_FLAG_PRIO_MASK | SWAP_FLAG_PREFER | \ SWAP_FLAG_DISCARD | SWAP_FLAG_DISCARD_ONCE | \ SWAP_FLAG_DISCARD_PAGES) -#define SWAP_BATCH 64 static inline int current_is_kswapd(void) { @@ -175,7 +170,6 @@ static inline void mm_account_reclaimed_pages(unsigned long pages) struct address_space; struct sysinfo; -struct writeback_control; struct zone; /* @@ -442,7 +436,6 @@ extern sector_t swapdev_block(int, pgoff_t); extern int __swap_count(swp_entry_t entry); extern bool swap_entry_swapped(struct swap_info_struct *si, swp_entry_t entry); extern int swp_swapcount(swp_entry_t entry); -struct backing_dev_info; extern struct swap_info_struct *get_swap_device(swp_entry_t entry); sector_t swap_folio_sector(struct folio *folio); From 7a0cfedc71ab8e0b483b726c19de438d91408812 Mon Sep 17 00:00:00 2001 From: Gregory Price Date: Mon, 8 Jun 2026 20:29:19 -0400 Subject: [PATCH 222/562] mm: constify oom_control, scan_control, and alloc_context nodemask MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The nodemasks in these structures may come from a variety of sources, including tasks and cpusets - and should never be modified by any code when being passed around inside another context. Link: https://lore.kernel.org/20260609002919.3967782-1-gourry@gourry.net Signed-off-by: Gregory Price Reviewed-by: Lorenzo Stoakes Acked-by: David Hildenbrand (Arm) Reviewed-by: Barry Song Acked-by: Vlastimil Babka (SUSE) Tested-by: SeongJae Park Acked-by: SeongJae Park Acked-by: Waiman Long Acked-by: Zi Yan Cc: Axel Rasmussen Cc: Baoquan He Cc: Brendan Jackman Cc: Chris Li Cc: David Rientjes Cc: Johannes Weiner Cc: Kairui Song Cc: Kemeng Shi Cc: Liam R. Howlett Cc: Michal Hocko Cc: Michal Koutný Cc: Mike Rapoport Cc: Nhat Pham Cc: Shakeel Butt Cc: Suren Baghdasaryan Cc: Tejun Heo Cc: Wei Xu Cc: Yuanchu Xie Signed-off-by: Andrew Morton --- include/linux/cpuset.h | 4 ++-- include/linux/mm.h | 4 ++-- include/linux/mmzone.h | 6 +++--- include/linux/oom.h | 2 +- include/linux/swap.h | 2 +- kernel/cgroup/cpuset.c | 2 +- mm/internal.h | 2 +- mm/mmzone.c | 5 +++-- mm/page_alloc.c | 6 +++--- mm/show_mem.c | 9 ++++++--- mm/vmscan.c | 6 +++--- 11 files changed, 26 insertions(+), 22 deletions(-) diff --git a/include/linux/cpuset.h b/include/linux/cpuset.h index 65d76a38974b..a80d38e752d2 100644 --- a/include/linux/cpuset.h +++ b/include/linux/cpuset.h @@ -83,7 +83,7 @@ extern bool cpuset_cpus_allowed_fallback(struct task_struct *p); extern nodemask_t cpuset_mems_allowed(struct task_struct *p); #define cpuset_current_mems_allowed (current->mems_allowed) void cpuset_init_current_mems_allowed(void); -int cpuset_nodemask_valid_mems_allowed(nodemask_t *nodemask); +int cpuset_nodemask_valid_mems_allowed(const nodemask_t *nodemask); extern bool cpuset_current_node_allowed(int node, gfp_t gfp_mask); @@ -224,7 +224,7 @@ static inline nodemask_t cpuset_mems_allowed(struct task_struct *p) #define cpuset_current_mems_allowed (node_states[N_MEMORY]) static inline void cpuset_init_current_mems_allowed(void) {} -static inline int cpuset_nodemask_valid_mems_allowed(nodemask_t *nodemask) +static inline int cpuset_nodemask_valid_mems_allowed(const nodemask_t *nodemask) { return 1; } diff --git a/include/linux/mm.h b/include/linux/mm.h index 485df9c2dbdd..2101e5205fc0 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -4042,7 +4042,7 @@ extern int __meminit early_pfn_to_nid(unsigned long pfn); extern void mem_init(void); extern void __init mmap_init(void); -extern void __show_mem(unsigned int flags, nodemask_t *nodemask, int max_zone_idx); +extern void __show_mem(unsigned int flags, const nodemask_t *nodemask, int max_zone_idx); static inline void show_mem(void) { __show_mem(0, NULL, MAX_NR_ZONES - 1); @@ -4052,7 +4052,7 @@ extern void si_meminfo(struct sysinfo * val); extern void si_meminfo_node(struct sysinfo *val, int nid); extern __printf(3, 4) -void warn_alloc(gfp_t gfp_mask, nodemask_t *nodemask, const char *fmt, ...); +void warn_alloc(gfp_t gfp_mask, const nodemask_t *nodemask, const char *fmt, ...); extern void setup_per_cpu_pageset(void); diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h index 35d1a7643dc4..242ec3cb8b52 100644 --- a/include/linux/mmzone.h +++ b/include/linux/mmzone.h @@ -1815,7 +1815,7 @@ static inline int zonelist_node_idx(const struct zoneref *zoneref) struct zoneref *__next_zones_zonelist(struct zoneref *z, enum zone_type highest_zoneidx, - nodemask_t *nodes); + const nodemask_t *nodes); /** * next_zones_zonelist - Returns the next zone at or below highest_zoneidx within the allowed nodemask using a cursor within a zonelist as a starting point @@ -1834,7 +1834,7 @@ struct zoneref *__next_zones_zonelist(struct zoneref *z, */ static __always_inline struct zoneref *next_zones_zonelist(struct zoneref *z, enum zone_type highest_zoneidx, - nodemask_t *nodes) + const nodemask_t *nodes) { if (likely(!nodes && zonelist_zone_idx(z) <= highest_zoneidx)) return z; @@ -1860,7 +1860,7 @@ static __always_inline struct zoneref *next_zones_zonelist(struct zoneref *z, */ static inline struct zoneref *first_zones_zonelist(struct zonelist *zonelist, enum zone_type highest_zoneidx, - nodemask_t *nodes) + const nodemask_t *nodes) { return next_zones_zonelist(zonelist->_zonerefs, highest_zoneidx, nodes); diff --git a/include/linux/oom.h b/include/linux/oom.h index 7b02bc1d0a7e..00da05d227e6 100644 --- a/include/linux/oom.h +++ b/include/linux/oom.h @@ -30,7 +30,7 @@ struct oom_control { struct zonelist *zonelist; /* Used to determine mempolicy */ - nodemask_t *nodemask; + const nodemask_t *nodemask; /* Memory cgroup in which oom is invoked, or NULL for global oom */ struct mem_cgroup *memcg; diff --git a/include/linux/swap.h b/include/linux/swap.h index 46c25523d7b8..3f31b6a56788 100644 --- a/include/linux/swap.h +++ b/include/linux/swap.h @@ -345,7 +345,7 @@ extern void swap_setup(void); /* linux/mm/vmscan.c */ extern unsigned long zone_reclaimable_pages(struct zone *zone); extern unsigned long try_to_free_pages(struct zonelist *zonelist, int order, - gfp_t gfp_mask, nodemask_t *mask); + gfp_t gfp_mask, const nodemask_t *mask); unsigned long lruvec_lru_size(struct lruvec *lruvec, enum lru_list lru, int zone_idx); #define MEMCG_RECLAIM_MAY_SWAP (1 << 1) diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c index b21c31650583..c1de9a17cbc1 100644 --- a/kernel/cgroup/cpuset.c +++ b/kernel/cgroup/cpuset.c @@ -4157,7 +4157,7 @@ nodemask_t cpuset_mems_allowed(struct task_struct *tsk) * * Are any of the nodes in the nodemask allowed in current->mems_allowed? */ -int cpuset_nodemask_valid_mems_allowed(nodemask_t *nodemask) +int cpuset_nodemask_valid_mems_allowed(const nodemask_t *nodemask) { return nodes_intersects(*nodemask, current->mems_allowed); } diff --git a/mm/internal.h b/mm/internal.h index dc10291f14bb..430aa72a4575 100644 --- a/mm/internal.h +++ b/mm/internal.h @@ -675,7 +675,7 @@ void page_alloc_sysctl_init(void); */ struct alloc_context { struct zonelist *zonelist; - nodemask_t *nodemask; + const nodemask_t *nodemask; struct zoneref *preferred_zoneref; int migratetype; diff --git a/mm/mmzone.c b/mm/mmzone.c index 0c8f181d9d50..59dc3f2076a6 100644 --- a/mm/mmzone.c +++ b/mm/mmzone.c @@ -43,7 +43,8 @@ struct zone *next_zone(struct zone *zone) return zone; } -static inline int zref_in_nodemask(struct zoneref *zref, nodemask_t *nodes) +static inline int zref_in_nodemask(struct zoneref *zref, + const nodemask_t *nodes) { #ifdef CONFIG_NUMA return node_isset(zonelist_node_idx(zref), *nodes); @@ -55,7 +56,7 @@ static inline int zref_in_nodemask(struct zoneref *zref, nodemask_t *nodes) /* Returns the next zone at or below highest_zoneidx in a zonelist */ struct zoneref *__next_zones_zonelist(struct zoneref *z, enum zone_type highest_zoneidx, - nodemask_t *nodes) + const nodemask_t *nodes) { /* * Find the next suitable zone to use for the allocation. diff --git a/mm/page_alloc.c b/mm/page_alloc.c index a460811eaa4d..549fa83045eb 100644 --- a/mm/page_alloc.c +++ b/mm/page_alloc.c @@ -3978,7 +3978,7 @@ get_page_from_freelist(gfp_t gfp_mask, unsigned int order, int alloc_flags, return NULL; } -static void warn_alloc_show_mem(gfp_t gfp_mask, nodemask_t *nodemask) +static void warn_alloc_show_mem(gfp_t gfp_mask, const nodemask_t *nodemask) { unsigned int filter = SHOW_MEM_FILTER_NODES; @@ -3998,7 +3998,7 @@ static void warn_alloc_show_mem(gfp_t gfp_mask, nodemask_t *nodemask) mem_cgroup_show_protected_memory(NULL); } -void warn_alloc(gfp_t gfp_mask, nodemask_t *nodemask, const char *fmt, ...) +void warn_alloc(gfp_t gfp_mask, const nodemask_t *nodemask, const char *fmt, ...) { struct va_format vaf; va_list args; @@ -4680,7 +4680,7 @@ check_retry_cpuset(int cpuset_mems_cookie, struct alloc_context *ac) return false; } -static void check_alloc_stall_warn(gfp_t gfp_mask, nodemask_t *nodemask, +static void check_alloc_stall_warn(gfp_t gfp_mask, const nodemask_t *nodemask, unsigned int order, unsigned long alloc_start_time) { static DEFINE_SPINLOCK(alloc_stall_lock); diff --git a/mm/show_mem.c b/mm/show_mem.c index 43aca5a2ac99..1b721a8ade67 100644 --- a/mm/show_mem.c +++ b/mm/show_mem.c @@ -116,7 +116,8 @@ void si_meminfo_node(struct sysinfo *val, int nid) * Determine whether the node should be displayed or not, depending on whether * SHOW_MEM_FILTER_NODES was passed to show_free_areas(). */ -static bool show_mem_node_skip(unsigned int flags, int nid, nodemask_t *nodemask) +static bool show_mem_node_skip(unsigned int flags, int nid, + const nodemask_t *nodemask) { if (!(flags & SHOW_MEM_FILTER_NODES)) return false; @@ -177,7 +178,8 @@ static bool node_has_managed_zones(pg_data_t *pgdat, int max_zone_idx) * SHOW_MEM_FILTER_NODES: suppress nodes that are not allowed by current's * cpuset. */ -static void show_free_areas(unsigned int filter, nodemask_t *nodemask, int max_zone_idx) +static void show_free_areas(unsigned int filter, const nodemask_t *nodemask, + int max_zone_idx) { unsigned long free_pcp = 0; int cpu, nid; @@ -402,7 +404,8 @@ static void show_free_areas(unsigned int filter, nodemask_t *nodemask, int max_z show_swap_cache_info(); } -void __show_mem(unsigned int filter, nodemask_t *nodemask, int max_zone_idx) +void __show_mem(unsigned int filter, const nodemask_t *nodemask, + int max_zone_idx) { unsigned long total = 0, reserved = 0, highmem = 0; struct zone *zone; diff --git a/mm/vmscan.c b/mm/vmscan.c index 35c3bb15ae96..754c5f5d716a 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -79,7 +79,7 @@ struct scan_control { * Nodemask of nodes allowed by the caller. If NULL, all nodes * are scanned. */ - nodemask_t *nodemask; + const nodemask_t *nodemask; /* * The memory cgroup that hit its limit and as a result is the @@ -6594,7 +6594,7 @@ static bool allow_direct_reclaim(pg_data_t *pgdat) * happens, the page allocator should not consider triggering the OOM killer. */ static bool throttle_direct_reclaim(gfp_t gfp_mask, struct zonelist *zonelist, - nodemask_t *nodemask) + const nodemask_t *nodemask) { struct zoneref *z; struct zone *zone; @@ -6674,7 +6674,7 @@ static bool throttle_direct_reclaim(gfp_t gfp_mask, struct zonelist *zonelist, } unsigned long try_to_free_pages(struct zonelist *zonelist, int order, - gfp_t gfp_mask, nodemask_t *nodemask) + gfp_t gfp_mask, const nodemask_t *nodemask) { unsigned long nr_reclaimed; struct scan_control sc = { From 9bdee249b2bc583f446c6b9f2ff6a3f97f750d2e Mon Sep 17 00:00:00 2001 From: Usama Arif Date: Mon, 8 Jun 2026 07:32:42 -0700 Subject: [PATCH 223/562] mm/swap_state: remove unnecessary lru_add_drain() from readahead swap_cluster_readahead() and swap_vma_readahead() end the readahead loop with an explicit lru_add_drain() call. That drain is a leftover from 2.6.12 era code and serves no functional purpose for the callers: - do_swap_page() ignores LRU residency for the readahead folios; it only needs the target folio it called swapin_readahead() for, and if the write-fault path needs the target folio on the LRU to count references accurately, it runs its own lru_add_drain() at the wp_can_reuse_anon_folio() and do_swap_page() sites. - shmem_swapin_cluster() immediately locks the returned folio, waits for writeback, then operates on it - LRU residency of either the target or the readahead folios is irrelevant. - try_to_unuse() likewise locks the folio and calls unuse_pte() without depending on LRU presence. Folios newly added to the swap cache by the readahead loop sit in the per-CPU LRU folio_batch and will be drained naturally as the batch fills (FOLIO_BATCH_SIZE),by the next reclaim/compaction lru_add_drain_all() and so on. The unconditional drain only synchronously flushes a partial batch and forces contention on lruvec_lock. On a 176-CPU production host running a memory-pressured workload, this path was observed to call folio_batch_move_lru() from swap_cluster_readahead() ~28K/min, a very large source of LRU lock traffic. This is a direct continuation of the cleanup started in commit 1aa43598c03b ("mm: remove unnecessary calls to lru_add_drain") which removed the equivalent drain from free_pages_and_swap_cache() with the same rationale. A detailed reasoning for this is present in [1]. Remove both drains. Link: https://lore.kernel.org/20260608143242.2869392-1-usama.arif@linux.dev Link: https://lore.kernel.org/all/dca2824e8e88e826c6b260a831d79089b5b9c79d.camel@surriel.com/T/#u [1] Signed-off-by: Usama Arif Acked-by: Shakeel Butt Reviewed-by: Barry Song Reviewed-by: Kairui Song Acked-by: Johannes Weiner Cc: Baoquan He Cc: Chris Li Cc: David Hildenbrand Cc: Kemeng Shi Cc: Nhat Pham Cc: Rik van Riel Signed-off-by: Andrew Morton --- mm/swap_state.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/mm/swap_state.c b/mm/swap_state.c index 9c3a5cf99778..6fd6e3415b71 100644 --- a/mm/swap_state.c +++ b/mm/swap_state.c @@ -836,7 +836,6 @@ struct folio *swap_cluster_readahead(swp_entry_t entry, gfp_t gfp_mask, } blk_finish_plug(&plug); swap_read_unplug(splug); - lru_add_drain(); /* Push any new pages onto the LRU now */ skip: /* The page was likely read above, so no need for plugging here */ return swap_cache_read_folio(entry, gfp_mask, mpol, ilx, NULL, false); @@ -951,7 +950,6 @@ static struct folio *swap_vma_readahead(swp_entry_t targ_entry, gfp_t gfp_mask, pte_unmap(pte); blk_finish_plug(&plug); swap_read_unplug(splug); - lru_add_drain(); skip: /* The folio was likely read above, so no need for plugging here */ folio = swap_cache_read_folio(targ_entry, gfp_mask, mpol, targ_ilx, From 6adeea818ba0c0449f5df98be0a8feaaa12d8837 Mon Sep 17 00:00:00 2001 From: Sourav Panda Date: Sun, 28 Jun 2026 19:01:55 +0000 Subject: [PATCH 224/562] mm/hugetlb_cma: support percentage-based hugetlb_cma reservation Currently, hugetlb_cma reservation only supports absolute sizes (e.g., hugetlb_cma=2G or hugetlb_cma=0:1G,1:1G). This can be restrictive in heterogeneous environments or when deploying common kernel command lines across machines with different memory capacities. Add support for percentage-based hugetlb_cma reservation (e.g., hugetlb_cma=20% or hugetlb_cma=0:20%,1:10%). The percentage is calculated against the total memory (for global settings) or against the node-specific memory (for node-specific settings) using memblock APIs during early boot. Link: https://lore.kernel.org/20260628190155.3655895-1-souravpanda@google.com Signed-off-by: Sourav Panda Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202606262023.IKUrn01I-lkp@intel.com/ Cc: David Hildenbrand Cc: David Rientjes Cc: Frank van der Linden Cc: Greg Thelen Cc: Muchun Song Cc: Oscar Salvador Cc: Suren Baghdasaryan Signed-off-by: Andrew Morton --- .../admin-guide/kernel-parameters.txt | 7 +- mm/hugetlb_cma.c | 96 ++++++++++++++++++- 2 files changed, 97 insertions(+), 6 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index b5493a7f8f22..53f08950a630 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -2064,8 +2064,11 @@ Kernel parameters hugetlb_cma= [HW,CMA,EARLY] The size of a CMA area used for allocation of gigantic hugepages. Or using node format, the size of a CMA area per node can be specified. - Format: nn[KMGTPE] or (node format) - :nn[KMGTPE][,:nn[KMGTPE]] + The size can be an absolute value (e.g., 2G) or a + percentage of the total memory or node memory (e.g., 20%). + Format: nn[KMGTPE] or nn% or (node format) + :nn[KMGTPE][,:nn[KMGTPE]] or + :nn%[,:nn%] The size must be a multiple of the gigantic page size. When using node format, this applies to each per-node size. diff --git a/mm/hugetlb_cma.c b/mm/hugetlb_cma.c index d0dc28f08b3c..07faf625675b 100644 --- a/mm/hugetlb_cma.c +++ b/mm/hugetlb_cma.c @@ -9,6 +9,9 @@ #include #include +#include +#include +#include #include "internal.h" #include "hugetlb_cma.h" @@ -18,6 +21,28 @@ static unsigned long hugetlb_cma_size_in_node[MAX_NUMNODES] __initdata; static bool hugetlb_cma_only __ro_after_init; static unsigned long hugetlb_cma_size __ro_after_init; +static unsigned int hugetlb_cma_percent __initdata; +static unsigned int hugetlb_cma_percent_in_node[MAX_NUMNODES] __initdata; + +#ifdef CONFIG_NUMA +static phys_addr_t __init memblock_node_memory_size(int nid) +{ + struct memblock_region *reg; + phys_addr_t size = 0; + + for_each_mem_region(reg) { + if (reg->nid == nid) + size += reg->size; + } + return size; +} +#else +static phys_addr_t __init memblock_node_memory_size(int nid) +{ + return memblock_phys_mem_size(); +} +#endif + void hugetlb_cma_free_frozen_folio(struct folio *folio) { WARN_ON_ONCE(!cma_release_frozen(hugetlb_cma[folio_nid(folio)], @@ -90,14 +115,28 @@ static int __init cmdline_parse_hugetlb_cma(char *p) break; if (s[count] == ':') { + char *next; + if (tmp >= MAX_NUMNODES) break; nid = array_index_nospec(tmp, MAX_NUMNODES); s += count + 1; - tmp = memparse(s, &s); - hugetlb_cma_size_in_node[nid] = tmp; - hugetlb_cma_size += tmp; + tmp = memparse(s, &next); + if (*next == '%') { + if (tmp > 100) { + pr_warn("hugetlb_cma: invalid percentage %lu for node %d\n", + tmp, nid); + break; + } + hugetlb_cma_percent_in_node[nid] = tmp; + hugetlb_cma_size_in_node[nid] = 0; + s = next + 1; + } else { + hugetlb_cma_size_in_node[nid] = tmp; + hugetlb_cma_percent_in_node[nid] = 0; + s = next; + } /* * Skip the separator if have one, otherwise @@ -108,7 +147,28 @@ static int __init cmdline_parse_hugetlb_cma(char *p) else break; } else { - hugetlb_cma_size = memparse(p, &p); + char *next; + + tmp = memparse(p, &next); + if (*next == '%') { + if (tmp > 100) { + pr_warn("hugetlb_cma: invalid percentage %lu\n", tmp); + } else { + hugetlb_cma_percent = tmp; + hugetlb_cma_size = 0; + for (nid = 0; nid < MAX_NUMNODES; nid++) { + hugetlb_cma_size_in_node[nid] = 0; + hugetlb_cma_percent_in_node[nid] = 0; + } + } + } else { + hugetlb_cma_size = tmp; + hugetlb_cma_percent = 0; + for (nid = 0; nid < MAX_NUMNODES; nid++) { + hugetlb_cma_size_in_node[nid] = 0; + hugetlb_cma_percent_in_node[nid] = 0; + } + } break; } } @@ -134,8 +194,36 @@ void __init hugetlb_cma_reserve(void) { unsigned long size, reserved, per_node, order, gigantic_page_size; bool node_specific_cma_alloc = false; + bool has_node_specific_param = false; int nid; + for (nid = 0; nid < MAX_NUMNODES; nid++) { + if (hugetlb_cma_size_in_node[nid] || hugetlb_cma_percent_in_node[nid]) { + has_node_specific_param = true; + break; + } + } + + if (has_node_specific_param) { + hugetlb_cma_size = 0; + for (nid = 0; nid < MAX_NUMNODES; nid++) { + if (hugetlb_cma_percent_in_node[nid]) { + phys_addr_t node_gfp_mem = memblock_node_memory_size(nid); + u64 s; + + s = mul_u64_u32_div((u64)node_gfp_mem, + hugetlb_cma_percent_in_node[nid], + 100); + + hugetlb_cma_size_in_node[nid] = s; + } + hugetlb_cma_size += hugetlb_cma_size_in_node[nid]; + } + } else if (hugetlb_cma_percent) { + hugetlb_cma_size = mul_u64_u32_div((u64)memblock_phys_mem_size(), + hugetlb_cma_percent, 100); + } + if (!hugetlb_cma_size) return; From 49c2b25b7f15a44b7d0ab67cef07a138fab2bce3 Mon Sep 17 00:00:00 2001 From: Imran Khan Date: Thu, 4 Jun 2026 21:42:45 +0800 Subject: [PATCH 225/562] mm/vmstat: avoid taking zone lock in /proc/buddyinfo reads frag_show_print() just reads zone->free_area[order].nr_free, so it can safely do this without needing the zone->lock. Pass nolock=true from frag_show(), so that walk_zones_in_node() can skip the zone->lock acquisition. Link: https://lore.kernel.org/20260604134245.1580287-1-imran.f.khan@oracle.com Signed-off-by: Imran Khan Reviewed-by: Joshua Hahn Acked-by: Vlastimil Babka (SUSE) Reviewed-by: Lorenzo Stoakes Acked-by: David Hildenbrand (Arm) Cc: Liam R. Howlett Cc: Michal Hocko Cc: Mike Rapoport Cc: Suren Baghdasaryan Signed-off-by: Andrew Morton --- mm/vmstat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/vmstat.c b/mm/vmstat.c index f534972f517d..7b93fbf9af09 100644 --- a/mm/vmstat.c +++ b/mm/vmstat.c @@ -1568,7 +1568,7 @@ static void frag_show_print(struct seq_file *m, pg_data_t *pgdat, static int frag_show(struct seq_file *m, void *arg) { pg_data_t *pgdat = (pg_data_t *)arg; - walk_zones_in_node(m, pgdat, true, false, frag_show_print); + walk_zones_in_node(m, pgdat, true, true, frag_show_print); return 0; } From f435e5acc3ed67e8ed4187bd479e6fd1983ac823 Mon Sep 17 00:00:00 2001 From: Jianhui Zhou Date: Mon, 1 Jun 2026 16:26:09 +0800 Subject: [PATCH 226/562] mm/userfaultfd: clear uffd-wp PTE state when re-registering without WP UFFDIO_REGISTER can be issued on a range that is already registered in the same userfaultfd context, replacing the VMA's userfaultfd tracking mode. For example, a range can be registered with UFFDIO_REGISTER_MODE_WP and later re-registered with UFFDIO_REGISTER_MODE_MISSING. When the second registration removes VM_UFFD_WP, the VMA flags are updated but existing uffd-wp state in page-table entries is left behind. That stale state can survive in swap PTEs. On swapin, do_swap_page() restores _PAGE_UFFD_WP from the swap PTE and can then install a writable PTE, triggering page_table_check: pte_uffd_wp(pte) && pte_write(pte) Handle removal of WP mode through UFFDIO_REGISTER the same way as UFFDIO_UNREGISTER: resolve the per-PTE uffd-wp state before dropping VM_UFFD_WP from the VMA. Also make the same-context fast path require an exact UFFD mode match. The old subset check treats MISSING|WP -> MISSING as a no-op, even though WP mode is being removed. Link: https://lore.kernel.org/20260601082609.170076-1-jianhuizzzzz@gmail.com Fixes: f45ec5ff16a7 ("userfaultfd: wp: support swap and page migration") Signed-off-by: Jianhui Zhou Reported-by: syzbot+18d274a59b87cf80e86d@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=18d274a59b87cf80e86d Cc: Mike Rapoport Cc: Peter Xu Cc: Signed-off-by: Andrew Morton --- mm/userfaultfd.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/mm/userfaultfd.c b/mm/userfaultfd.c index c3adedaaf7d5..33ef42d5a04e 100644 --- a/mm/userfaultfd.c +++ b/mm/userfaultfd.c @@ -2238,13 +2238,21 @@ static int userfaultfd_register_range(struct userfaultfd_ctx *ctx, * userfaultfd and with the right tracking mode too. */ if (vma->vm_userfaultfd_ctx.ctx == ctx && - vma_test_all_mask(vma, vma_flags)) + (vma->vm_flags & __VM_UFFD_FLAGS) == vm_flags) goto skip; if (vma->vm_start > start) start = vma->vm_start; vma_end = min(end, vma->vm_end); + /* + * Re-registering into the same userfaultfd can remove WP mode. + * Clear any per-PTE uffd-wp state before dropping VM_UFFD_WP, + * matching the UFFDIO_UNREGISTER cleanup semantics. + */ + if (userfaultfd_wp(vma) && !(vm_flags & VM_UFFD_WP)) + uffd_wp_range(vma, start, vma_end - start, false); + new_vma_flags = vma->flags; vma_flags_clear_mask(&new_vma_flags, __VMA_UFFD_FLAGS); vma_flags_set_mask(&new_vma_flags, vma_flags); From 9c863994576677c8a32ca09d4a3bb25a918d6d86 Mon Sep 17 00:00:00 2001 From: Yichong Chen Date: Mon, 29 Jun 2026 09:43:14 +0800 Subject: [PATCH 227/562] tools/mm/page_owner_sort: return explicit filter results Patch series "tools/mm/page_owner_sort: fix filtering and cleanup issues", v5. Patch 1 renames is_need() to filter_record() and makes the filter path return explicit results. Patch 2 fixes the per-record allocation leaks. Patch 3 bounds search_pattern() output copies, addressing the pre-existing issue reported by Sashiko/AI review. This patch (of 3): Rename is_need() to filter_record() and make the filter path return explicit error, skip, and match results. This lets callers distinguish allocation failures from records that simply do not match active filters. Link: https://lore.kernel.org/20260629014316.130307-1-chenyichong@uniontech.com Link: https://lore.kernel.org/20260629014316.130307-2-chenyichong@uniontech.com Signed-off-by: Yichong Chen Reviewed-by: Vishal Moola Cc: Ye Liu Cc: Zhen Ni Cc: Zi Yan Signed-off-by: Andrew Morton --- tools/mm/page_owner_sort.c | 40 +++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/tools/mm/page_owner_sort.c b/tools/mm/page_owner_sort.c index e6954909401c..3c754826cec5 100644 --- a/tools/mm/page_owner_sort.c +++ b/tools/mm/page_owner_sort.c @@ -43,6 +43,13 @@ enum FILTER_BIT { FILTER_TGID = 1<<2, FILTER_COMM = 1<<3 }; + +enum FILTER_RESULT { + FILTER_ERROR, + FILTER_SKIP, + FILTER_MATCH +}; + enum CULL_BIT { CULL_PID = 1<<1, CULL_TGID = 1<<2, @@ -372,6 +379,9 @@ static char *get_comm(char *buf) { char *comm_str = malloc(TASK_COMM_LEN); + if (!comm_str) + return NULL; + memset(comm_str, 0, TASK_COMM_LEN); search_pattern(&comm_pattern, comm_str, buf); @@ -450,32 +460,44 @@ static bool match_str_list(const char *str, char **list, int list_size) return false; } -static bool is_need(char *buf) +static enum FILTER_RESULT filter_record(char *buf) { + char *comm; + if ((filter & FILTER_PID) && !match_num_list(get_pid(buf), fc.pids, fc.pids_size)) - return false; + return FILTER_SKIP; if ((filter & FILTER_TGID) && !match_num_list(get_tgid(buf), fc.tgids, fc.tgids_size)) - return false; + return FILTER_SKIP; + if (!(filter & FILTER_COMM)) + return FILTER_MATCH; - char *comm = get_comm(buf); + comm = get_comm(buf); + if (!comm) + return FILTER_ERROR; - if ((filter & FILTER_COMM) && - !match_str_list(comm, fc.comms, fc.comms_size)) { + if (!match_str_list(comm, fc.comms, fc.comms_size)) { free(comm); - return false; + return FILTER_SKIP; } free(comm); - return true; + return FILTER_MATCH; } static bool add_list(char *buf, int len, char *ext_buf) { + enum FILTER_RESULT filter_result; + if (list_size == max_size) { fprintf(stderr, "max_size too small??\n"); return false; } - if (!is_need(buf)) + filter_result = filter_record(buf); + if (filter_result == FILTER_ERROR) { + fprintf(stderr, "Out of memory\n"); + return false; + } + if (filter_result == FILTER_SKIP) return true; list[list_size].pid = get_pid(buf); list[list_size].tgid = get_tgid(buf); From ae669734ff9daf187d781548f1bf4f1387e82cae Mon Sep 17 00:00:00 2001 From: Yichong Chen Date: Mon, 29 Jun 2026 09:43:15 +0800 Subject: [PATCH 228/562] tools/mm/page_owner_sort: free per-record allocations add_list() allocates comm and txt for each page owner record, but the cleanup path only frees the outer list array. This leaks both buffers for every retained record. Free partial allocations in add_list(), discarded records during culling, and retained records on exit. Link: https://lore.kernel.org/20260629014316.130307-3-chenyichong@uniontech.com Signed-off-by: Yichong Chen Reviewed-by: Vishal Moola Cc: Ye Liu Cc: Zhen Ni Cc: Zi Yan Signed-off-by: Andrew Morton --- tools/mm/page_owner_sort.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/tools/mm/page_owner_sort.c b/tools/mm/page_owner_sort.c index 3c754826cec5..4c9be28abe3b 100644 --- a/tools/mm/page_owner_sort.c +++ b/tools/mm/page_owner_sort.c @@ -396,6 +396,12 @@ static char *get_comm(char *buf) return comm_str; } +static void free_block_list(struct block_list *block) +{ + free(block->comm); + free(block->txt); +} + static int get_arg_type(const char *arg) { if (!strcmp(arg, "pid") || !strcmp(arg, "p")) @@ -502,9 +508,14 @@ static bool add_list(char *buf, int len, char *ext_buf) list[list_size].pid = get_pid(buf); list[list_size].tgid = get_tgid(buf); list[list_size].comm = get_comm(buf); - list[list_size].txt = malloc(len+1); + if (!list[list_size].comm) { + fprintf(stderr, "Out of memory\n"); + return false; + } + list[list_size].txt = malloc(len + 1); if (!list[list_size].txt) { fprintf(stderr, "Out of memory\n"); + free(list[list_size].comm); return false; } memcpy(list[list_size].txt, buf, len); @@ -863,8 +874,10 @@ int main(int argc, char **argv) } else { list[count-1].num += list[i].num; list[count-1].page_num += list[i].page_num; + free_block_list(&list[i]); } } + list_size = count; qsort(list, count, sizeof(list[0]), compare_sort_condition); @@ -898,8 +911,11 @@ int main(int argc, char **argv) free(ext_buf); if (buf) free(buf); - if (list) + if (list) { + for (i = 0; i < list_size; i++) + free_block_list(&list[i]); free(list); + } out_ts: regfree(&ts_nsec_pattern); out_comm: From 42dd70be699db9038f55ab22c8de0ab3ca50416f Mon Sep 17 00:00:00 2001 From: Yichong Chen Date: Mon, 29 Jun 2026 09:43:16 +0800 Subject: [PATCH 229/562] tools/mm/page_owner_sort: bound pattern output copies search_pattern() copies a regex capture into caller-provided buffers without knowing their sizes. Several callers pass fixed-size buffers, including FIELD_BUFF and TASK_COMM_LEN. Pass the destination size to search_pattern(), reject captures that do not fit before copying them, and terminate the output string inside search_pattern(). Link: https://lore.kernel.org/20260629014316.130307-4-chenyichong@uniontech.com Signed-off-by: Yichong Chen Cc: Vishal Moola Cc: Ye Liu Cc: Zhen Ni Cc: Zi Yan Signed-off-by: Andrew Morton --- tools/mm/page_owner_sort.c | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/tools/mm/page_owner_sort.c b/tools/mm/page_owner_sort.c index 4c9be28abe3b..35d3d254941c 100644 --- a/tools/mm/page_owner_sort.c +++ b/tools/mm/page_owner_sort.c @@ -237,7 +237,8 @@ static int remove_pattern(regex_t *pattern, char *buf, int len) return len - (pmatch[1].rm_eo - pmatch[1].rm_so); } -static int search_pattern(regex_t *pattern, char *pattern_str, char *buf) +static int search_pattern(regex_t *pattern, char *pattern_str, + size_t pattern_str_size, char *buf) { int err, val_len; regmatch_t pmatch[2]; @@ -249,8 +250,14 @@ static int search_pattern(regex_t *pattern, char *pattern_str, char *buf) return -1; } val_len = pmatch[1].rm_eo - pmatch[1].rm_so; + if ((size_t)val_len >= pattern_str_size) { + if (debug_on) + fprintf(stderr, "pattern too long in %s\n", buf); + return -1; + } memcpy(pattern_str, buf + pmatch[1].rm_so, val_len); + pattern_str[val_len] = '\0'; return 0; } @@ -307,7 +314,8 @@ static int get_page_num(char *buf) char order_str[FIELD_BUFF] = {0}; char *endptr; - search_pattern(&order_pattern, order_str, buf); + if (search_pattern(&order_pattern, order_str, sizeof(order_str), buf) < 0) + return 0; errno = 0; order_val = strtol(order_str, &endptr, 10); if (order_val > 64 || errno != 0 || endptr == order_str || *endptr != '\0') { @@ -325,7 +333,8 @@ static pid_t get_pid(char *buf) char pid_str[FIELD_BUFF] = {0}; char *endptr; - search_pattern(&pid_pattern, pid_str, buf); + if (search_pattern(&pid_pattern, pid_str, sizeof(pid_str), buf) < 0) + return -1; errno = 0; pid = strtol(pid_str, &endptr, 10); if (errno != 0 || endptr == pid_str || *endptr != '\0') { @@ -344,7 +353,8 @@ static pid_t get_tgid(char *buf) char tgid_str[FIELD_BUFF] = {0}; char *endptr; - search_pattern(&tgid_pattern, tgid_str, buf); + if (search_pattern(&tgid_pattern, tgid_str, sizeof(tgid_str), buf) < 0) + return -1; errno = 0; tgid = strtol(tgid_str, &endptr, 10); if (errno != 0 || endptr == tgid_str || *endptr != '\0') { @@ -363,7 +373,9 @@ static __u64 get_ts_nsec(char *buf) char ts_nsec_str[FIELD_BUFF] = {0}; char *endptr; - search_pattern(&ts_nsec_pattern, ts_nsec_str, buf); + if (search_pattern(&ts_nsec_pattern, ts_nsec_str, + sizeof(ts_nsec_str), buf) < 0) + return -1; errno = 0; ts_nsec = strtoull(ts_nsec_str, &endptr, 10); if (errno != 0 || endptr == ts_nsec_str || *endptr != '\0') { @@ -384,7 +396,10 @@ static char *get_comm(char *buf) memset(comm_str, 0, TASK_COMM_LEN); - search_pattern(&comm_pattern, comm_str, buf); + if (search_pattern(&comm_pattern, comm_str, TASK_COMM_LEN, buf) < 0) { + free(comm_str); + return NULL; + } errno = 0; if (errno != 0) { if (debug_on) From 3a419625c0a778119b8cf36aad415e9b9301ca76 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Sun, 28 Jun 2026 14:54:40 -0700 Subject: [PATCH 230/562] samples/damon/wsse: handle damon_start() failure Patch series "samples/damon: handle damon_{start,stop}() failures". All DAMON sample modules are not correctly handling failures from damon_start(). Among those, mtier also has an additional problem for handling of damon_stop() failures. wsse and prcl also have a problem in their damon_call() failure handling. As a result, memory leaks, next DAMON operation disruptions, and use-after-free can happen. Fix those. Note that only the damon_start() failure caused issues can reliably be reproduced. Reproducing those issues require the admin permission, though. This patch (of 6): damon_sample_wsse_start() callers assume it will clean up resources when it fails. And the function does the cleanup for context buildup failures. However, it is not doing the cleanup for damon_start() failure. As a result, when damon_start() fails, it leaks the memory for DAMON context. Free the context in case of the failure to fix the issues. Note that the issue can reliably be reproduced because the module calls damon_start() in the exclusive mode. For example, $ sudo damo start $ echo $$ | sudo tee /sys/module/damon_sample_wsse/parameters/target_pid $ echo Y | sudo tee /sys/module/damon_sample_wsse/parameters/enabled $ sudo cat /proc/allocinfo | grep damon_new_ctx Because the first command is running another DAMON instance, the third command fails the damon_start() call because the new DAMON instance cannot exclusively run. And without this fix, by repeating the third and the fourth commands above, we can show the memory consumption is only increasing due to the leaks. It requires the sudo permission though. The issue was discovered [1] by Sashiko. Link: https://lore.kernel.org/20260628215447.96166-2-sj@kernel.org Link: https://lore.kernel.org/20260609145814.70163-1-sj@kernel.org [1] Fixes: b757c6cfc696 ("samples/damon/wsse: start and stop DAMON as the user requests") Signed-off-by: SJ Park Reviewed-by: Zenghui Yu Cc: # 6.14.x Signed-off-by: Andrew Morton --- samples/damon/wsse.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/samples/damon/wsse.c b/samples/damon/wsse.c index 799ad4443943..bbd9392ab5b3 100644 --- a/samples/damon/wsse.c +++ b/samples/damon/wsse.c @@ -87,8 +87,10 @@ static int damon_sample_wsse_start(void) target->pid = target_pidp; err = damon_start(&ctx, 1, true); - if (err) + if (err) { + damon_destroy_ctx(ctx); return err; + } repeat_call_control.data = ctx; return damon_call(ctx, &repeat_call_control); } From cf9fe9a8ccbeaefdff3e76ce88ee254ba00d8b07 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Sun, 28 Jun 2026 14:54:41 -0700 Subject: [PATCH 231/562] samples/damon/prcl: handle damon_start() failure damon_sample_prcl_start() callers assume it will clean up resources when it fails. And the function does the cleanup for context buildup failures. However, it is not doing the cleanup for damon_start() failure. As a result, when damon_start() fails, it leaks the memory for DAMON context. Free the context in case of the failure to fix the issues. Note that the issue can reliably be reproduced because the module calls damon_start() in the exclusive mode. For example, $ sudo damo start $ echo $$ | sudo tee /sys/module/damon_sample_prcl/parameters/target_pid $ echo Y | sudo tee /sys/module/damon_sample_prcl/parameters/enabled $ sudo cat /proc/allocinfo | grep damon_new_ctx Because the first command is running another DAMON instance, the third command fails the damon_start() call because the new DAMON instance cannot exclusively run. And without this fix, by repeating the third and the fourth commands above, we can show the memory consumption is only increasing due to the leaks. It requires the sudo permission though. The issue was discovered [1] by Sashiko. Link: https://lore.kernel.org/20260628215447.96166-3-sj@kernel.org Link: https://lore.kernel.org/20260609145814.70163-1-sj@kernel.org [1] Fixes: 2aca254620a8 ("samples/damon: introduce a skeleton of a smaple DAMON module for proactive reclamation") Signed-off-by: SJ Park Reviewed-by: Zenghui Yu Cc: # 6.14.x Signed-off-by: Andrew Morton --- samples/damon/prcl.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/samples/damon/prcl.c b/samples/damon/prcl.c index b7c50f2656ce..0db259894691 100644 --- a/samples/damon/prcl.c +++ b/samples/damon/prcl.c @@ -106,8 +106,10 @@ static int damon_sample_prcl_start(void) damon_set_schemes(ctx, &scheme, 1); err = damon_start(&ctx, 1, true); - if (err) + if (err) { + damon_destroy_ctx(ctx); return err; + } repeat_call_control.data = ctx; return damon_call(ctx, &repeat_call_control); From 6f985b238325d04acb6a4c6ae55e61e44aa42745 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Sun, 28 Jun 2026 14:54:42 -0700 Subject: [PATCH 232/562] samples/damon/mtier: handle damon_start() failure damon_sample_mtier_start() callers assume it will clean up resources when it fails. And the function does the cleanup for context buildup failures. However, it is not doing the cleanup for damon_start() failure. As a result, when damon_start() fails, it could leak the memory for DAMON context. Also, if damon_start() fails for only the second context, the first context will indefinitely run, and avoid starting other DAMON contexts since it is running in the exclusive mode. Stop possibly started DAMON context and free the contexts in case of the failure to fix the issues. Note that the issue can reliably be reproduced because the module calls damon_start() in the exclusive mode. For example, $ sudo damo start $ echo Y | sudo tee /sys/module/damon_sample_mtier/parameters/enabled $ sudo cat /proc/allocinfo | grep damon_new_ctx Because the first command is running another DAMON instance, the second command fails the damon_start() call because the new DAMON instance cannot exclusively run. And without this fix, by repeating the second and the third commands above, we can show the memory consumption is only increasing due to the leaks. It requires the sudo permission though. The issue was discovered [1] by Sashiko. Link: https://lore.kernel.org/20260628215447.96166-4-sj@kernel.org Link: https://lore.kernel.org/20260608112455.274231F00893@smtp.kernel.org [1] Fixes: 82a08bde3cf7 ("samples/damon: implement a DAMON module for memory tiering") Signed-off-by: SJ Park Reviewed-by: Zenghui Yu Cc: # 6.16.x Signed-off-by: Andrew Morton --- samples/damon/mtier.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/samples/damon/mtier.c b/samples/damon/mtier.c index 3785b0c7ffb1..564212e054de 100644 --- a/samples/damon/mtier.c +++ b/samples/damon/mtier.c @@ -177,6 +177,7 @@ static struct damon_ctx *damon_sample_mtier_build_ctx(bool promote) static int damon_sample_mtier_start(void) { struct damon_ctx *ctx; + int err; ctx = damon_sample_mtier_build_ctx(true); if (!ctx) @@ -188,7 +189,15 @@ static int damon_sample_mtier_start(void) return -ENOMEM; } ctxs[1] = ctx; - return damon_start(ctxs, 2, true); + err = damon_start(ctxs, 2, true); + if (!err) + return 0; + + if (damon_is_running(ctxs[0])) + damon_stop(ctxs, 1); + damon_destroy_ctx(ctxs[0]); + damon_destroy_ctx(ctxs[1]); + return err; } static void damon_sample_mtier_stop(void) From e457dc543bb20d2871a731cdbb062ddabf534d52 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Sun, 28 Jun 2026 14:54:43 -0700 Subject: [PATCH 233/562] samples/damon/mtier: handle damon_stop() failure damon_sample_mtier_stop() assumes its damon_stop() call will always successfully stops the two DAMON contexts. Hence it deallocates the two DAMON contexts after the damon_stop() call. However, if a given context is already stopped, damon_stop() fails and returns an error while letting the DAMON contexts that have not yet stopped keep running. This kind of unexpected early DAMON context stops could happen due to memory allocation failures in kdamond_fn(). Because damon_sample_mtier_stop() just deallocates all DAMON contexts with damon_target and damon_region objects that are linked to the contexts, the execution of the unstopped DAMON context (kdamond) ends up using the memory that freed (use-after-free). Fix the issue by separating the damon_stop() to be invoked per context. Note that DAMON_SYSFS also allows multiple DAMON contexts execution. But, it calls damon_stop() for each context one by one. Hence this issue is only in mtier. For the long term, it would be better to refactor damon_stop() to always ensure stopping all contexts regardless of the failures in the middle. Make this fix in the current way, though, to keep it simple and easy to backport. I will do the refactoring later. The issue was discovered [1] by Sashiko. Link: https://lore.kernel.org/20260628215447.96166-5-sj@kernel.org Link: https://lore.kernel.org/20260609014219.3013-1-sj@kernel.org [1] Fixes: 82a08bde3cf7 ("samples/damon: implement a DAMON module for memory tiering") Signed-off-by: SJ Park Reviewed-by: Zenghui Yu Cc: # 6.16.x Signed-off-by: Andrew Morton --- samples/damon/mtier.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/samples/damon/mtier.c b/samples/damon/mtier.c index 564212e054de..e567f4edd80e 100644 --- a/samples/damon/mtier.c +++ b/samples/damon/mtier.c @@ -202,7 +202,8 @@ static int damon_sample_mtier_start(void) static void damon_sample_mtier_stop(void) { - damon_stop(ctxs, 2); + damon_stop(ctxs, 1); + damon_stop(&ctxs[1], 1); damon_destroy_ctx(ctxs[0]); damon_destroy_ctx(ctxs[1]); } From a8886008c27a4d017769cd37a10493730eda59d1 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Sun, 28 Jun 2026 14:54:44 -0700 Subject: [PATCH 234/562] samples/damon/wsse: stop and free damon ctx when damon_call() fails damon_sample_wsse_start() calls damon_call() right after damon_start() is succeeded. The kdamond that has started by the damon_start() could be terminated by itself before or in the middle of the damon_call() execution. There could be multiple reasons for such a stop including monitoring target process termination and kdamond_fn() internal memory allocation failures. In the case, damon_call() will fail and return an error without cleaning up the DAMON context object. The damon_sample_wsse_start() caller assumes it would clean up the object, though. When the user requests to start DAMON again, damon_sample_wsse_start() is called again, allocates a new DAMON context object and overwrites the pointer for the previous object. As a result, the previous context object is leaked. Safely stop the kdamond and deallocate the context object when the failure is returned. Note that the kdamond should be stopped first, because damon_call() failure means not complete termination of the kdamond but only the fact that the termination process has started. The user impact shouldn't be that significant because the race is not easy to happen, and only up to one DAMON context object can be leaked per race. The issue was discovered [1] by Sashiko. Link: https://lore.kernel.org/20260628215447.96166-6-sj@kernel.org Link: https://lore.kernel.org/20260610034828.4632-1-sj@kernel.org [1] Fixes: cc9c1b8c205b ("samples/damon/wsse: use damon_call() repeat mode instead of damon_callback") Signed-off-by: SJ Park Reviewed-by: Zenghui Yu Cc: # 6.17.x Signed-off-by: Andrew Morton --- samples/damon/wsse.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/samples/damon/wsse.c b/samples/damon/wsse.c index bbd9392ab5b3..ff5e8a890f44 100644 --- a/samples/damon/wsse.c +++ b/samples/damon/wsse.c @@ -92,7 +92,12 @@ static int damon_sample_wsse_start(void) return err; } repeat_call_control.data = ctx; - return damon_call(ctx, &repeat_call_control); + err = damon_call(ctx, &repeat_call_control); + if (err) { + damon_stop(&ctx, 1); + damon_destroy_ctx(ctx); + } + return err; } static void damon_sample_wsse_stop(void) From 8fd6b059354a25225990f80ebfd458faa78c17d1 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Sun, 28 Jun 2026 14:54:45 -0700 Subject: [PATCH 235/562] samples/damon/prcl: stop and free damon ctx when damon_call() fails damon_sample_prcl_start() calls damon_call() right after damon_start() is succeeded. The kdamond that has started by the damon_start() could be terminated by itself before or in the middle of the damon_call() execution. There could be multiple reasons for such a stop including monitoring target process termination and kdamond_fn() internal memory allocation failures. In the case, damon_call() will fail and return an error without cleaning up the DAMON context object. The damon_sample_prcl_start() caller assumes it would clean up the object, though. When the user requests to start DAMON again, damon_sample_prcl_start() is called again, allocates a new DAMON context object and overwrites the pointer for the previous object. As a result, the previous context object is leaked. Safely stop the kdamond and deallocate the context object when the failure is returned. Note that the kdamond should be stopped first, because damon_call() failure means not complete termination of the kdamond but only the fact that the termination process has started. The user impact shouldn't be that significant because the race is not easy to happen, and only up to one DAMON context object can be leaked per race. The issue was discovered [1] by Sashiko. Link: https://lore.kernel.org/20260628215447.96166-7-sj@kernel.org Link: https://lore.kernel.org/20260610035214.4850-1-sj@kernel.org [1] Fixes: a6c33f1054e3 ("samples/damon/prcl: use damon_call() repeat mode instead of damon_callback") Signed-off-by: SJ Park Reviewed-by: Zenghui Yu Cc: # 6.17.x Signed-off-by: Andrew Morton --- samples/damon/prcl.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/samples/damon/prcl.c b/samples/damon/prcl.c index 0db259894691..edeae145c4a8 100644 --- a/samples/damon/prcl.c +++ b/samples/damon/prcl.c @@ -112,7 +112,12 @@ static int damon_sample_prcl_start(void) } repeat_call_control.data = ctx; - return damon_call(ctx, &repeat_call_control); + err = damon_call(ctx, &repeat_call_control); + if (err) { + damon_stop(&ctx, 1); + damon_destroy_ctx(ctx); + } + return err; } static void damon_sample_prcl_stop(void) From c6d5f48cfbda8b00d99af90a32a220bb92a57150 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Sun, 28 Jun 2026 15:01:10 -0700 Subject: [PATCH 236/562] mm/damon/sysfs: kobject_del() target (normal), context and kdamond dirs Patch series "mm/damon/sysfs: kobject_del() directories that users can create/remove". DAMON sysfs interface allows users to create and remove arbitrary number of directories on sysfs, using a few files having 'nr_' prefix. For example, 'nr_kdamonds'. When the user writes a number 'N' to the files, directories having name starting from '0' to 'N - 1' are created in the same directory. The pre-existing number-named directories are removed before creating the new directories. For the removal of the existing directories, DAMON sysfs interface use only kobject_put(). Because DAMON sysfs interface is the only kernel component that manages the directories, there is no problem in normal situations. However, if CONFIG_DEBUG_KOBJECT_RELEASE is enabled, the removal of dirs are delayed. Let's suppose a user writes a non-zero number to the 'nr_*' files while there are pre-existing number-named directories, on the config enabled kernel. DAMON sysfs interface decreases the reference counts of the existing directories and immediately creates new directories. Because the removal of the sysfs directories is delayed, it shows some pre-existing directories of the same names when it tries to create the new directories, and fails. For example, the issue can be triggered like below: # grep DEBUG_KOBJECT_RELEASE /boot/config-$(uname -r) CONFIG_DEBUG_KOBJECT_RELEASE=y # ls nr_kdamonds # echo 1 > nr_kdamonds # echo 1 > nr_kdamonds bash: echo: write error: File exists # dmesg [...] [ 300.880458] kobject: kobject_add_internal failed for 0 with -EEXIST, don't try to register things with the same name in the same directory. [...] Some of the error handling paths of the directories also lack the kobject_del() call. If the user uses nr_* file right after the errors, similar issues can happen. This doesn't cause catastrophic issues like kernel panics or memory corruptions. Users can work around by removing all directories first (write 0 to the nr_* files) and then create new directories after confirming the old directories are gone. But, this is definitely a bug that causes a bad user experience. Fix the issues by calling kobject_del() before creating new directories. This patch (of 11) On CONFIG_DEBUG_KOBJECT_RELEASE enabled kernel, lack of kobject_del() could cause directories creation failures due to the name conflicts. Fix those issues for normal creation paths of target, context and kdamond directories, and error paths of context and kdamond directories by adding kobject_del() calls. Note that this fix for target directories is not complete since it has a similar issue in the damon_sysfs_targets_add_dirs() error path. Because the normal path issue and the error path issue are introduced by different commits, this commit is fixing only the normal path issue. A commit for the error path will be added next. Link: https://lore.kernel.org/20260628220121.97360-1-sj@kernel.org Link: https://lore.kernel.org/20260628220121.97360-2-sj@kernel.org Fixes: c951cd3b8901 ("mm/damon: implement a minimal stub for sysfs-based DAMON interface") Signed-off-by: SJ Park Cc: # 5.18.x Signed-off-by: Andrew Morton --- mm/damon/sysfs.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/mm/damon/sysfs.c b/mm/damon/sysfs.c index a9e187158067..38f3b02481f0 100644 --- a/mm/damon/sysfs.c +++ b/mm/damon/sysfs.c @@ -331,6 +331,7 @@ static void damon_sysfs_targets_rm_dirs(struct damon_sysfs_targets *targets) for (i = 0; i < targets->nr; i++) { damon_sysfs_target_rm_dirs(targets_arr[i]); + kobject_del(&targets_arr[i]->kobj); kobject_put(&targets_arr[i]->kobj); } targets->nr = 0; @@ -1640,6 +1641,7 @@ static void damon_sysfs_contexts_rm_dirs(struct damon_sysfs_contexts *contexts) for (i = 0; i < contexts->nr; i++) { damon_sysfs_context_rm_dirs(contexts_arr[i]); + kobject_del(&contexts_arr[i]->kobj); kobject_put(&contexts_arr[i]->kobj); } contexts->nr = 0; @@ -1678,13 +1680,15 @@ static int damon_sysfs_contexts_add_dirs(struct damon_sysfs_contexts *contexts, err = damon_sysfs_context_add_dirs(context); if (err) - goto out; + goto del_out; contexts_arr[i] = context; contexts->nr++; } return 0; +del_out: + kobject_del(&context->kobj); out: damon_sysfs_contexts_rm_dirs(contexts); kobject_put(&context->kobj); @@ -2499,6 +2503,7 @@ static void damon_sysfs_kdamonds_rm_dirs(struct damon_sysfs_kdamonds *kdamonds) for (i = 0; i < kdamonds->nr; i++) { damon_sysfs_kdamond_rm_dirs(kdamonds_arr[i]); + kobject_del(&kdamonds_arr[i]->kobj); kobject_put(&kdamonds_arr[i]->kobj); } kdamonds->nr = 0; @@ -2553,13 +2558,15 @@ static int damon_sysfs_kdamonds_add_dirs(struct damon_sysfs_kdamonds *kdamonds, err = damon_sysfs_kdamond_add_dirs(kdamond); if (err) - goto out; + goto del_out; kdamonds_arr[i] = kdamond; kdamonds->nr++; } return 0; +del_out: + kobject_del(&kdamond->kobj); out: damon_sysfs_kdamonds_rm_dirs(kdamonds); kobject_put(&kdamond->kobj); From 3772d3d9997001535e32f36a7f00ce5b7cfbc589 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Sun, 28 Jun 2026 15:01:11 -0700 Subject: [PATCH 237/562] mm/damon/sysfs: kobject_del() region and target (error) dirs On CONFIG_DEBUG_KOBJECT_RELEASE enabled kernel, lack of kobject_del() could cause directories creation failures due to the name conflicts. Fix those issues for the normal creation path of region directories and the error path of target directories, by adding kobject_del() calls. Link: https://lore.kernel.org/20260628220121.97360-3-sj@kernel.org Fixes: 2031b14ea757 ("mm/damon/sysfs: support the physical address space monitoring") Signed-off-by: SJ Park Cc: # 5.18.x Signed-off-by: Andrew Morton --- mm/damon/sysfs.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mm/damon/sysfs.c b/mm/damon/sysfs.c index 38f3b02481f0..204aed6a3e5d 100644 --- a/mm/damon/sysfs.c +++ b/mm/damon/sysfs.c @@ -105,8 +105,10 @@ static void damon_sysfs_regions_rm_dirs(struct damon_sysfs_regions *regions) struct damon_sysfs_region **regions_arr = regions->regions_arr; int i; - for (i = 0; i < regions->nr; i++) + for (i = 0; i < regions->nr; i++) { + kobject_del(®ions_arr[i]->kobj); kobject_put(®ions_arr[i]->kobj); + } regions->nr = 0; kfree(regions_arr); regions->regions_arr = NULL; @@ -370,13 +372,15 @@ static int damon_sysfs_targets_add_dirs(struct damon_sysfs_targets *targets, err = damon_sysfs_target_add_dirs(target); if (err) - goto out; + goto del_out; targets_arr[i] = target; targets->nr++; } return 0; +del_out: + kobject_del(&target->kobj); out: damon_sysfs_targets_rm_dirs(targets); kobject_put(&target->kobj); From 72a886c13b63d2f9a6bba556052965eb579e7e99 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Sun, 28 Jun 2026 15:01:12 -0700 Subject: [PATCH 238/562] mm/damon/sysfs-schemes: kobject_del() scheme dirs On CONFIG_DEBUG_KOBJECT_RELEASE enabled kernel, lack of kobject_del() could cause directories creation failures due to the name conflicts. Fix those issues for scheme directories by adding kobject_del() calls. Link: https://lore.kernel.org/20260628220121.97360-4-sj@kernel.org Fixes: 7e84b1f8212a ("mm/damon/sysfs: support DAMON-based Operation Schemes") Signed-off-by: SJ Park Cc: # 5.18.x Signed-off-by: Andrew Morton --- mm/damon/sysfs-schemes.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mm/damon/sysfs-schemes.c b/mm/damon/sysfs-schemes.c index 3cbeccd436e4..db496d2e493a 100644 --- a/mm/damon/sysfs-schemes.c +++ b/mm/damon/sysfs-schemes.c @@ -2681,6 +2681,7 @@ void damon_sysfs_schemes_rm_dirs(struct damon_sysfs_schemes *schemes) for (i = 0; i < schemes->nr; i++) { damon_sysfs_scheme_rm_dirs(schemes_arr[i]); + kobject_del(&schemes_arr[i]->kobj); kobject_put(&schemes_arr[i]->kobj); } schemes->nr = 0; @@ -2722,13 +2723,15 @@ static int damon_sysfs_schemes_add_dirs(struct damon_sysfs_schemes *schemes, goto out; err = damon_sysfs_scheme_add_dirs(scheme); if (err) - goto out; + goto del_out; schemes_arr[i] = scheme; schemes->nr++; } return 0; +del_out: + kobject_del(&scheme->kobj); out: damon_sysfs_schemes_rm_dirs(schemes); kobject_put(&scheme->kobj); From 655d9f79d822a8e2cb0115629e114383ac48f10a Mon Sep 17 00:00:00 2001 From: SJ Park Date: Sun, 28 Jun 2026 15:01:13 -0700 Subject: [PATCH 239/562] mm/damon/sysfs-schemes: kobject_del() scheme region dirs On CONFIG_DEBUG_KOBJECT_RELEASE enabled kernel, lack of kobject_del() could cause directories creation failures due to the name conflicts. Fix those issues for scheme region directories by adding kobject_del() calls. This issue was discovered [1] by Sashiko, though its analysis was partially incorrect. Link: https://lore.kernel.org/20260628220121.97360-5-sj@kernel.org Link: https://lore.kernel.org/20260517205828.6204-1-sj@kernel.org [1] Fixes: 9277d0367ba1 ("mm/damon/sysfs-schemes: implement scheme region directory") Signed-off-by: SJ Park Cc: # 6.2.x Signed-off-by: Andrew Morton --- mm/damon/sysfs-schemes.c | 1 + 1 file changed, 1 insertion(+) diff --git a/mm/damon/sysfs-schemes.c b/mm/damon/sysfs-schemes.c index db496d2e493a..9eb28fe77b5b 100644 --- a/mm/damon/sysfs-schemes.c +++ b/mm/damon/sysfs-schemes.c @@ -332,6 +332,7 @@ static void damon_sysfs_scheme_regions_rm_dirs( list_for_each_entry_safe(r, next, ®ions->regions_list, list) { damos_sysfs_region_rm_dirs(r); list_del(&r->list); + kobject_del(&r->kobj); kobject_put(&r->kobj); regions->nr_regions--; } From c724e75b48e34c3df570b458bbea8ac436901147 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Sun, 28 Jun 2026 15:01:14 -0700 Subject: [PATCH 240/562] mm/damon/sysfs-schemes: kobject_del() scheme filter dirs On CONFIG_DEBUG_KOBJECT_RELEASE enabled kernel, lack of kobject_del() could cause directories creation failures due to the name conflicts. Fix those issues for scheme filter directories by adding kobject_del() calls. Link: https://lore.kernel.org/20260628220121.97360-6-sj@kernel.org Fixes: 472e2b70eda6 ("mm/damon/sysfs-schemes: connect filter directory and filters directory") Signed-off-by: SJ Park Cc: # 6.3.x Signed-off-by: Andrew Morton --- mm/damon/sysfs-schemes.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mm/damon/sysfs-schemes.c b/mm/damon/sysfs-schemes.c index 9eb28fe77b5b..e955fb916a7e 100644 --- a/mm/damon/sysfs-schemes.c +++ b/mm/damon/sysfs-schemes.c @@ -912,8 +912,10 @@ static void damon_sysfs_scheme_filters_rm_dirs( struct damon_sysfs_scheme_filter **filters_arr = filters->filters_arr; int i; - for (i = 0; i < filters->nr; i++) + for (i = 0; i < filters->nr; i++) { + kobject_del(&filters_arr[i]->kobj); kobject_put(&filters_arr[i]->kobj); + } filters->nr = 0; kfree(filters_arr); filters->filters_arr = NULL; From 1a2006c2fe9f27b660054638ec6c73ace97e8ab8 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Sun, 28 Jun 2026 15:01:15 -0700 Subject: [PATCH 241/562] mm/damon/sysfs-schemes: kobject_del() scheme quota goal dirs On CONFIG_DEBUG_KOBJECT_RELEASE enabled kernel, lack of kobject_del() could cause directories creation failures due to the name conflicts. Fix those issues for scheme quota goal directories by adding kobject_del() calls. Link: https://lore.kernel.org/20260628220121.97360-7-sj@kernel.org Fixes: 7f262da0a30d ("mm/damon/sysfs-schemes: implement files for scheme quota goals setup") Signed-off-by: SJ Park Cc: # 6.8.x Signed-off-by: Andrew Morton --- mm/damon/sysfs-schemes.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mm/damon/sysfs-schemes.c b/mm/damon/sysfs-schemes.c index e955fb916a7e..58051185713c 100644 --- a/mm/damon/sysfs-schemes.c +++ b/mm/damon/sysfs-schemes.c @@ -1463,8 +1463,10 @@ static void damos_sysfs_quota_goals_rm_dirs( struct damos_sysfs_quota_goal **goals_arr = goals->goals_arr; int i; - for (i = 0; i < goals->nr; i++) + for (i = 0; i < goals->nr; i++) { + kobject_del(&goals_arr[i]->kobj); kobject_put(&goals_arr[i]->kobj); + } goals->nr = 0; kfree(goals_arr); goals->goals_arr = NULL; From c7da06eab49254d1cdf8773b041fdfd7274479ce Mon Sep 17 00:00:00 2001 From: SJ Park Date: Sun, 28 Jun 2026 15:01:16 -0700 Subject: [PATCH 242/562] mm/damon/sysfs-schemes: kobject_del() scheme action destination dirs On CONFIG_DEBUG_KOBJECT_RELEASE enabled kernel, lack of kobject_del() could cause directories creation failures due to the name conflicts. Fix those issues for scheme action destination directories by adding kobject_del() calls. Link: https://lore.kernel.org/20260628220121.97360-8-sj@kernel.org Fixes: 2cd0bf85a203 ("mm/damon/sysfs-schemes: implement DAMOS action destinations directory") Signed-off-by: SJ Park Cc: # 6.17.x Signed-off-by: Andrew Morton --- mm/damon/sysfs-schemes.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mm/damon/sysfs-schemes.c b/mm/damon/sysfs-schemes.c index 58051185713c..e2c8716be6c9 100644 --- a/mm/damon/sysfs-schemes.c +++ b/mm/damon/sysfs-schemes.c @@ -2143,8 +2143,10 @@ static void damos_sysfs_dests_rm_dirs( struct damos_sysfs_dest **dests_arr = dests->dests_arr; int i; - for (i = 0; i < dests->nr; i++) + for (i = 0; i < dests->nr; i++) { + kobject_del(&dests_arr[i]->kobj); kobject_put(&dests_arr[i]->kobj); + } dests->nr = 0; kfree(dests_arr); dests->dests_arr = NULL; From e9b8f7d19c2be545ab295514902e0f7155fe6ef4 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Sun, 28 Jun 2026 15:01:17 -0700 Subject: [PATCH 243/562] mm/damon/sysfs: kobject_del() probe dirs On CONFIG_DEBUG_KOBJECT_RELEASE enabled kernel, lack of kobject_del() could cause directories creation failures due to the name conflicts. Fix those issues for data attribute probe directories by adding kobject_del() calls. Link: https://lore.kernel.org/20260628220121.97360-9-sj@kernel.org Fixes: bf3ea3d30880 ("mm/damon/sysfs: implement probe dir") Signed-off-by: SJ Park Signed-off-by: Andrew Morton --- mm/damon/sysfs.c | 1 + 1 file changed, 1 insertion(+) diff --git a/mm/damon/sysfs.c b/mm/damon/sysfs.c index 204aed6a3e5d..9f92ebdb2857 100644 --- a/mm/damon/sysfs.c +++ b/mm/damon/sysfs.c @@ -1137,6 +1137,7 @@ static void damon_sysfs_probes_rm_dirs( for (i = 0; i < probes->nr; i++) { damon_sysfs_probe_rm_dirs(probes_arr[i]); + kobject_del(&probes_arr[i]->kobj); kobject_put(&probes_arr[i]->kobj); } probes->nr = 0; From 0fab9e125c9e5e546f57589681c7913abed1b069 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Sun, 28 Jun 2026 15:01:18 -0700 Subject: [PATCH 244/562] mm/damon/sysfs: kobject_del() probe filter dirs On CONFIG_DEBUG_KOBJECT_RELEASE enabled kernel, lack of kobject_del() could cause directories creation failures due to the name conflicts. Fix those issues for data attribute probe filter directories by adding kobject_del() calls. Link: https://lore.kernel.org/20260628220121.97360-10-sj@kernel.org Fixes: 82e66aef7714 ("mm/damon/sysfs: implement filter dir") Signed-off-by: SJ Park Signed-off-by: Andrew Morton --- mm/damon/sysfs.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mm/damon/sysfs.c b/mm/damon/sysfs.c index 9f92ebdb2857..0ccdc71275d5 100644 --- a/mm/damon/sysfs.c +++ b/mm/damon/sysfs.c @@ -960,8 +960,10 @@ static void damon_sysfs_filters_rm_dirs(struct damon_sysfs_filters *filters) struct damon_sysfs_filter **filters_arr = filters->filters_arr; int i; - for (i = 0; i < filters->nr; i++) + for (i = 0; i < filters->nr; i++) { + kobject_del(&filters_arr[i]->kobj); kobject_put(&filters_arr[i]->kobj); + } filters->nr = 0; kfree(filters_arr); filters->filters_arr = NULL; From b72fe39302f0f871b499baa77cdf7b06c0fa249d Mon Sep 17 00:00:00 2001 From: SJ Park Date: Sun, 28 Jun 2026 15:01:19 -0700 Subject: [PATCH 245/562] mm/damon/sysfs: kobject_del() probe dirs in probes_addd_dir error path On CONFIG_DEBUG_KOBJECT_RELEASE enabled kernel, lack of kobject_del() could cause directories creation failures due to the name conflicts. Fix the issue for data attribute probe filter directories in the error handling path of damon_sysfs_probes_add_dirs() by adding a kobject_del() call. Link: https://lore.kernel.org/20260628220121.97360-11-sj@kernel.org Fixes: af7cb41af9a9 ("mm/damon/sysfs: implement filters directory") Signed-off-by: SJ Park Signed-off-by: Andrew Morton --- mm/damon/sysfs.c | 1 + 1 file changed, 1 insertion(+) diff --git a/mm/damon/sysfs.c b/mm/damon/sysfs.c index 0ccdc71275d5..e3526a263e20 100644 --- a/mm/damon/sysfs.c +++ b/mm/damon/sysfs.c @@ -1181,6 +1181,7 @@ static int damon_sysfs_probes_add_dirs( err = damon_sysfs_probe_add_dirs(probe); if (err) { + kobject_del(&probe->kobj); kobject_put(&probe->kobj); damon_sysfs_probes_rm_dirs(probes); return err; From a8284526f10190403ba9107281c8344f7d91488e Mon Sep 17 00:00:00 2001 From: SJ Park Date: Sun, 28 Jun 2026 15:01:20 -0700 Subject: [PATCH 246/562] mm/damon/sysfs-schemes: kobject_del() region for populate_region error On CONFIG_DEBUG_KOBJECT_RELEASE enabled kernel, lack of kobject_del() could cause directories creation failures due to the name conflicts. Fix the issue for tried region directories in the error handling path of damon_sysfs_populate_region_dir() by adding a kobject_del() call. Link: https://lore.kernel.org/20260628220121.97360-12-sj@kernel.org Fixes: b574a82d10de ("mm/damon/sysfs-schemes: implement tried_regions//probes/") Signed-off-by: SJ Park Signed-off-by: Andrew Morton --- mm/damon/sysfs-schemes.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mm/damon/sysfs-schemes.c b/mm/damon/sysfs-schemes.c index e2c8716be6c9..41f93a1823bf 100644 --- a/mm/damon/sysfs-schemes.c +++ b/mm/damon/sysfs-schemes.c @@ -3132,12 +3132,14 @@ void damos_sysfs_populate_region_dir(struct damon_sysfs_schemes *sysfs_schemes, sysfs_regions->nr_regions)) goto out; if (damos_sysfs_region_add_dirs(region, ctx, r)) - goto out; + goto del_out; list_add_tail(®ion->list, &sysfs_regions->regions_list); sysfs_regions->nr_regions++; return; +del_out: + kobject_del(®ion->kobj); out: kobject_put(®ion->kobj); } From 87fdc31dccbc23a8ca8180f3dc8c341d77df870c Mon Sep 17 00:00:00 2001 From: "David Hildenbrand (Arm)" Date: Mon, 29 Jun 2026 15:49:47 +0200 Subject: [PATCH 247/562] sparc/mm: drop custom pte_clear_not_present_full() Patch series "mm: cleanup clear_not_present_full_ptes()", v2. While doing some review, I stumbled over clear_not_present_full_ptes() and concluded that it needs some love. Let's remove pte_clear_not_present_full() and cleanup clear_not_present_full_ptes(), renaming it to clear_non_present_ptes(). This patch (of 3): On sparc64, pte_clear_not_present_full() nowadays does a simple __set_pte_at(). In __set_pte_at() -> maybe_tlb_batch_add(), we check pte_accessible() to see whether to call tlb_batch_add(). However, non-present PTEs are surely not accessible, so tlb_batch_add() is never called and the "full" parameter is irrelevant. Let's drop the helper and just let common code do a pte_clear(). pte_clear() on sparc64 maps to set_pte_at()->set_ptes()->__set_pte_at() ... so it ends up calling the same function, just with "full=0". Given that "full" is irrelevant, there is no change. We added pte_clear_not_present_full() for sparc64 in commit 90f08e399d05 ("sparc: mmu_gather rework"), and I suspect that it was already not required back then. Link: https://lore.kernel.org/20260629-clear_not_present_full_ptes-v2-0-96089871a1e7@kernel.org Link: https://lore.kernel.org/20260629-clear_not_present_full_ptes-v2-1-96089871a1e7@kernel.org Signed-off-by: David Hildenbrand (Arm) Reviewed-by: Lance Yang Cc: Peter Zijlstra Cc: Andreas Larsson Cc: David S. Miller Cc: Jann Horn Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Oscar Salvador (SUSE) Signed-off-by: Andrew Morton --- arch/sparc/include/asm/pgtable_64.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/arch/sparc/include/asm/pgtable_64.h b/arch/sparc/include/asm/pgtable_64.h index 74ede706fb32..0837ebbc5dce 100644 --- a/arch/sparc/include/asm/pgtable_64.h +++ b/arch/sparc/include/asm/pgtable_64.h @@ -945,10 +945,6 @@ static inline void set_ptes(struct mm_struct *mm, unsigned long addr, #define pte_clear(mm,addr,ptep) \ set_pte_at((mm), (addr), (ptep), __pte(0UL)) -#define __HAVE_ARCH_PTE_CLEAR_NOT_PRESENT_FULL -#define pte_clear_not_present_full(mm,addr,ptep,fullmm) \ - __set_pte_at((mm), (addr), (ptep), __pte(0UL), (fullmm)) - #ifdef DCACHE_ALIASING_POSSIBLE #define __HAVE_ARCH_MOVE_PTE #define move_pte(pte, old_addr, new_addr) \ From 0b8781e500ac7d79411171003600cd85542d9a1d Mon Sep 17 00:00:00 2001 From: "David Hildenbrand (Arm)" Date: Mon, 29 Jun 2026 15:49:48 +0200 Subject: [PATCH 248/562] mm: drop pte_clear_not_present_full() In general, there is no good reason to do anything special when clearing non-present PTEs. In theory, HW that does have to invalidate TLBs for non-present PTEs could benefit from a "full" parameter, but fortunately pte_clear_not_present_full() is not wired up anymore ... and there would have to be something very convincing for us to care about that to re-add it. So, let's just use pte_clear() directly now. To prevent the compiler complaining on some configs about "set but not used" addr parameter, silence that here. Link: https://lore.kernel.org/20260629-clear_not_present_full_ptes-v2-2-96089871a1e7@kernel.org Signed-off-by: David Hildenbrand (Arm) Reviewed-by: Oscar Salvador (SUSE) Reviewed-by: Lance Yang Cc: Andreas Larsson Cc: David S. Miller Cc: Jann Horn Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Peter Zijlstra Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- include/linux/pgtable.h | 21 ++++----------------- mm/madvise.c | 4 ++-- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/include/linux/pgtable.h b/include/linux/pgtable.h index dc804296d78f..0b81e396816a 100644 --- a/include/linux/pgtable.h +++ b/include/linux/pgtable.h @@ -988,21 +988,6 @@ static inline void update_mmu_tlb(struct vm_area_struct *vma, update_mmu_tlb_range(vma, address, ptep, 1); } -/* - * Some architectures may be able to avoid expensive synchronization - * primitives when modifications are made to PTE's which are already - * not present, or in the process of an address space destruction. - */ -#ifndef __HAVE_ARCH_PTE_CLEAR_NOT_PRESENT_FULL -static inline void pte_clear_not_present_full(struct mm_struct *mm, - unsigned long address, - pte_t *ptep, - int full) -{ - pte_clear(mm, address, ptep); -} -#endif - #ifndef clear_not_present_full_ptes /** * clear_not_present_full_ptes - Clear multiple not present PTEs which are @@ -1014,7 +999,7 @@ static inline void pte_clear_not_present_full(struct mm_struct *mm, * @full: Whether we are clearing a full mm. * * May be overridden by the architecture; otherwise, implemented as a simple - * loop over pte_clear_not_present_full(). + * loop over pte_clear(). * * Context: The caller holds the page table lock. The PTEs are all not present. * The PTEs are all in the same PMD. @@ -1022,8 +1007,10 @@ static inline void pte_clear_not_present_full(struct mm_struct *mm, static inline void clear_not_present_full_ptes(struct mm_struct *mm, unsigned long addr, pte_t *ptep, unsigned int nr, int full) { + (void)addr; + for (;;) { - pte_clear_not_present_full(mm, addr, ptep, full); + pte_clear(mm, addr, ptep); if (--nr == 0) break; ptep++; diff --git a/mm/madvise.c b/mm/madvise.c index 77552b03d318..e483b63bdcef 100644 --- a/mm/madvise.c +++ b/mm/madvise.c @@ -697,7 +697,7 @@ static int madvise_free_pte_range(pmd_t *pmd, unsigned long addr, clear_not_present_full_ptes(mm, addr, pte, nr, tlb->fullmm); } else if (softleaf_is_hwpoison(entry) || softleaf_is_poison_marker(entry)) { - pte_clear_not_present_full(mm, addr, pte, tlb->fullmm); + pte_clear(mm, addr, pte); } continue; } @@ -1233,7 +1233,7 @@ static int guard_remove_pte_entry(pte_t *pte, unsigned long addr, if (is_guard_pte_marker(ptent)) { /* Simply clear the PTE marker. */ - pte_clear_not_present_full(walk->mm, addr, pte, false); + pte_clear(walk->mm, addr, pte); update_mmu_cache(walk->vma, addr, pte); } From 56a6b97634fca6c9e7023908c8cfd35db78e145a Mon Sep 17 00:00:00 2001 From: "David Hildenbrand (Arm)" Date: Mon, 29 Jun 2026 15:49:49 +0200 Subject: [PATCH 249/562] mm: cleanup clear_not_present_full_ptes() and rename to clear_non_present_ptes() Let's clean it up a bit: (1) There is no need to pass "full" anymore. (2) No architecture overwrites it, and there isn't really a good reason to do so when dealing with non-present PTEs. (3) While at it, call it "non-present", similar to copy_nonpresent_pte() and zap_nonpresent_ptes(). It's a shame that we have clear_non_present_ptes() correspond to pte_clear() and clear_ptes() correspond to ptep_get_and_clear*(). Link: https://lore.kernel.org/20260629-clear_not_present_full_ptes-v2-3-96089871a1e7@kernel.org Signed-off-by: David Hildenbrand (Arm) Reviewed-by: Oscar Salvador (SUSE) Reviewed-by: Lance Yang Cc: Andreas Larsson Cc: David S. Miller Cc: Jann Horn Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Peter Zijlstra Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- include/linux/pgtable.h | 14 ++++---------- mm/madvise.c | 2 +- mm/memory.c | 2 +- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/include/linux/pgtable.h b/include/linux/pgtable.h index 0b81e396816a..d52d2a976e5a 100644 --- a/include/linux/pgtable.h +++ b/include/linux/pgtable.h @@ -988,24 +988,19 @@ static inline void update_mmu_tlb(struct vm_area_struct *vma, update_mmu_tlb_range(vma, address, ptep, 1); } -#ifndef clear_not_present_full_ptes /** - * clear_not_present_full_ptes - Clear multiple not present PTEs which are - * consecutive in the pgtable. + * clear_nonpresent_ptes - Clear multiple non-present PTEs which are + * consecutive in the pgtable. * @mm: Address space the ptes represent. * @addr: Address of the first pte. * @ptep: Page table pointer for the first entry. * @nr: Number of entries to clear. - * @full: Whether we are clearing a full mm. - * - * May be overridden by the architecture; otherwise, implemented as a simple - * loop over pte_clear(). * * Context: The caller holds the page table lock. The PTEs are all not present. * The PTEs are all in the same PMD. */ -static inline void clear_not_present_full_ptes(struct mm_struct *mm, - unsigned long addr, pte_t *ptep, unsigned int nr, int full) +static inline void clear_nonpresent_ptes(struct mm_struct *mm, + unsigned long addr, pte_t *ptep, unsigned int nr) { (void)addr; @@ -1017,7 +1012,6 @@ static inline void clear_not_present_full_ptes(struct mm_struct *mm, addr += PAGE_SIZE; } } -#endif #ifndef __HAVE_ARCH_PTEP_CLEAR_FLUSH extern pte_t ptep_clear_flush(struct vm_area_struct *vma, diff --git a/mm/madvise.c b/mm/madvise.c index e483b63bdcef..9292f60b19aa 100644 --- a/mm/madvise.c +++ b/mm/madvise.c @@ -694,7 +694,7 @@ static int madvise_free_pte_range(pmd_t *pmd, unsigned long addr, nr = swap_pte_batch(pte, max_nr, ptent); nr_swap -= nr; swap_put_entries_direct(entry, nr); - clear_not_present_full_ptes(mm, addr, pte, nr, tlb->fullmm); + clear_nonpresent_ptes(mm, addr, pte, nr); } else if (softleaf_is_hwpoison(entry) || softleaf_is_poison_marker(entry)) { pte_clear(mm, addr, pte); diff --git a/mm/memory.c b/mm/memory.c index ff338c2abe92..a3fcaf8cfe8f 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -1797,7 +1797,7 @@ static inline int zap_nonpresent_ptes(struct mmu_gather *tlb, pr_alert("unrecognized swap entry 0x%lx\n", entry.val); WARN_ON_ONCE(1); } - clear_not_present_full_ptes(vma->vm_mm, addr, pte, nr, tlb->fullmm); + clear_nonpresent_ptes(vma->vm_mm, addr, pte, nr); *any_skipped = zap_install_uffd_wp_if_needed(vma, addr, pte, nr, details, ptent); return nr; From bde792ec4e68c1a173a5be31357b4a2dfada9f44 Mon Sep 17 00:00:00 2001 From: Kunwu Chan Date: Mon, 29 Jun 2026 07:46:44 -0700 Subject: [PATCH 250/562] selftests/damon: prevent cross-context state pollution in DamonCtx Patch series "selftests/damon: misc fixes for test bugs", v3. This series fixes several bugs in the DAMON selftests. Most are trivial but makes test output wrong or even silently pass the one test case for 'avail_operation' file existence check. Patch 1 fixes mutable default arguments in DamonCtx.__init__() that cause state to leak between test instances. Patch 2 fixes wrong operator precedence and join TypeError in damos_tried_regions.py. Patch 3 fixes several wrong strings that produce dead elif branches, skipped file existence checks, and broken dict key lookups. This patch (of 3): DamonCtx.__init__() uses mutable default values for monitoring_attrs, targets, and schemes. In Python these are evaluated once at function definition time, so multiple DamonCtx instances can unintentionally share the same lists and DamonAttrs instance. Replace the mutable defaults with None sentinels and initialize the objects when needed. Link: https://lore.kernel.org/20260629144648.134092-1-sj@kernel.org Link: https://lore.kernel.org/20260601032314.424013-2-kunwu.chan@linux.dev Link: https://lore.kernel.org/20260629144648.134092-2-sj@kernel.org Co-developed-by: Wang Lian Signed-off-by: Wang Lian Signed-off-by: Kunwu Chan Signed-off-by: SJ Park Reviewed-by: SJ Park Cc: Kunwu Chan Cc: Wang Lian Signed-off-by: Andrew Morton --- tools/testing/selftests/damon/_damon_sysfs.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/damon/_damon_sysfs.py b/tools/testing/selftests/damon/_damon_sysfs.py index 8b12cc048440..2f6f2699db25 100644 --- a/tools/testing/selftests/damon/_damon_sysfs.py +++ b/tools/testing/selftests/damon/_damon_sysfs.py @@ -624,17 +624,23 @@ class DamonCtx: pause = None idx = None - def __init__(self, ops='paddr', monitoring_attrs=DamonAttrs(), targets=[], - schemes=[], pause=False): + def __init__(self, ops='paddr', monitoring_attrs=None, targets=None, + schemes=None, pause=False): self.ops = ops + if monitoring_attrs is None: + monitoring_attrs = DamonAttrs() self.monitoring_attrs = monitoring_attrs self.monitoring_attrs.context = self + if targets is None: + targets = [] self.targets = targets for idx, target in enumerate(self.targets): target.idx = idx target.context = self + if schemes is None: + schemes = [] self.schemes = schemes for idx, scheme in enumerate(self.schemes): scheme.idx = idx From 1562ab3b1de10418edaf970ade3f210b2f596d1d Mon Sep 17 00:00:00 2001 From: Kunwu Chan Date: Mon, 29 Jun 2026 07:46:45 -0700 Subject: [PATCH 251/562] selftests/damon/damos_tried_regions: fix expectation output and join TypeError The expectation print has wrong operator precedence: '%' binds before the conditional expression, so the else branch prints 'not met' without the prefix 'expectation (>= 14) is'. Add parentheses to fix it. Also, '\n'.join() on the list of ints raises TypeError; convert to str in the list comprehension. Link: https://lore.kernel.org/20260601032314.424013-3-kunwu.chan@linux.dev Link: https://lore.kernel.org/20260629144648.134092-3-sj@kernel.org Co-developed-by: Wang Lian Signed-off-by: Wang Lian Signed-off-by: Kunwu Chan Signed-off-by: SJ Park Reviewed-by: SJ Park Cc: Kunwu Chan Cc: Wang Lian Signed-off-by: Andrew Morton --- tools/testing/selftests/damon/damos_tried_regions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/damon/damos_tried_regions.py b/tools/testing/selftests/damon/damos_tried_regions.py index 3b347eb28bd2..d6472e6a6e08 100755 --- a/tools/testing/selftests/damon/damos_tried_regions.py +++ b/tools/testing/selftests/damon/damos_tried_regions.py @@ -55,10 +55,10 @@ def main(): collected_nr_regions.sort() sample = collected_nr_regions[4] print('50-th percentile nr_regions: %d' % sample) - print('expectation (>= 14) is %s' % 'met' if sample >= 14 else 'not met') + print('expectation (>= 14) is %s' % ('met' if sample >= 14 else 'not met')) if collected_nr_regions[4] < 14: print('full nr_regions:') - print('\n'.join(collected_nr_regions)) + print('\n'.join(['%d' % x for x in collected_nr_regions])) exit(1) if __name__ == '__main__': From 5c08de39699c19fc40f6d4bbd2fcad07d6344591 Mon Sep 17 00:00:00 2001 From: Kunwu Chan Date: Mon, 29 Jun 2026 07:46:46 -0700 Subject: [PATCH 252/562] selftests/damon: fix dead code, skipped checks, and broken lookups 'hugeapge_size' in drgn_dump_damon_status.py was a dead elif branch. $fail_reason in sysfs.sh was undefined, silently emptying the error message. 'exit' instead of 'exist' in sysfs.sh skipped a file existence check. 'nohugeapge' in sysfs.py broke an action dict lookup. Fix other wrong strings in the same files. Link: https://lore.kernel.org/20260601032314.424013-4-kunwu.chan@linux.dev Link: https://lore.kernel.org/20260629144648.134092-4-sj@kernel.org Co-developed-by: Wang Lian Signed-off-by: Wang Lian Signed-off-by: Kunwu Chan Signed-off-by: SJ Park Reviewed-by: SJ Park Cc: Kunwu Chan Cc: Wang Lian Signed-off-by: Andrew Morton --- tools/testing/selftests/damon/_damon_sysfs.py | 2 +- tools/testing/selftests/damon/damos_apply_interval.py | 2 +- tools/testing/selftests/damon/damos_quota_goal.py | 2 +- tools/testing/selftests/damon/drgn_dump_damon_status.py | 2 +- tools/testing/selftests/damon/sysfs.py | 4 ++-- tools/testing/selftests/damon/sysfs.sh | 6 +++--- .../sysfs_update_schemes_tried_regions_wss_estimation.py | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tools/testing/selftests/damon/_damon_sysfs.py b/tools/testing/selftests/damon/_damon_sysfs.py index 2f6f2699db25..f6127081dfb2 100644 --- a/tools/testing/selftests/damon/_damon_sysfs.py +++ b/tools/testing/selftests/damon/_damon_sysfs.py @@ -837,7 +837,7 @@ def commit_schemes_quota_goals(self): for goal in scheme.quota.goals: err = goal.stage() if err is not None: - print('commit_schemes_quota_goals failed stagign: %s'% + print('commit_schemes_quota_goals failed staging: %s'% err) exit(1) return write_file(os.path.join(self.sysfs_dir(), 'state'), diff --git a/tools/testing/selftests/damon/damos_apply_interval.py b/tools/testing/selftests/damon/damos_apply_interval.py index f04d43702481..0f2f36584e48 100755 --- a/tools/testing/selftests/damon/damos_apply_interval.py +++ b/tools/testing/selftests/damon/damos_apply_interval.py @@ -56,7 +56,7 @@ def main(): # Because the second scheme was having the apply interval that is ten times # lower than that of the first scheme, the second scheme should be tried # about ten times more frequently than the first scheme. For possible - # timing errors, check if it was at least nine times more freuqnetly tried. + # timing errors, check if it was at least nine times more frequently tried. ratio = nr_tried_stats[1] / nr_tried_stats[0] if ratio < 9: print('%d / %d = %f (< 9)' % diff --git a/tools/testing/selftests/damon/damos_quota_goal.py b/tools/testing/selftests/damon/damos_quota_goal.py index f76e0412b564..661e4ba4765a 100755 --- a/tools/testing/selftests/damon/damos_quota_goal.py +++ b/tools/testing/selftests/damon/damos_quota_goal.py @@ -66,7 +66,7 @@ def main(): # effective quota was already minimum that cannot be more reduced if expect_increase is False and last_effective_bytes == 1: continue - print('efective bytes not changed: %d' % goal.effective_bytes) + print('effective bytes not changed: %d' % goal.effective_bytes) exit(1) increased = last_effective_bytes < goal.effective_bytes diff --git a/tools/testing/selftests/damon/drgn_dump_damon_status.py b/tools/testing/selftests/damon/drgn_dump_damon_status.py index 972948e6215f..26b207e44268 100755 --- a/tools/testing/selftests/damon/drgn_dump_damon_status.py +++ b/tools/testing/selftests/damon/drgn_dump_damon_status.py @@ -163,7 +163,7 @@ def damos_filter_to_dict(damos_filter): int(damos_filter.addr_range.end)] elif type_ == 'target': dict_['target_idx'] = int(damos_filter.target_idx) - elif type_ == 'hugeapge_size': + elif type_ == 'hugepage_size': dict_['sz_range'] = [int(damos_filter.sz_range.min), int(damos_filter.sz_range.max)] return dict_ diff --git a/tools/testing/selftests/damon/sysfs.py b/tools/testing/selftests/damon/sysfs.py index aa03a1187489..99412f0d31f3 100755 --- a/tools/testing/selftests/damon/sysfs.py +++ b/tools/testing/selftests/damon/sysfs.py @@ -119,7 +119,7 @@ def assert_access_pattern_committed(pattern, dump): 'max_nr_accesses', dump) assert_true(dump['min_age_region'] == pattern.age[0], 'min_age_region', dump) - assert_true(dump['max_age_region'] == pattern.age[1], 'miaxage_region', + assert_true(dump['max_age_region'] == pattern.age[1], 'max_age_region', dump) def assert_scheme_committed(scheme, dump): @@ -129,7 +129,7 @@ def assert_scheme_committed(scheme, dump): 'cold': 1, 'pageout': 2, 'hugepage': 3, - 'nohugeapge': 4, + 'nohugepage': 4, 'collapse': 5, 'lru_prio': 6, 'lru_deprio': 7, diff --git a/tools/testing/selftests/damon/sysfs.sh b/tools/testing/selftests/damon/sysfs.sh index 78f4badb5beb..2eaaa5ae3c5e 100755 --- a/tools/testing/selftests/damon/sysfs.sh +++ b/tools/testing/selftests/damon/sysfs.sh @@ -3,7 +3,7 @@ source _common.sh -# Kselftest frmework requirement - SKIP code is 4. +# Kselftest framework requirement - SKIP code is 4. ksft_skip=4 ensure_write_succ() @@ -28,7 +28,7 @@ ensure_write_fail() if (echo "$content" > "$file") 2> /dev/null then - echo "writing $content to $file succeed ($fail_reason)" + echo "writing $content to $file succeeded ($reason)" echo "expected failure because $reason" exit 1 fi @@ -363,7 +363,7 @@ test_context() { context_dir=$1 ensure_dir "$context_dir" "exist" - ensure_file "$context_dir/avail_operations" "exit" 400 + ensure_file "$context_dir/avail_operations" "exist" 400 ensure_file "$context_dir/operations" "exist" 600 ensure_file "$context_dir/addr_unit" "exist" 600 ensure_file "$context_dir/pause" "exist" 600 diff --git a/tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_wss_estimation.py b/tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_wss_estimation.py index 35c724a63f6c..16fdc6e7fc56 100755 --- a/tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_wss_estimation.py +++ b/tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_wss_estimation.py @@ -7,7 +7,7 @@ import _damon_sysfs def pass_wss_estimation(sz_region): - # access two regions of given size, 2 seocnds per each region + # access two regions of given size, 2 seconds per each region proc = subprocess.Popen( ['./access_memory', '2', '%d' % sz_region, '2000', 'repeat']) kdamonds = _damon_sysfs.Kdamonds([_damon_sysfs.Kdamond( From 58a000394d57071e7fadb70fca45dd1399cd691c Mon Sep 17 00:00:00 2001 From: Cheng Nie Date: Mon, 29 Jun 2026 07:48:10 -0700 Subject: [PATCH 253/562] selftests/damon/_damon_sysfs.py: fix memcg_path assignment Patch series "selftests/damon: fix memcg_path staging handling", v5. Fix a bug in _damon_sysfs.py for damos_filter memcg_path setup, and add a test case of it in sysfs.py. This patch (of 2): DamosFilter stores memcg_path for sysfs staging, but the constructor assigns it with a trailing comma and therefore turns it into a tuple. Fix the assignment so memcg_path is stored as the intended string. This makes memcg filter staging and follow-up validation use the written path correctly. Link: https://lore.kernel.org/20260629144812.134159-1-sj@kernel.org Link: https://lore.kernel.org/464AE12D4BC6B6F4+20260601090519.240482-1-niecheng1@uniontech.com Link: https://lore.kernel.org/20260629144812.134159-2-sj@kernel.org Signed-off-by: Cheng Nie Signed-off-by: SJ Park Reviewed-by: SJ Park Signed-off-by: Andrew Morton --- tools/testing/selftests/damon/_damon_sysfs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/damon/_damon_sysfs.py b/tools/testing/selftests/damon/_damon_sysfs.py index f6127081dfb2..c197ab99bcc0 100644 --- a/tools/testing/selftests/damon/_damon_sysfs.py +++ b/tools/testing/selftests/damon/_damon_sysfs.py @@ -271,7 +271,7 @@ def __init__(self, type_='anon', matching=False, allow=False, self.type_ = type_ self.matching = matching self.allow = allow - self.memcg_path = memcg_path, + self.memcg_path = memcg_path self.addr_start = addr_start self.addr_end = addr_end self.target_idx = target_idx From b887f57e8b426504899ade87d16dc01b6167e9e0 Mon Sep 17 00:00:00 2001 From: Cheng Nie Date: Mon, 29 Jun 2026 07:48:11 -0700 Subject: [PATCH 254/562] selftests/damon/sysfs.py: validate memcg_path staging readback Add a dedicated test at the end of main() that stages memcg_path via sysfs and verifies its readback. Configure the memcg filter before start(), do not call commit(), and ignore start() failures so the test does not depend on CONFIG_MEMCG or cgroup layout. Call stop() for cleanup without checking its return value. Link: https://lore.kernel.org/D2B37130D38E09AC+20260601090634.241864-1-niecheng1@uniontech.com Link: https://lore.kernel.org/20260629144812.134159-3-sj@kernel.org Signed-off-by: Cheng Nie Signed-off-by: SJ Park Reviewed-by: SJ Park Signed-off-by: Andrew Morton --- tools/testing/selftests/damon/sysfs.py | 31 ++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tools/testing/selftests/damon/sysfs.py b/tools/testing/selftests/damon/sysfs.py index 99412f0d31f3..3ffa054b6386 100755 --- a/tools/testing/selftests/damon/sysfs.py +++ b/tools/testing/selftests/damon/sysfs.py @@ -250,6 +250,35 @@ def assert_ctxs_committed(kdamonds): if ctx in ctxs_paused_for_dump: ctx.pause = False +def test_memcg_filter_memcg_path_staging(): + global kdamonds + memcg_filter = _damon_sysfs.DamosFilter( + type_='memcg', matching=True, allow=True, memcg_path='/') + kdamonds = _damon_sysfs.Kdamonds( + [_damon_sysfs.Kdamond( + contexts=[_damon_sysfs.DamonCtx( + targets=[_damon_sysfs.DamonTarget(pid=-1)], + schemes=[_damon_sysfs.Damos( + ops_filters=[memcg_filter])], + )])]) + kdamonds.start() + + shown, rd_err = _damon_sysfs.read_file( + os.path.join(memcg_filter.sysfs_dir(), 'memcg_path')) + if rd_err is not None: + print('memcg_path staging: sysfs read (%s)' % rd_err) + kdamonds.stop() + exit(1) + if shown.rstrip('\n') != memcg_filter.memcg_path: + print('memcg_path staging: memcg_path readback ' + '(shown=%s, expected=%s)' % + (shown.rstrip('\n'), memcg_filter.memcg_path)) + kdamonds.stop() + exit(1) + + kdamonds.stop() + kdamonds = None + def main(): global kdamonds kdamonds = _damon_sysfs.Kdamonds( @@ -356,5 +385,7 @@ def main(): assert_ctxs_committed(kdamonds) kdamonds.stop() + test_memcg_filter_memcg_path_staging() + if __name__ == '__main__': main() From 6cef5bdcd9888bcfc7cffcc7c8f0e0223ae49975 Mon Sep 17 00:00:00 2001 From: Ruslan Valiyev Date: Mon, 29 Jun 2026 07:49:25 -0700 Subject: [PATCH 255/562] selftests/damon/_damon_sysfs: support kdamond refresh_ms Patch series "selftests/damon: test kdamond refresh_ms", v2. The kdamond 'refresh_ms' sysfs file makes DAMON periodically update its read-only sysfs files (DAMOS stats, tuned monitoring intervals and the kdamond pid) on its own, so users don't have to write update keywords such as 'update_schemes_stats' to the 'state' file. It has no selftest coverage. The first patch adds refresh_ms support to the _damon_sysfs.py test control module. The second adds a test that sets refresh_ms and confirms a scheme's stats are updated under sysfs without an explicit update request; the test skips on kernels that predate the refresh_ms file. Tested on current mainline under a DAMON-enabled kernel: the new test passes and the existing DAMON selftests show no new failures. This patch (of 2): The Kdamond class has no way to set the kdamond-level 'refresh_ms' sysfs file, which makes DAMON periodically update the read-only sysfs files (DAMOS stats, tuned monitoring intervals and the kdamond pid) on its own. Add a 'refresh_ms' parameter to Kdamond. When it is set (including to zero, to disable the periodic update), write it before turning the kdamond on, so tests can exercise the auto-update behavior. Leaving it unset keeps the previous behavior of not touching the file, so callers running against kernels without the feature are unaffected. Link: https://lore.kernel.org/20260602131217.2210912-2-linuxoid@gmail.com Link: https://lore.kernel.org/20260629144927.134237-2-sj@kernel.org Signed-off-by: Ruslan Valiyev Reviewed-by: SJ Park Signed-off-by: SJ Park Cc: Shuah Khan Signed-off-by: Andrew Morton --- tools/testing/selftests/damon/_damon_sysfs.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/damon/_damon_sysfs.py b/tools/testing/selftests/damon/_damon_sysfs.py index c197ab99bcc0..e6a2265d721e 100644 --- a/tools/testing/selftests/damon/_damon_sysfs.py +++ b/tools/testing/selftests/damon/_damon_sysfs.py @@ -698,12 +698,14 @@ def stage(self): class Kdamond: state = None pid = None + refresh_ms = None contexts = None idx = None # index of this kdamond between siblings kdamonds = None # parent - def __init__(self, contexts=[]): + def __init__(self, contexts=[], refresh_ms=None): self.contexts = contexts + self.refresh_ms = refresh_ms for idx, context in enumerate(self.contexts): context.idx = idx context.kdamond = self @@ -726,6 +728,11 @@ def start(self): err = context.stage() if err is not None: return err + if self.refresh_ms is not None: + err = write_file(os.path.join(self.sysfs_dir(), 'refresh_ms'), + '%d' % self.refresh_ms) + if err is not None: + return err err = write_file(os.path.join(self.sysfs_dir(), 'state'), 'on') if err is not None: return err From 5275220c02aac6bb3eff78e05f5470eb6374b738 Mon Sep 17 00:00:00 2001 From: Ruslan Valiyev Date: Mon, 29 Jun 2026 07:49:26 -0700 Subject: [PATCH 256/562] selftests/damon/sysfs_refresh: test kdamond refresh_ms Writing a non-zero value to a kdamond's 'refresh_ms' sysfs file should make DAMON periodically update the read-only sysfs files on its own, without the user writing update keywords such as 'update_schemes_stats' to the 'state' file. This behavior has no test coverage. Add a test that starts a kdamond with refresh_ms set and a 'stat' scheme whose default access pattern matches every monitored region, then polls the scheme's 'nr_tried' stats file directly, without requesting an update. The value can become non-zero only via the periodic refresh, so the test confirms refresh_ms works; with refresh_ms disabled the stat stays zero and the test fails. Link: https://lore.kernel.org/20260602131217.2210912-3-linuxoid@gmail.com Link: https://lore.kernel.org/20260629144927.134237-3-sj@kernel.org Signed-off-by: Ruslan Valiyev Reviewed-by: SJ Park Signed-off-by: SJ Park Cc: Shuah Khan Signed-off-by: Andrew Morton --- tools/testing/selftests/damon/Makefile | 1 + .../testing/selftests/damon/sysfs_refresh.py | 75 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100755 tools/testing/selftests/damon/sysfs_refresh.py diff --git a/tools/testing/selftests/damon/Makefile b/tools/testing/selftests/damon/Makefile index 2180c328a825..ece244e5c5b9 100644 --- a/tools/testing/selftests/damon/Makefile +++ b/tools/testing/selftests/damon/Makefile @@ -13,6 +13,7 @@ TEST_PROGS += sysfs.py TEST_PROGS += sysfs_update_schemes_tried_regions_wss_estimation.py TEST_PROGS += damos_quota.py damos_quota_goal.py damos_apply_interval.py TEST_PROGS += damos_tried_regions.py damon_nr_regions.py +TEST_PROGS += sysfs_refresh.py TEST_PROGS += reclaim.sh lru_sort.sh # regression tests (reproducers of previously found bugs) diff --git a/tools/testing/selftests/damon/sysfs_refresh.py b/tools/testing/selftests/damon/sysfs_refresh.py new file mode 100755 index 000000000000..012b7e8f509f --- /dev/null +++ b/tools/testing/selftests/damon/sysfs_refresh.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 + +import os +import subprocess +import time + +import _damon_sysfs + +def main(): + # Continuously access a memory region for far longer than the test needs, + # so the kdamond always has a live target to monitor while we poll. + sz_region = 10 * 1024 * 1024 + proc = subprocess.Popen( + ['./access_memory', '1', '%d' % sz_region, '60000', 'repeat']) + + # A 'stat' scheme with the default (maximally wide) access pattern matches + # every monitored region, so its 'nr_tried' stat increases as the kdamond + # runs. refresh_ms should make DAMON update the schemes' stats files under + # sysfs on its own, without a manual 'update_schemes_stats' request. + kdamond = _damon_sysfs.Kdamond( + refresh_ms=100, + contexts=[_damon_sysfs.DamonCtx( + ops='vaddr', + targets=[_damon_sysfs.DamonTarget(pid=proc.pid)], + schemes=[_damon_sysfs.Damos(action='stat')], + )]) + kdamonds = _damon_sysfs.Kdamonds([kdamond]) + + err = kdamonds.start() + if err is not None: + # Kernels older than the refresh_ms feature have no such file; treat + # that as unsupported rather than a failure. + if not os.path.exists(os.path.join(kdamond.sysfs_dir(), 'refresh_ms')): + proc.terminate() + proc.wait() + print('kdamond has no refresh_ms file; skipping') + exit(_damon_sysfs.ksft_skip) + proc.terminate() + proc.wait() + print('kdamond start failed: %s' % err) + exit(1) + + scheme = kdamond.contexts[0].schemes[0] + nr_tried_path = os.path.join(scheme.sysfs_dir(), 'stats', 'nr_tried') + + try: + # Poll the stat file directly. We never request an update (e.g. + # 'update_schemes_stats'), so 'nr_tried' can become non-zero only + # through the periodic refresh that refresh_ms enables. + nr_tried = 0 + deadline = time.monotonic() + 10 + while time.monotonic() < deadline: + if proc.poll() is not None: + print('the access_memory target exited unexpectedly') + exit(1) + content, err = _damon_sysfs.read_file(nr_tried_path) + if err is not None: + print('reading %s failed: %s' % (nr_tried_path, err)) + exit(1) + nr_tried = int(content) + if nr_tried > 0: + break + time.sleep(0.1) + finally: + kdamonds.stop() + proc.terminate() + proc.wait() + + if nr_tried == 0: + print('refresh_ms did not auto-update the schemes stats') + exit(1) + +if __name__ == '__main__': + main() From 69703f5ff677445ca865709a91370b0499562b14 Mon Sep 17 00:00:00 2001 From: Akinobu Mita Date: Mon, 29 Jun 2026 07:55:32 -0700 Subject: [PATCH 257/562] mm/damon/core: use kvmalloc for target regions array Patch series "mm/damon: five misc fixups" Five patches for miscellaneous DAMON fixups. Use better fit kernel functions, cleanup/fixup documents, and add unit tests. The five patches were initially sent and revisioned by different individuals. Each patch contains changelog on their commentary area. The patches are curated into this series by SJ, for the convenience in reposting. This patch (of 5): damon_commit_target_regions() temporarily allocates a single contiguous memory region using kmalloc to store copies of all damon_regions of the damon_target. However, if the damon_target has a large number of damon_regions, the total size may exceed KMALLOC_MAX_SIZE. This problem can be avoided by using kvmalloc instead of kmalloc. Link: https://lore.kernel.org/20260629145538.134832-1-sj@kernel.org Link: https://lore.kernel.org/20260629145538.134832-2-sj@kernel.org Signed-off-by: Akinobu Mita Signed-off-by: SJ Park Reviewed-by: SJ Park Cc: Brendan Higgins Cc: David Hildenbrand Cc: Jonathan Corbet Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Asier Gutierrez Cc: Doehyun Baek Cc: Philippe Laferriere Cc: Sailesh Nandanavanam Cc: Shuah Khan Signed-off-by: Andrew Morton --- mm/damon/core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mm/damon/core.c b/mm/damon/core.c index eab553fcb0b5..957cd8067bc0 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -1369,14 +1369,14 @@ static int damon_commit_target_regions(struct damon_target *dst, if (!i) return 0; - ranges = kmalloc_objs(*ranges, i, GFP_KERNEL | __GFP_NOWARN); + ranges = kvmalloc_objs(*ranges, i, GFP_KERNEL | __GFP_NOWARN); if (!ranges) return -ENOMEM; i = 0; damon_for_each_region(src_region, src) ranges[i++] = src_region->ar; err = damon_set_regions(dst, ranges, i, src_min_region_sz); - kfree(ranges); + kvfree(ranges); return err; } From ef52ee27069876eae3d3e498863dd619eb387afd Mon Sep 17 00:00:00 2001 From: Philippe Laferriere Date: Mon, 29 Jun 2026 07:55:33 -0700 Subject: [PATCH 258/562] mm/damon/stat: use secs_to_jiffies() instead of msecs_to_jiffies() The conversion of a duration expressed in seconds reads as msecs_to_jiffies(5 * MSEC_PER_SEC), which obscures the intent and needlessly goes through milliseconds. Use the dedicated secs_to_jiffies() helper, which expresses the 5-second refresh interval directly. No functional change. Found using Coccinelle (scripts/coccinelle/misc/secs_to_jiffies.cocci). Link: https://lore.kernel.org/20260629145538.134832-3-sj@kernel.org Signed-off-by: Philippe Laferriere Signed-off-by: SJ Park Reviewed-by: SJ Park Cc: Akinobu Mita Cc: Asier Gutierrez Cc: Brendan Higgins Cc: David Hildenbrand Cc: Doehyun Baek Cc: Jonathan Corbet Cc: "Liam R. Howlett" Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Sailesh Nandanavanam Cc: Shuah Khan Cc: SJ Park Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/damon/stat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/damon/stat.c b/mm/damon/stat.c index 0e14f5bb8f75..b05b68f73e10 100644 --- a/mm/damon/stat.c +++ b/mm/damon/stat.c @@ -138,7 +138,7 @@ static int damon_stat_damon_call_fn(void *data) /* avoid unnecessarily frequent stat update */ if (time_before_eq(jiffies, damon_stat_last_refresh_jiffies + - msecs_to_jiffies(5 * MSEC_PER_SEC))) + secs_to_jiffies(5))) return 0; damon_stat_last_refresh_jiffies = jiffies; From 072814c8eaf7599e22b2e928774219ecd65152be Mon Sep 17 00:00:00 2001 From: Doehyun Baek Date: Mon, 29 Jun 2026 07:55:34 -0700 Subject: [PATCH 259/562] Docs/{admin-guide,mm}/damon: fix DAMON documentation details Fix minor DAMON documentation issues. Correct the sysfs scheme file name apply_interval_us, the DAMON_STAT module count, a malformed reference, a misplaced label indentation, and a few typos. Link: https://lore.kernel.org/20260629145538.134832-4-sj@kernel.org Signed-off-by: Doehyun Baek Signed-off-by: SJ Park Reviewed-by: SJ Park Cc: David Hildenbrand Cc: Lorenzo Stoakes Cc: "Liam R. Howlett" Cc: Vlastimil Babka Cc: Mike Rapoport Cc: Suren Baghdasaryan Cc: Michal Hocko Cc: Jonathan Corbet Cc: Shuah Khan Cc: Akinobu Mita Cc: Asier Gutierrez Cc: Brendan Higgins Cc: Philippe Laferriere Cc: Sailesh Nandanavanam Cc: SJ Park Signed-off-by: Andrew Morton --- Documentation/admin-guide/mm/damon/usage.rst | 8 ++++---- Documentation/mm/damon/design.rst | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Documentation/admin-guide/mm/damon/usage.rst b/Documentation/admin-guide/mm/damon/usage.rst index 011296f1e7c2..b2649ea011f9 100644 --- a/Documentation/admin-guide/mm/damon/usage.rst +++ b/Documentation/admin-guide/mm/damon/usage.rst @@ -246,7 +246,7 @@ writing to and reading from the files. Under ``nr_regions`` directory, two files for the lower-bound and upper-bound of DAMON's monitoring regions (``min`` and ``max``, respectively), which controls the monitoring overhead, exist. You can set and get the values by -writing to and rading from the files. +writing to and reading from the files. For more details about the intervals and monitoring regions range, please refer to the Design document (:doc:`/mm/damon/design`). @@ -264,7 +264,7 @@ Please refer to the :ref:`design document of the feature ` for the internal of the tuning mechanism. Reading and writing the four files under ``intervals_goal`` directory shows and updates the tuning parameters that described in the -:ref:design doc ` with the same +:ref:`design doc ` with the same names. The tuning starts with the user-set ``sample_us`` and ``aggr_us``. The tuning-applied current values of the two intervals can be read from the ``sample_us`` and ``aggr_us`` files after writing ``update_tuned_intervals`` to @@ -377,7 +377,7 @@ schemes// In each scheme directory, nine directories (``access_pattern``, ``quotas``, ``watermarks``, ``core_filters``, ``ops_filters``, ``filters``, ``dests``, ``stats``, and ``tried_regions``) and three files (``action``, ``target_nid`` -and ``apply_interval``) exist. +and ``apply_interval_us``) exist. The ``action`` file is for setting and getting the scheme's :ref:`action `. The keywords that can be written to and read @@ -743,7 +743,7 @@ counter). Finally the tenth field (``X``) shows the ``age`` of the region (refer to :ref:`design ` for more details of the counter). -If the event was ``damon:damos_beofre_apply``, the ``perf script`` output would +If the event was ``damon:damos_before_apply``, the ``perf script`` output would be somewhat like below:: kdamond.0 47293 [000] 80801.060214: damon:damos_before_apply: ctx_idx=0 scheme_idx=0 target_idx=0 nr_regions=11 121932607488-135128711168: 0 136 diff --git a/Documentation/mm/damon/design.rst b/Documentation/mm/damon/design.rst index 2da7ca0d3d17..c16a3bb288d0 100644 --- a/Documentation/mm/damon/design.rst +++ b/Documentation/mm/damon/design.rst @@ -86,7 +86,7 @@ To know how user-space can do the configuration via :ref:`DAMON sysfs interface documentation. - .. _damon_design_vaddr_target_regions_construction: +.. _damon_design_vaddr_target_regions_construction: VMA-based Target Address Range Construction ------------------------------------------- @@ -930,11 +930,11 @@ control parameters for the usage would also need to be optimized for the purpose. To support such cases, yet more DAMON API user kernel modules that provide more -simple and optimized user space interfaces are available. Currently, two -modules for proactive reclamation and LRU lists manipulation are provided. For -more detail, please read the usage documents for those -(:doc:`/admin-guide/mm/damon/stat`, :doc:`/admin-guide/mm/damon/reclaim` and -:doc:`/admin-guide/mm/damon/lru_sort`). +simple and optimized user space interfaces are available. Currently, three +modules for access monitoring statistics, proactive reclamation, and LRU lists +manipulation are provided. For more detail, please read the usage documents for +those (:doc:`/admin-guide/mm/damon/stat`, :doc:`/admin-guide/mm/damon/reclaim` +and :doc:`/admin-guide/mm/damon/lru_sort`). .. _damon_design_special_purpose_modules_exclusivity: From df861db8d44cdf664a1734d1a77e04e84162b2f1 Mon Sep 17 00:00:00 2001 From: Asier Gutierrez Date: Mon, 29 Jun 2026 07:55:35 -0700 Subject: [PATCH 260/562] samples/damon: fix typos in Kconfig help text Fix a couple of typos in samples/damon/Kconfig help text. Change "Thps" to "This", and "tierign" to "tiering". Link: https://lore.kernel.org/20260629145538.134832-5-sj@kernel.org Signed-off-by: Asier Gutierrez Signed-off-by: SJ Park Reviewed-by: SJ Park Cc: Akinobu Mita Cc: Brendan Higgins Cc: David Hildenbrand Cc: Doehyun Baek Cc: Jonathan Corbet Cc: "Liam R. Howlett" Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Philippe Laferriere Cc: Sailesh Nandanavanam Cc: Shuah Khan Cc: SJ Park Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- samples/damon/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/damon/Kconfig b/samples/damon/Kconfig index cbf96fd8a8bf..00be3e6bdd65 100644 --- a/samples/damon/Kconfig +++ b/samples/damon/Kconfig @@ -31,7 +31,7 @@ config SAMPLE_DAMON_MTIER bool "DAMON sample module for memory tiering" depends on DAMON && DAMON_PADDR help - Thps builds DAMON sample module for memory tierign. + This builds DAMON sample module for memory tiering. The module assumes the system is constructed with two NUMA nodes, which seems as local and remote nodes to all CPUs. For example, From cff53aef06c91bfda8acd01c86f210253deb89f2 Mon Sep 17 00:00:00 2001 From: Sailesh Nandanavanam Date: Mon, 29 Jun 2026 07:55:36 -0700 Subject: [PATCH 261/562] mm/damon/tests/core-kunit: add KUnit test for walk_control_obsolete behavior Add a KUnit test to verify that damos_walk() rejects new requests when walk_control_obsolete is set. Commit 33c3f6c2b48c ("mm/damon/core: fix damos_walk() vs kdamond_fn() exit race") introduced walk_control_obsolete to prevent a race condition where new requests could be registered during kdamond shutdown and never handled. This test simulates the shutdown condition by setting walk_control_obsolete and verifies that damos_walk() returns -ECANCELED immediately. This validates the invariant introduced by the fix and helps prevent regressions. Link: https://patch.msgid.link/20260612062337.2459-1-saileshnandanavanam@gmail.com Link: https://lore.kernel.org/20260629145538.134832-6-sj@kernel.org Suggested-by: SJ Park Signed-off-by: Sailesh Nandanavanam Signed-off-by: SJ Park Reviewed-by: SJ Park Cc: Akinobu Mita Cc: Asier Gutierrez Cc: Brendan Higgins Cc: David Hildenbrand Cc: Doehyun Baek Cc: Jonathan Corbet Cc: "Liam R. Howlett" Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Philippe Laferriere Cc: Shuah Khan Cc: SJ Park Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/damon/tests/core-kunit.h | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/mm/damon/tests/core-kunit.h b/mm/damon/tests/core-kunit.h index fcf7c7fadb5f..c5f5124c3d1f 100644 --- a/mm/damon/tests/core-kunit.h +++ b/mm/damon/tests/core-kunit.h @@ -1456,6 +1456,33 @@ static void damon_test_is_last_region(struct kunit *test) damon_free_target(t); } +/* + * Verify that damos_walk() rejects new requests when + * walk_control_obsolete is set. + * + * This tests the invariant introduced by: + * commit 33c3f6c2b48c ("mm/damon/core: fix damos_walk() vs kdamond_fn() exit race") + */ +static void damon_test_walk_control_obsolete(struct kunit *test) +{ + struct damon_ctx *ctx; + struct damos_walk_control control = {}; + int ret; + + ctx = damon_new_ctx(); + if (!ctx) + kunit_skip(test, "ctx alloc fail"); + + /* Simulate shutdown phase */ + ctx->walk_control_obsolete = true; + + ret = damos_walk(ctx, &control); + + KUNIT_EXPECT_EQ(test, ret, -ECANCELED); + + damon_destroy_ctx(ctx); +} + static struct kunit_case damon_test_cases[] = { KUNIT_CASE(damon_test_target), KUNIT_CASE(damon_test_regions), @@ -1485,6 +1512,7 @@ static struct kunit_case damon_test_cases[] = { KUNIT_CASE(damon_test_set_filters_default_reject), KUNIT_CASE(damon_test_apply_min_nr_regions), KUNIT_CASE(damon_test_is_last_region), + KUNIT_CASE(damon_test_walk_control_obsolete), {}, }; From 2d34f09ec9c470c9d30ca30b418d8306cc3b63d1 Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Mon, 29 Jun 2026 07:56:28 -0700 Subject: [PATCH 262/562] mm/damon/core: split a fraction of regions when nr_regions exceeds max/2 Patch series "mm/damon/core: detect internal variation above max_nr_regions/2", v3. kdamond_split_regions() bails out early when nr_regions is already above max_nr_regions / 2. A large region that picks up new internal variation after that point never gets split, so we lose visibility into its hot/cold structure. We hit this with damon-paddr on hugepage workloads and damon-vaddr on processes that mmap a large anonymous range. Example with max_nr_regions == 1500. A target ends up with 799 small hot/cold regions plus one big region (an earlier merge collapsed a uniformly-accessed range into a single piece): H:hot C:cold r1 r2 r3 r800 HHHHHH|CCCCCC|HHHHHH|...|HHHHHH..........................| nr_regions = 800 > max_nr_regions / 2 = 750 Now a cold subarea shows up inside r800: r1 r2 r3 r800 HHHHHH|CCCCCC|HHHHHH|...|HHHHHH........CCCCCC.............| The small regions can't merge with each other (their access counts differ), so budget never frees up. r800 can't be split because nr_regions > max_nr_regions / 2 returns early. The cold subarea stays invisible. Patch 1 keeps refining on this path: when nr_regions is above max_nr_regions / 2 but still under the maximum, it splits a fraction of the regions instead of returning. The fraction shrinks as the remaining budget shrinks, so the count approaches max_nr_regions smoothly. A useless split is undone by the next merge cycle. Patch 2 adds a KUnit test for the case where nr_regions is already above max_nr_regions / 2. Thanks to SJ for the suggestion to drive the split fraction from the remaining budget rather than an age-based filter. This patch (of 2): kdamond_split_regions() returns early when nr_regions is above max_nr_regions / 2, leaving internal access variation inside a large region undetected. Such a layout is common with damon-paddr on hugepage workloads or damon-vaddr on processes with a large anonymous mmap. For example, with max_nr_regions == 1500, a target may end up with 799 small alternating-temperature regions plus one large region that absorbed a uniformly-accessed range during an earlier merge: H:hot C:cold r1 r2 r3 r800 HHHHHH|CCCCCC|HHHHHH|...|HHHHHH..........................| nr_regions = 800 > max_nr_regions / 2 = 750 If a cold subarea later emerges inside r800: r1 r2 r3 r800 HHHHHH|CCCCCC|HHHHHH|...|HHHHHH........CCCCCC.............| The small regions cannot merge with each other (different access counts), so the budget stays full. r800 cannot be split because nr_regions > max_nr_regions / 2 causes an early return. The cold subarea is never discovered. When nr_regions is above max_nr_regions / 2 but still under the maximum, split only a fraction of the regions instead of returning. One region in every 'max_nr_regions / budget' regions is split, where budget is the remaining room (max_nr_regions - nr_regions), starting from a rotating offset so different regions get picked over time. The fraction shrinks as the budget shrinks, so the region count keeps refining while approaching max_nr_regions smoothly rather than overshooting it. An unnecessary split is reverted by the next kdamond_merge_regions(). Link: https://lore.kernel.org/20260629145630.134891-1-sj@kernel.org Link: https://lore.kernel.org/20260626085851.70754-2-jiayuan.chen@linux.dev Link: https://lore.kernel.org/20260629145630.134891-2-sj@kernel.org Signed-off-by: Jiayuan Chen Signed-off-by: SJ Park Reviewed-by: SJ Park Cc: Shu Anzai Signed-off-by: Andrew Morton --- mm/damon/core.c | 49 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/mm/damon/core.c b/mm/damon/core.c index 957cd8067bc0..a99458c57851 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -3247,6 +3247,37 @@ static void damon_split_regions_of(struct damon_ctx *ctx, } } +/* Split one in every @split_step regions into two, from a rotating offset */ +static void damon_split_some_regions(struct damon_ctx *ctx, + unsigned long split_step) +{ + static unsigned long rotation; + struct damon_target *t; + struct damon_region *r, *next; + unsigned long offset = rotation++ % split_step; + unsigned long idx = 0; + + damon_for_each_target(t, ctx) { + damon_for_each_region_safe(r, next, t) { + unsigned long sz_region, sz_sub; + + if (idx++ % split_step != offset) + continue; + sz_region = damon_sz_region(r); + if (sz_region < 2 * ctx->min_region_sz) + continue; + + sz_sub = ALIGN_DOWN(damon_rand(ctx, 1, 10) * + sz_region / 10, ctx->min_region_sz); + /* Do not allow blank region */ + if (sz_sub == 0 || sz_sub >= sz_region) + continue; + + damon_split_region_at(t, r, sz_sub); + } + } +} + /* * Split every target region into randomly-sized small regions * @@ -3260,25 +3291,33 @@ static void damon_split_regions_of(struct damon_ctx *ctx, static void kdamond_split_regions(struct damon_ctx *ctx) { struct damon_target *t; - unsigned int nr_regions = 0; - static unsigned int last_nr_regions; + unsigned long nr_regions = 0; + unsigned long max_nr_regions = ctx->attrs.max_nr_regions; + static unsigned long last_nr_regions; int nr_subregions = 2; damon_for_each_target(t, ctx) nr_regions += damon_nr_regions(t); - if (nr_regions > ctx->attrs.max_nr_regions / 2) - return; + if (nr_regions >= max_nr_regions) + goto done; + + if (nr_regions > max_nr_regions / 2) { + damon_split_some_regions(ctx, + max_nr_regions / (max_nr_regions - nr_regions)); + goto done; + } /* Maybe the middle of the region has different access frequency */ if (last_nr_regions == nr_regions && - nr_regions < ctx->attrs.max_nr_regions / 3) + nr_regions < max_nr_regions / 3) nr_subregions = 3; damon_for_each_target(t, ctx) damon_split_regions_of(ctx, t, nr_subregions, ctx->min_region_sz); +done: last_nr_regions = nr_regions; } From 5c007088fe718f11b2e0c2a17f592de9df397a6c Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Mon, 29 Jun 2026 07:56:29 -0700 Subject: [PATCH 263/562] mm/damon/tests/core-kunit: test split above max_nr_regions/2 Add a test that exercises kdamond_split_regions() when the total region count is already above max_nr_regions / 2, asserting that the function still splits a fraction of the regions (makes progress) and does not overshoot max_nr_regions. The region size and min_region_sz are picked so the split arithmetic does not depend on the page size. All tests pass: damon: pass:31 fail:0 skip:0 total:31 Totals: pass:31 fail:0 skip:0 total:31 Link: https://lore.kernel.org/20260626085851.70754-3-jiayuan.chen@linux.dev Link: https://lore.kernel.org/20260629145630.134891-3-sj@kernel.org Signed-off-by: Jiayuan Chen Signed-off-by: SJ Park Reviewed-by: SJ Park Cc: Shu Anzai Signed-off-by: Andrew Morton --- mm/damon/tests/core-kunit.h | 64 +++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/mm/damon/tests/core-kunit.h b/mm/damon/tests/core-kunit.h index c5f5124c3d1f..a00168730445 100644 --- a/mm/damon/tests/core-kunit.h +++ b/mm/damon/tests/core-kunit.h @@ -335,6 +335,69 @@ static void damon_test_split_regions_of(struct kunit *test) damon_destroy_ctx(c); } +/* + * When the total region count is already above max_nr_regions / 2, + * kdamond_split_regions() must keep refining the resolution by splitting a + * fraction of the regions (making progress), without exceeding + * max_nr_regions. + */ +static void damon_test_split_above_half_progresses(struct kunit *test) +{ + struct damon_ctx *c; + struct damon_target *t; + struct damon_region *r; + unsigned long start; + unsigned int nr_before, nr_after, i; + const unsigned int nr_init = 760; + const unsigned long region_sz = 100; + + c = damon_new_ctx(); + if (!c) + kunit_skip(test, "ctx alloc fail"); + + /* Keep the split arithmetic independent of the page size */ + c->min_region_sz = 1; + c->attrs.min_nr_regions = 10; + c->attrs.max_nr_regions = 1500; + + t = damon_new_target(); + if (!t) { + damon_destroy_ctx(c); + kunit_skip(test, "target alloc fail"); + } + + for (i = 0; i < nr_init; i++) { + start = i * region_sz; + r = damon_new_region(start, start + region_sz); + if (!r) { + damon_free_target(t); + damon_destroy_ctx(c); + kunit_skip(test, "region alloc fail"); + } + r->nr_accesses = (i & 1) ? 0 : 100; + r->age = 5; + damon_add_region(r, t); + } + + damon_add_target(c, t); + + nr_before = damon_nr_regions(t); + /* Above max_nr_regions / 2, so the blanket-split path is skipped */ + KUNIT_EXPECT_GT(test, (unsigned long)nr_before, + c->attrs.max_nr_regions / 2); + + kdamond_split_regions(c); + + nr_after = damon_nr_regions(t); + /* Still made progress ... */ + KUNIT_EXPECT_GT(test, nr_after, nr_before); + /* ... but did not overshoot the configured maximum */ + KUNIT_EXPECT_LE(test, (unsigned long)nr_after, + c->attrs.max_nr_regions); + + damon_destroy_ctx(c); +} + static void damon_test_ops_registration(struct kunit *test) { struct damon_ctx *c = damon_new_ctx(); @@ -1491,6 +1554,7 @@ static struct kunit_case damon_test_cases[] = { KUNIT_CASE(damon_test_merge_two), KUNIT_CASE(damon_test_merge_regions_of), KUNIT_CASE(damon_test_split_regions_of), + KUNIT_CASE(damon_test_split_above_half_progresses), KUNIT_CASE(damon_test_ops_registration), KUNIT_CASE(damon_test_set_regions), KUNIT_CASE(damon_test_nr_accesses_to_accesses_bp), From 71753347cf3e528e789a0a67f5d13a78d853b533 Mon Sep 17 00:00:00 2001 From: Johannes Weiner Date: Mon, 29 Jun 2026 12:33:37 -0400 Subject: [PATCH 264/562] mm: mempolicy: fix automatic numa balancing for shmem Neha reports that mapped shmem aren't considered for NUMA balancing, noting convergence problems and bandwidth bottlenecking for cachelib based workloads on tiered memory systems. Looking at the code and going through the git history, this doesn't actually seem intentional: Commit fc3147245d19 ("mm: numa: Limit NUMA scanning to migrate-on-fault VMAs") added a vma_policy_mof() gate to task_numa_work() so VMAs whose policy lacks MPOL_F_MOF are skipped from NUMA balancing scans. The motivation was a real usecase: Oracle was pinning shared segments with mbind(MPOL_BIND) so trapping faults was both expensive and pointless. The handling of NULL from vm_ops->get_policy, however, treated "user explicitly opted out" the same as "user never specified anything." For VMAs whose shared policy is absent - the common case for shmem - the scan was disabled too. This issue is old. It probably hurts less in conventional NUMA. But it's very noticeable on tiered systems, where entire tmpfs workingsets can get stuck on lower-bandwidth memory. Fix this by having vma_policy_mof() use __get_vma_policy() directly, and thereby handle the fallback to task policy (-> preferred_node_policy() has MPOL_F_MOF per default). Every other consumer of vm_ops->get_policy already handles it this way, the scan-eligibility check was the outlier. This preserves Mel's intended fix: don't scan stuff the user explicitly pinned. But allow default policy vmas to participate in balancing. Link: https://lore.kernel.org/20260629163337.1264881-1-hannes@cmpxchg.org Fixes: fc3147245d19 ("mm: numa: Limit NUMA scanning to migrate-on-fault VMAs") Signed-off-by: Johannes Weiner Reported-by: Neha Gholkar Tested-by: Neha Gholkar Reviewed-by: Gregory Price Acked-by: David Hildenbrand (Arm) Acked-by: Balbir Singh Cc: Alistair Popple Cc: Byungchul Park Cc: "Huang, Ying" Cc: Joshua Hahn Cc: Matthew Brost Cc: Rakie Kim Cc: Zi Yan Cc: Signed-off-by: Andrew Morton --- mm/mempolicy.c | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/mm/mempolicy.c b/mm/mempolicy.c index 36699fabd3c2..bba65898aee1 100644 --- a/mm/mempolicy.c +++ b/mm/mempolicy.c @@ -2057,24 +2057,15 @@ struct mempolicy *get_vma_policy(struct vm_area_struct *vma, bool vma_policy_mof(struct vm_area_struct *vma) { struct mempolicy *pol; + pgoff_t ilx; + bool mof; - if (vma->vm_ops && vma->vm_ops->get_policy) { - bool ret = false; - pgoff_t ilx; /* ignored here */ - - pol = vma->vm_ops->get_policy(vma, vma->vm_start, &ilx); - if (pol && (pol->flags & MPOL_F_MOF)) - ret = true; - mpol_cond_put(pol); - - return ret; - } - - pol = vma->vm_policy; + pol = __get_vma_policy(vma, vma->vm_start, &ilx); if (!pol) pol = get_task_policy(current); - - return pol->flags & MPOL_F_MOF; + mof = pol->flags & MPOL_F_MOF; + mpol_cond_put(pol); + return mof; } bool apply_policy_zone(struct mempolicy *policy, enum zone_type zone) From a8754c9b38cf2fa1d764fedd29ae8b9f3ace8058 Mon Sep 17 00:00:00 2001 From: Baolin Wang Date: Mon, 29 Jun 2026 16:04:06 +0800 Subject: [PATCH 265/562] mm: vmscan: remove the redundant FOLIOREF_RECLAIM_CLEAN logic folio_check_references() will return FOLIOREF_RECLAIM_CLEAN for referenced file folios, indicating that we can proceed to reclaim clean file folios or keep them if they are dirty file folios. However, after commit 6b0dfabb3555 ("fs: Remove aops->writepage"), we no longer attempt to write back filesystem folios through reclaim. Instead, we always activate dirty file folios and wakeup the flush workers to write them back. As a result, the FOLIOREF_RECLAIM_CLEAN logic is now redundant: for dirty file folios, we will no longer reach the 'references == FOLIOREF_RECLAIM_CLEAN' branch in shrink_folio_list(). Additionally, lazyfree folios are also placed on the file LRU list, but if a lazyfree folio becomes dirty, try_to_unmap() will fail and thus prevent reclaim of the re-dirtied lazyfree folios. Therefore, we can drop the FOLIOREF_RECLAIM_CLEAN-related logic. Link: https://lore.kernel.org/def70a713e10bcbdf3b9fccc2139ecc07b64f2cb.1782715791.git.baolin.wang@linux.alibaba.com Signed-off-by: Baolin Wang Acked-by: Johannes Weiner Reviewed-by: Shakeel Butt Cc: Axel Rasmussen Cc: Barry Song Cc: David Hildenbrand Cc: Kairui Song Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Wei Xu Cc: Yuanchu Xie Signed-off-by: Andrew Morton --- mm/vmscan.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/mm/vmscan.c b/mm/vmscan.c index 754c5f5d716a..f40cfe9d703b 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -823,7 +823,6 @@ void folio_putback_lru(struct folio *folio) enum folio_references { FOLIOREF_RECLAIM, - FOLIOREF_RECLAIM_CLEAN, FOLIOREF_KEEP, FOLIOREF_ACTIVATE, }; @@ -920,10 +919,6 @@ static enum folio_references folio_check_references(struct folio *folio, return FOLIOREF_KEEP; } - /* Reclaim if clean, defer dirty folios to writeback */ - if (referenced_folio && folio_is_file_lru(folio)) - return FOLIOREF_RECLAIM_CLEAN; - return FOLIOREF_RECLAIM; } @@ -1235,7 +1230,6 @@ static unsigned int shrink_folio_list(struct list_head *folio_list, stat->nr_ref_keep += nr_pages; goto keep_locked; case FOLIOREF_RECLAIM: - case FOLIOREF_RECLAIM_CLEAN: ; /* try to reclaim the folio below */ } @@ -1381,8 +1375,6 @@ static unsigned int shrink_folio_list(struct list_head *folio_list, goto activate_locked; } - if (references == FOLIOREF_RECLAIM_CLEAN) - goto keep_locked; if (!may_enter_fs(folio, sc->gfp_mask)) goto keep_locked; if (!sc->may_writepage) From ebbca13b1a6ea451ca7315b707795f36c45f3fc3 Mon Sep 17 00:00:00 2001 From: Usama Arif Date: Tue, 30 Jun 2026 09:34:38 -0700 Subject: [PATCH 266/562] mm: add softleaf_to_pmd() and convert existing callers Patch series "mm: preparatory patches for PMD level swap entries". This is the preparatory part of the PMD page table swapin work. The full PMD swap entry series has been split into two parts: 1. this preparatory series, which contains the first 6 patches. Zi [1] and Lance [2] suggested to separate this out from the core series. 2. the PMD swap entry core series, which depends on this one. I will send this once the preparatory series is merged in mm-new as v3 as the combined is currently at v2 [1]. I have not marked this prep series as v3, as its not really adding support for PMD swap entries. This series does not introduce PMD swap entries and does not install any new page-table entry type. It only cleans up existing PMD softleaf helpers and call sites so the follow-up PMD swap entry series can be smaller and easier to review. It should be safe to merge independently. The patches are either helper additions, refactors of existing open-coded logic, defensive checks that preserve current migration/device-private behavior, or a mechanical rename of the PMD softleaf Kconfig gate. The follow-up series depends on these helpers, but this series does not depend on the follow-up series. Patch breakdown: 1. mm: add softleaf_to_pmd() and convert existing callers Add the PMD counterpart to softleaf_to_pte() and convert existing swp_entry_to_pmd() users that are constructing PMD softleaf entries. 2. mm: extract mm_prepare_for_swap_entries() helper Hoist the "register mm with swapoff" double-checked-locking pattern out of try_to_unmap_one() and copy_nonpresent_pte() so future PMD-level users do not need another open-coded copy. 3. fs/proc: use softleaf_has_pfn() in pagemap PMD walker Avoid assuming every non-present PMD softleaf entry encodes a PFN. Existing migration/device-private behavior is preserved. 4. mm/huge_memory: move softleaf_to_folio() inside migration branch Keep the folio lookup in change_non_present_huge_pmd() scoped to the migration-entry branch that actually needs it. 5. mm/migrate_device: move softleaf_to_folio() inside device-private branch Apply the same ordering cleanup to migrate_vma_collect_pmd(): only derive a folio after confirming the PMD entry is device-private. 6. mm: rename ARCH_ENABLE_THP_MIGRATION to ARCH_SUPPORTS_PMD_SOFTLEAF Rename the architecture gate to describe what it actually enables: PMD softleaf entries. Migration remains the only current user in this series; the follow-up series adds PMD swap entries. This patch (of 6): Add softleaf_to_pmd() as the PMD counterpart to softleaf_to_pte(), completing the symmetry of the softleaf abstraction for page table leaf entries. The upcoming PMD swap entry support needs to construct PMD entries from swap entries. Converting existing swp_entry_to_pmd() callers to softleaf_to_pmd() in a prep patch keeps the feature patches focused on new functionality rather than mixing refactoring with new code. Link: https://lore.kernel.org/20260630164143.1595669-1-usama.arif@linux.dev Link: https://lore.kernel.org/20260630164143.1595669-2-usama.arif@linux.dev Link: https://lore.kernel.org/all/6E99CC4E-A026-4DE3-8A5A-34216771F521@nvidia.com/ [1] Link: https://lore.kernel.org/all/b08cafbb-a4b7-4609-84ae-dbb2cfcfc8be@linux.dev/#t [2] Link: https://lore.kernel.org/all/20260602142537.198755-1-usama.arif@linux.dev/ [3] Signed-off-by: Usama Arif Acked-by: David Hildenbrand (Arm) Reviewed-by: Zi Yan Reviewed-by: Lance Yang Cc: Alexandre Ghiti Cc: Baolin Wang Cc: Baoquan He Cc: Barry Song Cc: Chris Li Cc: Dev Jain Cc: "Huang, Ying" Cc: Johannes Weiner Cc: Kairui Song Cc: Kemeng Shi Cc: Kiryl Shutsemau Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Matthew Wilcox (Oracle) Cc: Nhat Pham Cc: Nico Pache Cc: Rik van Riel Cc: Ryan Roberts Cc: Shakeel Butt Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- include/linux/leafops.h | 20 ++++++++++++++++++++ mm/debug_vm_pgtable.c | 4 ++-- mm/huge_memory.c | 12 ++++++------ mm/migrate_device.c | 2 +- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/include/linux/leafops.h b/include/linux/leafops.h index 992cd8bd8ed0..803d312437df 100644 --- a/include/linux/leafops.h +++ b/include/linux/leafops.h @@ -108,6 +108,21 @@ static inline softleaf_t softleaf_from_pmd(pmd_t pmd) return swp_entry(__swp_type(arch_entry), __swp_offset(arch_entry)); } +/** + * softleaf_to_pmd() - Obtain a PMD entry from a leaf entry. + * @entry: Leaf entry. + * + * This generates an architecture-specific PMD entry that can be utilised to + * encode the metadata the leaf entry encodes. + * + * Returns: Architecture-specific PMD entry encoding leaf entry. + */ +static inline pmd_t softleaf_to_pmd(softleaf_t entry) +{ + /* Temporary until swp_entry_t eliminated. */ + return swp_entry_to_pmd(entry); +} + #else static inline softleaf_t softleaf_from_pmd(pmd_t pmd) @@ -115,6 +130,11 @@ static inline softleaf_t softleaf_from_pmd(pmd_t pmd) return softleaf_mk_none(); } +static inline pmd_t softleaf_to_pmd(softleaf_t entry) +{ + return __pmd(0); +} + #endif /** diff --git a/mm/debug_vm_pgtable.c b/mm/debug_vm_pgtable.c index 23dc3ee09561..18411fb09aab 100644 --- a/mm/debug_vm_pgtable.c +++ b/mm/debug_vm_pgtable.c @@ -758,7 +758,7 @@ static void __init pmd_leaf_soft_dirty_tests(struct pgtable_debug_args *args) return; pr_debug("Validating PMD swap soft dirty\n"); - pmd = swp_entry_to_pmd(args->leaf_entry); + pmd = softleaf_to_pmd(args->leaf_entry); WARN_ON(!pmd_is_huge(pmd)); WARN_ON(!pmd_is_valid_softleaf(pmd)); @@ -829,7 +829,7 @@ static void __init pmd_softleaf_tests(struct pgtable_debug_args *args) return; pr_debug("Validating PMD swap\n"); - pmd1 = swp_entry_to_pmd(args->leaf_entry); + pmd1 = softleaf_to_pmd(args->leaf_entry); WARN_ON(!pmd_is_huge(pmd1)); WARN_ON(!pmd_is_valid_softleaf(pmd1)); diff --git a/mm/huge_memory.c b/mm/huge_memory.c index 032702a4637b..b6f8c83c1000 100644 --- a/mm/huge_memory.c +++ b/mm/huge_memory.c @@ -1819,7 +1819,7 @@ static void copy_huge_non_present_pmd( if (softleaf_is_migration_write(entry) || softleaf_is_migration_read_exclusive(entry)) { entry = make_readable_migration_entry(swp_offset(entry)); - pmd = swp_entry_to_pmd(entry); + pmd = softleaf_to_pmd(entry); if (pmd_swp_soft_dirty(*src_pmd)) pmd = pmd_swp_mksoft_dirty(pmd); if (pmd_swp_uffd_wp(*src_pmd)) @@ -1832,7 +1832,7 @@ static void copy_huge_non_present_pmd( */ if (softleaf_is_device_private_write(entry)) { entry = make_readable_device_private_entry(swp_offset(entry)); - pmd = swp_entry_to_pmd(entry); + pmd = softleaf_to_pmd(entry); if (pmd_swp_soft_dirty(*src_pmd)) pmd = pmd_swp_mksoft_dirty(pmd); @@ -2570,12 +2570,12 @@ static void change_non_present_huge_pmd(struct mm_struct *mm, entry = make_readable_exclusive_migration_entry(swp_offset(entry)); else entry = make_readable_migration_entry(swp_offset(entry)); - newpmd = swp_entry_to_pmd(entry); + newpmd = softleaf_to_pmd(entry); if (pmd_swp_soft_dirty(*pmd)) newpmd = pmd_swp_mksoft_dirty(newpmd); } else if (softleaf_is_device_private_write(entry)) { entry = make_readable_device_private_entry(swp_offset(entry)); - newpmd = swp_entry_to_pmd(entry); + newpmd = softleaf_to_pmd(entry); if (pmd_swp_uffd_wp(*pmd)) newpmd = pmd_swp_mkuffd_wp(newpmd); } else { @@ -4927,7 +4927,7 @@ int set_pmd_migration_entry(struct page_vma_mapped_walk *pvmw, } /* Set PMD. */ - pmdswp = swp_entry_to_pmd(entry); + pmdswp = softleaf_to_pmd(entry); if (softdirty) pmdswp = pmd_swp_mksoft_dirty(pmdswp); if (uffd_wp) @@ -4980,7 +4980,7 @@ void remove_migration_pmd(struct page_vma_mapped_walk *pvmw, struct page *new) else entry = make_readable_device_private_entry( page_to_pfn(new)); - pmde = swp_entry_to_pmd(entry); + pmde = softleaf_to_pmd(entry); if (pmd_swp_soft_dirty(*pvmw->pmd)) pmde = pmd_swp_mksoft_dirty(pmde); diff --git a/mm/migrate_device.c b/mm/migrate_device.c index ae39173d6a0e..aa948db49501 100644 --- a/mm/migrate_device.c +++ b/mm/migrate_device.c @@ -835,7 +835,7 @@ static int migrate_vma_insert_huge_pmd_page(struct migrate_vma *migrate, else swp_entry = make_readable_device_private_entry( page_to_pfn(page)); - entry = swp_entry_to_pmd(swp_entry); + entry = softleaf_to_pmd(swp_entry); } else { if (folio_is_zone_device(folio) && !folio_is_device_coherent(folio)) { From 413e3b3ebdeac0aa41fbe7d686288b648c878bdc Mon Sep 17 00:00:00 2001 From: Usama Arif Date: Tue, 30 Jun 2026 09:34:39 -0700 Subject: [PATCH 267/562] mm: extract mm_prepare_for_swap_entries() helper When a swap entry is installed in a page table, the mm must be added to init_mm.mmlist so that swapoff can find and unuse its swap entries. This double-checked locking pattern is currently open-coded in try_to_unmap_one() and copy_nonpresent_pte(). Move it into mm_prepare_for_swap_entries() in mm/internal.h and convert both callers so it can be reused by upcoming PMD-level swap entry code paths that also need to register the mm with swapoff. copy_nonpresent_pte() previously inserted into &src_mm->mmlist rather than &init_mm.mmlist, but the insertion point is irrelevant, mmlist is a circular list and swapoff walks it entirely from init_mm.mmlist, so only membership matters, not position. Link: https://lore.kernel.org/20260630164143.1595669-3-usama.arif@linux.dev Signed-off-by: Usama Arif Reviewed-by: Dev Jain Reviewed-by: Zi Yan Acked-by: David Hildenbrand (Arm) Cc: Alexandre Ghiti Cc: Baolin Wang Cc: Baoquan He Cc: Barry Song Cc: Chris Li Cc: "Huang, Ying" Cc: Johannes Weiner Cc: Kairui Song Cc: Kemeng Shi Cc: Kiryl Shutsemau Cc: Lance Yang Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Matthew Wilcox (Oracle) Cc: Nhat Pham Cc: Nico Pache Cc: Rik van Riel Cc: Ryan Roberts Cc: Shakeel Butt Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/internal.h | 13 +++++++++++++ mm/memory.c | 9 +-------- mm/rmap.c | 7 +------ 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/mm/internal.h b/mm/internal.h index 430aa72a4575..fa4fb69444ec 100644 --- a/mm/internal.h +++ b/mm/internal.h @@ -1955,4 +1955,17 @@ static inline int get_sysctl_max_map_count(void) bool may_expand_vm(struct mm_struct *mm, const vma_flags_t *vma_flags, unsigned long npages); +/* + * Ensure @mm is on the init_mm.mmlist so swapoff can find it. + */ +static inline void mm_prepare_for_swap_entries(struct mm_struct *mm) +{ + if (list_empty(&mm->mmlist)) { + spin_lock(&mmlist_lock); + if (list_empty(&mm->mmlist)) + list_add(&mm->mmlist, &init_mm.mmlist); + spin_unlock(&mmlist_lock); + } +} + #endif /* __MM_INTERNAL_H */ diff --git a/mm/memory.c b/mm/memory.c index a3fcaf8cfe8f..6637c5b13c9b 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -953,14 +953,7 @@ copy_nonpresent_pte(struct mm_struct *dst_mm, struct mm_struct *src_mm, if (swap_dup_entry_direct(entry) < 0) return -EIO; - /* make sure dst_mm is on swapoff's mmlist. */ - if (unlikely(list_empty(&dst_mm->mmlist))) { - spin_lock(&mmlist_lock); - if (list_empty(&dst_mm->mmlist)) - list_add(&dst_mm->mmlist, - &src_mm->mmlist); - spin_unlock(&mmlist_lock); - } + mm_prepare_for_swap_entries(dst_mm); /* Mark the swap entry as shared. */ if (pte_swp_exclusive(orig_pte)) { pte = pte_swp_clear_exclusive(orig_pte); diff --git a/mm/rmap.c b/mm/rmap.c index 1c77d5dc06e9..b93caabd186f 100644 --- a/mm/rmap.c +++ b/mm/rmap.c @@ -2304,12 +2304,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, set_pte_at(mm, address, pvmw.pte, pteval); goto walk_abort; } - if (list_empty(&mm->mmlist)) { - spin_lock(&mmlist_lock); - if (list_empty(&mm->mmlist)) - list_add(&mm->mmlist, &init_mm.mmlist); - spin_unlock(&mmlist_lock); - } + mm_prepare_for_swap_entries(mm); dec_mm_counter(mm, MM_ANONPAGES); inc_mm_counter(mm, MM_SWAPENTS); swp_pte = swp_entry_to_pte(entry); From cd462d1b91bc9dd5d0d65515ce7f05267a1d5873 Mon Sep 17 00:00:00 2001 From: Usama Arif Date: Tue, 30 Jun 2026 09:34:40 -0700 Subject: [PATCH 268/562] fs/proc: use softleaf_has_pfn() in pagemap PMD walker pagemap_pmd_range_thp() assumes that every non-present PMD is a migration entry and unconditionally calls softleaf_to_page(). This will crash on any non-present PMD type that does not encode a PFN, such as the upcoming PMD-level swap entries. Guard the page lookup with softleaf_has_pfn(), matching how pte_to_pagemap_entry() already handles non-present PTEs. Link: https://lore.kernel.org/20260630164143.1595669-4-usama.arif@linux.dev Signed-off-by: Usama Arif Acked-by: David Hildenbrand (Arm) Reviewed-by: Zi Yan Cc: Alexandre Ghiti Cc: Baolin Wang Cc: Baoquan He Cc: Barry Song Cc: Chris Li Cc: Dev Jain Cc: "Huang, Ying" Cc: Johannes Weiner Cc: Kairui Song Cc: Kemeng Shi Cc: Kiryl Shutsemau Cc: Lance Yang Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Matthew Wilcox (Oracle) Cc: Nhat Pham Cc: Nico Pache Cc: Rik van Riel Cc: Ryan Roberts Cc: Shakeel Butt Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- fs/proc/task_mmu.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c index d32408f7cd5e..1fb5acd88ad0 100644 --- a/fs/proc/task_mmu.c +++ b/fs/proc/task_mmu.c @@ -2129,7 +2129,8 @@ static int pagemap_pmd_range_thp(pmd_t *pmdp, unsigned long addr, flags |= PM_SOFT_DIRTY; if (pmd_swp_uffd_wp(pmd)) flags |= PM_UFFD_WP; - page = softleaf_to_page(entry); + if (softleaf_has_pfn(entry)) + page = softleaf_to_page(entry); } if (page) { From 47172f3e337225dfba430b0dcfc1489481372c3f Mon Sep 17 00:00:00 2001 From: Usama Arif Date: Tue, 30 Jun 2026 09:34:41 -0700 Subject: [PATCH 269/562] mm/huge_memory: move softleaf_to_folio() inside migration branch change_non_present_huge_pmd() calls softleaf_to_folio() unconditionally at the top of the function. softleaf_to_folio() extracts a PFN from the entry and converts it to a folio pointer, which is only meaningful for migration and device_private entries that encode a real PFN. A swap entry encodes a swap offset instead, so softleaf_to_folio() would produce a bogus pointer and crash on mprotect() when a PMD swap entry is present. Move the call into the migration_write branch where the folio is actually used, so the function is safe for any non-present PMD type. Link: https://lore.kernel.org/20260630164143.1595669-5-usama.arif@linux.dev Signed-off-by: Usama Arif Acked-by: David Hildenbrand (Arm) Reviewed-by: Dev Jain Reviewed-by: Zi Yan Cc: Alexandre Ghiti Cc: Baolin Wang Cc: Baoquan He Cc: Barry Song Cc: Chris Li Cc: "Huang, Ying" Cc: Johannes Weiner Cc: Kairui Song Cc: Kemeng Shi Cc: Kiryl Shutsemau Cc: Lance Yang Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Matthew Wilcox (Oracle) Cc: Nhat Pham Cc: Nico Pache Cc: Rik van Riel Cc: Ryan Roberts Cc: Shakeel Butt Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/huge_memory.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mm/huge_memory.c b/mm/huge_memory.c index b6f8c83c1000..6ada1e862983 100644 --- a/mm/huge_memory.c +++ b/mm/huge_memory.c @@ -2557,11 +2557,12 @@ static void change_non_present_huge_pmd(struct mm_struct *mm, bool uffd_wp_resolve) { softleaf_t entry = softleaf_from_pmd(*pmd); - const struct folio *folio = softleaf_to_folio(entry); pmd_t newpmd; VM_WARN_ON(!pmd_is_valid_softleaf(*pmd)); if (softleaf_is_migration_write(entry)) { + const struct folio *folio = softleaf_to_folio(entry); + /* * A protection check is difficult so * just be safe and disable write From bddc61f421a4270033c5a5308db98bb82395f453 Mon Sep 17 00:00:00 2001 From: Usama Arif Date: Tue, 30 Jun 2026 09:34:42 -0700 Subject: [PATCH 270/562] mm/migrate_device: move softleaf_to_folio() inside device-private branch migrate_vma_collect_pmd() calls softleaf_to_folio() on a non-present PMD before checking the entry's type. softleaf_to_folio() converts the entry's offset to a PFN, which is only meaningful for migration or device-private entries. A PMD swap entry's offset is a swap offset, not a PFN, so the lookup would either return a bogus folio pointer or trip pfn_to_page validation on a debug kernel. In the non-device-private path the returned folio is then unused (the OR short-circuits to migrate_vma_collect_skip()), but the lookup itself is already unsafe. Move the softleaf_to_folio() call inside the device-private branch where the folio is actually needed, mirroring the equivalent change_non_present_huge_pmd() fix. Link: https://lore.kernel.org/20260630164143.1595669-6-usama.arif@linux.dev Signed-off-by: Usama Arif Reviewed-by: Zi Yan Acked-by: David Hildenbrand (Arm) Cc: Alexandre Ghiti Cc: Baolin Wang Cc: Baoquan He Cc: Barry Song Cc: Chris Li Cc: Dev Jain Cc: "Huang, Ying" Cc: Johannes Weiner Cc: Kairui Song Cc: Kemeng Shi Cc: Kiryl Shutsemau Cc: Lance Yang Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Matthew Wilcox (Oracle) Cc: Nhat Pham Cc: Nico Pache Cc: Rik van Riel Cc: Ryan Roberts Cc: Shakeel Butt Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/migrate_device.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/mm/migrate_device.c b/mm/migrate_device.c index aa948db49501..36287e958b2a 100644 --- a/mm/migrate_device.c +++ b/mm/migrate_device.c @@ -166,11 +166,14 @@ static int migrate_vma_collect_huge_pmd(pmd_t *pmdp, unsigned long start, } else if (!pmd_present(*pmdp)) { const softleaf_t entry = softleaf_from_pmd(*pmdp); - folio = softleaf_to_folio(entry); - if (!softleaf_is_device_private(entry) || - !(migrate->flags & MIGRATE_VMA_SELECT_DEVICE_PRIVATE) || - (folio->pgmap->owner != migrate->pgmap_owner)) { + !(migrate->flags & MIGRATE_VMA_SELECT_DEVICE_PRIVATE)) { + spin_unlock(ptl); + return migrate_vma_collect_skip(start, end, walk); + } + + folio = softleaf_to_folio(entry); + if (folio->pgmap->owner != migrate->pgmap_owner) { spin_unlock(ptl); return migrate_vma_collect_skip(start, end, walk); } From aca1a23c124fbea9d4418a714adb53926ac4688e Mon Sep 17 00:00:00 2001 From: Usama Arif Date: Tue, 30 Jun 2026 09:34:43 -0700 Subject: [PATCH 271/562] mm: rename ARCH_ENABLE_THP_MIGRATION to ARCH_SUPPORTS_PMD_SOFTLEAF CONFIG_ARCH_ENABLE_THP_MIGRATION started life gating just PMD-level migration entries, but has grown to gate the entire PMD-level softleaf machinery: migration entries, device-private entries, and soon swap entries. Rename CONFIG_ARCH_ENABLE_THP_MIGRATION to CONFIG_ARCH_SUPPORTS_PMD _SOFTLEAF to make this clear. This is a pure rename: the set of selecting architectures (x86, arm64, s390, riscv, loongarch, and powerpc on PPC_BOOK3S_64) and the gating semantics are unchanged. No functional change intended. Link: https://lore.kernel.org/20260630164143.1595669-7-usama.arif@linux.dev Signed-off-by: Usama Arif Reviewed-by: Zi Yan Cc: Alexandre Ghiti Cc: Baolin Wang Cc: Baoquan He Cc: Barry Song Cc: Chris Li Cc: David Hildenbrand (Arm) Cc: Dev Jain Cc: "Huang, Ying" Cc: Johannes Weiner Cc: Kairui Song Cc: Kemeng Shi Cc: Kiryl Shutsemau Cc: Lance Yang Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Matthew Wilcox (Oracle) Cc: Nhat Pham Cc: Nico Pache Cc: Rik van Riel Cc: Ryan Roberts Cc: Shakeel Butt Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- arch/arm64/Kconfig | 2 +- arch/arm64/include/asm/pgtable.h | 4 ++-- arch/loongarch/Kconfig | 2 +- arch/powerpc/include/asm/book3s/64/pgtable.h | 2 +- arch/powerpc/platforms/Kconfig.cputype | 2 +- arch/riscv/Kconfig | 2 +- arch/riscv/include/asm/pgtable.h | 8 ++++---- arch/s390/Kconfig | 2 +- arch/s390/include/asm/pgtable.h | 2 +- arch/x86/Kconfig | 2 +- arch/x86/include/asm/pgtable.h | 2 +- include/linux/huge_mm.h | 2 +- include/linux/leafops.h | 8 ++++---- include/linux/pgtable.h | 2 +- include/linux/swapops.h | 6 +++--- mm/Kconfig | 2 +- mm/debug_vm_pgtable.c | 8 ++++---- mm/hmm.c | 4 ++-- mm/huge_memory.c | 2 +- mm/migrate.c | 4 ++-- mm/migrate_device.c | 6 +++--- mm/rmap.c | 2 +- 22 files changed, 38 insertions(+), 38 deletions(-) diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index b3afe0688919..3eb94f36c77e 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -17,7 +17,7 @@ config ARM64 select ARCH_ENABLE_HUGEPAGE_MIGRATION if HUGETLB_PAGE && MIGRATION select ARCH_ENABLE_MEMORY_HOTPLUG select ARCH_ENABLE_SPLIT_PMD_PTLOCK if PGTABLE_LEVELS > 2 - select ARCH_ENABLE_THP_MIGRATION if TRANSPARENT_HUGEPAGE + select ARCH_SUPPORTS_PMD_SOFTLEAF if TRANSPARENT_HUGEPAGE select ARCH_HAS_CACHE_LINE_SIZE select ARCH_HAS_CC_PLATFORM select ARCH_HAS_CPU_CACHE_INVALIDATE_MEMREGION diff --git a/arch/arm64/include/asm/pgtable.h b/arch/arm64/include/asm/pgtable.h index 27689c62bd25..984badfa9a74 100644 --- a/arch/arm64/include/asm/pgtable.h +++ b/arch/arm64/include/asm/pgtable.h @@ -1534,10 +1534,10 @@ static inline pmd_t pmdp_establish(struct vm_area_struct *vma, #define __pte_to_swp_entry(pte) ((swp_entry_t) { pte_val(pte) }) #define __swp_entry_to_pte(swp) ((pte_t) { (swp).val }) -#ifdef CONFIG_ARCH_ENABLE_THP_MIGRATION +#ifdef CONFIG_ARCH_SUPPORTS_PMD_SOFTLEAF #define __pmd_to_swp_entry(pmd) ((swp_entry_t) { pmd_val(pmd) }) #define __swp_entry_to_pmd(swp) __pmd((swp).val) -#endif /* CONFIG_ARCH_ENABLE_THP_MIGRATION */ +#endif /* CONFIG_ARCH_SUPPORTS_PMD_SOFTLEAF */ /* * Ensure that there are not more swap files than can be encoded in the kernel diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig index d8d252325017..550142b7ee10 100644 --- a/arch/loongarch/Kconfig +++ b/arch/loongarch/Kconfig @@ -12,7 +12,7 @@ config LOONGARCH select ARCH_NEEDS_DEFER_KASAN select ARCH_DISABLE_KASAN_INLINE select ARCH_ENABLE_MEMORY_HOTPLUG - select ARCH_ENABLE_THP_MIGRATION if TRANSPARENT_HUGEPAGE + select ARCH_SUPPORTS_PMD_SOFTLEAF if TRANSPARENT_HUGEPAGE select ARCH_HAS_ACPI_TABLE_UPGRADE if ACPI select ARCH_HAS_CPU_FINALIZE_INIT select ARCH_HAS_CURRENT_STACK_POINTER diff --git a/arch/powerpc/include/asm/book3s/64/pgtable.h b/arch/powerpc/include/asm/book3s/64/pgtable.h index e67e64ac6e8c..6f30aa8a6490 100644 --- a/arch/powerpc/include/asm/book3s/64/pgtable.h +++ b/arch/powerpc/include/asm/book3s/64/pgtable.h @@ -1060,7 +1060,7 @@ static inline pte_t *pmdp_ptep(pmd_t *pmd) #define pmd_mksoft_dirty(pmd) pte_pmd(pte_mksoft_dirty(pmd_pte(pmd))) #define pmd_clear_soft_dirty(pmd) pte_pmd(pte_clear_soft_dirty(pmd_pte(pmd))) -#ifdef CONFIG_ARCH_ENABLE_THP_MIGRATION +#ifdef CONFIG_ARCH_SUPPORTS_PMD_SOFTLEAF #define pmd_swp_mksoft_dirty(pmd) pte_pmd(pte_swp_mksoft_dirty(pmd_pte(pmd))) #define pmd_swp_soft_dirty(pmd) pte_swp_soft_dirty(pmd_pte(pmd)) #define pmd_swp_clear_soft_dirty(pmd) pte_pmd(pte_swp_clear_soft_dirty(pmd_pte(pmd))) diff --git a/arch/powerpc/platforms/Kconfig.cputype b/arch/powerpc/platforms/Kconfig.cputype index bac02c83bb3e..4a0fa681bf98 100644 --- a/arch/powerpc/platforms/Kconfig.cputype +++ b/arch/powerpc/platforms/Kconfig.cputype @@ -112,7 +112,7 @@ config PPC_THP depends on PPC_RADIX_MMU || (PPC_64S_HASH_MMU && PAGE_SIZE_64KB) select HAVE_ARCH_TRANSPARENT_HUGEPAGE select HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD - select ARCH_ENABLE_THP_MIGRATION if TRANSPARENT_HUGEPAGE + select ARCH_SUPPORTS_PMD_SOFTLEAF if TRANSPARENT_HUGEPAGE choice prompt "CPU selection" diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig index 3f0a647218e4..bf7b271cccd8 100644 --- a/arch/riscv/Kconfig +++ b/arch/riscv/Kconfig @@ -22,7 +22,7 @@ config RISCV select ARCH_ENABLE_HUGEPAGE_MIGRATION if HUGETLB_PAGE && MIGRATION select ARCH_ENABLE_MEMORY_HOTPLUG if SPARSEMEM_VMEMMAP select ARCH_ENABLE_SPLIT_PMD_PTLOCK if PGTABLE_LEVELS > 2 - select ARCH_ENABLE_THP_MIGRATION if TRANSPARENT_HUGEPAGE + select ARCH_SUPPORTS_PMD_SOFTLEAF if TRANSPARENT_HUGEPAGE select ARCH_HAS_BINFMT_FLAT select ARCH_HAS_CC_CAN_LINK select ARCH_HAS_CURRENT_STACK_POINTER diff --git a/arch/riscv/include/asm/pgtable.h b/arch/riscv/include/asm/pgtable.h index 5d5756bda82e..2aa529e882d3 100644 --- a/arch/riscv/include/asm/pgtable.h +++ b/arch/riscv/include/asm/pgtable.h @@ -946,7 +946,7 @@ static inline pmd_t pmd_clear_soft_dirty(pmd_t pmd) return pte_pmd(pte_clear_soft_dirty(pmd_pte(pmd))); } -#ifdef CONFIG_ARCH_ENABLE_THP_MIGRATION +#ifdef CONFIG_ARCH_SUPPORTS_PMD_SOFTLEAF static inline bool pmd_swp_soft_dirty(pmd_t pmd) { return pte_swp_soft_dirty(pmd_pte(pmd)); @@ -961,7 +961,7 @@ static inline pmd_t pmd_swp_clear_soft_dirty(pmd_t pmd) { return pte_pmd(pte_swp_clear_soft_dirty(pmd_pte(pmd))); } -#endif /* CONFIG_ARCH_ENABLE_THP_MIGRATION */ +#endif /* CONFIG_ARCH_SUPPORTS_PMD_SOFTLEAF */ #endif /* CONFIG_HAVE_ARCH_SOFT_DIRTY */ static inline void set_pmd_at(struct mm_struct *mm, unsigned long addr, @@ -1208,10 +1208,10 @@ static inline pte_t pte_swp_clear_exclusive(pte_t pte) return __pte(pte_val(pte) & ~_PAGE_SWP_EXCLUSIVE); } -#ifdef CONFIG_ARCH_ENABLE_THP_MIGRATION +#ifdef CONFIG_ARCH_SUPPORTS_PMD_SOFTLEAF #define __pmd_to_swp_entry(pmd) ((swp_entry_t) { pmd_val(pmd) }) #define __swp_entry_to_pmd(swp) __pmd((swp).val) -#endif /* CONFIG_ARCH_ENABLE_THP_MIGRATION */ +#endif /* CONFIG_ARCH_SUPPORTS_PMD_SOFTLEAF */ /* * In the RV64 Linux scheme, we give the user half of the virtual-address space diff --git a/arch/s390/Kconfig b/arch/s390/Kconfig index 84404e6778d5..0719f68de0f3 100644 --- a/arch/s390/Kconfig +++ b/arch/s390/Kconfig @@ -82,7 +82,7 @@ config S390 select ARCH_CORRECT_STACKTRACE_ON_KRETPROBE select ARCH_ENABLE_MEMORY_HOTPLUG if SPARSEMEM select ARCH_ENABLE_SPLIT_PMD_PTLOCK if PGTABLE_LEVELS > 2 - select ARCH_ENABLE_THP_MIGRATION if TRANSPARENT_HUGEPAGE + select ARCH_SUPPORTS_PMD_SOFTLEAF if TRANSPARENT_HUGEPAGE select ARCH_HAS_CC_CAN_LINK select ARCH_HAS_CPU_FINALIZE_INIT select ARCH_HAS_CURRENT_STACK_POINTER diff --git a/arch/s390/include/asm/pgtable.h b/arch/s390/include/asm/pgtable.h index 859ce7c7d454..6faccfa63b09 100644 --- a/arch/s390/include/asm/pgtable.h +++ b/arch/s390/include/asm/pgtable.h @@ -903,7 +903,7 @@ static inline pmd_t pmd_clear_soft_dirty(pmd_t pmd) return clear_pmd_bit(pmd, __pgprot(_SEGMENT_ENTRY_SOFT_DIRTY)); } -#ifdef CONFIG_ARCH_ENABLE_THP_MIGRATION +#ifdef CONFIG_ARCH_SUPPORTS_PMD_SOFTLEAF #define pmd_swp_soft_dirty(pmd) pmd_soft_dirty(pmd) #define pmd_swp_mksoft_dirty(pmd) pmd_mksoft_dirty(pmd) #define pmd_swp_clear_soft_dirty(pmd) pmd_clear_soft_dirty(pmd) diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index bdad90f210e4..50e00d8417aa 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -70,7 +70,7 @@ config X86 select ARCH_ENABLE_HUGEPAGE_MIGRATION if X86_64 && HUGETLB_PAGE && MIGRATION select ARCH_ENABLE_MEMORY_HOTPLUG if X86_64 select ARCH_ENABLE_SPLIT_PMD_PTLOCK if (PGTABLE_LEVELS > 2) && (X86_64 || X86_PAE) - select ARCH_ENABLE_THP_MIGRATION if X86_64 && TRANSPARENT_HUGEPAGE + select ARCH_SUPPORTS_PMD_SOFTLEAF if X86_64 && TRANSPARENT_HUGEPAGE select ARCH_HAS_ACPI_TABLE_UPGRADE if ACPI select ARCH_HAS_CPU_ATTACK_VECTORS if CPU_MITIGATIONS select ARCH_HAS_CACHE_LINE_SIZE diff --git a/arch/x86/include/asm/pgtable.h b/arch/x86/include/asm/pgtable.h index ac295ca6c92f..e0fd318d4004 100644 --- a/arch/x86/include/asm/pgtable.h +++ b/arch/x86/include/asm/pgtable.h @@ -1545,7 +1545,7 @@ static inline pte_t pte_swp_clear_soft_dirty(pte_t pte) return pte_clear_flags(pte, _PAGE_SWP_SOFT_DIRTY); } -#ifdef CONFIG_ARCH_ENABLE_THP_MIGRATION +#ifdef CONFIG_ARCH_SUPPORTS_PMD_SOFTLEAF static inline pmd_t pmd_swp_mksoft_dirty(pmd_t pmd) { return pmd_set_flags(pmd, _PAGE_SWP_SOFT_DIRTY); diff --git a/include/linux/huge_mm.h b/include/linux/huge_mm.h index ad20f7f8c179..1487bf4af1a7 100644 --- a/include/linux/huge_mm.h +++ b/include/linux/huge_mm.h @@ -567,7 +567,7 @@ static inline struct folio *get_persistent_huge_zero_folio(void) static inline bool thp_migration_supported(void) { - return IS_ENABLED(CONFIG_ARCH_ENABLE_THP_MIGRATION); + return IS_ENABLED(CONFIG_ARCH_SUPPORTS_PMD_SOFTLEAF); } void split_huge_pmd_locked(struct vm_area_struct *vma, unsigned long address, diff --git a/include/linux/leafops.h b/include/linux/leafops.h index 803d312437df..88888daeb018 100644 --- a/include/linux/leafops.h +++ b/include/linux/leafops.h @@ -81,7 +81,7 @@ static inline pte_t softleaf_to_pte(softleaf_t entry) return swp_entry_to_pte(entry); } -#ifdef CONFIG_ARCH_ENABLE_THP_MIGRATION +#ifdef CONFIG_ARCH_SUPPORTS_PMD_SOFTLEAF /** * softleaf_from_pmd() - Obtain a leaf entry from a PMD entry. * @pmd: PMD entry. @@ -587,7 +587,7 @@ static inline bool pte_is_uffd_marker(pte_t pte) return false; } -#if defined(CONFIG_ZONE_DEVICE) && defined(CONFIG_ARCH_ENABLE_THP_MIGRATION) +#if defined(CONFIG_ZONE_DEVICE) && defined(CONFIG_ARCH_SUPPORTS_PMD_SOFTLEAF) /** * pmd_is_device_private_entry() - Check if PMD contains a device private swap @@ -606,14 +606,14 @@ static inline bool pmd_is_device_private_entry(pmd_t pmd) return softleaf_is_device_private(softleaf_from_pmd(pmd)); } -#else /* CONFIG_ZONE_DEVICE && CONFIG_ARCH_ENABLE_THP_MIGRATION */ +#else /* CONFIG_ZONE_DEVICE && CONFIG_ARCH_SUPPORTS_PMD_SOFTLEAF */ static inline bool pmd_is_device_private_entry(pmd_t pmd) { return false; } -#endif /* CONFIG_ZONE_DEVICE && CONFIG_ARCH_ENABLE_THP_MIGRATION */ +#endif /* CONFIG_ZONE_DEVICE && CONFIG_ARCH_SUPPORTS_PMD_SOFTLEAF */ /** * pmd_is_migration_entry() - Does this PMD entry encode a migration entry? diff --git a/include/linux/pgtable.h b/include/linux/pgtable.h index d52d2a976e5a..e38f069c1c91 100644 --- a/include/linux/pgtable.h +++ b/include/linux/pgtable.h @@ -1839,7 +1839,7 @@ static inline pgprot_t pgprot_modify(pgprot_t oldprot, pgprot_t newprot) #endif #ifdef CONFIG_HAVE_ARCH_SOFT_DIRTY -#ifndef CONFIG_ARCH_ENABLE_THP_MIGRATION +#ifndef CONFIG_ARCH_SUPPORTS_PMD_SOFTLEAF static inline pmd_t pmd_swp_mksoft_dirty(pmd_t pmd) { return pmd; diff --git a/include/linux/swapops.h b/include/linux/swapops.h index 8cfc966eae48..705a84154d28 100644 --- a/include/linux/swapops.h +++ b/include/linux/swapops.h @@ -321,7 +321,7 @@ static inline swp_entry_t make_guard_swp_entry(void) struct page_vma_mapped_walk; -#ifdef CONFIG_ARCH_ENABLE_THP_MIGRATION +#ifdef CONFIG_ARCH_SUPPORTS_PMD_SOFTLEAF extern int set_pmd_migration_entry(struct page_vma_mapped_walk *pvmw, struct page *page); @@ -338,7 +338,7 @@ static inline pmd_t swp_entry_to_pmd(swp_entry_t entry) return __swp_entry_to_pmd(arch_entry); } -#else /* CONFIG_ARCH_ENABLE_THP_MIGRATION */ +#else /* CONFIG_ARCH_SUPPORTS_PMD_SOFTLEAF */ static inline int set_pmd_migration_entry(struct page_vma_mapped_walk *pvmw, struct page *page) { @@ -358,7 +358,7 @@ static inline pmd_t swp_entry_to_pmd(swp_entry_t entry) return __pmd(0); } -#endif /* CONFIG_ARCH_ENABLE_THP_MIGRATION */ +#endif /* CONFIG_ARCH_SUPPORTS_PMD_SOFTLEAF */ #endif /* CONFIG_MMU */ #endif /* _LINUX_SWAPOPS_H */ diff --git a/mm/Kconfig b/mm/Kconfig index 9e0ca4824905..04fe5171bb8c 100644 --- a/mm/Kconfig +++ b/mm/Kconfig @@ -703,7 +703,7 @@ config DEVICE_MIGRATION config ARCH_ENABLE_HUGEPAGE_MIGRATION bool -config ARCH_ENABLE_THP_MIGRATION +config ARCH_SUPPORTS_PMD_SOFTLEAF bool config HUGETLB_PAGE_SIZE_VARIABLE diff --git a/mm/debug_vm_pgtable.c b/mm/debug_vm_pgtable.c index 18411fb09aab..507fbd1ae7e5 100644 --- a/mm/debug_vm_pgtable.c +++ b/mm/debug_vm_pgtable.c @@ -751,7 +751,7 @@ static void __init pmd_leaf_soft_dirty_tests(struct pgtable_debug_args *args) pmd_t pmd; if (!pgtable_supports_soft_dirty() || - !IS_ENABLED(CONFIG_ARCH_ENABLE_THP_MIGRATION)) + !IS_ENABLED(CONFIG_ARCH_SUPPORTS_PMD_SOFTLEAF)) return; if (!has_transparent_hugepage()) @@ -819,7 +819,7 @@ static void __init pte_swap_tests(struct pgtable_debug_args *args) WARN_ON(memcmp(&pte1, &pte2, sizeof(pte1))); } -#ifdef CONFIG_ARCH_ENABLE_THP_MIGRATION +#ifdef CONFIG_ARCH_SUPPORTS_PMD_SOFTLEAF static void __init pmd_softleaf_tests(struct pgtable_debug_args *args) { swp_entry_t arch_entry; @@ -837,9 +837,9 @@ static void __init pmd_softleaf_tests(struct pgtable_debug_args *args) pmd2 = __swp_entry_to_pmd(arch_entry); WARN_ON(memcmp(&pmd1, &pmd2, sizeof(pmd1))); } -#else /* !CONFIG_ARCH_ENABLE_THP_MIGRATION */ +#else /* !CONFIG_ARCH_SUPPORTS_PMD_SOFTLEAF */ static void __init pmd_softleaf_tests(struct pgtable_debug_args *args) { } -#endif /* CONFIG_ARCH_ENABLE_THP_MIGRATION */ +#endif /* CONFIG_ARCH_SUPPORTS_PMD_SOFTLEAF */ static void __init swap_migration_tests(struct pgtable_debug_args *args) { diff --git a/mm/hmm.c b/mm/hmm.c index c72c9ddfdb2f..4f3f627d2b47 100644 --- a/mm/hmm.c +++ b/mm/hmm.c @@ -331,7 +331,7 @@ static int hmm_vma_handle_pte(struct mm_walk *walk, unsigned long addr, return hmm_vma_fault(addr, end, required_fault, walk); } -#ifdef CONFIG_ARCH_ENABLE_THP_MIGRATION +#ifdef CONFIG_ARCH_SUPPORTS_PMD_SOFTLEAF static int hmm_vma_handle_absent_pmd(struct mm_walk *walk, unsigned long start, unsigned long end, unsigned long *hmm_pfns, pmd_t pmd) @@ -391,7 +391,7 @@ static int hmm_vma_handle_absent_pmd(struct mm_walk *walk, unsigned long start, return -EFAULT; return hmm_pfns_fill(start, end, range, HMM_PFN_ERROR); } -#endif /* CONFIG_ARCH_ENABLE_THP_MIGRATION */ +#endif /* CONFIG_ARCH_SUPPORTS_PMD_SOFTLEAF */ static int hmm_vma_walk_pmd(pmd_t *pmdp, unsigned long start, diff --git a/mm/huge_memory.c b/mm/huge_memory.c index 6ada1e862983..c0892cc533a9 100644 --- a/mm/huge_memory.c +++ b/mm/huge_memory.c @@ -4868,7 +4868,7 @@ static int __init split_huge_pages_debugfs(void) late_initcall(split_huge_pages_debugfs); #endif -#ifdef CONFIG_ARCH_ENABLE_THP_MIGRATION +#ifdef CONFIG_ARCH_SUPPORTS_PMD_SOFTLEAF int set_pmd_migration_entry(struct page_vma_mapped_walk *pvmw, struct page *page) { diff --git a/mm/migrate.c b/mm/migrate.c index 806508be3dec..a786549551e3 100644 --- a/mm/migrate.c +++ b/mm/migrate.c @@ -362,7 +362,7 @@ static bool remove_migration_pte(struct folio *folio, idx = linear_page_index(vma, pvmw.address) - pvmw.pgoff; new = folio_page(folio, idx); -#ifdef CONFIG_ARCH_ENABLE_THP_MIGRATION +#ifdef CONFIG_ARCH_SUPPORTS_PMD_SOFTLEAF /* PMD-mapped THP migration entry */ if (!pvmw.pte) { VM_BUG_ON_FOLIO(folio_test_hugetlb(folio) || @@ -545,7 +545,7 @@ void migration_entry_wait_huge(struct vm_area_struct *vma, unsigned long addr, p } #endif -#ifdef CONFIG_ARCH_ENABLE_THP_MIGRATION +#ifdef CONFIG_ARCH_SUPPORTS_PMD_SOFTLEAF void pmd_migration_entry_wait(struct mm_struct *mm, pmd_t *pmd) { spinlock_t *ptl; diff --git a/mm/migrate_device.c b/mm/migrate_device.c index 36287e958b2a..2f8b646302c2 100644 --- a/mm/migrate_device.c +++ b/mm/migrate_device.c @@ -771,7 +771,7 @@ int migrate_vma_setup(struct migrate_vma *args) } EXPORT_SYMBOL(migrate_vma_setup); -#ifdef CONFIG_ARCH_ENABLE_THP_MIGRATION +#ifdef CONFIG_ARCH_SUPPORTS_PMD_SOFTLEAF /** * migrate_vma_insert_huge_pmd_page: Insert a huge folio into @migrate->vma->vm_mm * at @addr. folio is already allocated as a part of the migration process with @@ -926,7 +926,7 @@ static int migrate_vma_split_unmapped_folio(struct migrate_vma *migrate, migrate->src[i+idx] = migrate_pfn(pfn + i) | flags; return ret; } -#else /* !CONFIG_ARCH_ENABLE_THP_MIGRATION */ +#else /* !CONFIG_ARCH_SUPPORTS_PMD_SOFTLEAF */ static int migrate_vma_insert_huge_pmd_page(struct migrate_vma *migrate, unsigned long addr, struct page *page, @@ -947,7 +947,7 @@ static int migrate_vma_split_unmapped_folio(struct migrate_vma *migrate, static unsigned long migrate_vma_nr_pages(unsigned long *src) { unsigned long nr = 1; -#ifdef CONFIG_ARCH_ENABLE_THP_MIGRATION +#ifdef CONFIG_ARCH_SUPPORTS_PMD_SOFTLEAF if (*src & MIGRATE_PFN_COMPOUND) nr = HPAGE_PMD_NR; #else diff --git a/mm/rmap.c b/mm/rmap.c index b93caabd186f..0fb7a1b82cf3 100644 --- a/mm/rmap.c +++ b/mm/rmap.c @@ -2472,7 +2472,7 @@ static bool try_to_migrate_one(struct folio *folio, struct vm_area_struct *vma, page_vma_mapped_walk_restart(&pvmw); continue; } -#ifdef CONFIG_ARCH_ENABLE_THP_MIGRATION +#ifdef CONFIG_ARCH_SUPPORTS_PMD_SOFTLEAF pmdval = pmdp_get(pvmw.pmd); if (likely(pmd_present(pmdval))) pfn = pmd_pfn(pmdval); From db57e8eb21ac39fd4c4ceec48caf388712e7f2b4 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Tue, 30 Jun 2026 07:17:15 -0700 Subject: [PATCH 272/562] Docs/mm/damon/design: update for DAMOS_QUOTA_NODE_ELIGIBLE_MEM_BP Patch series "mm/damon: update, optimize, and clean up doc, tests, and code". Patches 1 and 2 update the design and ABI documents for recently added DAMON features. Patches 3-7 add or update more unit and self tests for DAMON to cover recently changed or added functions and sysfs files. Patch 8 optimizes damon_commit_target_regions() to skip unnecessary adjacent ranges setup. Patches 9-11 clean and fix up recently added DAMON sysfs interface code for readability. This patch (of 11): Commit 9138e27a3bc3 ("mm/damon: add node_eligible_mem_bp goal metric") introduced DAMOS_QUOTA_NODE_ELIGIBLE_MEM_BP but forgot updating the DAMON design document for that. Update. Link: https://lore.kernel.org/20260630141726.92246-1-sj@kernel.org Link: https://lore.kernel.org/20260630141726.92246-2-sj@kernel.org Signed-off-by: SJ Park Cc: Brendan Higgins Cc: David Hildenbrand Cc: Jonathan Corbet Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: SeongJae Park Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- Documentation/mm/damon/design.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Documentation/mm/damon/design.rst b/Documentation/mm/damon/design.rst index c16a3bb288d0..457d6e8bc787 100644 --- a/Documentation/mm/damon/design.rst +++ b/Documentation/mm/damon/design.rst @@ -686,9 +686,11 @@ mechanism tries to make ``current_value`` of ``target_metric`` be same to (1/10,000). - ``inactive_mem_bp``: Inactive to active + inactive (LRU) memory size ratio in bp (1/10,000). +- ``node_eligible_mem_bp``: Scheme target access pattern-eligible memory ratio + of a node in bp (1/10,000). -``nid`` is optionally required for only ``node_mem_used_bp``, -``node_mem_free_bp``, ``node_memcg_used_bp`` and ``node_memcg_free_bp`` to +``nid`` is optionally required for ``node_mem_used_bp``, ``node_mem_free_bp``, +``node_memcg_used_bp``, ``node_memcg_free_bp`` and ``node_eligible_mem_bp`` to point the specific NUMA node. ``path`` is optionally required for only ``node_memcg_used_bp`` and From 2771eb50c419048b142cfdc89dbc51cbe4fe0bb2 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Tue, 30 Jun 2026 07:17:16 -0700 Subject: [PATCH 273/562] Docs/ABI/damon: document probe files DAMON ABI document is not updated for the DAMON probe sysfs files. Update. Link: https://lore.kernel.org/20260630141726.92246-3-sj@kernel.org Signed-off-by: SJ Park Cc: Brendan Higgins Cc: David Hildenbrand Cc: Jonathan Corbet Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- .../ABI/testing/sysfs-kernel-mm-damon | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-kernel-mm-damon b/Documentation/ABI/testing/sysfs-kernel-mm-damon index 4fdec63a47d4..dd6b5bd76e11 100644 --- a/Documentation/ABI/testing/sysfs-kernel-mm-damon +++ b/Documentation/ABI/testing/sysfs-kernel-mm-damon @@ -157,6 +157,46 @@ Description: Writing a value to this file sets the maximum number of monitoring regions of the DAMON context as the value. Reading this file returns the value. +What: /sys/kernel/mm/damon/admin/kdamonds//contexts//monitoring_attrs/probes/nr_probes +Date: May 2026 +Contact: SJ Park +Description: Writing a number 'N' to this file creates the number of + directories for each DAMON probe named '0' to 'N-1' under the + probes/ directory. + +What: /sys/kernel/mm/damon/admin/kdamonds//contexts//monitoring_attrs/probes/

/filters/nr_filters +Date: May 2026 +Contact: SJ Park +Description: Writing a number 'N' to this file creates the number of + directories for each DAMON probe filter named '0' to 'N-1' + under the filters/ directory. + +What: /sys/kernel/mm/damon/admin/kdamonds//contexts//monitoring_attrs/probes/

/filters//type +Date: May 2026 +Contact: SJ Park +Description: Writing to and reading from this file sets and gets the type of + the memory of the interest. + +What: /sys/kernel/mm/damon/admin/kdamonds//contexts//monitoring_attrs/probes/

/filters//path +Date: May 2026 +Contact: SJ Park +Description: If 'memcg' is written to the 'type' file, writing to and + reading from this file sets and gets the path to the memory + cgroup of the interest. + +What: /sys/kernel/mm/damon/admin/kdamonds//contexts//monitoring_attrs/probes/

/filters//matching +Date: May 2026 +Contact: SJ Park +Description: Writing 'Y' or 'N' to this file sets whether the filter is for + the memory of the 'type', or all except the 'type'. + +What: /sys/kernel/mm/damon/admin/kdamonds//contexts//monitoring_attrs/probes/

/filters//allow +Date: May 2026 +Contact: SJ Park +Description: Writing 'Y' or 'N' to this file sets whether to allow or reject + hitting the probe for the memory that satisfies the 'type' and + the 'matching' of the directory. + What: /sys/kernel/mm/damon/admin/kdamonds//contexts//targets/nr_targets Date: Mar 2022 Contact: SJ Park From fdba31117ca7b641b6e9d3b8cdd15d08c29f5a67 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Tue, 30 Jun 2026 07:17:17 -0700 Subject: [PATCH 274/562] mm/damon/tests/core-kunit: test damon_rand() Commit 9012c4e647df ("mm/damon: replace damon_rand() with a per-ctx lockless PRNG") optimized DAMON for better performance. Add a kunit test for ensuring the bounds of the output. Link: https://lore.kernel.org/20260630141726.92246-4-sj@kernel.org Signed-off-by: SJ Park Cc: Brendan Higgins Cc: David Hildenbrand Cc: Jonathan Corbet Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/damon/tests/core-kunit.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/mm/damon/tests/core-kunit.h b/mm/damon/tests/core-kunit.h index a00168730445..0ec7d14d354e 100644 --- a/mm/damon/tests/core-kunit.h +++ b/mm/damon/tests/core-kunit.h @@ -1546,6 +1546,20 @@ static void damon_test_walk_control_obsolete(struct kunit *test) damon_destroy_ctx(ctx); } +static void damon_test_rand(struct kunit *test) +{ + struct damon_ctx ctx; + int i; + + prandom_seed_state(&ctx.rnd_state, get_random_u64()); + for (i = 0; i < 10000; i++) { + unsigned long rnd = damon_rand(&ctx, 0, 10); + + KUNIT_EXPECT_GE(test, rnd, 0); + KUNIT_EXPECT_LE(test, rnd, 9); + } +} + static struct kunit_case damon_test_cases[] = { KUNIT_CASE(damon_test_target), KUNIT_CASE(damon_test_regions), @@ -1577,6 +1591,7 @@ static struct kunit_case damon_test_cases[] = { KUNIT_CASE(damon_test_apply_min_nr_regions), KUNIT_CASE(damon_test_is_last_region), KUNIT_CASE(damon_test_walk_control_obsolete), + KUNIT_CASE(damon_test_rand), {}, }; From 85a2ab1c8be59691adb0731e2bb146cc3579a631 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Tue, 30 Jun 2026 07:17:18 -0700 Subject: [PATCH 275/562] selftests/damon/sysfs.sh: test multiple probe dirs creation DAMON sysfs essential file operations test (sysfs.sh) was extended to test DAMON probes sysfs directory, by commit 14885da09b0f ("selftests/damon/sysfs.sh: test probes dir"). Unlike other DAMON sysfs files, it is testing only a single directory case. Extend it for multiple directories. Link: https://lore.kernel.org/20260630141726.92246-5-sj@kernel.org Signed-off-by: SJ Park Cc: Brendan Higgins Cc: David Hildenbrand Cc: Jonathan Corbet Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- tools/testing/selftests/damon/sysfs.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/testing/selftests/damon/sysfs.sh b/tools/testing/selftests/damon/sysfs.sh index 2eaaa5ae3c5e..d528dfea44c3 100755 --- a/tools/testing/selftests/damon/sysfs.sh +++ b/tools/testing/selftests/damon/sysfs.sh @@ -346,8 +346,13 @@ test_probes() ensure_write_succ "$probes_dir/nr_probes" "1" "valid input" test_probe "$probes_dir/0" + ensure_write_succ "$probes_dir/nr_probes" "2" "valid input" + test_probe "$probes_dir/0" + test_probe "$probes_dir/1" + ensure_write_succ "$probes_dir/nr_probes" "0" "valid input" ensure_dir "$probes_dir/0" "not_exist" + ensure_dir "$probes_dir/1" "not_exist" } test_monitoring_attrs() From dd7933641670c7147ed77ab90989bce42fbcb316 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Tue, 30 Jun 2026 07:17:19 -0700 Subject: [PATCH 276/562] selftests/damon/sysfs.sh: test {core,ops}_filters/ directories DAMON sysfs interface essential file operations test (sysf.sh) is not testing DAMOS {core,ops}_filters directories. Add the tests. Link: https://lore.kernel.org/20260630141726.92246-6-sj@kernel.org Signed-off-by: SJ Park Cc: Brendan Higgins Cc: David Hildenbrand Cc: Jonathan Corbet Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- tools/testing/selftests/damon/sysfs.sh | 28 ++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/tools/testing/selftests/damon/sysfs.sh b/tools/testing/selftests/damon/sysfs.sh index d528dfea44c3..78eea0d13c27 100755 --- a/tools/testing/selftests/damon/sysfs.sh +++ b/tools/testing/selftests/damon/sysfs.sh @@ -103,10 +103,28 @@ test_filter() { filter_dir=$1 ensure_file "$filter_dir/type" "exist" "600" - ensure_write_succ "$filter_dir/type" "anon" "valid input" - ensure_write_succ "$filter_dir/type" "memcg" "valid input" - ensure_write_succ "$filter_dir/type" "addr" "valid input" - ensure_write_succ "$filter_dir/type" "target" "valid input" + + local dir_name=$(basename "$(dirname "$filter_dir")") + if [ "$dir_name" = "filters" ] || [ "$dir_name" = "ops_filters" ] + then + ensure_write_succ "$filter_dir/type" "anon" "valid input" + ensure_write_succ "$filter_dir/type" "memcg" "valid input" + fi + if [ "$dir_name" = "filters" ] || [ "$dir_name" = "core_filters" ] + then + ensure_write_succ "$filter_dir/type" "addr" "valid input" + ensure_write_succ "$filter_dir/type" "target" "valid input" + fi + if [ "$dir_name" = "core_filters" ] + then + ensure_write_fail "$filter_dir/type" "anon" "ops type" + ensure_write_fail "$filter_dir/type" "memcg" "ops type" + fi + if [ "$dir_name" = "ops_filters" ] + then + ensure_write_fail "$filter_dir/type" "addr" "core type" + ensure_write_fail "$filter_dir/type" "target" "core type" + fi ensure_write_fail "$filter_dir/type" "foo" "invalid input" ensure_file "$filter_dir/matching" "exist" "600" ensure_file "$filter_dir/memcg_path" "exist" "600" @@ -208,6 +226,8 @@ test_scheme() test_quotas "$scheme_dir/quotas" test_watermarks "$scheme_dir/watermarks" test_filters "$scheme_dir/filters" + test_filters "$scheme_dir/core_filters" + test_filters "$scheme_dir/ops_filters" test_stats "$scheme_dir/stats" test_tried_regions "$scheme_dir/tried_regions" } From fd4109c3fd6708f5b8122e17e4fa5c1590b71cb2 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Tue, 30 Jun 2026 07:17:20 -0700 Subject: [PATCH 277/562] selftests/damon/sysfs.sh: test dests dir DAMON selftest interface essential file operations test (sysfs.sh) is not testing DAMOS dests/ directory. Add the test. Link: https://lore.kernel.org/20260630141726.92246-7-sj@kernel.org Signed-off-by: SJ Park Cc: Brendan Higgins Cc: David Hildenbrand Cc: Jonathan Corbet Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- tools/testing/selftests/damon/sysfs.sh | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tools/testing/selftests/damon/sysfs.sh b/tools/testing/selftests/damon/sysfs.sh index 78eea0d13c27..f8d2092be004 100755 --- a/tools/testing/selftests/damon/sysfs.sh +++ b/tools/testing/selftests/damon/sysfs.sh @@ -99,6 +99,29 @@ test_stats() done } +test_dest() +{ + dest_dir=$1 + ensure_file "$dest_dir/id" "exist" "600" + ensure_file "$dest_dir/weight" "exist" "600" +} + +test_dests() +{ + dests_dir=$1 + ensure_file "$dests_dir/nr_dests" "exist" "600" + ensure_write_succ "$dests_dir/nr_dests" "1" "valid input" + test_dest "$dests_dir/0" + + ensure_write_succ "$dests_dir/nr_dests" "2" "valid input" + test_dest "$dests_dir/0" + test_dest "$dests_dir/1" + + ensure_write_succ "$dests_dir/nr_dests" "0" "valid input" + ensure_dir "$dests_dir/0" "not_exist" + ensure_dir "$dests_dir/1" "not_exist" +} + test_filter() { filter_dir=$1 @@ -225,6 +248,7 @@ test_scheme() ensure_file "$scheme_dir/apply_interval_us" "exist" "600" test_quotas "$scheme_dir/quotas" test_watermarks "$scheme_dir/watermarks" + test_dests "$scheme_dir/dests" test_filters "$scheme_dir/filters" test_filters "$scheme_dir/core_filters" test_filters "$scheme_dir/ops_filters" From b50c689feeadf5b3bec5900d058e0955bb97c776 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Tue, 30 Jun 2026 07:17:21 -0700 Subject: [PATCH 278/562] selftests/damon/sysfs.sh: test all files in quota goal dir DAMON sysfs interface for DAMOS quota has quite extended since its initial introduction. The test case for that in DAMON sysfs interface essential file operations test (sysfs.sh) has not accordingly extended, though. Extend the test case to test all existing files. Link: https://lore.kernel.org/20260630141726.92246-8-sj@kernel.org Signed-off-by: SJ Park Cc: Brendan Higgins Cc: David Hildenbrand Cc: Jonathan Corbet Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- tools/testing/selftests/damon/sysfs.sh | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tools/testing/selftests/damon/sysfs.sh b/tools/testing/selftests/damon/sysfs.sh index f8d2092be004..b43deee60fe9 100755 --- a/tools/testing/selftests/damon/sysfs.sh +++ b/tools/testing/selftests/damon/sysfs.sh @@ -199,6 +199,20 @@ test_goal() ensure_dir "$goal_dir" "exist" ensure_file "$goal_dir/target_value" "exist" "600" ensure_file "$goal_dir/current_value" "exist" "600" + ensure_file "$goal_dir/target_metric" "exist" "600" + local fpath="$goal_dir/target_metric" + ensure_write_succ "$fpath" "user_input" "valid input" + ensure_write_succ "$fpath" "some_mem_psi_us" "valid input" + ensure_write_succ "$fpath" "node_mem_used_bp" "valid input" + ensure_write_succ "$fpath" "node_mem_free_bp" "valid input" + ensure_write_succ "$fpath" "node_memcg_used_bp" "valid input" + ensure_write_succ "$fpath" "node_memcg_free_bp" "valid input" + ensure_write_succ "$fpath" "active_mem_bp" "valid input" + ensure_write_succ "$fpath" "inactive_mem_bp" "valid input" + ensure_write_succ "$fpath" "node_eligible_mem_bp" "valid input" + ensure_write_fail "$fpath" "foo" "invalid input" + ensure_file "$goal_dir/nid" "exist" "600" + ensure_file "$goal_dir/path" "exist" "600" } test_goals() From 9c98dadaab8b84ae3f50ef25348fd940fa626e62 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Tue, 30 Jun 2026 07:17:22 -0700 Subject: [PATCH 279/562] mm/damon/core: reduce range setup in damon_commit_target_regions() damon_commit_target_regions() calls damon_set_regions() for updating the destination target's monitoring target region boundaries. It sets the boundaries same to source target's monitoring regions, even if they are adjacent. Meanwhile, damon_set_region() sets the destination target regions exactly the same to the source, only when the target regions are empty. When there are existing target regions, only a few regions are expanded or shrunk to fit on only the boundaries for disjoint regions in the source. Hence the adjacent source ranges mean nothing in common cases. When there are many regions, such adjacent range setup is only a waste of time and space. We recently found [1] it is actually causing memory overhead. Setup the ranges for only distinct ranges. Link: https://lore.kernel.org/20260630141726.92246-9-sj@kernel.org Link: https://lore.kernel.org/20260603112306.58490-1-akinobu.mita@gmail.com [1] Signed-off-by: SJ Park Cc: Brendan Higgins Cc: David Hildenbrand Cc: Jonathan Corbet Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/damon/core.c | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/mm/damon/core.c b/mm/damon/core.c index a99458c57851..dbff0e4c1c17 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -1360,21 +1360,33 @@ static struct damon_target *damon_nth_target(int n, struct damon_ctx *ctx) static int damon_commit_target_regions(struct damon_target *dst, struct damon_target *src, unsigned long src_min_region_sz) { - struct damon_region *src_region; + struct damon_region *src_region, *prev = NULL; struct damon_addr_range *ranges; int i = 0, err; - damon_for_each_region(src_region, src) - i++; + damon_for_each_region(src_region, src) { + if (!prev || prev->ar.end != src_region->ar.start) + i++; + prev = src_region; + } if (!i) return 0; ranges = kvmalloc_objs(*ranges, i, GFP_KERNEL | __GFP_NOWARN); if (!ranges) return -ENOMEM; + prev = NULL; i = 0; - damon_for_each_region(src_region, src) - ranges[i++] = src_region->ar; + damon_for_each_region(src_region, src) { + if (!prev) { + ranges[i].start = src_region->ar.start; + } else if (prev->ar.end != src_region->ar.start) { + ranges[i++].end = prev->ar.end; + ranges[i].start = src_region->ar.start; + } + prev = src_region; + } + ranges[i++].end = damon_last_region(src)->ar.end; err = damon_set_regions(dst, ranges, i, src_min_region_sz); kvfree(ranges); return err; From 62f7f0132e3b8a9c567d53a7a804498b7b33852f Mon Sep 17 00:00:00 2001 From: SJ Park Date: Tue, 30 Jun 2026 07:17:23 -0700 Subject: [PATCH 280/562] mm/damon/sysfs: split probe setup function out damon_sysfs_set_probes() function is relatively long. It has two nested loop for setting two nested entities, namely probe and filter. Split out the probe level setup for readability. Link: https://lore.kernel.org/20260630141726.92246-10-sj@kernel.org Signed-off-by: SJ Park Cc: Brendan Higgins Cc: David Hildenbrand Cc: Jonathan Corbet Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/damon/sysfs.c | 80 ++++++++++++++++++++++++++++-------------------- 1 file changed, 46 insertions(+), 34 deletions(-) diff --git a/mm/damon/sysfs.c b/mm/damon/sysfs.c index e3526a263e20..02afd37b04df 100644 --- a/mm/damon/sysfs.c +++ b/mm/damon/sysfs.c @@ -1909,47 +1909,59 @@ static int damon_sysfs_set_attrs(struct damon_ctx *ctx, return damon_set_attrs(ctx, &attrs); } -static int damon_sysfs_set_probes(struct damon_ctx *ctx, - struct damon_sysfs_probes *sys_probes) +static int damon_sysfs_set_probe(struct damon_probe *probe, + struct damon_sysfs_probe *sys_probe) { + struct damon_sysfs_filters *sys_filters; int i; - for (i = 0; i < sys_probes->nr; i++) { - struct damon_sysfs_filters *sys_filters = - sys_probes->probes_arr[i]->filters; - struct damon_probe *c; - int j; + sys_filters = sys_probe->filters; + if (!sys_filters) + return 0; - if (!sys_filters) - continue; - c = damon_new_probe(); - if (!c) + for (i = 0; i < sys_filters->nr; i++) { + struct damon_sysfs_filter *sys_filter = + sys_filters->filters_arr[i]; + struct damon_filter *filter; + + filter = damon_new_filter(sys_filter->type, + sys_filter->matching, + sys_filter->allow); + if (!filter) return -ENOMEM; - damon_add_probe(ctx, c); - - for (j = 0; j < sys_filters->nr; j++) { - struct damon_sysfs_filter *sys_filter = - sys_filters->filters_arr[j]; - struct damon_filter *filter; - - filter = damon_new_filter(sys_filter->type, - sys_filter->matching, - sys_filter->allow); - if (!filter) - return -ENOMEM; - if (filter->type == DAMON_FILTER_TYPE_MEMCG) { - int err; - - err = damon_sysfs_memcg_path_to_id( - sys_filter->path, - &filter->memcg_id); - if (err) { - damon_destroy_filter(filter); - return err; - } + if (filter->type == DAMON_FILTER_TYPE_MEMCG) { + int err; + + err = damon_sysfs_memcg_path_to_id( + sys_filter->path, + &filter->memcg_id); + if (err) { + damon_destroy_filter(filter); + return err; } - damon_add_filter(c, filter); } + damon_add_filter(probe, filter); + } + return 0; +} + +static int damon_sysfs_set_probes(struct damon_ctx *ctx, + struct damon_sysfs_probes *sys_probes) +{ + int i, err; + + for (i = 0; i < sys_probes->nr; i++) { + struct damon_sysfs_probe *sys_probe; + struct damon_probe *p; + + p = damon_new_probe(); + if (!p) + return -ENOMEM; + damon_add_probe(ctx, p); + sys_probe = sys_probes->probes_arr[i]; + err = damon_sysfs_set_probe(p, sys_probe); + if (err) + return err; } return 0; } From f6dd5f5355f5de563622ab5d76a1b23c9145727e Mon Sep 17 00:00:00 2001 From: SJ Park Date: Tue, 30 Jun 2026 07:17:24 -0700 Subject: [PATCH 281/562] mm/damon/sysfs: split out filters setup function damon_sysfs_set_probe() is doing not only probe setup but also filters setup. Split out filters setup for readability. Link: https://lore.kernel.org/20260630141726.92246-11-sj@kernel.org Signed-off-by: SJ Park Cc: Brendan Higgins Cc: David Hildenbrand Cc: Jonathan Corbet Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/damon/sysfs.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/mm/damon/sysfs.c b/mm/damon/sysfs.c index 02afd37b04df..5b2ae2fe948f 100644 --- a/mm/damon/sysfs.c +++ b/mm/damon/sysfs.c @@ -1909,16 +1909,11 @@ static int damon_sysfs_set_attrs(struct damon_ctx *ctx, return damon_set_attrs(ctx, &attrs); } -static int damon_sysfs_set_probe(struct damon_probe *probe, - struct damon_sysfs_probe *sys_probe) +static int damon_sysfs_set_filters(struct damon_probe *probe, + struct damon_sysfs_filters *sys_filters) { - struct damon_sysfs_filters *sys_filters; int i; - sys_filters = sys_probe->filters; - if (!sys_filters) - return 0; - for (i = 0; i < sys_filters->nr; i++) { struct damon_sysfs_filter *sys_filter = sys_filters->filters_arr[i]; @@ -1945,6 +1940,17 @@ static int damon_sysfs_set_probe(struct damon_probe *probe, return 0; } +static int damon_sysfs_set_probe(struct damon_probe *probe, + struct damon_sysfs_probe *sys_probe) +{ + struct damon_sysfs_filters *sys_filters; + + sys_filters = sys_probe->filters; + if (!sys_filters) + return 0; + return damon_sysfs_set_filters(probe, sys_filters); +} + static int damon_sysfs_set_probes(struct damon_ctx *ctx, struct damon_sysfs_probes *sys_probes) { From ea22d50d70f54f207290358e85f86f7cf1781677 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Tue, 30 Jun 2026 07:17:25 -0700 Subject: [PATCH 282/562] mm/damon/sysfs: fix typos in probe_{add,rm}_dirs: s/attr/probe/ damon_sysfs_probe_{add,rm}_dirs names a variable for damon_sysf_probe as 'attr'. Probably a trivial copy-pasta error, but it makes the code not pleasant to read. Fix those. Link: https://lore.kernel.org/20260630141726.92246-12-sj@kernel.org Signed-off-by: SJ Park Cc: Brendan Higgins Cc: David Hildenbrand Cc: Jonathan Corbet Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/damon/sysfs.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/mm/damon/sysfs.c b/mm/damon/sysfs.c index 5b2ae2fe948f..6710b6d019bf 100644 --- a/mm/damon/sysfs.c +++ b/mm/damon/sysfs.c @@ -1073,7 +1073,7 @@ static struct damon_sysfs_probe *damon_sysfs_probe_alloc(void) return kzalloc_obj(struct damon_sysfs_probe); } -static int damon_sysfs_probe_add_dirs(struct damon_sysfs_probe *attr) +static int damon_sysfs_probe_add_dirs(struct damon_sysfs_probe *probe) { struct damon_sysfs_filters *filters; int err; @@ -1081,22 +1081,22 @@ static int damon_sysfs_probe_add_dirs(struct damon_sysfs_probe *attr) filters = damon_sysfs_filters_alloc(); if (!filters) return -ENOMEM; - attr->filters = filters; + probe->filters = filters; err = kobject_init_and_add(&filters->kobj, &damon_sysfs_filters_ktype, - &attr->kobj, "filters"); + &probe->kobj, "filters"); if (err) { kobject_put(&filters->kobj); - attr->filters = NULL; + probe->filters = NULL; } return err; } -static void damon_sysfs_probe_rm_dirs(struct damon_sysfs_probe *attr) +static void damon_sysfs_probe_rm_dirs(struct damon_sysfs_probe *probe) { - if (attr->filters) { - damon_sysfs_filters_rm_dirs(attr->filters); - kobject_put(&attr->filters->kobj); + if (probe->filters) { + damon_sysfs_filters_rm_dirs(probe->filters); + kobject_put(&probe->filters->kobj); } } From 6b59d89b47ae5b67328e18d467ab13eb89579c72 Mon Sep 17 00:00:00 2001 From: JP Kobryn Date: Mon, 22 Jun 2026 17:46:00 -0700 Subject: [PATCH 283/562] mm/page_alloc: use existing highatomic reserves on the buddy fastpath ALLOC_HIGHATOMIC currently provides both access to MIGRATE_HIGHATOMIC free pages and permission to create new highatomic pageblock reserves. This makes it unsuitable for the fastpath. However, the fastpath can reach rmqueue_buddy() while MIGRATE_HIGHATOMIC reserves have free pages available. In this situation, the allocation can fall back to other migratetypes without trying those reserves first. Allow high-priority non-blocking allocations to use existing MIGRATE_HIGHATOMIC reserves on the buddy fastpath without growing them. First tighten the criteria for reserving pageblocks so that growth may only occur in the slowpath. Then allow fastpath usage by enabling ALLOC_HIGHATOMIC when the GFP mask describes a non-blocking high-priority allocation. This logic has been factored out from gfp_to_alloc_flags() to a new function gfp_to_alloc_flags_nonblocking(). A UDP receive workload was run with free MIGRATE_HIGHATOMIC pageblocks available in the target zone. Before this patch, the workload did not consume these blocks. With this patch, eligible order-1 allocations reaching the buddy path consumed existing MIGRATE_HIGHATOMIC pageblocks, with no highatomic misses observed. The workload did not grow highatomic reserves and NAPI page-frag allocations remained healthy with no failures or order-0 fallbacks. Link: https://lore.kernel.org/20260623004600.113347-1-jp.kobryn@linux.dev Signed-off-by: JP Kobryn Reviewed-by: Vlastimil Babka (SUSE) Acked-by: Johannes Weiner Reviewed-by: Shakeel Butt Cc: Brendan Jackman Cc: David Hildenbrand Cc: Frank van der Linden Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Suren Baghdasaryan Cc: Zi Yan Signed-off-by: Andrew Morton --- mm/page_alloc.c | 44 ++++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/mm/page_alloc.c b/mm/page_alloc.c index 549fa83045eb..62f71ece7ca1 100644 --- a/mm/page_alloc.c +++ b/mm/page_alloc.c @@ -3249,10 +3249,11 @@ struct page *rmqueue_buddy(struct zone *preferred_zone, struct zone *zone, } while (check_new_pages(page, order)); /* - * If this is a high-order atomic allocation then check - * if the pageblock should be reserved for the future + * Slowpath (precarious) high-atomic allocations may reserve + * a pageblock for future use. */ - if (unlikely(alloc_flags & ALLOC_HIGHATOMIC)) + if (unlikely((alloc_flags & ALLOC_HIGHATOMIC) && + ((alloc_flags & ALLOC_WMARK_MASK) == ALLOC_WMARK_MIN))) reserve_highatomic_pageblock(page, order, zone); __count_zid_vm_events(PGALLOC, page_zonenum(page), 1 << order); @@ -4472,6 +4473,29 @@ static void wake_all_kswapds(unsigned int order, gfp_t gfp_mask, } } +static inline unsigned int +gfp_to_alloc_flags_nonblocking(gfp_t gfp_mask, unsigned int order) +{ + unsigned int alloc_flags = 0; + + if (gfp_mask & __GFP_DIRECT_RECLAIM) + return 0; + + /* + * Not worth trying to allocate harder for __GFP_NOMEMALLOC even + * if it can't schedule. + */ + if (gfp_mask & __GFP_NOMEMALLOC) + return 0; + + alloc_flags |= ALLOC_NON_BLOCK; + + if (order > 0 && (gfp_mask & __GFP_HIGH)) + alloc_flags |= ALLOC_HIGHATOMIC; + + return alloc_flags; +} + static inline unsigned int gfp_to_alloc_flags(gfp_t gfp_mask, unsigned int order) { @@ -4488,18 +4512,9 @@ gfp_to_alloc_flags(gfp_t gfp_mask, unsigned int order) if (gfp_mask & __GFP_KSWAPD_RECLAIM) alloc_flags |= ALLOC_KSWAPD; - if (!(gfp_mask & __GFP_DIRECT_RECLAIM)) { - /* - * Not worth trying to allocate harder for __GFP_NOMEMALLOC even - * if it can't schedule. - */ - if (!(gfp_mask & __GFP_NOMEMALLOC)) { - alloc_flags |= ALLOC_NON_BLOCK; - - if (order > 0 && (alloc_flags & ALLOC_MIN_RESERVE)) - alloc_flags |= ALLOC_HIGHATOMIC; - } + alloc_flags |= gfp_to_alloc_flags_nonblocking(gfp_mask, order); + if (!(gfp_mask & __GFP_DIRECT_RECLAIM)) { /* * Ignore cpuset mems for non-blocking __GFP_HIGH (probably * GFP_ATOMIC) rather than fail, see the comment for @@ -5292,6 +5307,7 @@ struct page *__alloc_frozen_pages_noprof(gfp_t gfp, unsigned int order, * memory until all local zones are considered. */ alloc_flags |= alloc_flags_nofragment(zonelist_zone(ac.preferred_zoneref), gfp); + alloc_flags |= gfp_to_alloc_flags_nonblocking(gfp, order) & ALLOC_HIGHATOMIC; /* First allocation attempt */ page = get_page_from_freelist(alloc_gfp, order, alloc_flags, &ac); From 65140d0426e31d3422d3b30bdb991d13127336f1 Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Tue, 30 Jun 2026 15:08:25 -0400 Subject: [PATCH 284/562] maple_tree: add rcu locking check when LOCKDEP is enabled Patch series "maple_tree: lock checking and clean ups", v2. The goals of this series are: 1. Lock issue detection A number of syzbot reports are incorrectly pointing to the mm exit as a source of the locking error. The first three patches attempt to help users detect errors in their locking - but they still have to use LOCKDEP. I guess it's still down to hope and prayers. 2. Documentation fixes The documentation was lacking clarity, there are updates to try and help the users, especially around the erase() cases. 3. Two benign issues The cyclic allocator may have a race, although no in-kernel user can hit it. The erase functions may cause allocation issues if used with the incorrect locking type, but none are present in-tree. Beyond these goals there are some test fixes, some general speed-up patches targeting extra work and cycles, and dropping dead code. This patch (of 19): When CONFIG_LOCKDEP and CONFIG_RCU_STRICT_GRACE_PERIOD is enabled, check for rcu locking issues by recording the grace period in the maple state and checking the rcu window is still valid whenever the maple state is reused with a state that is not MA_START or MA_PAUSED. Link: https://lore.kernel.org/20260630190843.3563858-1-liam@infradead.org Link: https://lore.kernel.org/20260630190843.3563858-2-liam@infradead.org Signed-off-by: Liam R. Howlett (Oracle) Cc: Boqun Feng Cc: Chris Mason Cc: Chuck Lever Cc: Ingo Molnar Cc: Jason Gunthorpe Cc: Joe Perches Cc: Peter Zijlstra Cc: Rik van Riel Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- include/linux/maple_tree.h | 3 +++ lib/maple_tree.c | 50 +++++++++++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/include/linux/maple_tree.h b/include/linux/maple_tree.h index 4a5631906aff..11a16c3508dc 100644 --- a/include/linux/maple_tree.h +++ b/include/linux/maple_tree.h @@ -485,6 +485,9 @@ struct ma_state { unsigned char mas_flags; unsigned char end; /* The end of the node */ enum store_type store_type; /* The type of store needed for this operation */ +#if IS_ENABLED(CONFIG_LOCKDEP) && IS_ENABLED(CONFIG_RCU_STRICT_GRACE_PERIOD) + unsigned long rcu_gp; +#endif }; struct ma_wr_state { diff --git a/lib/maple_tree.c b/lib/maple_tree.c index e52876435b77..3dff0435c13a 100644 --- a/lib/maple_tree.c +++ b/lib/maple_tree.c @@ -1153,6 +1153,42 @@ static inline void mas_free(struct ma_state *mas, struct maple_enode *used) ma_free_rcu(mte_to_node(used)); } +void mas_lock_check(struct ma_state *mas) +{ + +#if IS_ENABLED(CONFIG_LOCKDEP) && IS_ENABLED(CONFIG_RCU_STRICT_GRACE_PERIOD) + if (!mas_is_active(mas)) + return; + + if (!mt_locked(mas->tree)) { + if (mt_in_rcu(mas->tree)) + WARN_ON_ONCE(poll_state_synchronize_rcu(mas->rcu_gp)); + } +#endif + +} + +void mas_init_lock_check(struct ma_state *mas) +{ +#if IS_ENABLED(CONFIG_LOCKDEP) && IS_ENABLED(CONFIG_RCU_STRICT_GRACE_PERIOD) + if (!mt_locked(mas->tree)) { + if (mt_in_rcu(mas->tree)) + mas->rcu_gp = get_state_synchronize_rcu(); + } +#endif + +} + +static void mas_may_init_lock_check(struct ma_state *mas) +{ +#if IS_ENABLED(CONFIG_LOCKDEP) && IS_ENABLED(CONFIG_RCU_STRICT_GRACE_PERIOD) + if (mas_is_start(mas) || mas_is_paused(mas)) + mas_init_lock_check(mas); + else + mas_lock_check(mas); +#endif +} + /* * mas_start() - Sets up maple state for operations. * @mas: The maple state. @@ -1171,6 +1207,7 @@ static inline struct maple_enode *mas_start(struct ma_state *mas) if (likely(mas_is_start(mas))) { struct maple_enode *root; + mas_init_lock_check(mas); mas->min = 0; mas->max = ULONG_MAX; @@ -4360,6 +4397,7 @@ void *mas_walk(struct ma_state *mas) { void *entry; + mas_may_init_lock_check(mas); if (!mas_is_active(mas) && !mas_is_start(mas)) mas->status = ma_start; retry: @@ -4997,6 +5035,7 @@ static void mas_may_activate(struct ma_state *mas) mas->status = ma_start; } else { mas->status = ma_active; + mas_lock_check(mas); } } @@ -5074,6 +5113,7 @@ void *mas_next(struct ma_state *mas, unsigned long max) { void *entry = NULL; + mas_may_init_lock_check(mas); if (mas_next_setup(mas, max, &entry)) return entry; @@ -5097,6 +5137,7 @@ void *mas_next_range(struct ma_state *mas, unsigned long max) { void *entry = NULL; + mas_may_init_lock_check(mas); if (mas_next_setup(mas, max, &entry)) return entry; @@ -5205,6 +5246,7 @@ void *mas_prev(struct ma_state *mas, unsigned long min) { void *entry = NULL; + mas_may_init_lock_check(mas); if (mas_prev_setup(mas, min, &entry)) return entry; @@ -5228,6 +5270,7 @@ void *mas_prev_range(struct ma_state *mas, unsigned long min) { void *entry = NULL; + mas_may_init_lock_check(mas); if (mas_prev_setup(mas, min, &entry)) return entry; @@ -5274,6 +5317,7 @@ EXPORT_SYMBOL_GPL(mt_prev); */ void mas_pause(struct ma_state *mas) { + mas_lock_check(mas); mas->status = ma_pause; mas->node = NULL; } @@ -5382,6 +5426,7 @@ void *mas_find(struct ma_state *mas, unsigned long max) { void *entry = NULL; + mas_may_init_lock_check(mas); if (mas_find_setup(mas, max, &entry)) return entry; @@ -5409,6 +5454,7 @@ void *mas_find_range(struct ma_state *mas, unsigned long max) { void *entry = NULL; + mas_may_init_lock_check(mas); if (mas_find_setup(mas, max, &entry)) return entry; @@ -5521,6 +5567,7 @@ void *mas_find_rev(struct ma_state *mas, unsigned long min) { void *entry = NULL; + mas_may_init_lock_check(mas); if (mas_find_rev_setup(mas, min, &entry)) return entry; @@ -5547,6 +5594,7 @@ void *mas_find_range_rev(struct ma_state *mas, unsigned long min) { void *entry = NULL; + mas_may_init_lock_check(mas); if (mas_find_rev_setup(mas, min, &entry)) return entry; @@ -5623,7 +5671,7 @@ bool mas_nomem(struct ma_state *mas, gfp_t gfp) if (!mas->sheaf && !mas->alloc) return false; - mas->status = ma_start; + mas_reset(mas); return true; } From 54bec97938f5c8868baf6095f0445789d85063de Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Tue, 30 Jun 2026 15:08:26 -0400 Subject: [PATCH 285/562] locking/lockdep: add sequence counter to held_lock Add an 8 bit small sequence counter to the held_lock struct to detect if the lock as been dropped and reacquired. This is useful when a data structure depends on a constant locking context, but is not able to detect locking and unlocking of the lock through its own API. Since the __lock_unpin_lock() will no longer detect underflow by casting the unsigned int to a signed int, update the casting code to use a temp variable for calculations using a signed int. Link: https://lore.kernel.org/20260630190843.3563858-3-liam@infradead.org Link: https://lore.kernel.org/all/h3tpnj5kzcrxms5picmimtkpg4aypcpip5wbd6bt2rpdj5k7eb@nhtzs3lefrkq/ Signed-off-by: Liam R. Howlett (Oracle) Suggested-by: Peter Zijlstra Cc: Ingo Molnar Cc: Will Deacon Cc: Boqun Feng Cc: Waiman Long Cc: Chris Mason Cc: Chuck Lever Cc: Jason Gunthorpe Cc: Joe Perches Cc: Rik van Riel Signed-off-by: Andrew Morton --- include/linux/lockdep.h | 3 ++ include/linux/lockdep_types.h | 3 +- include/linux/sched.h | 1 + kernel/locking/lockdep.c | 58 ++++++++++++++++++++++++++++++----- 4 files changed, 57 insertions(+), 8 deletions(-) diff --git a/include/linux/lockdep.h b/include/linux/lockdep.h index 621566345406..a6451ecbbe9a 100644 --- a/include/linux/lockdep.h +++ b/include/linux/lockdep.h @@ -273,6 +273,9 @@ extern struct pin_cookie lock_pin_lock(struct lockdep_map *lock); extern void lock_repin_lock(struct lockdep_map *lock, struct pin_cookie); extern void lock_unpin_lock(struct lockdep_map *lock, struct pin_cookie); +extern u32 lock_sequence(struct lockdep_map *lock); +#define lockdep_sequence(lock) lock_sequence(&(lock)->dep_map) + #define lockdep_depth(tsk) (debug_locks ? (tsk)->lockdep_depth : 0) #define lockdep_assert(cond) \ diff --git a/include/linux/lockdep_types.h b/include/linux/lockdep_types.h index eae115a26488..55c4b152fedf 100644 --- a/include/linux/lockdep_types.h +++ b/include/linux/lockdep_types.h @@ -253,7 +253,8 @@ struct held_lock { unsigned int hardirqs_off:1; unsigned int sync:1; unsigned int references:11; /* 32 bits */ - unsigned int pin_count; + unsigned int pin_count:24; + unsigned int seq_count:8; }; #else /* !CONFIG_LOCKDEP */ diff --git a/include/linux/sched.h b/include/linux/sched.h index 373bcc0598d1..14d5ce8dd613 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -1288,6 +1288,7 @@ struct task_struct { u64 curr_chain_key; int lockdep_depth; unsigned int lockdep_recursion; + unsigned int lockdep_seq; struct held_lock held_locks[MAX_LOCK_DEPTH]; #endif diff --git a/kernel/locking/lockdep.c b/kernel/locking/lockdep.c index 2d4c5bab5af8..a69567bdd791 100644 --- a/kernel/locking/lockdep.c +++ b/kernel/locking/lockdep.c @@ -5077,7 +5077,7 @@ static int __lock_is_held(const struct lockdep_map *lock, int read); static int __lock_acquire(struct lockdep_map *lock, unsigned int subclass, int trylock, int read, int check, int hardirqs_off, struct lockdep_map *nest_lock, unsigned long ip, - int references, int pin_count, int sync) + int references, int pin_count, int sync, int seq) { struct task_struct *curr = current; struct lock_class *class = NULL; @@ -5183,6 +5183,7 @@ static int __lock_acquire(struct lockdep_map *lock, unsigned int subclass, hlock->holdtime_stamp = lockstat_clock(); #endif hlock->pin_count = pin_count; + hlock->seq_count = seq; if (check_wait_context(curr, hlock)) return 0; @@ -5388,7 +5389,7 @@ static int reacquire_held_locks(struct task_struct *curr, unsigned int depth, hlock->read, hlock->check, hlock->hardirqs_off, hlock->nest_lock, hlock->acquire_ip, - hlock->references, hlock->pin_count, 0)) { + hlock->references, hlock->pin_count, 0, hlock->seq_count)) { case 0: return 1; case 1: @@ -5669,14 +5670,17 @@ static void __lock_unpin_lock(struct lockdep_map *lock, struct pin_cookie cookie struct held_lock *hlock = curr->held_locks + i; if (match_held_lock(hlock, lock)) { + int pin_count; + if (WARN(!hlock->pin_count, "unpinning an unpinned lock\n")) return; - hlock->pin_count -= cookie.val; + pin_count = hlock->pin_count - cookie.val; - if (WARN((int)hlock->pin_count < 0, "pin count corrupted\n")) - hlock->pin_count = 0; + if (WARN(pin_count < 0, "pin count corrupted\n")) + pin_count = 0; + hlock->pin_count = pin_count; return; } } @@ -5684,6 +5688,24 @@ static void __lock_unpin_lock(struct lockdep_map *lock, struct pin_cookie cookie WARN(1, "unpinning an unheld lock\n"); } +static u32 __lock_sequence(struct lockdep_map *lock) +{ + struct task_struct *curr = current; + int i; + + if (unlikely(!debug_locks)) + return ~0; + + for (i = 0; i < curr->lockdep_depth; i++) { + struct held_lock *hlock = curr->held_locks + i; + + if (match_held_lock(hlock, lock)) + return hlock->seq_count; + } + + return ~0; +} + /* * Check whether we follow the irq-flags state precisely: */ @@ -5866,7 +5888,8 @@ void lock_acquire(struct lockdep_map *lock, unsigned int subclass, lockdep_recursion_inc(); __lock_acquire(lock, subclass, trylock, read, check, - irqs_disabled_flags(flags), nest_lock, ip, 0, 0, 0); + irqs_disabled_flags(flags), nest_lock, ip, 0, 0, 0, + ++current->lockdep_seq); lockdep_recursion_finish(); raw_local_irq_restore(flags); } @@ -5914,7 +5937,8 @@ void lock_sync(struct lockdep_map *lock, unsigned subclass, int read, lockdep_recursion_inc(); __lock_acquire(lock, subclass, 0, read, check, - irqs_disabled_flags(flags), nest_lock, ip, 0, 0, 1); + irqs_disabled_flags(flags), nest_lock, ip, 0, 0, 1, + ++current->lockdep_seq); check_chain_key(current); lockdep_recursion_finish(); raw_local_irq_restore(flags); @@ -6000,6 +6024,26 @@ void lock_unpin_lock(struct lockdep_map *lock, struct pin_cookie cookie) } EXPORT_SYMBOL_GPL(lock_unpin_lock); +u32 lock_sequence(struct lockdep_map *lock) +{ + unsigned long flags; + u32 seq = ~0; + + if (unlikely(!lockdep_enabled())) + return seq; + + raw_local_irq_save(flags); + check_flags(flags); + + lockdep_recursion_inc(); + seq = __lock_sequence(lock); + lockdep_recursion_finish(); + raw_local_irq_restore(flags); + + return seq; +} +EXPORT_SYMBOL_GPL(lock_sequence); + #ifdef CONFIG_LOCK_STAT static void print_lock_contention_bug(struct task_struct *curr, struct lockdep_map *lock, From 28c2b4f7954fdd47f01eafc9fc47ceaf4d832092 Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Tue, 30 Jun 2026 15:08:27 -0400 Subject: [PATCH 286/562] maple_tree: add write lock checking with lockdep sequence numbers Use the lockdep sequence numbers to ensure the write lock is not dropped between write operations. The lockdep sequence is recorded on any walk that starts from the top of the tree and re-checked prior to any operation using an active node. Link: https://lore.kernel.org/20260630190843.3563858-4-liam@infradead.org Signed-off-by: Liam R. Howlett (Oracle) Cc: Boqun Feng Cc: Chris Mason Cc: Chuck Lever Cc: Ingo Molnar Cc: Jason Gunthorpe Cc: Joe Perches Cc: Peter Zijlstra Cc: Rik van Riel Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- include/linux/maple_tree.h | 7 +++-- lib/maple_tree.c | 58 ++++++++++++++++++++++++++++++-------- 2 files changed, 51 insertions(+), 14 deletions(-) diff --git a/include/linux/maple_tree.h b/include/linux/maple_tree.h index 11a16c3508dc..220b380cdd49 100644 --- a/include/linux/maple_tree.h +++ b/include/linux/maple_tree.h @@ -485,9 +485,12 @@ struct ma_state { unsigned char mas_flags; unsigned char end; /* The end of the node */ enum store_type store_type; /* The type of store needed for this operation */ -#if IS_ENABLED(CONFIG_LOCKDEP) && IS_ENABLED(CONFIG_RCU_STRICT_GRACE_PERIOD) +#ifdef CONFIG_LOCKDEP + u32 ld_seq; +#ifdef CONFIG_RCU_STRICT_GRACE_PERIOD unsigned long rcu_gp; -#endif +#endif /* CONFIG_RCU_STRICT_GRACE_PERIOD */ +#endif /* CONFIG_LOCKDEP */ }; struct ma_wr_state { diff --git a/lib/maple_tree.c b/lib/maple_tree.c index 3dff0435c13a..e2f747457231 100644 --- a/lib/maple_tree.c +++ b/lib/maple_tree.c @@ -1153,40 +1153,71 @@ static inline void mas_free(struct ma_state *mas, struct maple_enode *used) ma_free_rcu(mte_to_node(used)); } -void mas_lock_check(struct ma_state *mas) + +#ifdef CONFIG_LOCKDEP +static struct lockdep_map *mas_lockdep_map(struct ma_state *mas) { + struct maple_tree *mt = mas->tree; + + if (mt_external_lock(mt)) + return mt->ma_external_lock; + + return &(mt->ma_lock).dep_map; +} + +#endif -#if IS_ENABLED(CONFIG_LOCKDEP) && IS_ENABLED(CONFIG_RCU_STRICT_GRACE_PERIOD) +static void mas_lock_check(struct ma_state *mas) +{ +#ifdef CONFIG_LOCKDEP + struct lockdep_map *map; if (!mas_is_active(mas)) return; +#ifdef CONFIG_RCU_STRICT_GRACE_PERIOD if (!mt_locked(mas->tree)) { if (mt_in_rcu(mas->tree)) WARN_ON_ONCE(poll_state_synchronize_rcu(mas->rcu_gp)); } -#endif +#endif /* CONFIG_RCU_STRICT_GRACE_PERIOD */ + + map = mas_lockdep_map(mas); + if (map && lock_is_held(map)) + WARN_ON_ONCE(mas->ld_seq != lock_sequence(map)); +#endif /* CONFIG_LOCKDEP */ } -void mas_init_lock_check(struct ma_state *mas) +static void mas_init_lock_check(struct ma_state *mas) { -#if IS_ENABLED(CONFIG_LOCKDEP) && IS_ENABLED(CONFIG_RCU_STRICT_GRACE_PERIOD) +#ifdef CONFIG_LOCKDEP + struct lockdep_map *map; +#ifdef CONFIG_RCU_STRICT_GRACE_PERIOD if (!mt_locked(mas->tree)) { if (mt_in_rcu(mas->tree)) mas->rcu_gp = get_state_synchronize_rcu(); + return; } -#endif +#endif /* CONFIG_RCU_STRICT_GRACE_PERIOD */ + + map = mas_lockdep_map(mas); + if (map && lock_is_held(map)) + mas->ld_seq = lock_sequence(mas_lockdep_map(mas)); +#endif /* CONFIG_LOCKDEP */ } static void mas_may_init_lock_check(struct ma_state *mas) { -#if IS_ENABLED(CONFIG_LOCKDEP) && IS_ENABLED(CONFIG_RCU_STRICT_GRACE_PERIOD) - if (mas_is_start(mas) || mas_is_paused(mas)) +#ifdef CONFIG_LOCKDEP +#ifdef CONFIG_RCU_STRICT_GRACE_PERIOD + if (mas_is_start(mas) || mas_is_paused(mas)) { mas_init_lock_check(mas); - else - mas_lock_check(mas); -#endif + return; + } +#endif /* CONFIG_RCU_STRICT_GRACE_PERIOD */ + mas_lock_check(mas); +#endif /* CONFIG_LOCKDEP */ } /* @@ -4869,6 +4900,7 @@ void *mas_store(struct ma_state *mas, void *entry) { MA_WR_STATE(wr_mas, mas, entry); + mas_may_init_lock_check(mas); trace_ma_write(TP_FCT, mas, 0, entry); #ifdef CONFIG_DEBUG_MAPLE_TREE if (MAS_WARN_ON(mas, mas->index > mas->last)) @@ -4927,6 +4959,7 @@ int mas_store_gfp(struct ma_state *mas, void *entry, gfp_t gfp) MA_WR_STATE(wr_mas, mas, entry); int ret = 0; + mas_may_init_lock_check(mas); retry: mas_wr_preallocate(&wr_mas, entry); if (unlikely(mas_nomem(mas, gfp))) { @@ -4957,6 +4990,7 @@ void mas_store_prealloc(struct ma_state *mas, void *entry) { MA_WR_STATE(wr_mas, mas, entry); + mas_lock_check(mas); if (mas->store_type == wr_store_root) { mas_wr_prealloc_setup(&wr_mas); goto store; @@ -4989,6 +5023,7 @@ int mas_preallocate(struct ma_state *mas, void *entry, gfp_t gfp) { MA_WR_STATE(wr_mas, mas, entry); + mas_may_init_lock_check(mas); mas_wr_prealloc_setup(&wr_mas); mas->store_type = mas_wr_store_type(&wr_mas); mas_prealloc_calc(&wr_mas, entry); @@ -5474,7 +5509,6 @@ EXPORT_SYMBOL_GPL(mas_find_range); static bool mas_find_rev_setup(struct ma_state *mas, unsigned long min, void **entry) { - switch (mas->status) { case ma_active: goto active; From bcb7a3ce645e7d1de148b079a511ea9d4787b0bc Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Tue, 30 Jun 2026 15:08:28 -0400 Subject: [PATCH 287/562] maple_tree: documentation fix Don't include the word flag in the quotes with the actual flag. Link: https://lore.kernel.org/20260630190843.3563858-5-liam@infradead.org Signed-off-by: Liam R. Howlett (Oracle) Cc: Boqun Feng Cc: Chris Mason Cc: Chuck Lever Cc: Ingo Molnar Cc: Jason Gunthorpe Cc: Joe Perches Cc: Peter Zijlstra Cc: Rik van Riel Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- Documentation/core-api/maple_tree.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/core-api/maple_tree.rst b/Documentation/core-api/maple_tree.rst index ccdd1615cf97..34964ec88d17 100644 --- a/Documentation/core-api/maple_tree.rst +++ b/Documentation/core-api/maple_tree.rst @@ -211,7 +211,7 @@ Advanced Locking The maple tree uses a spinlock by default, but external locks can be used for tree updates as well. To use an external lock, the tree must be initialized -with the ``MT_FLAGS_LOCK_EXTERN flag``, this is usually done with the +with the ``MT_FLAGS_LOCK_EXTERN`` flag, this is usually done with the MTREE_INIT_EXT() #define, which takes an external lock as an argument. Functions and structures From ab51f3b9bffc30f15de86f8786133b4873cb86c3 Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Tue, 30 Jun 2026 15:08:29 -0400 Subject: [PATCH 288/562] maple_tree: drop dead code from mas_extend_spanning_null() mas_extend_spanning_null() had a clause if the end of the range being written (mas->last) is the same as the end of the existing range it is overwriting (wr_mas->r_max), action will be taken. This code path is not possible because the only calling function increments mas->last (unless it's ULONG_MAX) to walk to one beyond the write and then resets the value back to the initial value. In the case of mas->last == ULONG_MAX, then the second part of the statement will always be false - mas->last cannot be less than the node max. This code never executed and is flawed anyways (the arguments are incorrectly ordered), so removing it is the safest action. Since the code never executes, it is not fixing any issue so Fixes tag is not given. Link: https://lore.kernel.org/20260630190843.3563858-6-liam@infradead.org Signed-off-by: Liam R. Howlett (Oracle) Cc: Boqun Feng Cc: Chris Mason Cc: Chuck Lever Cc: Ingo Molnar Cc: Jason Gunthorpe Cc: Joe Perches Cc: Peter Zijlstra Cc: Rik van Riel Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- lib/maple_tree.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/maple_tree.c b/lib/maple_tree.c index e2f747457231..97849db132e7 100644 --- a/lib/maple_tree.c +++ b/lib/maple_tree.c @@ -2996,13 +2996,6 @@ static inline void mas_extend_spanning_null(struct ma_wr_state *l_wr_mas, if (r_mas->last < r_wr_mas->r_max) r_mas->last = r_wr_mas->r_max; r_mas->offset++; - } else if ((r_mas->last == r_wr_mas->r_max) && - (r_mas->last < r_mas->max) && - !mas_slot_locked(r_mas, r_wr_mas->slots, r_mas->offset + 1)) { - r_mas->last = mas_safe_pivot(r_mas, r_wr_mas->pivots, - r_wr_mas->type, r_mas->offset + 1); - r_mas->offset++; - r_wr_mas->r_max = r_mas->last; } } From f8fff51c8b3b94978c4465a96446c51c260372f8 Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Tue, 30 Jun 2026 15:08:30 -0400 Subject: [PATCH 289/562] maple_tree: drop MAPLE_ALLOC_SLOTS MAPLE_ALLOC_SLOTS is no longer used, so remove it. Link: https://lore.kernel.org/20260630190843.3563858-7-liam@infradead.org Signed-off-by: Liam R. Howlett (Oracle) Cc: Boqun Feng Cc: Chris Mason Cc: Chuck Lever Cc: Ingo Molnar Cc: Jason Gunthorpe Cc: Joe Perches Cc: Peter Zijlstra Cc: Rik van Riel Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- include/linux/maple_tree.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/linux/maple_tree.h b/include/linux/maple_tree.h index 220b380cdd49..346eae48cf15 100644 --- a/include/linux/maple_tree.h +++ b/include/linux/maple_tree.h @@ -29,13 +29,11 @@ #define MAPLE_NODE_SLOTS 31 /* 256 bytes including ->parent */ #define MAPLE_RANGE64_SLOTS 16 /* 256 bytes */ #define MAPLE_ARANGE64_SLOTS 10 /* 240 bytes */ -#define MAPLE_ALLOC_SLOTS (MAPLE_NODE_SLOTS - 1) #else /* 32bit sizes */ #define MAPLE_NODE_SLOTS 63 /* 256 bytes including ->parent */ #define MAPLE_RANGE64_SLOTS 32 /* 256 bytes */ #define MAPLE_ARANGE64_SLOTS 21 /* 240 bytes */ -#define MAPLE_ALLOC_SLOTS (MAPLE_NODE_SLOTS - 2) #endif /* defined(CONFIG_64BIT) || defined(BUILD_VDSO32_64) */ #define MAPLE_NODE_MASK 255UL From e89290fa5a63cf1fb83298f0488f74918e1fa473 Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Tue, 30 Jun 2026 15:08:31 -0400 Subject: [PATCH 290/562] maple_tree: clarify comments on mas_nomem() When an allocation completely fails, the return is false. If the allocation succeeds or partially succeeds, return true to indicate a retry of the operation. Note that since the lock may have been dropped, the operation is retried from the start - including potentially allocating more memory. Link: https://lore.kernel.org/20260630190843.3563858-8-liam@infradead.org Signed-off-by: Liam R. Howlett (Oracle) Cc: Boqun Feng Cc: Chris Mason Cc: Chuck Lever Cc: Ingo Molnar Cc: Jason Gunthorpe Cc: Joe Perches Cc: Peter Zijlstra Cc: Rik van Riel Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- lib/maple_tree.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/maple_tree.c b/lib/maple_tree.c index 97849db132e7..3886b856d6e3 100644 --- a/lib/maple_tree.c +++ b/lib/maple_tree.c @@ -5676,10 +5676,11 @@ EXPORT_SYMBOL_GPL(mas_erase); /** * mas_nomem() - Check if there was an error allocating and do the allocation - * if necessary If there are allocations, then free them. + * if necessary. + * * @mas: The maple state * @gfp: The GFP_FLAGS to use for allocations - * Return: true on allocation, false otherwise. + * Return: False on no memory. True otherwise (partial success as well) */ bool mas_nomem(struct ma_state *mas, gfp_t gfp) __must_hold(mas->tree->ma_lock) @@ -5695,6 +5696,10 @@ bool mas_nomem(struct ma_state *mas, gfp_t gfp) mas_alloc_nodes(mas, gfp); } + /* + * Return false on zero forward progress. Partial allocations are kept + * so the retry path will attempt to get the rest. + */ if (!mas->sheaf && !mas->alloc) return false; From c854bc7ba7554e46fdeb7e213806013340e60912 Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Tue, 30 Jun 2026 15:08:32 -0400 Subject: [PATCH 291/562] maple_tree: use prefetched value in mas_wr_store_type() The slot contents exist in wr_mas->content, which has less overhead than reading the slot again. Link: https://lore.kernel.org/20260630190843.3563858-9-liam@infradead.org Signed-off-by: Liam R. Howlett (Oracle) Cc: Boqun Feng Cc: Chris Mason Cc: Chuck Lever Cc: Ingo Molnar Cc: Jason Gunthorpe Cc: Joe Perches Cc: Peter Zijlstra Cc: Rik van Riel Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- lib/maple_tree.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/maple_tree.c b/lib/maple_tree.c index 3886b856d6e3..8f5a7fa10cf4 100644 --- a/lib/maple_tree.c +++ b/lib/maple_tree.c @@ -3279,7 +3279,7 @@ static inline void mas_wr_slot_store(struct ma_wr_state *wr_mas) void __rcu **slots = wr_mas->slots; bool gap = false; - gap |= !mt_slot_locked(mas->tree, slots, offset); + gap |= !wr_mas->content; gap |= !mt_slot_locked(mas->tree, slots, offset + 1); if (wr_mas->offset_end - offset == 1) { From b991fb8620d9943c1b2d69d0c36fcf232098b0f8 Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Tue, 30 Jun 2026 15:08:33 -0400 Subject: [PATCH 292/562] maple_tree: optimise mas_wr_node_store() when not in rcu mode Clearing the entire node on the stack is unnecessary since most of the node will be overwritten anyways. Just clear what isn't used after the data is in place. Benchmarking shows a speedup of 0.67% on a height 4 tree with 2048 entries. Link: https://lore.kernel.org/20260630190843.3563858-10-liam@infradead.org Signed-off-by: Liam R. Howlett (Oracle) Cc: Boqun Feng Cc: Chris Mason Cc: Chuck Lever Cc: Ingo Molnar Cc: Jason Gunthorpe Cc: Joe Perches Cc: Peter Zijlstra Cc: Rik van Riel Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- lib/maple_tree.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/lib/maple_tree.c b/lib/maple_tree.c index 8f5a7fa10cf4..1c96b3a5dab6 100644 --- a/lib/maple_tree.c +++ b/lib/maple_tree.c @@ -3186,7 +3186,7 @@ static void mas_wr_spanning_store(struct ma_wr_state *wr_mas) static inline void mas_wr_node_store(struct ma_wr_state *wr_mas) { unsigned char dst_offset, offset_end; - unsigned char copy_size, node_pivots; + unsigned char copy_size, node_pivots, node_slots; struct maple_node reuse, *newnode; unsigned long *dst_pivots; void __rcu **dst_slots; @@ -3199,6 +3199,7 @@ static inline void mas_wr_node_store(struct ma_wr_state *wr_mas) in_rcu = mt_in_rcu(mas->tree); offset_end = wr_mas->offset_end; node_pivots = mt_pivots[wr_mas->type]; + node_slots = mt_slots[wr_mas->type]; /* Assume last adds an entry */ new_end = mas->end + 1 - offset_end + mas->offset; if (mas->last == wr_mas->end_piv) { @@ -3210,7 +3211,6 @@ static inline void mas_wr_node_store(struct ma_wr_state *wr_mas) if (in_rcu) { newnode = mas_pop_node(mas); } else { - memset(&reuse, 0, sizeof(struct maple_node)); newnode = &reuse; } @@ -3254,7 +3254,21 @@ static inline void mas_wr_node_store(struct ma_wr_state *wr_mas) dst_pivots[new_end] = mas->max; done: - mas_leaf_set_meta(newnode, maple_leaf_64, new_end); + if (!in_rcu && new_end + 2 < node_slots) { + unsigned char clear_from = new_end + 1; + + /* + * Note that the last slot is never cleared, since the metadata + * will be stored there or it has a value. + */ + memset(dst_slots + clear_from, 0, + sizeof(void __rcu *) * (node_slots - clear_from)); + if (clear_from < node_pivots) + memset(dst_pivots + clear_from, 0, + sizeof(unsigned long) * (node_pivots - clear_from)); + } + + mas_leaf_set_meta(newnode, wr_mas->type, new_end); if (in_rcu) { struct maple_enode *old_enode = mas->node; From f277fc99a79e73ed3b3021073a182445b595f7e4 Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Tue, 30 Jun 2026 15:08:34 -0400 Subject: [PATCH 293/562] maple_tree: micro optimisation of mas_wr_store_type() Use three new local booleans instead of reading other structures. This has shown an increase of 0.62% on a 2048 entry tree of height 4. Link: https://lore.kernel.org/20260630190843.3563858-11-liam@infradead.org Signed-off-by: Liam R. Howlett (Oracle) Cc: Boqun Feng Cc: Chris Mason Cc: Chuck Lever Cc: Ingo Molnar Cc: Jason Gunthorpe Cc: Joe Perches Cc: Peter Zijlstra Cc: Rik van Riel Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- lib/maple_tree.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/maple_tree.c b/lib/maple_tree.c index 1c96b3a5dab6..999a1d095e36 100644 --- a/lib/maple_tree.c +++ b/lib/maple_tree.c @@ -3736,6 +3736,9 @@ static inline enum store_type mas_wr_store_type(struct ma_wr_state *wr_mas) { struct ma_state *mas = wr_mas->mas; unsigned char new_end; + bool appending; + bool one_slot; + bool in_rcu; if (unlikely(mas_is_none(mas) || mas_is_ptr(mas))) return wr_store_root; @@ -3755,21 +3758,30 @@ static inline enum store_type mas_wr_store_type(struct ma_wr_state *wr_mas) return wr_new_root; new_end = mas_wr_new_end(wr_mas); + in_rcu = mt_in_rcu(mas->tree); + appending = mas->offset == mas->end; + one_slot = wr_mas->offset_end - mas->offset == 1; + /* Potential spanning rebalance collapsing a node */ if (new_end < mt_min_slots[wr_mas->type]) { if (!mte_is_root(mas->node)) return wr_rebalance; + if (!in_rcu) { + if (appending) + return wr_append; + else if (mas->end == new_end && one_slot) + return wr_slot_store; + } return wr_node_store; } if (new_end >= mt_slots[wr_mas->type]) return wr_split_store; - if (!mt_in_rcu(mas->tree) && (mas->offset == mas->end)) + if (!in_rcu && appending) return wr_append; - if ((new_end == mas->end) && (!mt_in_rcu(mas->tree) || - (wr_mas->offset_end - mas->offset == 1))) + if (new_end == mas->end && (!in_rcu || one_slot)) return wr_slot_store; return wr_node_store; From d25e4f3678d4682dd5f045c0fd21bc0b0dc424ec Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Tue, 30 Jun 2026 15:08:35 -0400 Subject: [PATCH 294/562] maple_tree: add bulk parent set helper Instead of calculating the parent pointer each time for a child, cache the majority of the parent pointer and only change the slot per child. Drop the mas_set_parent() function since the last user has been removed. Testing on a tree containing 2048 entries of height 4 had an increased gain of 3.51% on nodes tracking gaps. Link: https://lore.kernel.org/20260630190843.3563858-12-liam@infradead.org Signed-off-by: Liam R. Howlett (Oracle) Cc: Boqun Feng Cc: Chris Mason Cc: Chuck Lever Cc: Ingo Molnar Cc: Jason Gunthorpe Cc: Joe Perches Cc: Peter Zijlstra Cc: Rik van Riel Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- lib/maple_tree.c | 97 +++++++++++++++++++++--------------------------- 1 file changed, 42 insertions(+), 55 deletions(-) diff --git a/lib/maple_tree.c b/lib/maple_tree.c index 999a1d095e36..768193406c3d 100644 --- a/lib/maple_tree.c +++ b/lib/maple_tree.c @@ -455,46 +455,6 @@ enum maple_type mas_parent_type(struct ma_state *mas, struct maple_enode *enode) return 0; } -/* - * mas_set_parent() - Set the parent node and encode the slot - * @mas: The maple state - * @enode: The encoded maple node. - * @parent: The encoded maple node that is the parent of @enode. - * @slot: The slot that @enode resides in @parent. - * - * Slot number is encoded in the enode->parent bit 3-6 or 2-6, depending on the - * parent type. - */ -static inline -void mas_set_parent(struct ma_state *mas, struct maple_enode *enode, - const struct maple_enode *parent, unsigned char slot) -{ - unsigned long val = (unsigned long)parent; - unsigned long shift; - unsigned long type; - enum maple_type p_type = mte_node_type(parent); - - MAS_BUG_ON(mas, p_type == maple_dense); - MAS_BUG_ON(mas, p_type == maple_leaf_64); - - switch (p_type) { - case maple_range_64: - case maple_arange_64: - shift = MAPLE_PARENT_SLOT_SHIFT; - type = MAPLE_PARENT_RANGE64; - break; - default: - case maple_dense: - case maple_leaf_64: - shift = type = 0; - break; - } - - val &= ~MAPLE_NODE_MASK; /* Clear all node metadata in parent */ - val |= (slot << shift) | type; - mte_to_node(enode)->parent = ma_parent_ptr(val); -} - /* * mte_parent_slot() - get the parent slot of @enode. * @enode: The encoded maple node. @@ -876,6 +836,42 @@ static inline void ma_set_meta_gap(struct maple_node *mn, enum maple_type mt, meta->gap = offset; } +/* + * mas_set_parent_slots() - Bulk operation to set many slot parent pointers + * @mas: The maple state + * @parent: The encoded maple node that is the parent of @enode. + * @slot: The slot that of the @enode. + * @start_slot: The offset into @slot + * @count: The number of slots to set (eg: exclusive) + */ +static inline +void mas_set_parent_slots(struct ma_state *mas, struct maple_enode *parent, + void __rcu **slots, unsigned char start_slot, unsigned char count) +{ + unsigned long val; + unsigned long shift; + unsigned long type; + enum maple_type p_type = mte_node_type(parent); + unsigned char i; + + MAS_BUG_ON(mas, p_type != maple_range_64 && + p_type != maple_arange_64); + + shift = MAPLE_PARENT_SLOT_SHIFT; + type = MAPLE_PARENT_RANGE64; + + val = (unsigned long)parent; + val &= ~MAPLE_NODE_MASK; + + for (i = 0; i < count; i++) { + unsigned long pval = val | ((start_slot + i) << shift) | type; + struct maple_enode *child; + + child = mt_slot_locked(mas->tree, slots, i); + mte_to_node(child)->parent = ma_parent_ptr(pval); + } +} + /* * mat_add() - Add a @dead_enode to the ma_topiary of a list of dead nodes. * @mat: the ma_topiary, a linked list of dead nodes. @@ -1608,14 +1604,10 @@ static inline void mas_adopt_children(struct ma_state *mas, struct maple_node *node = mte_to_node(parent); void __rcu **slots = ma_slots(node, type); unsigned long *pivots = ma_pivots(node, type); - struct maple_enode *child; - unsigned char offset; + unsigned char end; - offset = ma_data_end(node, type, pivots, mas->max); - do { - child = mas_slot_locked(mas, slots, offset); - mas_set_parent(mas, child, parent, offset); - } while (offset--); + end = ma_data_end(node, type, pivots, mas->max); + mas_set_parent_slots(mas, parent, slots, 0, end + 1); } /* @@ -1997,15 +1989,10 @@ unsigned long node_copy(struct ma_state *mas, struct maple_node *src, s_slots = ma_slots(src, s_mt) + start; s_pivots = ma_pivots(src, s_mt) + start; memcpy(d_slots, s_slots, size * sizeof(void __rcu *)); - if (!ma_is_leaf(d_mt) && s_mt == maple_copy) { - struct maple_enode *edst = mt_mk_node(dst, d_mt); - - for (int i = 0; i < size; i++) - mas_set_parent(mas, - mt_slot_locked(mas->tree, d_slots, i), - edst, d_start + i); - } + if (!ma_is_leaf(d_mt) && s_mt == maple_copy) + mas_set_parent_slots(mas, mt_mk_node(dst, d_mt), + d_slots, d_start, size); d_gaps = ma_gaps(dst, d_mt); if (d_gaps) { From f2c38a2810fefad1f7f89ff2a6c5f4ef52fb3755 Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Tue, 30 Jun 2026 15:08:36 -0400 Subject: [PATCH 295/562] maple_tree: catch race in mas_alloc_cyclic() If mas_alloc_cyclic() is called during a low memory situation, it is possible the lock may be dropped so reclaim can occur. There is a window where some other task may allocate the same id and cause the mas_insert() to fail with -EEXIST. In this scenario the function will return -EEXIST, which is not expected. Modifying the retry on mas_nomem() to re-search for a slot means that any race with other writes will not matter as the lock will be held between finding the index and writing the index. Moving the flag logic avoids cases where the flag is modified on drop lock/reacquire or when the write fails after clearing the flag. No existing users are exposed to this issue. Link: https://lore.kernel.org/20260630190843.3563858-13-liam@infradead.org Fixes: 9b6713cc7522 ("maple_tree: Add mtree_alloc_cyclic()") Signed-off-by: Liam R. Howlett (Oracle) Reported-by: Chris Mason Reviewed-by: Chuck Lever Cc: Boqun Feng Cc: Ingo Molnar Cc: Jason Gunthorpe Cc: Joe Perches Cc: Peter Zijlstra Cc: Rik van Riel Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- lib/maple_tree.c | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/lib/maple_tree.c b/lib/maple_tree.c index 768193406c3d..1bc177bf42f9 100644 --- a/lib/maple_tree.c +++ b/lib/maple_tree.c @@ -3867,35 +3867,40 @@ int mas_alloc_cyclic(struct ma_state *mas, unsigned long *startp, void *entry, unsigned long range_lo, unsigned long range_hi, unsigned long *next, gfp_t gfp) { - unsigned long min = range_lo; - int ret = 0; - - range_lo = max(min, *next); - ret = mas_empty_area(mas, range_lo, range_hi, 1); - if ((mas->tree->ma_flags & MT_FLAGS_ALLOC_WRAPPED) && ret == 0) { - mas->tree->ma_flags &= ~MT_FLAGS_ALLOC_WRAPPED; - ret = 1; - } - if (ret < 0 && range_lo > min) { - mas_reset(mas); - ret = mas_empty_area(mas, min, range_hi, 1); - if (ret == 0) - ret = 1; - } - if (ret < 0) - return ret; + int ret; + unsigned long min; + min = range_lo; do { + range_lo = max(min, *next); + ret = mas_empty_area(mas, range_lo, range_hi, 1); + if (ret < 0 && range_lo > min) { + mas_reset(mas); + ret = mas_empty_area(mas, min, range_hi, 1); + if (ret == 0) + ret = 1; + } + if (ret < 0) + goto out; + mas_insert(mas, entry); } while (mas_nomem(mas, gfp)); - if (mas_is_err(mas)) - return xa_err(mas->node); + if (mas_is_err(mas)) { + ret = xa_err(mas->node); + goto out; + } + + if ((mas->tree->ma_flags & MT_FLAGS_ALLOC_WRAPPED) && ret == 0) { + mas->tree->ma_flags &= ~MT_FLAGS_ALLOC_WRAPPED; + ret = 1; + } *startp = mas->index; *next = *startp + 1; if (*next == 0) mas->tree->ma_flags |= MT_FLAGS_ALLOC_WRAPPED; +out: mas_destroy(mas); return ret; } From 63668e80dff191e36f81d426a86f407e4bfa8904 Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Tue, 30 Jun 2026 15:08:37 -0400 Subject: [PATCH 296/562] maple_tree: document that erase may use GFP_KERNEL for allocations State that the mas_erase() and mtree_erase() functions may use GFP_KERNEL on allocation retry. Don't just depend on people reading the documentation by adding a check that will warn of the use. Link: https://lore.kernel.org/20260630190843.3563858-14-liam@infradead.org Signed-off-by: Liam R. Howlett (Oracle) Reviewed-by: Rik van Riel Cc: Jason Gunthorpe Cc: Boqun Feng Cc: Chris Mason Cc: Chuck Lever Cc: Ingo Molnar Cc: Joe Perches Cc: Peter Zijlstra Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- lib/maple_tree.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/lib/maple_tree.c b/lib/maple_tree.c index 1bc177bf42f9..15e8081f6180 100644 --- a/lib/maple_tree.c +++ b/lib/maple_tree.c @@ -5657,6 +5657,10 @@ EXPORT_SYMBOL_GPL(mas_find_range_rev); * Searches for @mas->index, sets @mas->index and @mas->last to the range and * erases that range. * + * Note that erase requires allocations and will use GFP_KERNEL to do so if + * necessary. If the allocation fails, the internal lock will be dropped to + * retry. + * * Return: the entry that was erased or %NULL, @mas->index and @mas->last are updated. */ void *mas_erase(struct ma_state *mas) @@ -5665,13 +5669,21 @@ void *mas_erase(struct ma_state *mas) unsigned long index = mas->index; MA_WR_STATE(wr_mas, mas, NULL); + /* + * In low memory situations, the allocation is retried with the gfp flag + * GFP_KERNEL. The internal spinlock is dropped in mas_nomem(), however + * the external lock is not dropped. + */ + if (mt_external_lock(mas->tree)) + might_alloc(GFP_KERNEL); + if (!mas_is_active(mas) || !mas_is_start(mas)) mas->status = ma_start; write_retry: entry = mas_state_walk(mas); if (!entry) - return NULL; + goto out; /* Must reset to ensure spanning writes of last slot are detected */ mas_reset(mas); @@ -5682,8 +5694,10 @@ void *mas_erase(struct ma_state *mas) goto write_retry; } - if (mas_is_err(mas)) + if (mas_is_err(mas)) { + entry = NULL; goto out; + } mas_wr_store_entry(&wr_mas); out: @@ -6011,6 +6025,10 @@ EXPORT_SYMBOL(mtree_alloc_rrange); * Erasing is the same as a walk to an entry then a store of a NULL to that * ENTIRE range. In fact, it is implemented as such using the advanced API. * + * Note that erase requires allocations and will use GFP_KERNEL to do so if + * necessary. If the allocation fails, the internal lock will be dropped to + * retry. + * * Return: The entry stored at the @index or %NULL */ void *mtree_erase(struct maple_tree *mt, unsigned long index) @@ -6020,6 +6038,7 @@ void *mtree_erase(struct maple_tree *mt, unsigned long index) MA_STATE(mas, mt, index, index); trace_ma_op(TP_FCT, &mas); + might_alloc(GFP_KERNEL); mtree_lock(mt); entry = mas_erase(&mas); mtree_unlock(mt); From 9ba2885853c9324ee39455d29c7aff58c8281c48 Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Tue, 30 Jun 2026 15:08:38 -0400 Subject: [PATCH 297/562] maple_tree: WARN_ON_ONCE when allocations fail Allocations should never fail in the circumstances that are expected to occur. Add checks in the code to ensure the circumstances are correctly set up by the user and warn if they are not. Also add a warning on failure to allocate, which should never happen. Link: https://lore.kernel.org/20260630190843.3563858-15-liam@infradead.org Signed-off-by: Liam R. Howlett (Oracle) Cc: Rik van Riel Cc: Jason Gunthorpe Cc: Boqun Feng Cc: Chris Mason Cc: Chuck Lever Cc: Ingo Molnar Cc: Joe Perches Cc: Peter Zijlstra Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- lib/maple_tree.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/maple_tree.c b/lib/maple_tree.c index 15e8081f6180..afe65ff0f8a6 100644 --- a/lib/maple_tree.c +++ b/lib/maple_tree.c @@ -5720,6 +5720,10 @@ bool mas_nomem(struct ma_state *mas, gfp_t gfp) if (likely(mas->node != MA_ERROR(-ENOMEM))) return false; + /* Allocations can fail, don't do this. */ + WARN_ON_ONCE(!gfpflags_allow_blocking(gfp) && + mt_external_lock(mas->tree)); + if (gfpflags_allow_blocking(gfp) && !mt_external_lock(mas->tree)) { mtree_unlock(mas->tree); mas_alloc_nodes(mas, gfp); @@ -5730,9 +5734,12 @@ bool mas_nomem(struct ma_state *mas, gfp_t gfp) /* * Return false on zero forward progress. Partial allocations are kept - * so the retry path will attempt to get the rest. + * so the retry path will attempt to get the rest. The failure should + * not happen as we try our best to reclaim. The user would need an + * external lock with a non-blocking gfp in a low memory situation - + * which would have triggered the first warning in this function. */ - if (!mas->sheaf && !mas->alloc) + if (WARN_ON_ONCE(!mas->sheaf && !mas->alloc)) return false; mas_reset(mas); From 93acb0fbd119462f60095dfe89dddc5329635d25 Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Tue, 30 Jun 2026 15:08:39 -0400 Subject: [PATCH 298/562] maple_tree: document erase and allocations better During a discussion on the maple tree erase process and GFP flags, Jason suggested there be an amendment to the documentation to clarify the situation on allocations within the tree. The added text is an attempt to better explain that the tree may allocate, even when erasing, and provide some guidance on how to work around such issues. Link: https://lore.kernel.org/all/20260617180419.GA231643@ziepe.ca/ Link: https://lore.kernel.org/20260630190843.3563858-16-liam@infradead.org Signed-off-by: Liam R. Howlett (Oracle) Suggested-by: Jason Gunthorpe Cc: Rik van Riel Cc: Boqun Feng Cc: Chris Mason Cc: Chuck Lever Cc: Ingo Molnar Cc: Joe Perches Cc: Peter Zijlstra Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- Documentation/core-api/maple_tree.rst | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/Documentation/core-api/maple_tree.rst b/Documentation/core-api/maple_tree.rst index 34964ec88d17..1eae24a9c18b 100644 --- a/Documentation/core-api/maple_tree.rst +++ b/Documentation/core-api/maple_tree.rst @@ -17,7 +17,8 @@ supports iterating over a range of entries and going to the previous or next entry in a cache-efficient manner. The tree can also be put into an RCU-safe mode of operation which allows reading and writing concurrently. Writers must synchronize on a lock, which can be the default spinlock, or the user can set -the lock to an external lock of a different type. +the lock to an external lock of a different type. Note that external locks may +interfere with allocations in a low memory situation. The Maple Tree maintains a small memory footprint and was designed to use modern processor cache efficiently. The majority of the users will be able to @@ -42,6 +43,15 @@ successful store operation within a given code segment when allocating cannot be done. Allocations of nodes are relatively small at around 256 bytes. +Since the maple tree uses internal nodes that are allocated and has rules on +data density, erasing an entry may cause allocations to occur. That is, +erasing an entry may consume memory. Users must take care to ensure that they +do not violate the larger system constraints on when and how memory is +allocated. Most situations are fine to allocate, but the pre-allocation +support is provided as a mechanism to avoid trickier situations. There is also +the possibility of using special entries and clean up the tree later, in +extreme circumstances. + .. _maple-tree-normal-api: Normal API @@ -63,7 +73,8 @@ success or an error code otherwise. mtree_store_range() works in the same way but takes a range. mtree_load() is used to retrieve the entry stored at a given index. You can use mtree_erase() to erase an entire range by only knowing one value within that range, or mtree_store() call with an entry of -NULL may be used to partially erase a range or many ranges at once. +NULL may be used to partially erase a range or many ranges at once. Note that +mtree_erase() may use GFP_KERNEL on allocations. If you want to only store a new entry to a range (or index) if that range is currently ``NULL``, you can use mtree_insert_range() or mtree_insert() which @@ -163,7 +174,9 @@ You can use mas_erase() to erase an entire range by setting index and last of the maple state to the desired range to erase. This will erase the first range that is found in that range, set the maple state index and last as the range that was erased and return the entry that existed -at that location. +at that location. Note that mas_erase() may allocate with the GFP_KERNEL flag. +If this is not okay, consider using mas_store_gfp() and pass it a ``NULL``, +after setting up the correct range by walking to the entry. You can walk each entry within a range by using mas_for_each(). If you want to walk each element of the tree then ``0`` and ``ULONG_MAX`` may be used as From 6fccf27f88d811742e9d3edb27debcee02b9e008 Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Tue, 30 Jun 2026 15:08:40 -0400 Subject: [PATCH 299/562] maple_tree: change two GFP flags in tests The GFP flags in two tests are obviously incorrect. Make the tests correctly run by updating the GFP flags. Link: https://lore.kernel.org/all/d9cbb89faa5bdb71d451781d214a51ce8923a83e.camel@perches.com/ Link: https://lore.kernel.org/20260630190843.3563858-17-liam@infradead.org Signed-off-by: Liam R. Howlett (Oracle) Reported-by: Joe Perches Cc: Boqun Feng Cc: Chris Mason Cc: Chuck Lever Cc: Ingo Molnar Cc: Jason Gunthorpe Cc: Peter Zijlstra Cc: Rik van Riel Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- tools/testing/radix-tree/maple.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/testing/radix-tree/maple.c b/tools/testing/radix-tree/maple.c index 0607913a3022..d967e76a3c06 100644 --- a/tools/testing/radix-tree/maple.c +++ b/tools/testing/radix-tree/maple.c @@ -35234,7 +35234,7 @@ static noinline void __init check_prealloc(struct maple_tree *mt) mt_set_non_kernel(1); /* Spanning store */ mas_set_range(&mas, 1, 100); - MT_BUG_ON(mt, mas_preallocate(&mas, ptr, GFP_KERNEL & GFP_NOWAIT) == 0); + MT_BUG_ON(mt, mas_preallocate(&mas, ptr, GFP_NOWAIT) == 0); allocated = mas_allocated(&mas); height = mas_mt_height(&mas); MT_BUG_ON(mt, allocated != 0); @@ -35257,7 +35257,7 @@ static noinline void __init check_prealloc(struct maple_tree *mt) MT_BUG_ON(mt, mas_allocated(&mas) != 0); mas_set_range(&mas, 0, 200); mt_set_non_kernel(1); - MT_BUG_ON(mt, mas_preallocate(&mas, ptr, GFP_KERNEL & GFP_NOWAIT) == 0); + MT_BUG_ON(mt, mas_preallocate(&mas, ptr, GFP_NOWAIT) == 0); allocated = mas_allocated(&mas); height = mas_mt_height(&mas); MT_BUG_ON(mt, allocated != 0); From 50df0412f9bf0cc6b18eea2da554b7f62f13c2f7 Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Tue, 30 Jun 2026 15:08:41 -0400 Subject: [PATCH 300/562] maple_tree: fix argument name in header The mas_prev_range() function takes a min and not a max. Link: https://lore.kernel.org/20260630190843.3563858-18-liam@infradead.org Fixes: 6b9e93e01020 ("maple_tree: add mas_prev_range() and mas_find_range_rev interface") Signed-off-by: Liam R. Howlett (Oracle) Cc: Boqun Feng Cc: Chris Mason Cc: Chuck Lever Cc: Ingo Molnar Cc: Jason Gunthorpe Cc: Joe Perches Cc: Peter Zijlstra Cc: Rik van Riel Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- include/linux/maple_tree.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/maple_tree.h b/include/linux/maple_tree.h index 346eae48cf15..5e0bd2857d94 100644 --- a/include/linux/maple_tree.h +++ b/include/linux/maple_tree.h @@ -576,7 +576,7 @@ void maple_tree_init(void); void mas_destroy(struct ma_state *mas); void *mas_prev(struct ma_state *mas, unsigned long min); -void *mas_prev_range(struct ma_state *mas, unsigned long max); +void *mas_prev_range(struct ma_state *mas, unsigned long min); void *mas_next(struct ma_state *mas, unsigned long max); void *mas_next_range(struct ma_state *mas, unsigned long max); From f46322660a2e48b963726a38c6fd767f8a480d87 Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Tue, 30 Jun 2026 15:08:42 -0400 Subject: [PATCH 301/562] maple_tree: avoid extra gap calculation Prior to ending the ascension loop of larger operations like split, rebalance, and spanning store the gap in the node had been calculated. Once the node is inserted into the tree, the gap is recalculated in mas_update_gap(). This can be avoided by creating a helper for mas_update_gap() that accepts the known gap value, which reduces the operations required for gap updating path. Link: https://lore.kernel.org/20260630190843.3563858-19-liam@infradead.org Signed-off-by: Liam R. Howlett (Oracle) Cc: Boqun Feng Cc: Chris Mason Cc: Chuck Lever Cc: Ingo Molnar Cc: Jason Gunthorpe Cc: Joe Perches Cc: Peter Zijlstra Cc: Rik van Riel Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- lib/maple_tree.c | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/lib/maple_tree.c b/lib/maple_tree.c index afe65ff0f8a6..4336a9876398 100644 --- a/lib/maple_tree.c +++ b/lib/maple_tree.c @@ -1565,14 +1565,26 @@ static inline void mas_parent_gap(struct ma_state *mas, unsigned char offset, goto ascend; } +static __always_inline void mas_update_gap_known(struct ma_state *mas, + unsigned long gap) +{ + unsigned char pslot; + unsigned long p_gap; + + pslot = mte_parent_slot(mas->node); + p_gap = ma_gaps(mte_parent(mas->node), + mas_parent_type(mas, mas->node))[pslot]; + + if (p_gap != gap) + mas_parent_gap(mas, pslot, gap); +} + /* * mas_update_gap() - Update a nodes gaps and propagate up if necessary. * @mas: the maple state. */ static inline void mas_update_gap(struct ma_state *mas) { - unsigned char pslot; - unsigned long p_gap; unsigned long max_gap; if (!mt_is_alloc(mas->tree)) @@ -1582,13 +1594,7 @@ static inline void mas_update_gap(struct ma_state *mas) return; max_gap = mas_max_gap(mas); - - pslot = mte_parent_slot(mas->node); - p_gap = ma_gaps(mte_parent(mas->node), - mas_parent_type(mas, mas->node))[pslot]; - - if (p_gap != max_gap) - mas_parent_gap(mas, pslot, max_gap); + mas_update_gap_known(mas, max_gap); } /* @@ -2136,8 +2142,8 @@ static inline void mas_wmb_replace(struct ma_state *mas, struct maple_copy *cp) mas->node = mt_slot_locked(mas->tree, cp->slot, 0); /* Insert the new data in the tree */ mas_topiary_replace(mas, old_enode, cp->height); - if (!mte_is_leaf(mas->node)) - mas_update_gap(mas); + if (mt_is_alloc(mas->tree) && !mte_is_root(mas->node)) + mas_update_gap_known(mas, cp->gap[0]); mtree_range_walk(mas); } From ff350ca9578bd9810598f18fbc84bd189b590f50 Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Oracle)" Date: Tue, 30 Jun 2026 15:08:43 -0400 Subject: [PATCH 302/562] maple_tree: add helper mas_make_walkable() A check in mas_walk() was incorrect and caused inefficient use of the maple state. The same issue existed in mas_erase(), but was left unfixed. Making a helper function is the obvious answer. Link: https://lore.kernel.org/20260630190843.3563858-20-liam@infradead.org Signed-off-by: Liam R. Howlett (Oracle) Cc: Boqun Feng Cc: Chris Mason Cc: Chuck Lever Cc: Ingo Molnar Cc: Jason Gunthorpe Cc: Joe Perches Cc: Peter Zijlstra Cc: Rik van Riel Cc: Waiman Long Cc: Will Deacon Signed-off-by: Andrew Morton --- lib/maple_tree.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/maple_tree.c b/lib/maple_tree.c index 4336a9876398..06cc05b79fbd 100644 --- a/lib/maple_tree.c +++ b/lib/maple_tree.c @@ -261,6 +261,12 @@ static inline bool mas_is_underflow(struct ma_state *mas) return mas->status == ma_underflow; } +static inline void mas_make_walkable(struct ma_state *mas) +{ + if (!mas_is_active(mas) && !mas_is_start(mas)) + mas->status = ma_start; +} + static __always_inline struct maple_node *mte_to_node( const struct maple_enode *entry) { @@ -4446,8 +4452,7 @@ void *mas_walk(struct ma_state *mas) void *entry; mas_may_init_lock_check(mas); - if (!mas_is_active(mas) && !mas_is_start(mas)) - mas->status = ma_start; + mas_make_walkable(mas); retry: entry = mas_state_walk(mas); if (mas_is_start(mas)) { @@ -5683,9 +5688,7 @@ void *mas_erase(struct ma_state *mas) if (mt_external_lock(mas->tree)) might_alloc(GFP_KERNEL); - if (!mas_is_active(mas) || !mas_is_start(mas)) - mas->status = ma_start; - + mas_make_walkable(mas); write_retry: entry = mas_state_walk(mas); if (!entry) From db9909115df644e4dc343338a52ad2e4b13e750d Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Tue, 30 Jun 2026 19:38:57 +0800 Subject: [PATCH 303/562] mm/hugetlb: avoid unnecessary TLB flush for empty folio list in vmemmap optimize Since 79359d6d24df ("hugetlb: perform vmemmap optimization on a list of pages") __hugetlb_vmemmap_optimize_folios() unconditionally issues a final flush_tlb_all() in its out path. However, a TLB flush must be paired with an actual page table modification. When the input folio list is empty, neither PMD splitting nor PTE remapping takes place, so no page tables are modified and the flush is pure overhead. An empty list is reached in common paths such as gather_bootmem_prealloc_node() on nodes without bootmem gigantic pages, hugetlb_pages_alloc_boot_node() when no pages were allocated, and runtime allocation failure paths in set_max_huge_pages(). Add an early return for empty lists. This restores the basic invariant that TLB flushes are only issued when page tables have been modified, and it also makes the NULL hstate passed by gather_bootmem_prealloc_node() on an empty list harmless. Assisted-by: kimi-cli:kimi-k2.7 code Assisted-by: Github Copilot:gpt-5.2 #Reported-by Link: https://lore.kernel.org/20260701053422.3664813-1-guanwentao@uniontech.com Link: https://lore.kernel.org/20260630113857.3319612-1-guanwentao@uniontech.com Fixes: 79359d6d24df ("hugetlb: perform vmemmap optimization on a list of pages") Signed-off-by: Wentao Guan Reviewed-by: Muchun Song Cc: David Hildenbrand Cc: Guan Wentao Cc: Oscar Salvador Signed-off-by: Andrew Morton --- mm/hugetlb_vmemmap.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c index eefd6b5f9706..1430a5aa2de5 100644 --- a/mm/hugetlb_vmemmap.c +++ b/mm/hugetlb_vmemmap.c @@ -624,6 +624,9 @@ static void __hugetlb_vmemmap_optimize_folios(struct hstate *h, LIST_HEAD(vmemmap_pages); unsigned long flags = VMEMMAP_REMAP_NO_TLB_FLUSH; + if (list_empty(folio_list)) + return; + nr_to_optimize = 0; list_for_each_entry(folio, folio_list, lru) { int ret; From 06af0231acd10e5df16e8b221a5e4b49c3d3fc35 Mon Sep 17 00:00:00 2001 From: Usama Arif Date: Tue, 30 Jun 2026 04:23:32 -0700 Subject: [PATCH 304/562] mm/vmpressure: skip tree=true accounting on cgroup v2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Patch series "mm/vmpressure: reduce CPU, memory and code overhead on cgroup v2", v3. The vmpressure subsystem has two distinct consumers, gated by the @tree argument: tree=false : in-kernel socket pressure, consumed by TCP/SCTP. This is cgroup v2 only; v1 sockets read memcg->tcpmem_pressure instead. tree=true : cgroup v1 userspace eventfd notifications via the memory.pressure_level / cgroup.event_control interface. v2 has no equivalent (userspace gets reclaim signals through memory.pressure / PSI, which doesn't touch vmpressure). So of the four (hierarchy, tree) combinations, only two carry data that anyone reads. The existing early return in vmpressure() covered v1 + tree=false; the symmetric v2 + tree=true case was falling through and doing the full lock / accumulate / schedule_work / parent-walk dance, even though the events list it eventually iterates is empty on cgroup v2 (vmpressure_register_event() is wired up only through the v1 cftype "memory.pressure_level" and can't be reached from a v2 memcg). Patch 1 extends the existing early return to also skip v2 + tree=true. On a v2-only host this eliminates a contended path where reclaimers can serialize on a single global sr_lock. bpftrace on a 176-core production host (cgroup v2, 285 memcgs, sustained reclaim) showed ~16,200 such calls per minute with tree = true. Patch 2 follows up with a cleanup: it splits the v1 userspace eventfd interface (struct vmpressure_event, the events list and its mutex, the work_struct and its handler, the parent walk, vmpressure_register_event / unregister_event, and vmpressure_prio) into a new mm/memcontrol-v1.c built only when CONFIG_MEMCG_V1=y, behind small no-op stubs in the header. mm/vmpressure.c keeps the shared bits and the tree=false socket-pressure path. The size of vmpressure.c goes down to half and the code is much more simpler. The only #ifdef CONFIG_MEMCG_V1 remaining in source is around the v1-only fields inside struct vmpressure itself. Memory savings on CONFIG_MEMCG_V1=n: struct vmpressure : 112B -> 24B struct mem_cgroup : 1664B -> 1536B This split is the first step toward eventually making vmpressure CONFIG_MEMCG_V1 only. The v2 in-kernel socket pressure path (tree=false) cannot be removed today immediately: PSI is not an exact replacement for vmpressure, and switching networking socket-buffer back-off to PSI may regress networking performance or increase memory pressure in workloads that today rely on vmpressure's hysteresis. The medium-term plan is to introduce a PSI-based socket-pressure path, keep vmpressure available for v2 behind a defconfig as an opt-out for several releases, and only then drop the tree=false path entirely, at which point everything that remains in mm/memcontrol-v1.c is the whole subsystem. This patch (of 2): vmpressure() has two outputs gated by the @tree argument: @tree=false drives in-kernel socket pressure (mem_cgroup_set_ socket_pressure), consumed by TCP/SCTP. This only applies on cgroup v2; on v1 socket memory is charged separately via tcpmem and the consumer reads memcg->tcpmem_pressure instead. @tree=true drives userspace eventfd notifications via the v1 memory.pressure_level / cgroup.event_control interface. v2 has no equivalent: userspace gets reclaim signals through memory.pressure (PSI), which does not touch vmpressure. The existing early return covered v1 + @tree=false. The symmetric v2 + @tree=true case was falling through and doing the full lock / accumulate / schedule_work / parent-walk dance for an events list that can never be populated. bpftrace on a 176-core production host (cgroup v2, CONFIG_MEMCG_V1=n, 285 memcgs, sustained reclaim) showed ~16,200 @tree=true vmpressure() calls per minute. Add an early return that skips cgroup v2 + tree = true which avoids us doing all this work. On a v2-only host this also eliminates a lock contention path that can serialise reclaimers on a single global sr_lock. Link: https://lore.kernel.org/20260630112617.1198623-1-usama.arif@linux.dev Link: https://lore.kernel.org/20260630112617.1198623-2-usama.arif@linux.dev Signed-off-by: Usama Arif Acked-by: Shakeel Butt Acked-by: Johannes Weiner Cc: David Hildenbrand Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Michal Koutný Cc: Mike Rapoport Cc: Roman Gushchin Cc: Suren Baghdasaryan Cc: Tejun Heo Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/vmpressure.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/mm/vmpressure.c b/mm/vmpressure.c index f053554e5826..c82cee1ab43b 100644 --- a/mm/vmpressure.c +++ b/mm/vmpressure.c @@ -246,11 +246,13 @@ void vmpressure(gfp_t gfp, int order, struct mem_cgroup *memcg, bool tree, return; /* - * The in-kernel users only care about the reclaim efficiency - * for this @memcg rather than the whole subtree, and there - * isn't and won't be any in-kernel user in a legacy cgroup. + * Only two combinations have a consumer: + * cgroup v2 + tree=false -> in-kernel socket pressure + * cgroup v1 + tree=true -> userspace eventfds (memory.pressure_level) + * Skip the other two: nothing consumes the result. */ - if (!cgroup_subsys_on_dfl(memory_cgrp_subsys) && !tree) + if ((!cgroup_subsys_on_dfl(memory_cgrp_subsys) && !tree) || + (cgroup_subsys_on_dfl(memory_cgrp_subsys) && tree)) return; vmpr = memcg_to_vmpressure(memcg); From 09686065bde02788c392c08f7e5b7c19df0bc47e Mon Sep 17 00:00:00 2001 From: Usama Arif Date: Tue, 30 Jun 2026 09:25:05 -0700 Subject: [PATCH 305/562] mm-vmpressure-skip-tree=true-accounting-on-cgroup-v2-fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simplify the guard. Both cgroup_subsys_on_dfl() and tree are bool, so the two combinations that have no consumer (v1 + tree=false, v2 + tree=true) are exactly the cases where dfl == tree. Link: https://lore.kernel.org/e8e1a409-48d8-4fa7-ae98-49485a1607f6@linux.dev Signed-off-by: Usama Arif Suggested-by: Johannes Weiner Cc: David Hildenbrand Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Michal Koutný Cc: Mike Rapoport Cc: Roman Gushchin Cc: Shakeel Butt Cc: Suren Baghdasaryan Cc: Tejun Heo Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- mm/vmpressure.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mm/vmpressure.c b/mm/vmpressure.c index c82cee1ab43b..93cc472bf2b2 100644 --- a/mm/vmpressure.c +++ b/mm/vmpressure.c @@ -251,8 +251,7 @@ void vmpressure(gfp_t gfp, int order, struct mem_cgroup *memcg, bool tree, * cgroup v1 + tree=true -> userspace eventfds (memory.pressure_level) * Skip the other two: nothing consumes the result. */ - if ((!cgroup_subsys_on_dfl(memory_cgrp_subsys) && !tree) || - (cgroup_subsys_on_dfl(memory_cgrp_subsys) && tree)) + if (cgroup_subsys_on_dfl(memory_cgrp_subsys) == tree) return; vmpr = memcg_to_vmpressure(memcg); From c8eb3bf5e5b9a72c686b5a042f7a92e939fe831a Mon Sep 17 00:00:00 2001 From: Usama Arif Date: Tue, 30 Jun 2026 04:23:33 -0700 Subject: [PATCH 306/562] mm/vmpressure: move v1 userspace eventfd code into memcontrol-v1.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clean up mm/vmpressure.c by separating the cgroup v1 userspace eventfd interface from the shared and v2 in-kernel code. Currently, almost half of mm/vmpressure.c exists to serve tree=true: struct vmpressure_event, the events list and its mutex, the work_struct and vmpressure_work_fn that drains tree_scanned/tree_reclaimed, the parent walk, vmpressure_event(), vmpressure_register_event(), vmpressure_unregister_event(), and vmpressure_prio() (which always calls vmpressure() with tree=true). Move it all into mm/memcontrol-v1.c (built only when CONFIG_MEMCG_V1=y) as a single contiguous block, following the per-component layout already used by that file. Keeping the v1 vmpressure code with the rest of the deprecated cgroup v1 memory controller makes the full footprint of the CONFIG_MEMCG_V1 option easy to see in one place, which matters more than component-level file separation for code that has no active development. vmpressure.c keeps the shared bits (constants, vmpressure_calc_level, the runtime hierarchy check, the tree=false body, init/cleanup plumbing) and calls into three small v1 hooks for the tree=true accumulator and the v1 portions of init/cleanup. The hooks have static-inline no-op stubs in include/linux/vmpressure.h for the !MEMCG_V1 case, so callers don't need ifdefs. vmpressure_prio() gets the same treatment, which means vmscan.c's call site disappears at compile time on v2-only kernels. The only #ifdef CONFIG_MEMCG_V1 in source remains around the v1-only fields inside struct vmpressure itself. Memory savings on CONFIG_MEMCG_V1=n (measured with pahole): struct vmpressure : 112B -> 24B struct mem_cgroup : 1664B -> 1536B This split is the first step toward eventually making vmpressure CONFIG_MEMCG_V1 only. The v2 in-kernel socket pressure path (tree=false) cannot be removed today immediately: PSI is not an exact replacement for vmpressure, and switching networking socket-buffer back-off to PSI may regress networking performance or increase memory pressure in workloads that today rely on vmpressure's hysteresis. The medium-term plan is to introduce a PSI-based socket-pressure path, keep vmpressure available for v2 behind a defconfig as an opt-out for several releases, and only then drop the tree=false path entirely, at which point everything that remains of the vmpressure block in mm/memcontrol-v1.c is the whole subsystem. Link: https://lore.kernel.org/20260630112617.1198623-3-usama.arif@linux.dev Signed-off-by: Usama Arif Acked-by: Shakeel Butt Cc: David Hildenbrand Cc: Johannes Weiner Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Michal Koutný Cc: Mike Rapoport Cc: Roman Gushchin Cc: Suren Baghdasaryan Cc: Tejun Heo Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- include/linux/vmpressure.h | 46 +++++- mm/memcontrol-v1.c | 292 +++++++++++++++++++++++++++++++++++++ mm/vmpressure.c | 292 ++----------------------------------- 3 files changed, 343 insertions(+), 287 deletions(-) diff --git a/include/linux/vmpressure.h b/include/linux/vmpressure.h index faecd5522401..b4d13457bc2a 100644 --- a/include/linux/vmpressure.h +++ b/include/linux/vmpressure.h @@ -13,18 +13,31 @@ struct vmpressure { unsigned long scanned; unsigned long reclaimed; + /* The lock is used to keep the scanned/reclaimed in sync. */ + spinlock_t sr_lock; +#ifdef CONFIG_MEMCG_V1 + /* + * tree=true accumulators feed the v1 userspace eventfd interface + * (memory.pressure_level). Drained by @work. v2 has no equivalent + * interface, so this state is omitted on CONFIG_MEMCG_V1=n builds. + */ unsigned long tree_scanned; unsigned long tree_reclaimed; - /* The lock is used to keep the scanned/reclaimed above in sync. */ - spinlock_t sr_lock; - /* The list of vmpressure_event structs. */ struct list_head events; /* Have to grab the lock on events traversal or modifications. */ struct mutex events_lock; struct work_struct work; +#endif +}; + +enum vmpressure_levels { + VMPRESSURE_LOW = 0, + VMPRESSURE_MEDIUM, + VMPRESSURE_CRITICAL, + VMPRESSURE_NUM_LEVELS, }; struct mem_cgroup; @@ -32,18 +45,41 @@ struct mem_cgroup; #ifdef CONFIG_MEMCG void vmpressure(gfp_t gfp, int order, struct mem_cgroup *memcg, bool tree, unsigned long scanned, unsigned long reclaimed); -extern void vmpressure_prio(gfp_t gfp, struct mem_cgroup *memcg, int prio); - extern void vmpressure_init(struct vmpressure *vmpr); extern void vmpressure_cleanup(struct vmpressure *vmpr); extern struct vmpressure *memcg_to_vmpressure(struct mem_cgroup *memcg); extern struct mem_cgroup *vmpressure_to_memcg(struct vmpressure *vmpr); + +/* Shared with the v1 vmpressure block in mm/memcontrol-v1.c. */ +extern const unsigned long vmpressure_win; +extern enum vmpressure_levels vmpressure_calc_level(unsigned long scanned, + unsigned long reclaimed); + +#ifdef CONFIG_MEMCG_V1 +extern void vmpressure_prio(gfp_t gfp, struct mem_cgroup *memcg, int prio); extern int vmpressure_register_event(struct mem_cgroup *memcg, struct eventfd_ctx *eventfd, const char *args); extern void vmpressure_unregister_event(struct mem_cgroup *memcg, struct eventfd_ctx *eventfd); + +/* v1 hooks called from mm/vmpressure.c; no-ops below when !MEMCG_V1. */ +extern void vmpressure_v1_init(struct vmpressure *vmpr); +extern void vmpressure_v1_cleanup(struct vmpressure *vmpr); +extern void vmpressure_v1_account_tree(struct vmpressure *vmpr, + unsigned long scanned, + unsigned long reclaimed); #else +static inline void vmpressure_prio(gfp_t gfp, struct mem_cgroup *memcg, + int prio) {} +static inline void vmpressure_v1_init(struct vmpressure *vmpr) {} +static inline void vmpressure_v1_cleanup(struct vmpressure *vmpr) {} +static inline void vmpressure_v1_account_tree(struct vmpressure *vmpr, + unsigned long scanned, + unsigned long reclaimed) {} +#endif /* CONFIG_MEMCG_V1 */ + +#else /* !CONFIG_MEMCG */ static inline void vmpressure(gfp_t gfp, int order, struct mem_cgroup *memcg, bool tree, unsigned long scanned, unsigned long reclaimed) {} diff --git a/mm/memcontrol-v1.c b/mm/memcontrol-v1.c index 765069211567..135622b6172b 100644 --- a/mm/memcontrol-v1.c +++ b/mm/memcontrol-v1.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -1476,6 +1477,297 @@ void memcg1_oom_finish(struct mem_cgroup *memcg, bool locked) mem_cgroup_oom_unlock(memcg); } +/* + * cgroup v1 userspace vmpressure interface (memory.pressure_level / + * cgroup.event_control). Kept here so v2-only kernels (CONFIG_MEMCG_V1=n) + * drop the whole eventfd accumulator, its work item, and the per-memcg + * state it requires. + * + * When there are too little pages left to scan, vmpressure() may miss the + * critical pressure as number of pages will be less than "window size". + * However, in that case the vmscan priority will raise fast as the + * reclaimer will try to scan LRUs more deeply. + * + * The vmscan logic considers these special priorities: + * + * prio == DEF_PRIORITY (12): reclaimer starts with that value + * prio <= DEF_PRIORITY - 2 : kswapd becomes somewhat overwhelmed + * prio == 0 : close to OOM, kernel scans every page in an lru + * + * Any value in this range is acceptable for this tunable (i.e. from 12 to + * 0). Current value for the vmpressure_level_critical_prio is chosen + * empirically, but the number, in essence, means that we consider + * critical level when scanning depth is ~10% of the lru size (vmscan + * scans 'lru_size >> prio' pages, so it is actually 12.5%, or one + * eights). + */ +static const unsigned int vmpressure_level_critical_prio = ilog2(100 / 10); + +enum vmpressure_modes { + VMPRESSURE_NO_PASSTHROUGH = 0, + VMPRESSURE_HIERARCHY, + VMPRESSURE_LOCAL, + VMPRESSURE_NUM_MODES, +}; + +static const char * const vmpressure_str_levels[] = { + [VMPRESSURE_LOW] = "low", + [VMPRESSURE_MEDIUM] = "medium", + [VMPRESSURE_CRITICAL] = "critical", +}; + +static const char * const vmpressure_str_modes[] = { + [VMPRESSURE_NO_PASSTHROUGH] = "default", + [VMPRESSURE_HIERARCHY] = "hierarchy", + [VMPRESSURE_LOCAL] = "local", +}; + +struct vmpressure_event { + struct eventfd_ctx *efd; + enum vmpressure_levels level; + enum vmpressure_modes mode; + struct list_head node; +}; + +static struct vmpressure *work_to_vmpressure(struct work_struct *work) +{ + return container_of(work, struct vmpressure, work); +} + +static struct vmpressure *vmpressure_parent(struct vmpressure *vmpr) +{ + struct mem_cgroup *memcg = vmpressure_to_memcg(vmpr); + + memcg = parent_mem_cgroup(memcg); + if (!memcg) + return NULL; + return memcg_to_vmpressure(memcg); +} + +static bool vmpressure_event(struct vmpressure *vmpr, + const enum vmpressure_levels level, + bool ancestor, bool signalled) +{ + struct vmpressure_event *ev; + bool ret = false; + + mutex_lock(&vmpr->events_lock); + list_for_each_entry(ev, &vmpr->events, node) { + if (ancestor && ev->mode == VMPRESSURE_LOCAL) + continue; + if (signalled && ev->mode == VMPRESSURE_NO_PASSTHROUGH) + continue; + if (level < ev->level) + continue; + eventfd_signal(ev->efd); + ret = true; + } + mutex_unlock(&vmpr->events_lock); + + return ret; +} + +static void vmpressure_work_fn(struct work_struct *work) +{ + struct vmpressure *vmpr = work_to_vmpressure(work); + unsigned long scanned; + unsigned long reclaimed; + enum vmpressure_levels level; + bool ancestor = false; + bool signalled = false; + + spin_lock(&vmpr->sr_lock); + /* + * Several contexts might be calling vmpressure(), so it is + * possible that the work was rescheduled again before the old + * work context cleared the counters. In that case we will run + * just after the old work returns, but then scanned might be zero + * here. No need for any locks here since we don't care if + * vmpr->reclaimed is in sync. + */ + scanned = vmpr->tree_scanned; + if (!scanned) { + spin_unlock(&vmpr->sr_lock); + return; + } + + reclaimed = vmpr->tree_reclaimed; + vmpr->tree_scanned = 0; + vmpr->tree_reclaimed = 0; + spin_unlock(&vmpr->sr_lock); + + level = vmpressure_calc_level(scanned, reclaimed); + + do { + if (vmpressure_event(vmpr, level, ancestor, signalled)) + signalled = true; + ancestor = true; + } while ((vmpr = vmpressure_parent(vmpr))); +} + +/* + * Tree-mode accumulator: accumulate per-memcg scanned/reclaimed and + * schedule the work that walks the parent chain and signals registered + * eventfd listeners once we cross the window threshold. + */ +void vmpressure_v1_account_tree(struct vmpressure *vmpr, + unsigned long scanned, + unsigned long reclaimed) +{ + spin_lock(&vmpr->sr_lock); + scanned = vmpr->tree_scanned += scanned; + vmpr->tree_reclaimed += reclaimed; + spin_unlock(&vmpr->sr_lock); + + if (scanned < vmpressure_win) + return; + schedule_work(&vmpr->work); +} + +void vmpressure_v1_init(struct vmpressure *vmpr) +{ + mutex_init(&vmpr->events_lock); + INIT_LIST_HEAD(&vmpr->events); + INIT_WORK(&vmpr->work, vmpressure_work_fn); +} + +void vmpressure_v1_cleanup(struct vmpressure *vmpr) +{ + /* + * Make sure there is no pending work before eventfd infrastructure + * goes away. + */ + flush_work(&vmpr->work); +} + +/** + * vmpressure_prio() - Account memory pressure through reclaimer priority level + * @gfp: reclaimer's gfp mask + * @memcg: cgroup memory controller handle + * @prio: reclaimer's priority + * + * This function should be called from the reclaim path every time when + * the vmscan's reclaiming priority (scanning depth) changes. + * + * This function does not return any value. + */ +void vmpressure_prio(gfp_t gfp, struct mem_cgroup *memcg, int prio) +{ + /* + * We only use prio for accounting critical level. For more info + * see comment for vmpressure_level_critical_prio variable above. + */ + if (prio > vmpressure_level_critical_prio) + return; + + /* + * OK, the prio is below the threshold, updating vmpressure + * information before shrinker dives into long shrinking of long + * range vmscan. Passing scanned = vmpressure_win, reclaimed = 0 + * to the vmpressure() basically means that we signal 'critical' + * level. + */ + vmpressure(gfp, 0, memcg, true, vmpressure_win, 0); +} + +#define MAX_VMPRESSURE_ARGS_LEN (strlen("critical") + strlen("hierarchy") + 2) + +/** + * vmpressure_register_event() - Bind vmpressure notifications to an eventfd + * @memcg: memcg that is interested in vmpressure notifications + * @eventfd: eventfd context to link notifications with + * @args: event arguments (pressure level threshold, optional mode) + * + * This function associates eventfd context with the vmpressure + * infrastructure, so that the notifications will be delivered to the + * @eventfd. The @args parameter is a comma-delimited string that denotes a + * pressure level threshold (one of vmpressure_str_levels, i.e. "low", "medium", + * or "critical") and an optional mode (one of vmpressure_str_modes, i.e. + * "hierarchy" or "local"). + * + * To be used as memcg event method. + * + * Return: 0 on success, -ENOMEM on memory failure or -EINVAL if @args could + * not be parsed. + */ +int vmpressure_register_event(struct mem_cgroup *memcg, + struct eventfd_ctx *eventfd, const char *args) +{ + struct vmpressure *vmpr = memcg_to_vmpressure(memcg); + struct vmpressure_event *ev; + enum vmpressure_modes mode = VMPRESSURE_NO_PASSTHROUGH; + enum vmpressure_levels level; + char *spec, *spec_orig; + char *token; + int ret = 0; + + spec_orig = spec = kstrndup(args, MAX_VMPRESSURE_ARGS_LEN, GFP_KERNEL); + if (!spec) + return -ENOMEM; + + /* Find required level */ + token = strsep(&spec, ","); + ret = match_string(vmpressure_str_levels, VMPRESSURE_NUM_LEVELS, token); + if (ret < 0) + goto out; + level = ret; + + /* Find optional mode */ + token = strsep(&spec, ","); + if (token) { + ret = match_string(vmpressure_str_modes, VMPRESSURE_NUM_MODES, token); + if (ret < 0) + goto out; + mode = ret; + } + + ev = kzalloc_obj(*ev); + if (!ev) { + ret = -ENOMEM; + goto out; + } + + ev->efd = eventfd; + ev->level = level; + ev->mode = mode; + + mutex_lock(&vmpr->events_lock); + list_add(&ev->node, &vmpr->events); + mutex_unlock(&vmpr->events_lock); + ret = 0; +out: + kfree(spec_orig); + return ret; +} + +/** + * vmpressure_unregister_event() - Unbind eventfd from vmpressure + * @memcg: memcg handle + * @eventfd: eventfd context that was used to link vmpressure with the @cg + * + * This function does internal manipulations to detach the @eventfd from + * the vmpressure notifications, and then frees internal resources + * associated with the @eventfd (but the @eventfd itself is not freed). + * + * To be used as memcg event method. + */ +void vmpressure_unregister_event(struct mem_cgroup *memcg, + struct eventfd_ctx *eventfd) +{ + struct vmpressure *vmpr = memcg_to_vmpressure(memcg); + struct vmpressure_event *ev; + + mutex_lock(&vmpr->events_lock); + list_for_each_entry(ev, &vmpr->events, node) { + if (ev->efd != eventfd) + continue; + list_del(&ev->node); + kfree(ev); + break; + } + mutex_unlock(&vmpr->events_lock); +} + static DEFINE_MUTEX(memcg_max_mutex); static int mem_cgroup_resize_max(struct mem_cgroup *memcg, diff --git a/mm/vmpressure.c b/mm/vmpressure.c index 93cc472bf2b2..9629240d77ad 100644 --- a/mm/vmpressure.c +++ b/mm/vmpressure.c @@ -7,16 +7,15 @@ * * Based on ideas from Andrew Morton, David Rientjes, KOSAKI Motohiro, * Leonid Moiseichuk, Mel Gorman, Minchan Kim and Pekka Enberg. + * + * Tree-mode (cgroup v1 userspace eventfd) bookkeeping lives in + * mm/memcontrol-v1.c; this file holds the shared code and the in-kernel + * (tree=false) socket-pressure path that runs on cgroup v2. */ #include -#include #include -#include #include -#include -#include -#include #include #include #include @@ -35,7 +34,7 @@ * TODO: Make the window size depend on machine size, as we do for vmstat * thresholds. Currently we set it to 512 pages (2MB for 4KB pages). */ -static const unsigned long vmpressure_win = SWAP_CLUSTER_MAX * 16; +const unsigned long vmpressure_win = SWAP_CLUSTER_MAX * 16; /* * These thresholds are used when we account memory pressure through @@ -46,68 +45,6 @@ static const unsigned long vmpressure_win = SWAP_CLUSTER_MAX * 16; static const unsigned int vmpressure_level_med = 60; static const unsigned int vmpressure_level_critical = 95; -/* - * When there are too little pages left to scan, vmpressure() may miss the - * critical pressure as number of pages will be less than "window size". - * However, in that case the vmscan priority will raise fast as the - * reclaimer will try to scan LRUs more deeply. - * - * The vmscan logic considers these special priorities: - * - * prio == DEF_PRIORITY (12): reclaimer starts with that value - * prio <= DEF_PRIORITY - 2 : kswapd becomes somewhat overwhelmed - * prio == 0 : close to OOM, kernel scans every page in an lru - * - * Any value in this range is acceptable for this tunable (i.e. from 12 to - * 0). Current value for the vmpressure_level_critical_prio is chosen - * empirically, but the number, in essence, means that we consider - * critical level when scanning depth is ~10% of the lru size (vmscan - * scans 'lru_size >> prio' pages, so it is actually 12.5%, or one - * eights). - */ -static const unsigned int vmpressure_level_critical_prio = ilog2(100 / 10); - -static struct vmpressure *work_to_vmpressure(struct work_struct *work) -{ - return container_of(work, struct vmpressure, work); -} - -static struct vmpressure *vmpressure_parent(struct vmpressure *vmpr) -{ - struct mem_cgroup *memcg = vmpressure_to_memcg(vmpr); - - memcg = parent_mem_cgroup(memcg); - if (!memcg) - return NULL; - return memcg_to_vmpressure(memcg); -} - -enum vmpressure_levels { - VMPRESSURE_LOW = 0, - VMPRESSURE_MEDIUM, - VMPRESSURE_CRITICAL, - VMPRESSURE_NUM_LEVELS, -}; - -enum vmpressure_modes { - VMPRESSURE_NO_PASSTHROUGH = 0, - VMPRESSURE_HIERARCHY, - VMPRESSURE_LOCAL, - VMPRESSURE_NUM_MODES, -}; - -static const char * const vmpressure_str_levels[] = { - [VMPRESSURE_LOW] = "low", - [VMPRESSURE_MEDIUM] = "medium", - [VMPRESSURE_CRITICAL] = "critical", -}; - -static const char * const vmpressure_str_modes[] = { - [VMPRESSURE_NO_PASSTHROUGH] = "default", - [VMPRESSURE_HIERARCHY] = "hierarchy", - [VMPRESSURE_LOCAL] = "local", -}; - static enum vmpressure_levels vmpressure_level(unsigned long pressure) { if (pressure >= vmpressure_level_critical) @@ -117,8 +54,8 @@ static enum vmpressure_levels vmpressure_level(unsigned long pressure) return VMPRESSURE_LOW; } -static enum vmpressure_levels vmpressure_calc_level(unsigned long scanned, - unsigned long reclaimed) +enum vmpressure_levels vmpressure_calc_level(unsigned long scanned, + unsigned long reclaimed) { unsigned long scale = scanned + reclaimed; unsigned long pressure = 0; @@ -147,74 +84,6 @@ static enum vmpressure_levels vmpressure_calc_level(unsigned long scanned, return vmpressure_level(pressure); } -struct vmpressure_event { - struct eventfd_ctx *efd; - enum vmpressure_levels level; - enum vmpressure_modes mode; - struct list_head node; -}; - -static bool vmpressure_event(struct vmpressure *vmpr, - const enum vmpressure_levels level, - bool ancestor, bool signalled) -{ - struct vmpressure_event *ev; - bool ret = false; - - mutex_lock(&vmpr->events_lock); - list_for_each_entry(ev, &vmpr->events, node) { - if (ancestor && ev->mode == VMPRESSURE_LOCAL) - continue; - if (signalled && ev->mode == VMPRESSURE_NO_PASSTHROUGH) - continue; - if (level < ev->level) - continue; - eventfd_signal(ev->efd); - ret = true; - } - mutex_unlock(&vmpr->events_lock); - - return ret; -} - -static void vmpressure_work_fn(struct work_struct *work) -{ - struct vmpressure *vmpr = work_to_vmpressure(work); - unsigned long scanned; - unsigned long reclaimed; - enum vmpressure_levels level; - bool ancestor = false; - bool signalled = false; - - spin_lock(&vmpr->sr_lock); - /* - * Several contexts might be calling vmpressure(), so it is - * possible that the work was rescheduled again before the old - * work context cleared the counters. In that case we will run - * just after the old work returns, but then scanned might be zero - * here. No need for any locks here since we don't care if - * vmpr->reclaimed is in sync. - */ - scanned = vmpr->tree_scanned; - if (!scanned) { - spin_unlock(&vmpr->sr_lock); - return; - } - - reclaimed = vmpr->tree_reclaimed; - vmpr->tree_scanned = 0; - vmpr->tree_reclaimed = 0; - spin_unlock(&vmpr->sr_lock); - - level = vmpressure_calc_level(scanned, reclaimed); - - do { - if (vmpressure_event(vmpr, level, ancestor, signalled)) - signalled = true; - ancestor = true; - } while ((vmpr = vmpressure_parent(vmpr))); -} - /** * vmpressure() - Account memory pressure through scanned/reclaimed ratio * @gfp: reclaimer's gfp mask @@ -282,14 +151,7 @@ void vmpressure(gfp_t gfp, int order, struct mem_cgroup *memcg, bool tree, return; if (tree) { - spin_lock(&vmpr->sr_lock); - scanned = vmpr->tree_scanned += scanned; - vmpr->tree_reclaimed += reclaimed; - spin_unlock(&vmpr->sr_lock); - - if (scanned < vmpressure_win) - return; - schedule_work(&vmpr->work); + vmpressure_v1_account_tree(vmpr, scanned, reclaimed); } else { enum vmpressure_levels level; @@ -331,134 +193,6 @@ void vmpressure(gfp_t gfp, int order, struct mem_cgroup *memcg, bool tree, } } -/** - * vmpressure_prio() - Account memory pressure through reclaimer priority level - * @gfp: reclaimer's gfp mask - * @memcg: cgroup memory controller handle - * @prio: reclaimer's priority - * - * This function should be called from the reclaim path every time when - * the vmscan's reclaiming priority (scanning depth) changes. - * - * This function does not return any value. - */ -void vmpressure_prio(gfp_t gfp, struct mem_cgroup *memcg, int prio) -{ - /* - * We only use prio for accounting critical level. For more info - * see comment for vmpressure_level_critical_prio variable above. - */ - if (prio > vmpressure_level_critical_prio) - return; - - /* - * OK, the prio is below the threshold, updating vmpressure - * information before shrinker dives into long shrinking of long - * range vmscan. Passing scanned = vmpressure_win, reclaimed = 0 - * to the vmpressure() basically means that we signal 'critical' - * level. - */ - vmpressure(gfp, 0, memcg, true, vmpressure_win, 0); -} - -#define MAX_VMPRESSURE_ARGS_LEN (strlen("critical") + strlen("hierarchy") + 2) - -/** - * vmpressure_register_event() - Bind vmpressure notifications to an eventfd - * @memcg: memcg that is interested in vmpressure notifications - * @eventfd: eventfd context to link notifications with - * @args: event arguments (pressure level threshold, optional mode) - * - * This function associates eventfd context with the vmpressure - * infrastructure, so that the notifications will be delivered to the - * @eventfd. The @args parameter is a comma-delimited string that denotes a - * pressure level threshold (one of vmpressure_str_levels, i.e. "low", "medium", - * or "critical") and an optional mode (one of vmpressure_str_modes, i.e. - * "hierarchy" or "local"). - * - * To be used as memcg event method. - * - * Return: 0 on success, -ENOMEM on memory failure or -EINVAL if @args could - * not be parsed. - */ -int vmpressure_register_event(struct mem_cgroup *memcg, - struct eventfd_ctx *eventfd, const char *args) -{ - struct vmpressure *vmpr = memcg_to_vmpressure(memcg); - struct vmpressure_event *ev; - enum vmpressure_modes mode = VMPRESSURE_NO_PASSTHROUGH; - enum vmpressure_levels level; - char *spec, *spec_orig; - char *token; - int ret = 0; - - spec_orig = spec = kstrndup(args, MAX_VMPRESSURE_ARGS_LEN, GFP_KERNEL); - if (!spec) - return -ENOMEM; - - /* Find required level */ - token = strsep(&spec, ","); - ret = match_string(vmpressure_str_levels, VMPRESSURE_NUM_LEVELS, token); - if (ret < 0) - goto out; - level = ret; - - /* Find optional mode */ - token = strsep(&spec, ","); - if (token) { - ret = match_string(vmpressure_str_modes, VMPRESSURE_NUM_MODES, token); - if (ret < 0) - goto out; - mode = ret; - } - - ev = kzalloc_obj(*ev); - if (!ev) { - ret = -ENOMEM; - goto out; - } - - ev->efd = eventfd; - ev->level = level; - ev->mode = mode; - - mutex_lock(&vmpr->events_lock); - list_add(&ev->node, &vmpr->events); - mutex_unlock(&vmpr->events_lock); - ret = 0; -out: - kfree(spec_orig); - return ret; -} - -/** - * vmpressure_unregister_event() - Unbind eventfd from vmpressure - * @memcg: memcg handle - * @eventfd: eventfd context that was used to link vmpressure with the @cg - * - * This function does internal manipulations to detach the @eventfd from - * the vmpressure notifications, and then frees internal resources - * associated with the @eventfd (but the @eventfd itself is not freed). - * - * To be used as memcg event method. - */ -void vmpressure_unregister_event(struct mem_cgroup *memcg, - struct eventfd_ctx *eventfd) -{ - struct vmpressure *vmpr = memcg_to_vmpressure(memcg); - struct vmpressure_event *ev; - - mutex_lock(&vmpr->events_lock); - list_for_each_entry(ev, &vmpr->events, node) { - if (ev->efd != eventfd) - continue; - list_del(&ev->node); - kfree(ev); - break; - } - mutex_unlock(&vmpr->events_lock); -} - /** * vmpressure_init() - Initialize vmpressure control structure * @vmpr: Structure to be initialized @@ -469,9 +203,7 @@ void vmpressure_unregister_event(struct mem_cgroup *memcg, void vmpressure_init(struct vmpressure *vmpr) { spin_lock_init(&vmpr->sr_lock); - mutex_init(&vmpr->events_lock); - INIT_LIST_HEAD(&vmpr->events); - INIT_WORK(&vmpr->work, vmpressure_work_fn); + vmpressure_v1_init(vmpr); } /** @@ -483,9 +215,5 @@ void vmpressure_init(struct vmpressure *vmpr) */ void vmpressure_cleanup(struct vmpressure *vmpr) { - /* - * Make sure there is no pending work before eventfd infrastructure - * goes away. - */ - flush_work(&vmpr->work); + vmpressure_v1_cleanup(vmpr); } From 2560f03bc9b5d0d4281422d1e657853f5df020b9 Mon Sep 17 00:00:00 2001 From: yahia ahmed Date: Tue, 30 Jun 2026 15:02:22 +0300 Subject: [PATCH 307/562] mm/shmem: fix data-race in shmem_fault shmem_fault and shmem_writeout access inode->i_private without holding a lock, while shmem_fallocate is modifying it while holding a lock, thus a data-race is created. Fix this by using READ_ONCE and WRITE_ONCE, which provides an atomic, lockless read and write of inode->i_private which prevents compiler optimizations such as caching in registers and add writing to inode->i_private with WRITE_ONCE to prevent the compiler from writing in registers. Link: https://lore.kernel.org/20260630120222.11562-1-yahia.a.abdrabou@gmail.com Signed-off-by: yahia ahmed Reported-by: syzbot+76cc716982cf0254f302@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=76cc716982cf0254f302 Cc: Baolin Wang Cc: Hugh Dickins Signed-off-by: Andrew Morton --- mm/shmem.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mm/shmem.c b/mm/shmem.c index b06c1ae2f50c..0ea74fa84f25 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -1657,7 +1657,7 @@ int shmem_writeout(struct folio *folio, struct swap_iocb **plug, * reactivate the folio, and let shmem_fallocate() quit when too many. */ if (!folio_test_uptodate(folio)) { - if (inode->i_private) { + if (READ_ONCE(inode->i_private)) { struct shmem_falloc *shmem_falloc; spin_lock(&inode->i_lock); shmem_falloc = inode->i_private; @@ -2693,7 +2693,7 @@ static vm_fault_t shmem_fault(struct vm_fault *vmf) * Trinity finds that probing a hole which tmpfs is punching can * prevent the hole-punch from ever completing: noted in i_private. */ - if (unlikely(inode->i_private)) { + if (unlikely(READ_ONCE(inode->i_private))) { ret = shmem_falloc_wait(vmf, inode); if (ret) return ret; @@ -3630,7 +3630,7 @@ static long shmem_fallocate(struct file *file, int mode, loff_t offset, shmem_falloc.start = (u64)unmap_start >> PAGE_SHIFT; shmem_falloc.next = (unmap_end + 1) >> PAGE_SHIFT; spin_lock(&inode->i_lock); - inode->i_private = &shmem_falloc; + WRITE_ONCE(inode->i_private, &shmem_falloc); spin_unlock(&inode->i_lock); if ((u64)unmap_end > (u64)unmap_start) @@ -3640,7 +3640,7 @@ static long shmem_fallocate(struct file *file, int mode, loff_t offset, /* No need to unmap again: hole-punching leaves COWed pages */ spin_lock(&inode->i_lock); - inode->i_private = NULL; + WRITE_ONCE(inode->i_private, NULL); wake_up_all(&shmem_falloc_waitq); WARN_ON_ONCE(!list_empty(&shmem_falloc_waitq.head)); spin_unlock(&inode->i_lock); @@ -3672,7 +3672,7 @@ static long shmem_fallocate(struct file *file, int mode, loff_t offset, shmem_falloc.nr_falloced = 0; shmem_falloc.nr_unswapped = 0; spin_lock(&inode->i_lock); - inode->i_private = &shmem_falloc; + WRITE_ONCE(inode->i_private, &shmem_falloc); spin_unlock(&inode->i_lock); /* @@ -3747,7 +3747,7 @@ static long shmem_fallocate(struct file *file, int mode, loff_t offset, i_size_write(inode, offset + len); undone: spin_lock(&inode->i_lock); - inode->i_private = NULL; + WRITE_ONCE(inode->i_private, NULL); spin_unlock(&inode->i_lock); out: if (!error) From ca4ce998d1a15153a8f1cef29f48524edf5024df Mon Sep 17 00:00:00 2001 From: Hongfu Li Date: Thu, 2 Jul 2026 14:23:28 +0800 Subject: [PATCH 308/562] selftests/mm: move pkey selftest helpers to pkey_util.c Patch series "selftests/mm: refactor pkey helpers and fix mmap error handling", v9. The main changes in this series are to refactor shared tracing and assertion helpers into a common file, unify both pkey selftests on pkey_assert() and per-test tracing for consistent diagnostics, and add missing mmap() return checks with MAP_FAILED used throughout for readability and consistency. This patch (of 5): Move pkey selftest debugging helpers into shared code so both pkey selftests can use the same tracing and abort-hook logic. Also fix cat_into_file() to print file, not str, in the open() failure message. Link: https://lore.kernel.org/20260702062332.911786-1-lihongfu@kylinos.cn Link: https://lore.kernel.org/20260702062332.911786-2-lihongfu@kylinos.cn Signed-off-by: Hongfu Li Acked-by: Mike Rapoport (Microsoft) Acked-by: Liam R. Howlett (Oracle) Reviewed-by: Kevin Brodsky Tested-by: Kevin Brodsky Cc: David Hildenbrand Cc: Joey Gouly Cc: John Hubbard Cc: Keith Lucas Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Muhammad Usama Anjum Cc: Ross Zwisler Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Yury Khrustalev Signed-off-by: Andrew Morton --- tools/testing/selftests/mm/pkey-helpers.h | 4 +- tools/testing/selftests/mm/pkey_util.c | 90 ++++++++++++++++++++ tools/testing/selftests/mm/protection_keys.c | 87 ------------------- 3 files changed, 93 insertions(+), 88 deletions(-) diff --git a/tools/testing/selftests/mm/pkey-helpers.h b/tools/testing/selftests/mm/pkey-helpers.h index 2c377f4e9df1..46a8a1878dc1 100644 --- a/tools/testing/selftests/mm/pkey-helpers.h +++ b/tools/testing/selftests/mm/pkey-helpers.h @@ -68,7 +68,9 @@ static inline void sigsafe_printf(const char *format, ...) #define dprintf3(args...) dprintf_level(3, args) #define dprintf4(args...) dprintf_level(4, args) -extern void abort_hooks(void); +void tracing_on(void); +void tracing_off(void); +void abort_hooks(void); #define pkey_assert(condition) do { \ if (!(condition)) { \ dprintf0("# assert() at %s::%d test_nr: %d iteration: %d\n", \ diff --git a/tools/testing/selftests/mm/pkey_util.c b/tools/testing/selftests/mm/pkey_util.c index 255b332f7a08..fbef3cd45447 100644 --- a/tools/testing/selftests/mm/pkey_util.c +++ b/tools/testing/selftests/mm/pkey_util.c @@ -2,9 +2,99 @@ #define __SANE_USERSPACE_TYPES__ #include #include +#include +#include +#include #include "pkey-helpers.h" +int iteration_nr = 1; +int test_nr; +int dprint_in_signal; + +#if CONTROL_TRACING > 0 +static void cat_into_file(char *str, char *file) +{ + int fd = open(file, O_RDWR); + int ret; + + dprintf2("%s(): writing '%s' to '%s'\n", __func__, str, file); + /* + * these need to be raw because they are called under + * pkey_assert() + */ + if (fd < 0) { + fprintf(stderr, "error opening '%s'\n", file); + perror("error: "); + exit(__LINE__); + } + + ret = write(fd, str, strlen(str)); + if (ret != strlen(str)) { + perror("write to file failed"); + fprintf(stderr, "filename: '%s' str: '%s'\n", file, str); + exit(__LINE__); + } + close(fd); +} + +static int warned_tracing; +static int tracing_root_ok(void) +{ + if (geteuid() != 0) { + if (!warned_tracing) + fprintf(stderr, "WARNING: not run as root, " + "can not do tracing control\n"); + warned_tracing = 1; + return 0; + } + return 1; +} +#endif + +void tracing_on(void) +{ +#if CONTROL_TRACING > 0 +#define TRACEDIR "/sys/kernel/tracing" + char pidstr[32]; + + if (!tracing_root_ok()) + return; + + sprintf(pidstr, "%d", getpid()); + cat_into_file("0", TRACEDIR "/tracing_on"); + cat_into_file("\n", TRACEDIR "/trace"); + if (1) { + cat_into_file("function_graph", TRACEDIR "/current_tracer"); + cat_into_file("1", TRACEDIR "/options/funcgraph-proc"); + } else { + cat_into_file("nop", TRACEDIR "/current_tracer"); + } + cat_into_file(pidstr, TRACEDIR "/set_ftrace_pid"); + cat_into_file("1", TRACEDIR "/tracing_on"); + dprintf1("enabled tracing\n"); +#endif +} + +void tracing_off(void) +{ +#if CONTROL_TRACING > 0 + if (!tracing_root_ok()) + return; + cat_into_file("0", "/sys/kernel/tracing/tracing_on"); +#endif +} + +void abort_hooks(void) +{ + fflush(stdout); + fprintf(stderr, "running %s()...\n", __func__); + tracing_off(); +#ifdef SLEEP_ON_ABORT + sleep(SLEEP_ON_ABORT); +#endif +} + int sys_pkey_alloc(unsigned long flags, unsigned long init_val) { int ret = syscall(SYS_pkey_alloc, flags, init_val); diff --git a/tools/testing/selftests/mm/protection_keys.c b/tools/testing/selftests/mm/protection_keys.c index 9a6d954ee371..be4a0470cc61 100644 --- a/tools/testing/selftests/mm/protection_keys.c +++ b/tools/testing/selftests/mm/protection_keys.c @@ -49,11 +49,7 @@ #include "hugepage_settings.h" #include "pkey-helpers.h" -int iteration_nr = 1; -int test_nr; - u64 shadow_pkey_reg; -int dprint_in_signal; noinline int read_ptr(int *ptr) { @@ -62,89 +58,6 @@ noinline int read_ptr(int *ptr) return *ptr; } -#if CONTROL_TRACING > 0 -static void cat_into_file(char *str, char *file) -{ - int fd = open(file, O_RDWR); - int ret; - - dprintf2("%s(): writing '%s' to '%s'\n", __func__, str, file); - /* - * these need to be raw because they are called under - * pkey_assert() - */ - if (fd < 0) { - fprintf(stderr, "error opening '%s'\n", str); - perror("error: "); - exit(__LINE__); - } - - ret = write(fd, str, strlen(str)); - if (ret != strlen(str)) { - perror("write to file failed"); - fprintf(stderr, "filename: '%s' str: '%s'\n", file, str); - exit(__LINE__); - } - close(fd); -} - -static int warned_tracing; -static int tracing_root_ok(void) -{ - if (geteuid() != 0) { - if (!warned_tracing) - fprintf(stderr, "WARNING: not run as root, " - "can not do tracing control\n"); - warned_tracing = 1; - return 0; - } - return 1; -} -#endif - -static void tracing_on(void) -{ -#if CONTROL_TRACING > 0 -#define TRACEDIR "/sys/kernel/tracing" - char pidstr[32]; - - if (!tracing_root_ok()) - return; - - sprintf(pidstr, "%d", getpid()); - cat_into_file("0", TRACEDIR "/tracing_on"); - cat_into_file("\n", TRACEDIR "/trace"); - if (1) { - cat_into_file("function_graph", TRACEDIR "/current_tracer"); - cat_into_file("1", TRACEDIR "/options/funcgraph-proc"); - } else { - cat_into_file("nop", TRACEDIR "/current_tracer"); - } - cat_into_file(pidstr, TRACEDIR "/set_ftrace_pid"); - cat_into_file("1", TRACEDIR "/tracing_on"); - dprintf1("enabled tracing\n"); -#endif -} - -static void tracing_off(void) -{ -#if CONTROL_TRACING > 0 - if (!tracing_root_ok()) - return; - cat_into_file("0", "/sys/kernel/tracing/tracing_on"); -#endif -} - -void abort_hooks(void) -{ - fflush(stdout); - fprintf(stderr, "running %s()...\n", __func__); - tracing_off(); -#ifdef SLEEP_ON_ABORT - sleep(SLEEP_ON_ABORT); -#endif -} - /* * This attempts to have roughly a page of instructions followed by a few * instructions that do a write, and another page of instructions. That From 01b63679170fb057822356abb91cc765652b13dd Mon Sep 17 00:00:00 2001 From: Hongfu Li Date: Thu, 2 Jul 2026 14:23:29 +0800 Subject: [PATCH 309/562] selftests/mm: unify pkey sighandler selftest assertions and tracing Add per-test tracing to the pkey signal-handler selftest and use pkey_assert() for error handling. Each test enables tracing at start and disables it at end; on failure, pkey_assert() calls abort_hooks() to turn tracing off so ftrace is not left enabled. Link: https://lore.kernel.org/20260702062332.911786-3-lihongfu@kylinos.cn Signed-off-by: Hongfu Li Acked-by: Mike Rapoport (Microsoft) Acked-by: Liam R. Howlett (Oracle) Tested-by: Kevin Brodsky Reviewed-by: Kevin Brodsky Cc: David Hildenbrand Cc: Joey Gouly Cc: John Hubbard Cc: Keith Lucas Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Muhammad Usama Anjum Cc: Ross Zwisler Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Yury Khrustalev Signed-off-by: Andrew Morton --- .../selftests/mm/pkey_sighandler_tests.c | 89 +++++++++---------- 1 file changed, 44 insertions(+), 45 deletions(-) diff --git a/tools/testing/selftests/mm/pkey_sighandler_tests.c b/tools/testing/selftests/mm/pkey_sighandler_tests.c index 302fef54049c..8b3400379423 100644 --- a/tools/testing/selftests/mm/pkey_sighandler_tests.c +++ b/tools/testing/selftests/mm/pkey_sighandler_tests.c @@ -19,7 +19,6 @@ #include #include #include -#include #include #include #include @@ -207,15 +206,14 @@ static void test_sigsegv_handler_with_pkey0_disabled(void) struct sigaction sa; pthread_attr_t attr; pthread_t thr; + int ret; sa.sa_flags = SA_SIGINFO; sa.sa_sigaction = sigsegv_handler; sigemptyset(&sa.sa_mask); - if (sigaction(SIGSEGV, &sa, NULL) == -1) { - perror("sigaction"); - exit(EXIT_FAILURE); - } + ret = sigaction(SIGSEGV, &sa, NULL); + pkey_assert(ret == 0); memset(&siginfo, 0, sizeof(siginfo)); @@ -247,15 +245,14 @@ static void test_sigsegv_handler_cannot_access_stack(void) struct sigaction sa; pthread_attr_t attr; pthread_t thr; + int ret; sa.sa_flags = SA_SIGINFO; sa.sa_sigaction = sigsegv_handler; sigemptyset(&sa.sa_mask); - if (sigaction(SIGSEGV, &sa, NULL) == -1) { - perror("sigaction"); - exit(EXIT_FAILURE); - } + ret = sigaction(SIGSEGV, &sa, NULL); + pkey_assert(ret == 0); memset(&siginfo, 0, sizeof(siginfo)); @@ -288,21 +285,20 @@ static void test_sigsegv_handler_with_different_pkey_for_stack(void) int parent_pid = 0; int child_pid = 0; u64 pkey_reg; + long ret; sa.sa_flags = SA_SIGINFO | SA_ONSTACK; sa.sa_sigaction = sigsegv_handler; sigemptyset(&sa.sa_mask); - if (sigaction(SIGSEGV, &sa, NULL) == -1) { - perror("sigaction"); - exit(EXIT_FAILURE); - } + ret = sigaction(SIGSEGV, &sa, NULL); + pkey_assert(ret == 0); stack = mmap(0, STACK_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - assert(stack != MAP_FAILED); + pkey_assert(stack != MAP_FAILED); /* Allow access to MPK 0 and MPK 1 */ pkey_reg = pkey_reg_restrictive_default(); @@ -323,13 +319,13 @@ static void test_sigsegv_handler_with_different_pkey_for_stack(void) memset(&siginfo, 0, sizeof(siginfo)); /* Use clone to avoid newer glibcs using rseq on new threads */ - long ret = clone_raw(CLONE_VM | CLONE_FS | CLONE_FILES | - CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM | - CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID | - CLONE_DETACHED, - stack + STACK_SIZE, - &parent_pid, - &child_pid); + ret = clone_raw(CLONE_VM | CLONE_FS | CLONE_FILES | + CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM | + CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID | + CLONE_DETACHED, + stack + STACK_SIZE, + &parent_pid, + &child_pid); if (ret < 0) { errno = -ret; @@ -358,6 +354,7 @@ static void test_pkru_preserved_after_sigusr1(void) { struct sigaction sa; u64 pkey_reg; + int ret; /* Allow access to MPK 0 and an arbitrary set of keys */ pkey_reg = pkey_reg_restrictive_default(); @@ -369,10 +366,8 @@ static void test_pkru_preserved_after_sigusr1(void) sa.sa_sigaction = sigusr1_handler; sigemptyset(&sa.sa_mask); - if (sigaction(SIGUSR1, &sa, NULL) == -1) { - perror("sigaction"); - exit(EXIT_FAILURE); - } + ret = sigaction(SIGUSR1, &sa, NULL); + pkey_assert(ret == 0); memset(&siginfo, 0, sizeof(siginfo)); @@ -444,6 +439,13 @@ static void test_pkru_sigreturn(void) int parent_pid = 0; int child_pid = 0; u64 pkey_reg; + long ret; + + /* + * SIGSEGV handler is reset to SIG_DFL below; turn tracing off first + * so a crash does not leave ftrace enabled. + */ + tracing_off(); sa.sa_handler = SIG_DFL; sa.sa_flags = 0; @@ -453,24 +455,20 @@ static void test_pkru_sigreturn(void) * For this testcase, we do not want to handle SIGSEGV. Reset handler * to default so that the application can crash if it receives SIGSEGV. */ - if (sigaction(SIGSEGV, &sa, NULL) == -1) { - perror("sigaction"); - exit(EXIT_FAILURE); - } + ret = sigaction(SIGSEGV, &sa, NULL); + pkey_assert(ret == 0); sa.sa_flags = SA_SIGINFO | SA_ONSTACK; sa.sa_sigaction = sigusr2_handler; sigemptyset(&sa.sa_mask); - if (sigaction(SIGUSR2, &sa, NULL) == -1) { - perror("sigaction"); - exit(EXIT_FAILURE); - } + ret = sigaction(SIGUSR2, &sa, NULL); + pkey_assert(ret == 0); stack = mmap(0, STACK_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - assert(stack != MAP_FAILED); + pkey_assert(stack != MAP_FAILED); /* * Allow access to MPK 0 and MPK 2. The child thread (to be created @@ -494,13 +492,13 @@ static void test_pkru_sigreturn(void) sigstack.ss_size = STACK_SIZE; /* Use clone to avoid newer glibcs using rseq on new threads */ - long ret = clone_raw(CLONE_VM | CLONE_FS | CLONE_FILES | - CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM | - CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID | - CLONE_DETACHED, - stack + STACK_SIZE, - &parent_pid, - &child_pid); + ret = clone_raw(CLONE_VM | CLONE_FS | CLONE_FILES | + CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM | + CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID | + CLONE_DETACHED, + stack + STACK_SIZE, + &parent_pid, + &child_pid); if (ret < 0) { errno = -ret; @@ -530,16 +528,17 @@ static void (*pkey_tests[])(void) = { int main(int argc, char *argv[]) { - int i; - ksft_print_header(); ksft_set_plan(ARRAY_SIZE(pkey_tests)); if (!is_pkeys_supported()) ksft_exit_skip("pkeys not supported\n"); - for (i = 0; i < ARRAY_SIZE(pkey_tests); i++) - (*pkey_tests[i])(); + for (test_nr = 0; test_nr < ARRAY_SIZE(pkey_tests); test_nr++) { + tracing_on(); + (*pkey_tests[test_nr])(); + tracing_off(); + } ksft_finished(); return 0; From 0c277a8d8d2e7613fd05a3e795a999f98db12ebb Mon Sep 17 00:00:00 2001 From: Hongfu Li Date: Thu, 2 Jul 2026 14:23:30 +0800 Subject: [PATCH 310/562] selftests/mm: use pkey_assert on clone_raw failure in pkey test Use pkey_assert(0) instead of perror("clone") when clone_raw() fails. The old path only printed an error and continued; the test now exits via pkey_assert() on failure so it does not hang or proceed with an invalid child. Link: https://lore.kernel.org/20260702062332.911786-4-lihongfu@kylinos.cn Signed-off-by: Hongfu Li Acked-by: Mike Rapoport (Microsoft) Acked-by: Liam R. Howlett (Oracle) Reviewed-by: Kevin Brodsky Tested-by: Kevin Brodsky Cc: David Hildenbrand Cc: Joey Gouly Cc: John Hubbard Cc: Keith Lucas Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Muhammad Usama Anjum Cc: Ross Zwisler Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Yury Khrustalev Signed-off-by: Andrew Morton --- tools/testing/selftests/mm/pkey_sighandler_tests.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/mm/pkey_sighandler_tests.c b/tools/testing/selftests/mm/pkey_sighandler_tests.c index 8b3400379423..188513e94fc5 100644 --- a/tools/testing/selftests/mm/pkey_sighandler_tests.c +++ b/tools/testing/selftests/mm/pkey_sighandler_tests.c @@ -329,7 +329,7 @@ static void test_sigsegv_handler_with_different_pkey_for_stack(void) if (ret < 0) { errno = -ret; - perror("clone"); + pkey_assert(0); } else if (ret == 0) { thread_segv_maperr_ptr(&sigstack); syscall_raw(SYS_exit, 0, 0, 0, 0, 0, 0); @@ -502,7 +502,7 @@ static void test_pkru_sigreturn(void) if (ret < 0) { errno = -ret; - perror("clone"); + pkey_assert(0); } else if (ret == 0) { thread_sigusr2_self(&sigstack); syscall_raw(SYS_exit, 0, 0, 0, 0, 0, 0); From 00156ddfcd0e38548c4beadf35bd418c70462d98 Mon Sep 17 00:00:00 2001 From: Hongfu Li Date: Thu, 2 Jul 2026 14:23:31 +0800 Subject: [PATCH 311/562] selftests/mm: add missing mmap() return checks in pkey tests Add missing checks against mmap() return value, replace (void *)-1 with MAP_FAILED for better readability and consistency. Link: https://lore.kernel.org/20260702062332.911786-5-lihongfu@kylinos.cn Signed-off-by: Hongfu Li Acked-by: Mike Rapoport (Microsoft) Acked-by: Liam R. Howlett (Oracle) Reviewed-by: Kevin Brodsky Tested-by: Kevin Brodsky Cc: David Hildenbrand Cc: Joey Gouly Cc: John Hubbard Cc: Keith Lucas Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Muhammad Usama Anjum Cc: Ross Zwisler Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Yury Khrustalev Signed-off-by: Andrew Morton --- tools/testing/selftests/mm/pkey-powerpc.h | 2 +- tools/testing/selftests/mm/pkey_sighandler_tests.c | 2 ++ tools/testing/selftests/mm/protection_keys.c | 12 +++++++----- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/tools/testing/selftests/mm/pkey-powerpc.h b/tools/testing/selftests/mm/pkey-powerpc.h index 17bf2d1b0192..2ce85580b404 100644 --- a/tools/testing/selftests/mm/pkey-powerpc.h +++ b/tools/testing/selftests/mm/pkey-powerpc.h @@ -126,7 +126,7 @@ static inline void *malloc_pkey_with_mprotect_subpage(long size, int prot, u16 p size, prot, pkey); pkey_assert(pkey < NR_PKEYS); ptr = mmap(NULL, size, prot, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); - pkey_assert(ptr != (void *)-1); + pkey_assert(ptr != MAP_FAILED); ret = syscall(__NR_subpage_prot, ptr, size, NULL); if (ret) { diff --git a/tools/testing/selftests/mm/pkey_sighandler_tests.c b/tools/testing/selftests/mm/pkey_sighandler_tests.c index 188513e94fc5..8f98e6d9349f 100644 --- a/tools/testing/selftests/mm/pkey_sighandler_tests.c +++ b/tools/testing/selftests/mm/pkey_sighandler_tests.c @@ -313,6 +313,7 @@ static void test_sigsegv_handler_with_different_pkey_for_stack(void) /* Set up alternate signal stack that will use the default MPK */ sigstack.ss_sp = mmap(0, STACK_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + pkey_assert(sigstack.ss_sp != MAP_FAILED); sigstack.ss_flags = 0; sigstack.ss_size = STACK_SIZE; @@ -488,6 +489,7 @@ static void test_pkru_sigreturn(void) /* Set up alternate signal stack that will use the default MPK */ sigstack.ss_sp = mmap(0, STACK_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + pkey_assert(sigstack.ss_sp != MAP_FAILED); sigstack.ss_flags = 0; sigstack.ss_size = STACK_SIZE; diff --git a/tools/testing/selftests/mm/protection_keys.c b/tools/testing/selftests/mm/protection_keys.c index be4a0470cc61..ae6e1530b354 100644 --- a/tools/testing/selftests/mm/protection_keys.c +++ b/tools/testing/selftests/mm/protection_keys.c @@ -582,7 +582,7 @@ static void *malloc_pkey_with_mprotect(long size, int prot, u16 pkey) size, prot, pkey); pkey_assert(pkey < NR_PKEYS); ptr = mmap(NULL, size, prot, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); - pkey_assert(ptr != (void *)-1); + pkey_assert(ptr != MAP_FAILED); ret = mprotect_pkey((void *)ptr, PAGE_SIZE, prot, pkey); pkey_assert(!ret); record_pkey_malloc(ptr, size, prot); @@ -605,7 +605,7 @@ static void *malloc_pkey_anon_huge(long size, int prot, u16 pkey) */ size = ALIGN_UP(size, HPAGE_SIZE * 2); ptr = mmap(NULL, size, PROT_NONE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); - pkey_assert(ptr != (void *)-1); + pkey_assert(ptr != MAP_FAILED); record_pkey_malloc(ptr, size, prot); mprotect_pkey(ptr, size, prot, pkey); @@ -663,7 +663,7 @@ static void *malloc_pkey_hugetlb(long size, int prot, u16 pkey) size = ALIGN_UP(size, HPAGE_SIZE * 2); pkey_assert(pkey < NR_PKEYS); ptr = mmap(NULL, size, PROT_NONE, flags, -1, 0); - pkey_assert(ptr != (void *)-1); + pkey_assert(ptr != MAP_FAILED); mprotect_pkey(ptr, size, prot, pkey); record_pkey_malloc(ptr, size, prot); @@ -692,7 +692,7 @@ static void *malloc_pkey(long size, int prot, u16 pkey) pkey_assert(malloc_type < nr_malloc_types); ret = pkey_malloc[malloc_type](size, prot, pkey); - pkey_assert(ret != (void *)-1); + pkey_assert(ret != MAP_FAILED); malloc_type++; if (malloc_type >= nr_malloc_types) @@ -1110,6 +1110,7 @@ static void arch_force_pkey_reg_init(void) * doing the XSAVE size enumeration dance. */ buf = mmap(NULL, 1*MB, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); + pkey_assert(buf != MAP_FAILED); /* These __builtins require compiling with -mxsave */ @@ -1676,7 +1677,8 @@ int main(void) ksft_print_msg("running PKEY tests for unsupported CPU/OS\n"); ptr = mmap(NULL, size, PROT_NONE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); - assert(ptr != (void *)-1); + if (ptr == MAP_FAILED) + ksft_exit_fail_perror("mmap"); test_mprotect_pkey_on_unsupported_cpu(ptr, 1); ksft_test_result_pass("pkey on unsupported CPU/OS\n"); ksft_finished(); From 01777c94d98c84f371e0fe57ca02e3655ab92b06 Mon Sep 17 00:00:00 2001 From: Hongfu Li Date: Thu, 2 Jul 2026 14:23:32 +0800 Subject: [PATCH 312/562] selftests/mm: add missing pthread_create() return checks in pkey tests Add missing pthread_create() return checks in pkey sighandler tests to avoid hanging in pthread_cond_wait() when thread creation fails. Link: https://lore.kernel.org/20260702062332.911786-6-lihongfu@kylinos.cn Signed-off-by: Hongfu Li Acked-by: Mike Rapoport (Microsoft) Acked-by: Liam R. Howlett (Oracle) Reviewed-by: Kevin Brodsky Tested-by: Kevin Brodsky Cc: David Hildenbrand Cc: Joey Gouly Cc: John Hubbard Cc: Keith Lucas Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Muhammad Usama Anjum Cc: Ross Zwisler Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Yury Khrustalev Signed-off-by: Andrew Morton --- tools/testing/selftests/mm/pkey_sighandler_tests.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/mm/pkey_sighandler_tests.c b/tools/testing/selftests/mm/pkey_sighandler_tests.c index 8f98e6d9349f..cbc24d6cf770 100644 --- a/tools/testing/selftests/mm/pkey_sighandler_tests.c +++ b/tools/testing/selftests/mm/pkey_sighandler_tests.c @@ -220,7 +220,11 @@ static void test_sigsegv_handler_with_pkey0_disabled(void) pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - pthread_create(&thr, &attr, thread_segv_with_pkey0_disabled, NULL); + ret = pthread_create(&thr, &attr, thread_segv_with_pkey0_disabled, NULL); + if (ret) { + errno = ret; + pkey_assert(0); + } pthread_mutex_lock(&mutex); while (siginfo.si_signo == 0) @@ -259,7 +263,11 @@ static void test_sigsegv_handler_cannot_access_stack(void) pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - pthread_create(&thr, &attr, thread_segv_pkuerr_stack, NULL); + ret = pthread_create(&thr, &attr, thread_segv_pkuerr_stack, NULL); + if (ret) { + errno = ret; + pkey_assert(0); + } pthread_mutex_lock(&mutex); while (siginfo.si_signo == 0) From 5c291c49dd37e04aedadfd2563ec47d9271c809a Mon Sep 17 00:00:00 2001 From: Hui Zhu Date: Tue, 30 Jun 2026 15:08:10 +0800 Subject: [PATCH 313/562] mm: fix ASSERT_EXCLUSIVE_BITS by passing memdesc_flags_t by pointer KCSAN reports a data race between page_to_nid()/folio_pgdat() reading page->flags and folio_trylock()/folio_lock() concurrently doing test_and_set_bit_lock(PG_locked, ...) on the same word, e.g.: BUG: KCSAN: data-race in __lruvec_stat_mod_folio / shmem_get_folio_gfp The race is benign: nid/zone bits are set once at page init and never overlap with PG_locked. However, ASSERT_EXCLUSIVE_BITS() inside memdesc_nid/zonenum() was checking a by-value copy of the flags word, not the live page->flags, so it failed to annotate the real access. Change memdesc_nid(), memdesc_zonenum(), memdesc_section(), and memdesc_is_zone_device() to take a const memdesc_flags_t * and update all callers to pass &page->flags / &folio->flags, so ASSERT_EXCLUSIVE_BITS() operates on the actual shared word. Guard the ASSERT_EXCLUSIVE_BITS() calls in memdesc_zonenum() and memdesc_section() under ZONES_WIDTH != 0 / SECTIONS_WIDTH != 0 to avoid a zero-mask check on configs where the corresponding field is absent. Under CONFIG_NUMA=n, stub out page_to_nid() and folio_nid() as plain "return 0" instead of reading page->flags when NODES_MASK is 0 and the check can never fire. Link: https://lore.kernel.org/20260630070810.470763-1-hui.zhu@linux.dev Signed-off-by: Hui Zhu Co-developed-by: David Hildenbrand (Arm) Signed-off-by: David Hildenbrand (Arm) Cc: Axel Rasmussen Cc: Barry Song Cc: Kairui Song Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Shakeel Butt Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Wei Xu Cc: Yuanchu Xie Signed-off-by: Andrew Morton --- include/asm-generic/memory_model.h | 2 +- include/linux/mm.h | 42 ++++++++++++++++++++++++------ include/linux/mm_inline.h | 4 +-- include/linux/mmzone.h | 26 +++++++++--------- mm/page_alloc.c | 6 ++--- mm/slab.h | 2 +- mm/sparse.c | 2 +- 7 files changed, 56 insertions(+), 28 deletions(-) diff --git a/include/asm-generic/memory_model.h b/include/asm-generic/memory_model.h index fd74de50b054..c6b4eafaf4cb 100644 --- a/include/asm-generic/memory_model.h +++ b/include/asm-generic/memory_model.h @@ -53,7 +53,7 @@ static inline int pfn_valid(unsigned long pfn) */ #define __page_to_pfn(pg) \ ({ const struct page *__pg = (pg); \ - int __sec = memdesc_section(__pg->flags); \ + int __sec = memdesc_section(&__pg->flags); \ (unsigned long)(__pg - __section_mem_map_addr(__nr_to_section(__sec))); \ }) diff --git a/include/linux/mm.h b/include/linux/mm.h index 2101e5205fc0..113e1752548c 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -37,6 +37,7 @@ #include #include #include +#include struct mempolicy; struct anon_vma; @@ -2286,23 +2287,45 @@ static inline int page_zone_id(struct page *page) } #ifdef NODE_NOT_IN_PAGE_FLAGS -int memdesc_nid(memdesc_flags_t mdf); +int memdesc_nid(const memdesc_flags_t *mdf); #else -static inline int memdesc_nid(memdesc_flags_t mdf) +#ifdef CONFIG_NUMA +static inline int memdesc_nid(const memdesc_flags_t *mdf) { - return (mdf.f >> NODES_PGSHIFT) & NODES_MASK; + ASSERT_EXCLUSIVE_BITS(mdf->f, NODES_MASK << NODES_PGSHIFT); + return (mdf->f >> NODES_PGSHIFT) & NODES_MASK; +} +#else +static inline int memdesc_nid(const memdesc_flags_t *mdf) +{ + return 0; } #endif +#endif + +#ifdef CONFIG_NUMA +static inline int page_to_nid(const struct page *page) +{ + const struct page *p = PF_POISONED_CHECK(page); + return memdesc_nid(&p->flags); +} + +static inline int folio_nid(const struct folio *folio) +{ + return memdesc_nid(&folio->flags); +} +#else static inline int page_to_nid(const struct page *page) { - return memdesc_nid(PF_POISONED_CHECK(page)->flags); + return 0; } static inline int folio_nid(const struct folio *folio) { - return memdesc_nid(folio->flags); + return 0; } +#endif #ifdef CONFIG_NUMA_BALANCING /* page access time bits needs to hold at least 4 seconds */ @@ -2541,12 +2564,15 @@ static inline void set_page_section(struct page *page, unsigned long section) page->flags.f |= (section & SECTIONS_MASK) << SECTIONS_PGSHIFT; } -static inline unsigned long memdesc_section(memdesc_flags_t mdf) +static inline unsigned long memdesc_section(const memdesc_flags_t *mdf) { - return (mdf.f >> SECTIONS_PGSHIFT) & SECTIONS_MASK; +#if SECTIONS_WIDTH != 0 + ASSERT_EXCLUSIVE_BITS(mdf->f, SECTIONS_MASK << SECTIONS_PGSHIFT); +#endif + return (mdf->f >> SECTIONS_PGSHIFT) & SECTIONS_MASK; } #else /* !SECTION_IN_PAGE_FLAGS */ -static inline unsigned long memdesc_section(memdesc_flags_t mdf) +static inline unsigned long memdesc_section(const memdesc_flags_t *mdf) { return 0; } diff --git a/include/linux/mm_inline.h b/include/linux/mm_inline.h index a8430a7ae054..efcddb9925ad 100644 --- a/include/linux/mm_inline.h +++ b/include/linux/mm_inline.h @@ -650,7 +650,7 @@ static inline bool vma_has_recency(const struct vm_area_struct *vma) static inline size_t num_pages_contiguous(struct page **pages, size_t nr_pages) { struct page *cur_page = pages[0]; - unsigned long section = memdesc_section(cur_page->flags); + unsigned long section = memdesc_section(&cur_page->flags); size_t i; for (i = 1; i < nr_pages; i++) { @@ -660,7 +660,7 @@ static inline size_t num_pages_contiguous(struct page **pages, size_t nr_pages) * In unproblematic kernel configs, page_to_section() == 0 and * the whole check will get optimized out. */ - if (memdesc_section(cur_page->flags) != section) + if (memdesc_section(&cur_page->flags) != section) break; } diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h index 242ec3cb8b52..0507193b3ae3 100644 --- a/include/linux/mmzone.h +++ b/include/linux/mmzone.h @@ -1272,31 +1272,33 @@ static inline bool zone_is_empty(const struct zone *zone) #define KASAN_TAG_MASK ((1UL << KASAN_TAG_WIDTH) - 1) #define ZONEID_MASK ((1UL << ZONEID_SHIFT) - 1) -static inline enum zone_type memdesc_zonenum(memdesc_flags_t flags) +static inline enum zone_type memdesc_zonenum(const memdesc_flags_t *flags) { - ASSERT_EXCLUSIVE_BITS(flags.f, ZONES_MASK << ZONES_PGSHIFT); - return (flags.f >> ZONES_PGSHIFT) & ZONES_MASK; +#if ZONES_WIDTH != 0 + ASSERT_EXCLUSIVE_BITS(flags->f, ZONES_MASK << ZONES_PGSHIFT); +#endif + return (flags->f >> ZONES_PGSHIFT) & ZONES_MASK; } static inline enum zone_type page_zonenum(const struct page *page) { - return memdesc_zonenum(page->flags); + return memdesc_zonenum(&page->flags); } static inline enum zone_type folio_zonenum(const struct folio *folio) { - return memdesc_zonenum(folio->flags); + return memdesc_zonenum(&folio->flags); } #ifdef CONFIG_ZONE_DEVICE -static inline bool memdesc_is_zone_device(memdesc_flags_t mdf) +static inline bool memdesc_is_zone_device(const memdesc_flags_t *mdf) { return memdesc_zonenum(mdf) == ZONE_DEVICE; } static inline struct dev_pagemap *page_pgmap(const struct page *page) { - VM_WARN_ON_ONCE_PAGE(!memdesc_is_zone_device(page->flags), page); + VM_WARN_ON_ONCE_PAGE(!memdesc_is_zone_device(&page->flags), page); return page_folio(page)->pgmap; } @@ -1311,9 +1313,9 @@ static inline struct dev_pagemap *page_pgmap(const struct page *page) static inline bool zone_device_pages_have_same_pgmap(const struct page *a, const struct page *b) { - if (memdesc_is_zone_device(a->flags) != memdesc_is_zone_device(b->flags)) + if (memdesc_is_zone_device(&a->flags) != memdesc_is_zone_device(&b->flags)) return false; - if (!memdesc_is_zone_device(a->flags)) + if (!memdesc_is_zone_device(&a->flags)) return true; return page_pgmap(a) == page_pgmap(b); } @@ -1321,7 +1323,7 @@ static inline bool zone_device_pages_have_same_pgmap(const struct page *a, extern void memmap_init_zone_device(struct zone *, unsigned long, unsigned long, struct dev_pagemap *); #else -static inline bool memdesc_is_zone_device(memdesc_flags_t mdf) +static inline bool memdesc_is_zone_device(const memdesc_flags_t *mdf) { return false; } @@ -1338,12 +1340,12 @@ static inline struct dev_pagemap *page_pgmap(const struct page *page) static inline bool is_zone_device_page(const struct page *page) { - return memdesc_is_zone_device(page->flags); + return memdesc_is_zone_device(&page->flags); } static inline bool folio_is_zone_device(const struct folio *folio) { - return memdesc_is_zone_device(folio->flags); + return memdesc_is_zone_device(&folio->flags); } static inline bool is_zone_movable_page(const struct page *page) diff --git a/mm/page_alloc.c b/mm/page_alloc.c index 62f71ece7ca1..762d9b6bc792 100644 --- a/mm/page_alloc.c +++ b/mm/page_alloc.c @@ -6913,15 +6913,15 @@ static void __free_contig_range_common(unsigned long pfn, unsigned long nr_pages continue; } - if (start && memdesc_section(page->flags) != start_sec) { + if (start && memdesc_section(&page->flags) != start_sec) { free_prepared_contig_range(start, i - nr_start); start = page; nr_start = i; - start_sec = memdesc_section(page->flags); + start_sec = memdesc_section(&page->flags); } else if (!start) { start = page; nr_start = i; - start_sec = memdesc_section(page->flags); + start_sec = memdesc_section(&page->flags); } } diff --git a/mm/slab.h b/mm/slab.h index 281a65233795..9ded319495a0 100644 --- a/mm/slab.h +++ b/mm/slab.h @@ -179,7 +179,7 @@ static inline void *slab_address(const struct slab *slab) static inline int slab_nid(const struct slab *slab) { - return memdesc_nid(slab->flags); + return memdesc_nid(&slab->flags); } static inline pg_data_t *slab_pgdat(const struct slab *slab) diff --git a/mm/sparse.c b/mm/sparse.c index 324213d8bdcb..058ef9300367 100644 --- a/mm/sparse.c +++ b/mm/sparse.c @@ -43,7 +43,7 @@ static u8 section_to_node_table[NR_MEM_SECTIONS] __cacheline_aligned; static u16 section_to_node_table[NR_MEM_SECTIONS] __cacheline_aligned; #endif -int memdesc_nid(memdesc_flags_t mdf) +int memdesc_nid(const memdesc_flags_t *mdf) { return section_to_node_table[memdesc_section(mdf)]; } From 0bddc3391b42923d00994958c613e80f852c581f Mon Sep 17 00:00:00 2001 From: Andrew Morton Date: Tue, 30 Jun 2026 22:52:22 -0700 Subject: [PATCH 314/562] mm-fix-assert_exclusive_bits-by-passing-memdesc_flags_t-by-pointer-fix fix page_owner.c Cc: Axel Rasmussen Cc: Barry Song Cc: David Hildenbrand (Arm) Cc: Hui Zhu Cc: Kairui Song Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Shakeel Butt Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Wei Xu Cc: Yuanchu Xie Signed-off-by: Andrew Morton --- mm/page_owner.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/page_owner.c b/mm/page_owner.c index fe92affbef90..26d6ab6530ce 100644 --- a/mm/page_owner.c +++ b/mm/page_owner.c @@ -819,7 +819,7 @@ read_page_owner(struct file *file, char __user *buf, size_t count, loff_t *ppos) spin_unlock_irqrestore(&state->lock, flags); goto ext_put_continue; } - nid = memdesc_nid(page_flags); + nid = memdesc_nid(&page_flags); if (!node_isset(nid, state->nid_filter)) { spin_unlock_irqrestore(&state->lock, flags); goto ext_put_continue; From 047bc491257ffa479e8469954f0a65ce5178259a Mon Sep 17 00:00:00 2001 From: Usama Arif Date: Wed, 1 Jul 2026 07:06:38 -0700 Subject: [PATCH 315/562] mm/migrate_device: pin large folios before splitting migrate_vma_collect_pmd() can detect a large folio while holding the PTE lock, then drop the PTE lock before calling migrate_vma_split_folio(). The split helper took its own reference, but only after the lock had already been dropped. One way to hit this is device migration over a range that contains a large folio. The walker reads the PTE while holding the PTE lock and derives the folio either from a present PTE via vm_normal_page(), or from a non-present PTE that encodes a device-private softleaf entry. It then has to drop the PTE lock because split_folio() can block. Before migrate_vma_split_folio() gets a folio reference, concurrent reclaim, migration, or truncation can replace or clear the entry and drop the last reference to the folio. The split helper would then take a reference and lock on a stale folio pointer. Take a temporary reference before dropping the PTE lock and pass that reference into migrate_vma_split_folio(). The helper consumes the reference, so split_folio() still sees only the expected caller pin instead of an extra pin that could make the split fail. Link: https://lore.kernel.org/20260701140638.840773-1-usama.arif@linux.dev Fixes: 022a12deda53 ("mm/migrate_device: handle partially mapped folios during collection") Signed-off-by: Usama Arif Reported-by: sashiko-bot Link: https://sashiko.dev/#/patchset/20260630164143.1595669-1-usama.arif%40linux.dev Acked-by: David Hildenbrand (Arm) Reviewed-by: Zi Yan Reviewed-by: Lance Yang Reviewed-by: SJ Park Cc: Alistair Popple Cc: Byungchul Park Cc: Gregory Price Cc: "Huang, Ying" Cc: Johannes Weiner Cc: Joshua Hahn Cc: Matthew Brost Cc: Rakie Kim Cc: Shakeel Butt Signed-off-by: Andrew Morton --- mm/migrate_device.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/mm/migrate_device.c b/mm/migrate_device.c index 2f8b646302c2..f5a5f699e98e 100644 --- a/mm/migrate_device.c +++ b/mm/migrate_device.c @@ -77,6 +77,9 @@ static int migrate_vma_collect_hole(unsigned long start, * @folio: the folio to split * @fault_page: struct page associated with the fault if any * + * If @folio is not the folio containing @fault_page, the caller must hold a + * reference on @folio. The helper consumes that reference. + * * Returns 0 on success */ static int migrate_vma_split_folio(struct folio *folio, @@ -86,10 +89,8 @@ static int migrate_vma_split_folio(struct folio *folio, struct folio *fault_folio = fault_page ? page_folio(fault_page) : NULL; struct folio *new_fault_folio = NULL; - if (folio != fault_folio) { - folio_get(folio); + if (folio != fault_folio) folio_lock(folio); - } ret = split_folio(folio); if (ret) { @@ -310,6 +311,13 @@ static int migrate_vma_collect_pmd(pmd_t *pmdp, if (folio_test_large(folio)) { int ret; + /* + * Keep the folio stable after dropping the PTE + * lock. migrate_vma_split_folio() consumes this + * reference. + */ + if (folio != fault_folio) + folio_get(folio); lazy_mmu_mode_disable(); pte_unmap_unlock(ptep, ptl); ret = migrate_vma_split_folio(folio, @@ -353,6 +361,13 @@ static int migrate_vma_collect_pmd(pmd_t *pmdp, if (folio && folio_test_large(folio)) { int ret; + /* + * Keep the folio stable after dropping the + * PTE lock. migrate_vma_split_folio() consumes + * this reference. + */ + if (folio != fault_folio) + folio_get(folio); lazy_mmu_mode_disable(); pte_unmap_unlock(ptep, ptl); ret = migrate_vma_split_folio(folio, From a153e2d384bd37d45b45011a761ee6c1f2808176 Mon Sep 17 00:00:00 2001 From: Usama Arif Date: Wed, 1 Jul 2026 11:12:54 -0700 Subject: [PATCH 316/562] condense comment about folio reference per various review comments Link: https://lore.kernel.org/87bbf335-648f-4065-abc8-3eaab5a3beeb@linux.dev Signed-off-by: Usama Arif Cc: Alistair Popple Cc: Byungchul Park Cc: David Hildenbrand Cc: Gregory Price Cc: "Huang, Ying" Cc: Johannes Weiner Cc: Joshua Hahn Cc: Matthew Brost Cc: Rakie Kim Cc: Shakeel Butt Cc: Zi Yan Signed-off-by: Andrew Morton --- mm/migrate_device.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/mm/migrate_device.c b/mm/migrate_device.c index f5a5f699e98e..052167f9ad54 100644 --- a/mm/migrate_device.c +++ b/mm/migrate_device.c @@ -311,11 +311,7 @@ static int migrate_vma_collect_pmd(pmd_t *pmdp, if (folio_test_large(folio)) { int ret; - /* - * Keep the folio stable after dropping the PTE - * lock. migrate_vma_split_folio() consumes this - * reference. - */ + /* migrate_vma_split_folio() consumes this reference */ if (folio != fault_folio) folio_get(folio); lazy_mmu_mode_disable(); @@ -361,11 +357,7 @@ static int migrate_vma_collect_pmd(pmd_t *pmdp, if (folio && folio_test_large(folio)) { int ret; - /* - * Keep the folio stable after dropping the - * PTE lock. migrate_vma_split_folio() consumes - * this reference. - */ + /* migrate_vma_split_folio() consumes this reference */ if (folio != fault_folio) folio_get(folio); lazy_mmu_mode_disable(); From a9f756ecd57fcafb13c5e2065ab0690841988a7f Mon Sep 17 00:00:00 2001 From: Gregory Price Date: Wed, 1 Jul 2026 18:16:13 -0400 Subject: [PATCH 317/562] mm/mm_init: handle alloc_percpu failure in free_area_init_core_hotplug We miss a failed allocation check for pgdat->per_cpu_nodestats, which results in a NULL deref when we offset into the per-cpu area. Propagate -ENOMEM up the stack and leave per_cpu_nodestats pointing at boot_nodestats so a later online can retry the allocation. hotadd_init_pgdat() returns NULL on failure, which __try_online_node() already maps to -ENOMEM. On failure nothing needs to be unwound: - the node is never marked online - per_cpu_nodestats is left pointing at boot_nodestats - __add_memory_resource() cleans up pending memblock resources - later online attempts retry the per_cpu_nodestats allocation Link: https://lore.kernel.org/20260701221613.2818148-1-gourry@gourry.net Fixes: 75ef71840539 ("mm, vmstat: add infrastructure for per-node vmstats") Signed-off-by: Gregory Price Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260627202243.758289-1-gourry%40gourry.net Acked-by: David Hildenbrand (Arm) Cc: Johannes Weiner Cc: Mel Gorman Cc: Mike Rapoport Cc: Oscar Salvador Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- include/linux/memory_hotplug.h | 2 +- mm/memory_hotplug.c | 3 ++- mm/mm_init.c | 14 +++++++++++--- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/include/linux/memory_hotplug.h b/include/linux/memory_hotplug.h index 7c9d66729c60..06c58cb05779 100644 --- a/include/linux/memory_hotplug.h +++ b/include/linux/memory_hotplug.h @@ -289,7 +289,7 @@ static inline void __remove_memory(u64 start, u64 size) {} /* Default online_type (MMOP_*) when new memory blocks are added. */ extern enum mmop mhp_get_default_online_type(void); extern void mhp_set_default_online_type(enum mmop online_type); -extern void __ref free_area_init_core_hotplug(struct pglist_data *pgdat); +int __ref free_area_init_core_hotplug(struct pglist_data *pgdat); extern int __add_memory(int nid, u64 start, u64 size, mhp_t mhp_flags); extern int add_memory(int nid, u64 start, u64 size, mhp_t mhp_flags); extern int add_memory_resource(int nid, struct resource *resource, diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c index 7ac19fab2263..8b137328dcf0 100644 --- a/mm/memory_hotplug.c +++ b/mm/memory_hotplug.c @@ -1263,7 +1263,8 @@ static pg_data_t *hotadd_init_pgdat(int nid) pgdat = NODE_DATA(nid); /* init node's zones as empty zones, we don't have any present pages.*/ - free_area_init_core_hotplug(pgdat); + if (free_area_init_core_hotplug(pgdat)) + return NULL; /* * The node we allocated has no zone fallback lists. For avoiding diff --git a/mm/mm_init.c b/mm/mm_init.c index cfd0b2722d83..07a8c74cf7ad 100644 --- a/mm/mm_init.c +++ b/mm/mm_init.c @@ -1526,7 +1526,7 @@ static inline void __init set_pageblock_order(void) * NOTE: this function is only called during memory hotplug */ #ifdef CONFIG_MEMORY_HOTPLUG -void __ref free_area_init_core_hotplug(struct pglist_data *pgdat) +int __ref free_area_init_core_hotplug(struct pglist_data *pgdat) { int nid = pgdat->node_id; enum zone_type z; @@ -1534,8 +1534,14 @@ void __ref free_area_init_core_hotplug(struct pglist_data *pgdat) pgdat_init_internals(pgdat); - if (pgdat->per_cpu_nodestats == &boot_nodestats) - pgdat->per_cpu_nodestats = alloc_percpu(struct per_cpu_nodestat); + if (pgdat->per_cpu_nodestats == &boot_nodestats) { + struct per_cpu_nodestat __percpu *p; + + p = alloc_percpu(struct per_cpu_nodestat); + if (!p) + return -ENOMEM; + pgdat->per_cpu_nodestats = p; + } /* * Reset the nr_zones, order and highest_zoneidx before reuse. @@ -1573,6 +1579,8 @@ void __ref free_area_init_core_hotplug(struct pglist_data *pgdat) zone->present_pages = 0; zone_init_internals(zone, z, nid, 0); } + + return 0; } #endif From 7ebb84641c8715172226489a7a945e000c9a94f1 Mon Sep 17 00:00:00 2001 From: Stanislav Kinsburskii Date: Wed, 1 Jul 2026 15:02:20 -0700 Subject: [PATCH 318/562] lib/test_hmm: fail dmirror_fault() when the mirrored mm is gone dmirror_fault() is called from the dmirror_read() and dmirror_write() retry loops after dmirror_do_read() or dmirror_do_write() finds a missing device page table entry. If the mirrored mm has already exited, mmget_not_zero() fails. The current code returns 0 in that case, which tells the caller that faulting succeeded even though no page was faulted and no device page table entry was installed. The caller then retries the same address, hits -ENOENT again, and can loop forever without making progress. Return -EFAULT instead, so the ioctl fails when the mirrored mm is no longer faultable. Link: https://lore.kernel.org/178294308408.327222.3319445682023999403.stgit@skinsburskii Fixes: b2ef9f5a5cb37 ("mm/hmm/test: add selftest driver for HMM") Signed-off-by: Stanislav Kinsburskii Cc: Jason Gunthorpe Cc: Leon Romanovsky Cc: Ralph Campbell Signed-off-by: Andrew Morton --- lib/test_hmm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/test_hmm.c b/lib/test_hmm.c index c4adbf98fac7..45c0cb992218 100644 --- a/lib/test_hmm.c +++ b/lib/test_hmm.c @@ -407,7 +407,7 @@ static int dmirror_fault(struct dmirror *dmirror, unsigned long start, /* Since the mm is for the mirrored process, get a reference first. */ if (!mmget_not_zero(mm)) - return 0; + return -EFAULT; for (addr = start; addr < end; addr = range.end) { range.start = addr; From 5c0f75a404f16c7712c72decdb0af0414dd968f6 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Wed, 1 Jul 2026 23:09:32 +0300 Subject: [PATCH 319/562] selftests/mm/uffd: don't treat UFFDIO_COPY -ENOENT as a failure Non-cooperarive uffd events are inherently racy and can happen in parallel with other userfaultfd operations. During event tests in uffd-unit-tests, the uffd monitor calls UFFDIO_UNREGISTER upon receiving UFFD_EVENT_REMOVE. In parallel, the faulting_process() verifies that the removed memory is actually zeroed. If a verification read wins the race with UFFDIO_UNREGISTER, it causes a missing fault that uffd monitor would receive after UFFDIO_UNREGISTER is complete. The monitor resolves the fault using UFFDIO_COPY that fails with -ENOENT which means that VMA has been changed (see commit 27d02568f529 ("userfaultfd: mcopy_atomic: return -ENOENT when no compatible VMA found")). Treat -ENOENT returned by UFFDIO_COPY as non-fatal, the same way -EEXIST is treated for concurrent faults, and don't fail the test. Link: https://lore.kernel.org/20260701200932.1470525-1-rppt@kernel.org Signed-off-by: Mike Rapoport (Microsoft) Reviewed-by: Lorenzo Stoakes Reviewed-by: David Hildenbrand (Arm) Cc: Liam R. Howlett Cc: Michal Hocko Cc: Peter Xu Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- tools/testing/selftests/mm/uffd-common.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/mm/uffd-common.c b/tools/testing/selftests/mm/uffd-common.c index edd02328f77b..f48f5d4594ab 100644 --- a/tools/testing/selftests/mm/uffd-common.c +++ b/tools/testing/selftests/mm/uffd-common.c @@ -639,8 +639,13 @@ int __copy_page(uffd_global_test_opts_t *gopts, unsigned long offset, bool retry uffdio_copy.mode = 0; uffdio_copy.copy = 0; if (ioctl(gopts->uffd, UFFDIO_COPY, &uffdio_copy)) { - /* real retval in ufdio_copy.copy */ - if (uffdio_copy.copy != -EEXIST) + /* + * real retval in uffdio_copy.copy + * + * -EEXIST: the page was faulted in concurrently + * -ENOENT: the destination range was concurrently removed + */ + if (uffdio_copy.copy != -EEXIST && uffdio_copy.copy != -ENOENT) err("UFFDIO_COPY error: %"PRId64, (int64_t)uffdio_copy.copy); wake_range(gopts->uffd, uffdio_copy.dst, gopts->page_size); From 7f6aea01ed99b5c478a5b1e446ab33af5208cbaf Mon Sep 17 00:00:00 2001 From: Johannes Weiner Date: Wed, 1 Jul 2026 14:21:02 -0400 Subject: [PATCH 320/562] mm: gfp_types: fix __GFP_ACCOUNT, GFP_KERNEL_ACCOUNT documentation Gregory points out that these descriptions are cursed and confusing, considering what these flags actually do. This is mostly due to historic implementation choices and cgroup1 baggage. Improve the description of their actual effects. Link: https://lore.kernel.org/20260701182102.1586784-1-hannes@cmpxchg.org Signed-off-by: Johannes Weiner Reported-by: Gregory Price Reviewed-by: Vlastimil Babka (SUSE) Acked-by: Shakeel Butt Acked-by: Mike Rapoport (Microsoft) Reviewed-by: Gregory Price Acked-by: SJ Park Cc: David Hildenbrand Cc: Liam Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Muchun Song Cc: Roman Gushchin Cc: Suren Baghdasaryan Signed-off-by: Andrew Morton --- include/linux/gfp_types.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/linux/gfp_types.h b/include/linux/gfp_types.h index 54ca0c88bab6..463b551d12d9 100644 --- a/include/linux/gfp_types.h +++ b/include/linux/gfp_types.h @@ -136,7 +136,8 @@ enum { * %__GFP_THISNODE forces the allocation to be satisfied from the requested * node with no fallbacks or placement policy enforcements. * - * %__GFP_ACCOUNT causes the allocation to be accounted to kmemcg. + * %__GFP_ACCOUNT causes the allocation to be accounted to the active + * cgroup context. * * %__GFP_NO_OBJ_EXT causes slab allocation to have no object extension. * mark_obj_codetag_empty() should be called upon freeing for objects allocated @@ -320,7 +321,7 @@ enum { * %ZONE_NORMAL or a lower zone for direct access but can direct reclaim. * * %GFP_KERNEL_ACCOUNT is the same as GFP_KERNEL, except the allocation is - * accounted to kmemcg. + * accounted to the active cgroup context. * * %GFP_NOWAIT is for kernel allocations that should not stall for direct * reclaim, start physical IO or use any filesystem callback. It is very From 58c6425c71882c82bdd467e8c4c2691a0b5d2764 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Mon, 29 Jun 2026 21:07:54 -0700 Subject: [PATCH 321/562] mm/damon/core: introduce damon_nr_accesses_mvsum() Patch series "mm/damon: optimize out nr_accesses_bp". TLDR: Replace damon_region->nr_accesses_bp, which is easy to be wrong, with a simpler on-demand moving sum function, damon_nr_accesses_mvsum(). Background ========== DAMON's monitoring output (access pattern snapshot, or more technically speaking, damon_region->nr_accesses) is completed once per aggregation interval, which is 100 ms by default. Users can arbitrarily increase the interval for demand. Under the suggested intervals auto-tuning setup, it can span up to 200 seconds. If the aggregation interval is too long, the snapshot users cannot use it in reasonable time. To mitigate this, we introduced a new field of damon_region, namely nr_accesses_bp. It contains a pseudo moving sum of nr_accesses in bp units and is updated for each sampling interval. It turned out keeping it correctly updated every sampling interval is not that easy. From online parameter update feature development and more experimental hacks, we found it is easy to be corrupted. Once it is corrupted, DAMON's monitoring outputs become quite insane. Hence we added a few validation checks. It is easy to be corrupted because it requires every update per sampling interval to be correct. Solution ======== There is no real reason to keep it updated every sampling interval. Due to the simple pseudo-moving sum mechanism and existing helper field (last_nr_accesses), we can also calculate the pseudo moving sum on demand in a much simpler way. Implement a function for getting the pseudo moving sum on demand, and replace nr_accessses_bp uses with the new function. Also remove no more needed tests for nr_accesses_bp and the per-sampling interval update functions. Finally, remove the nr_accesses_bp. The new function is quite simple. Discussion ========== Depending on the use case, multiple nr_accesses readers could be executed in the same kdamond_fn() main loop iteration, which is executed once per sampling interval. Such readers include DAMON region exporting tracepoints (damon_[region_]aggregated and damos_before_apply), DAMOS, and DAMON sysfs interface logic for update_schemes_tried_regions command. In this case, the new function will be called multiple times and this could be overhead compared to the old logic, which simply reads the field without any additional work. Nonetheless, the new function is quite simple. And the new approach does nothing while there is no need to read. The old approach had to execute its update function for each region for every sampling interval. Hence the new approach is believed to be even more lightweight in common case, and the overhead is anyway negligible. One more advantage of this change is that one field from the damon_region struct is removed. On setups that uses a high number of DAMON regions, this could be a potential memory space benefit. Patches Sequence ================ Patch 1 introduces the new function for getting the pseudo moving sum of nr_accesses on demands. Patch 2 implements a unit test for the new function's internal logic. Patch 3 and 4 update monitoring logic and the new function to ready for safe use on the existing logic. Patches 5-7 replace uses of nr_accesses_bp in DAMOS, tracepoints and DAMON sysfs interface with the new function, respectively. Patches 8-10 removes nr_accesses_bp validation functions in DAMON core, one by one. Patches 11 and 12 further remove tests and test helper for nr_accesses_bp, respectively. Patches 13 removes the setups and updates or nr_accesses_bp field. Patches 14-16 cleans up function parameters that are no more being used due to the previous patch. Patch 17 removes the function that was used for updating nr_accesses_bp field with its unit test, which is the single remaining caller of the function. Finally, patch 18 removes damon_region->nr_accesses_bp field. This patch (of 18): Introduce a new DAMON core function, damon_nr_accesses_mvsum(). It returns a pseudo moving sum value of a given region's nr_accesses for the last aggregation interval. The internal logic is the same to nr_accesses_bp. The difference is that nr_accesses_bp is updated for each sampling interval, while the new function needs to be executed only when requested. Hence the function's return value is the same as the value of nr_accesses_bp. Link: https://lore.kernel.org/20260630040812.149729-1-sj@kernel.org Link: https://lore.kernel.org/20260630040812.149729-2-sj@kernel.org Signed-off-by: SJ Park Cc: Brendan Higgins Cc: "Masami Hiramatsu (Google)" Cc: Mathieu Desnoyers Cc: Shuah Khan Cc: Steven Rostedt Signed-off-by: Andrew Morton --- include/linux/damon.h | 2 ++ mm/damon/core.c | 62 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/include/linux/damon.h b/include/linux/damon.h index cfbbf8ba28f6..87c1ff479da8 100644 --- a/include/linux/damon.h +++ b/include/linux/damon.h @@ -1011,6 +1011,8 @@ struct damon_probe *damon_new_probe(void); void damon_add_probe(struct damon_ctx *ctx, struct damon_probe *probe); struct damon_region *damon_new_region(unsigned long start, unsigned long end); +unsigned int damon_nr_accesses_mvsum(struct damon_region *r, + struct damon_ctx *ctx); int damon_set_regions(struct damon_target *t, struct damon_addr_range *ranges, unsigned int nr_ranges, unsigned long min_region_sz); diff --git a/mm/damon/core.c b/mm/damon/core.c index dbff0e4c1c17..21c15ffc26fd 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -208,6 +208,68 @@ static struct damon_probe *damon_nth_probe(int n, struct damon_ctx *ctx) return NULL; } +/* + * damon_mvsum() - Returns pseudo moving sum value for a time window. + * @current_nr: The value of the current aggregation window. + * @last_nr: The value of the last aggregation window. + * @left_window_bp: Left time of the current aggregation window. + * + * This function calculates a pseudo moving sum value of a counter that is + * aggregated for each time window. @current_nr is the value of the counter + * that aggregated so far (maybe not yet complete), from the beginning of the + * current aggregation time window. @last_nr is the value of the counter that + * has completely aggregated in the last aggregation time window. + * @left_window_bp represents how much time is left for the current aggregation + * time window in bp (1/10,000). For example, the aggregation time window is + * for every 10 seconds and 7 seconds has passed since the beginning of the + * current window, this parameter will be 3000 ((10 - 7) / 10 * 10000). + * + * The logic assumes the aggregation in the last phase was made in a single + * speed. Based on the assumption, the value from the last window that needs + * to be added to the current value is calculated as a portion of the last + * value based on the remaining time window. + */ +static unsigned long damon_mvsum(unsigned long current_nr, + unsigned long last_nr, unsigned long left_window_bp) +{ + return current_nr + mult_frac(last_nr, left_window_bp, 10000); +} + +/** + * damon_nr_accesses_mvsum() - Returns moving sum access frequency score. + * @r: Region to get the access frequency of. + * @ctx: DAMON context of @r. + * + * This function returns for how many sampling iterations in the last + * aggregation interval (&damon_attrs->aggr_interval) the region was found to + * be accessed. Hence the value can be interpreted as the relative access + * frequency score of the region (@r). The value is calculated as a pseudo + * moving sum, and hence it is not an exact value but just a best-effort + * reasonable estimation. + * + * Return: the pseudo moving sum access frequency score. + */ +unsigned int damon_nr_accesses_mvsum(struct damon_region *r, + struct damon_ctx *ctx) +{ + unsigned long sample_interval, aggr_interval; + unsigned long window_len, left_window, left_window_bp; + + sample_interval = ctx->attrs.sample_interval ? : 1; + aggr_interval = ctx->attrs.aggr_interval ? : 1; + window_len = aggr_interval / sample_interval; + if (time_after_eq(ctx->passed_sample_intervals, + ctx->next_aggregation_sis)) + left_window = 0; + else + left_window = ctx->next_aggregation_sis - + ctx->passed_sample_intervals; + left_window_bp = mult_frac(left_window, 10000, window_len); + + return damon_mvsum(r->nr_accesses, r->last_nr_accesses, + left_window_bp); +} + #ifdef CONFIG_DAMON_DEBUG_SANITY static void damon_verify_new_region(unsigned long start, unsigned long end) { From 65d35367ade8859db54a46d1d548f21834b95d0f Mon Sep 17 00:00:00 2001 From: SJ Park Date: Mon, 29 Jun 2026 21:07:55 -0700 Subject: [PATCH 322/562] mm/damon/tests/core-kunit: test damon_mvsum() Add a simple unit test for damon_nr_accesses_mvsum()'s internal core logic, damon_mvsum(). The test contains cases for just-started windows, partially completed windows, and just-completed windows. Link: https://lore.kernel.org/20260630040812.149729-3-sj@kernel.org Signed-off-by: SJ Park Cc: Brendan Higgins Cc: "Masami Hiramatsu (Google)" Cc: Mathieu Desnoyers Cc: Shuah Khan Cc: Steven Rostedt Signed-off-by: Andrew Morton --- mm/damon/tests/core-kunit.h | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/mm/damon/tests/core-kunit.h b/mm/damon/tests/core-kunit.h index 0ec7d14d354e..c45377dcc97e 100644 --- a/mm/damon/tests/core-kunit.h +++ b/mm/damon/tests/core-kunit.h @@ -682,6 +682,30 @@ static void damon_test_moving_sum(struct kunit *test) } } +static void damon_test_mvsum(struct kunit *test) +{ + unsigned long input_expects[] = { + /* current value, last value, remaining window (bp) */ + 0, 49, 10000, 49, /* 0 + 49 * 1 */ + 3, 10, 7000, 10, /* 3 + 10 * 0.7 */ + 3, 10, 5000, 8, /* 3 + 10 * 0.5 */ + 32, 100, 1000, 42, /* 32 + 100 * 0.1 */ + 42, 49, 0, 42, /* 42 + 49 * 0 */ + }; + + int i; + + for (i = 0; i < ARRAY_SIZE(input_expects); i += 4) { + unsigned long current_nr = input_expects[i]; + unsigned long last_nr = input_expects[i + 1]; + unsigned long left_window_bp = input_expects[i + 2]; + unsigned long expect = input_expects[i + 3]; + + KUNIT_EXPECT_EQ(test, damon_mvsum(current_nr, last_nr, + left_window_bp), expect); + } +} + static void damos_test_new_filter(struct kunit *test) { struct damos_filter *filter; @@ -1575,6 +1599,7 @@ static struct kunit_case damon_test_cases[] = { KUNIT_CASE(damon_test_update_monitoring_result), KUNIT_CASE(damon_test_set_attrs), KUNIT_CASE(damon_test_moving_sum), + KUNIT_CASE(damon_test_mvsum), KUNIT_CASE(damos_test_new_filter), KUNIT_CASE(damos_test_commit_quota_goal), KUNIT_CASE(damos_test_commit_quota_goals), From aaeca91f19d45378a7ee48b5f61d5881aa7438e1 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Mon, 29 Jun 2026 21:07:56 -0700 Subject: [PATCH 323/562] mm/damon/core: always update ->last_nr_accesses for intervals change Each iteration of kdamond_fn() main loop caches and use the next aggregation time (next_aggregation_sis) because it can be updated in the middle, inside kdamond_call(). If that happens, damon_update_monitoring_result() is called for scaling the access frequency information of each region according to the changed intervals. The function does not update damon_region->last_nr_accesses when it is at the end of the aggregation, because it will anyway be reset after the function is executed, in kdamond_reset_aggregated(). Let's suppose damon_nr_accesses_mvsum() is called with the not yet updated last_nr_accesses. It will use the fresh next_aggregation_sis in the context instead of the cached one, unlike kdamond_fn(). As a result, use of not updated last_nr_acceses with the updated next_aggregation_sis result in returning wrong value. There is no such damon_nr_accesses_nvsum() call at the moment, so this is no problem. It is planned to add such calls, though. Prevent the issue by updating last_nr_accesses always. This adds overhead, but that's fine because the overhead is not big, and it is anyway not a fast path. Link: https://lore.kernel.org/20260630040812.149729-4-sj@kernel.org Signed-off-by: SJ Park Cc: Brendan Higgins Cc: "Masami Hiramatsu (Google)" Cc: Mathieu Desnoyers Cc: Shuah Khan Cc: Steven Rostedt Signed-off-by: Andrew Morton --- mm/damon/core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mm/damon/core.c b/mm/damon/core.c index 21c15ffc26fd..e6f0f7a6752d 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -884,6 +884,8 @@ static void damon_update_monitoring_result(struct damon_region *r, struct damon_attrs *old_attrs, struct damon_attrs *new_attrs, bool aggregating) { + r->last_nr_accesses = damon_nr_accesses_for_new_attrs( + r->last_nr_accesses, old_attrs, new_attrs); if (!aggregating) { r->nr_accesses = damon_nr_accesses_for_new_attrs( r->nr_accesses, old_attrs, new_attrs); @@ -895,8 +897,6 @@ static void damon_update_monitoring_result(struct damon_region *r, * interval. In other words, make the status like * kdamond_reset_aggregated() is called. */ - r->last_nr_accesses = damon_nr_accesses_for_new_attrs( - r->last_nr_accesses, old_attrs, new_attrs); r->nr_accesses_bp = r->last_nr_accesses * 10000; r->nr_accesses = 0; } From 9e5f5c77782bdc972a52c84a4b360dfac4c8902a Mon Sep 17 00:00:00 2001 From: SJ Park Date: Mon, 29 Jun 2026 21:07:57 -0700 Subject: [PATCH 324/562] mm/damon/core: handle unreset nr_accesses in damon_nr_accesses_mvsum() damon_set_attrs() works like reverting aggregations that were made so far for this aggregation window. If this is the end of the aggregation, however, kdamond_fn() will do the operations at the end of the aggregation interval, using cached timestamps. For such operations that rely on damon_region->nr_accesses, damon_update_monitoring_results() doesn't reset the nr_accesses if it is called at the end of the aggregation window. damon_nr_accesses_mvsum() works with fresh timestamps, though. The nr_accesses that are not reset in this case can make the logic to unnecessarily count nr_accesses, resulting in returning higher-than-expected pseudo moving sum nr_accesses. No code is using damon_nr_accesses_mvsum() yet, so this is not causing a real problem. Following commits will add usage of the function, though. For safe usages, calculate the pseudo moving sum without nr_accesses if the remaining window is full. Link: https://lore.kernel.org/20260630040812.149729-5-sj@kernel.org Signed-off-by: SJ Park Cc: Brendan Higgins Cc: "Masami Hiramatsu (Google)" Cc: Mathieu Desnoyers Cc: Shuah Khan Cc: Steven Rostedt Signed-off-by: Andrew Morton --- mm/damon/core.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mm/damon/core.c b/mm/damon/core.c index e6f0f7a6752d..fcd7347e48e3 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -266,6 +266,9 @@ unsigned int damon_nr_accesses_mvsum(struct damon_region *r, ctx->passed_sample_intervals; left_window_bp = mult_frac(left_window, 10000, window_len); + if (left_window_bp == 10000) + return r->last_nr_accesses; + return damon_mvsum(r->nr_accesses, r->last_nr_accesses, left_window_bp); } From 6219b67097efc59b26ae4446e1efeebc7efebd10 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Mon, 29 Jun 2026 21:07:58 -0700 Subject: [PATCH 325/562] mm/damon/core: use damon_nr_accesses_mvsum() in __damos_valid_target() damon_nr_accesses_mvsum() returns a value same to nr_accesses_bp. Also the function is more simple and therefore more tolerant to errors. Execution of the function would be more expensive than the simple read of the field, but because the function is quite simple, the overhead should be negligible. Use it in __damos_valid_target() instead of the nr_accesses_bp. Link: https://lore.kernel.org/20260630040812.149729-6-sj@kernel.org Signed-off-by: SJ Park Cc: Brendan Higgins Cc: "Masami Hiramatsu (Google)" Cc: Mathieu Desnoyers Cc: Shuah Khan Cc: Steven Rostedt Signed-off-by: Andrew Morton --- mm/damon/core.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/mm/damon/core.c b/mm/damon/core.c index fcd7347e48e3..80ae08b704c6 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -2151,10 +2151,11 @@ static noinline_for_stack void kdamond_tune_intervals(struct damon_ctx *c) damon_set_attrs(c, &new_attrs); } -static bool __damos_valid_target(struct damon_region *r, struct damos *s) +static bool __damos_valid_target(struct damon_region *r, struct damos *s, + struct damon_ctx *c) { unsigned long sz; - unsigned int nr_accesses = r->nr_accesses_bp / 10000; + unsigned int nr_accesses = damon_nr_accesses_mvsum(r, c); sz = damon_sz_region(r); return s->pattern.min_sz_region <= sz && @@ -2180,7 +2181,7 @@ static bool damos_quota_is_set(struct damos_quota *quota) static bool damos_valid_target(struct damon_ctx *c, struct damon_region *r, struct damos *s) { - bool ret = __damos_valid_target(r, s); + bool ret = __damos_valid_target(r, s, c); if (!ret || !damos_quota_is_set(&s->quota) || !c->ops.get_scheme_score) return ret; @@ -2766,7 +2767,7 @@ static phys_addr_t damos_calc_eligible_bytes(struct damon_ctx *c, damon_for_each_region(r, t) { phys_addr_t addr, end_addr; - if (!__damos_valid_target(r, s)) + if (!__damos_valid_target(r, s, c)) continue; /* Convert from core address units to physical bytes */ @@ -3055,7 +3056,7 @@ static void damos_adjust_quota(struct damon_ctx *c, struct damos *s) (DAMOS_MAX_SCORE + 1)); damon_for_each_target(t, c) { damon_for_each_region(r, t) { - if (!__damos_valid_target(r, s)) + if (!__damos_valid_target(r, s, c)) continue; if (damos_core_filter_out(c, t, r, s)) continue; From 816477c14e4f9bd7eb64423e39004cf74623af77 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Mon, 29 Jun 2026 21:07:59 -0700 Subject: [PATCH 326/562] mm/damon/core: use damon_nr_accesses_mvsum() for damos region tracing damon_nr_accesses_mvsum() returns a value same to nr_accesses_bp. Also the function is more simple and therefore more tolerant to errors. Execution of the function would be more expensive than the simple read of the field, but because the function is quite simple, the overhead should be negligible. Use it in the DAMON region exporting trace points instead of the nr_accesses_bp. Link: https://lore.kernel.org/20260630040812.149729-7-sj@kernel.org Signed-off-by: SJ Park Cc: Brendan Higgins Cc: "Masami Hiramatsu (Google)" Cc: Mathieu Desnoyers Cc: Shuah Khan Cc: Steven Rostedt Signed-off-by: Andrew Morton --- include/trace/events/damon.h | 8 +++++--- mm/damon/core.c | 5 +++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/include/trace/events/damon.h b/include/trace/events/damon.h index 78388538acf4..8851727ae162 100644 --- a/include/trace/events/damon.h +++ b/include/trace/events/damon.h @@ -78,9 +78,11 @@ TRACE_EVENT_CONDITION(damos_before_apply, TP_PROTO(unsigned int context_idx, unsigned int scheme_idx, unsigned int target_idx, struct damon_region *r, - unsigned int nr_regions, bool do_trace), + unsigned int nr_accesses, unsigned int nr_regions, + bool do_trace), - TP_ARGS(context_idx, scheme_idx, target_idx, r, nr_regions, do_trace), + TP_ARGS(context_idx, scheme_idx, target_idx, r, nr_accesses, + nr_regions, do_trace), TP_CONDITION(do_trace), @@ -101,7 +103,7 @@ TRACE_EVENT_CONDITION(damos_before_apply, __entry->target_idx = target_idx; __entry->start = r->ar.start; __entry->end = r->ar.end; - __entry->nr_accesses = r->nr_accesses_bp / 10000; + __entry->nr_accesses = nr_accesses; __entry->age = r->age; __entry->nr_regions = nr_regions; ), diff --git a/mm/damon/core.c b/mm/damon/core.c index 80ae08b704c6..4628097fd9cb 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -2465,7 +2465,7 @@ static void damos_apply_scheme(struct damon_ctx *c, struct damon_target *t, struct damos *siter; /* schemes iterator */ unsigned int sidx = 0; struct damon_target *titer; /* targets iterator */ - unsigned int tidx = 0; + unsigned int tidx = 0, nr_accesses = 0; bool do_trace = false; /* get indices for trace_damos_before_apply() */ @@ -2480,6 +2480,7 @@ static void damos_apply_scheme(struct damon_ctx *c, struct damon_target *t, break; tidx++; } + nr_accesses = damon_nr_accesses_mvsum(r, c); do_trace = true; } @@ -2495,7 +2496,7 @@ static void damos_apply_scheme(struct damon_ctx *c, struct damon_target *t, if (damos_core_filter_out(c, t, r, s)) return; ktime_get_coarse_ts64(&begin); - trace_damos_before_apply(cidx, sidx, tidx, r, + trace_damos_before_apply(cidx, sidx, tidx, r, nr_accesses, damon_nr_regions(t), do_trace); sz_applied = c->ops.apply_scheme(c, t, r, s, &sz_ops_filter_passed); From 17d509defe92139e3097f529020cb39eaae203b5 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Mon, 29 Jun 2026 21:08:00 -0700 Subject: [PATCH 327/562] mm/damon/sysfs-schemes: use damon_nr_accesses_mvsum() for damo regions damon_nr_accesses_mvsum() returns a value same to nr_accesses_bp. Also the function is more simple and therefore more tolerant to errors. Execution of the function would be more expensive than the simple read of the field, but because the function is quite simple, the overhead should be negligible. Use it in the DAMON sysfs interface for scheme-tried regions, instead of the nr_accesses_bp. Link: https://lore.kernel.org/20260630040812.149729-8-sj@kernel.org Signed-off-by: SJ Park Cc: Brendan Higgins Cc: "Masami Hiramatsu (Google)" Cc: Mathieu Desnoyers Cc: Shuah Khan Cc: Steven Rostedt Signed-off-by: Andrew Morton --- mm/damon/sysfs-schemes.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mm/damon/sysfs-schemes.c b/mm/damon/sysfs-schemes.c index 41f93a1823bf..dbf2b0515d58 100644 --- a/mm/damon/sysfs-schemes.c +++ b/mm/damon/sysfs-schemes.c @@ -157,7 +157,7 @@ struct damon_sysfs_scheme_region { }; static struct damon_sysfs_scheme_region *damon_sysfs_scheme_region_alloc( - struct damon_region *region) + struct damon_region *region, struct damon_ctx *ctx) { struct damon_sysfs_scheme_region *sysfs_region = kmalloc_obj(*sysfs_region); @@ -165,7 +165,7 @@ static struct damon_sysfs_scheme_region *damon_sysfs_scheme_region_alloc( return NULL; sysfs_region->kobj = (struct kobject){}; sysfs_region->ar = region->ar; - sysfs_region->nr_accesses = region->nr_accesses_bp / 10000; + sysfs_region->nr_accesses = damon_nr_accesses_mvsum(region, ctx); sysfs_region->age = region->age; sysfs_region->probes = NULL; INIT_LIST_HEAD(&sysfs_region->list); @@ -3122,7 +3122,7 @@ void damos_sysfs_populate_region_dir(struct damon_sysfs_schemes *sysfs_schemes, if (total_bytes_only) return; - region = damon_sysfs_scheme_region_alloc(r); + region = damon_sysfs_scheme_region_alloc(r, ctx); if (!region) return; region->sz_filter_passed = sz_filter_passed; From 53eacc0a7714fbd3d74025366f524a211526a591 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Mon, 29 Jun 2026 21:08:01 -0700 Subject: [PATCH 328/562] mm/damon/core: remove damon_warn_fix_nr_accesses_corruption() nr_accesses_bp is delicate. Once it is corrupted, the consequence is the bad madness of DAMON monitoring results. From developments of features of size, we historically found nr_accesses_bp can be corrupted by complicated bugs that are not easy to debug. Hence we added a function for finding the corruption and fixing it right away. There are no more uses of nr_accesses_bp. Hence the function for corruption detection and fix is no more needed. Rip it out. Link: https://lore.kernel.org/20260630040812.149729-9-sj@kernel.org Signed-off-by: SJ Park Cc: Brendan Higgins Cc: "Masami Hiramatsu (Google)" Cc: Mathieu Desnoyers Cc: Shuah Khan Cc: Steven Rostedt Signed-off-by: Andrew Morton --- mm/damon/core.c | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/mm/damon/core.c b/mm/damon/core.c index 4628097fd9cb..19606c959503 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -2025,19 +2025,6 @@ int damos_walk(struct damon_ctx *ctx, struct damos_walk_control *control) return 0; } -/* - * Warn and fix corrupted ->nr_accesses[_bp] for investigations and preventing - * the problem being propagated. - */ -static void damon_warn_fix_nr_accesses_corruption(struct damon_region *r) -{ - if (r->nr_accesses_bp == r->nr_accesses * 10000) - return; - WARN_ONCE(true, "invalid nr_accesses_bp at reset: %u %u\n", - r->nr_accesses_bp, r->nr_accesses); - r->nr_accesses_bp = r->nr_accesses * 10000; -} - #ifdef CONFIG_DAMON_DEBUG_SANITY static void damon_verify_reset_aggregated(struct damon_region *r, struct damon_ctx *c) @@ -2079,7 +2066,6 @@ static void kdamond_reset_aggregated(struct damon_ctx *c) trace_damon_aggregated(ti, r, damon_nr_regions(t)); trace_damon_region_aggregated(ti, r, damon_nr_regions(t), nr_probes); - damon_warn_fix_nr_accesses_corruption(r); r->last_nr_accesses = r->nr_accesses; r->nr_accesses = 0; for (i = 0; i < DAMON_MAX_PROBES; i++) From 6365e13997f9d8e7983eb52788a52211e0c11887 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Mon, 29 Jun 2026 21:08:02 -0700 Subject: [PATCH 329/562] mm/damon/core: remove damon_verify_reset_aggregated() nr_accesses_bp is no longer being used in real use cases. Remove its validation function. Link: https://lore.kernel.org/20260630040812.149729-10-sj@kernel.org Signed-off-by: SJ Park Cc: Brendan Higgins Cc: "Masami Hiramatsu (Google)" Cc: Mathieu Desnoyers Cc: Shuah Khan Cc: Steven Rostedt Signed-off-by: Andrew Morton --- mm/damon/core.c | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/mm/damon/core.c b/mm/damon/core.c index 19606c959503..aa0b76ccac4c 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -2025,23 +2025,6 @@ int damos_walk(struct damon_ctx *ctx, struct damos_walk_control *control) return 0; } -#ifdef CONFIG_DAMON_DEBUG_SANITY -static void damon_verify_reset_aggregated(struct damon_region *r, - struct damon_ctx *c) -{ - WARN_ONCE(r->nr_accesses_bp != r->last_nr_accesses * 10000, - "nr_accesses_bp %u last_nr_accesses %u sis %lu %lu\n", - r->nr_accesses_bp, r->last_nr_accesses, - c->passed_sample_intervals, c->next_aggregation_sis); -} -#else -static void damon_verify_reset_aggregated(struct damon_region *r, - struct damon_ctx *c) -{ -} -#endif - - /* * Reset the aggregated monitoring results ('nr_accesses' of each region). */ @@ -2070,7 +2053,6 @@ static void kdamond_reset_aggregated(struct damon_ctx *c) r->nr_accesses = 0; for (i = 0; i < DAMON_MAX_PROBES; i++) r->probe_hits[i] = 0; - damon_verify_reset_aggregated(r, c); } ti++; } From f663b36f318655922e7c458160ef78a9cce84531 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Mon, 29 Jun 2026 21:08:03 -0700 Subject: [PATCH 330/562] mm/damon/core: remove damon_verify_merge_regions_of() damon_verify_merge_regions_of() is only for nr_accesses_bp validation. But nr_accesses_bp is no more being used for a real purpose. Remove the validation. Link: https://lore.kernel.org/20260630040812.149729-11-sj@kernel.org Signed-off-by: SJ Park Cc: Brendan Higgins Cc: "Masami Hiramatsu (Google)" Cc: Mathieu Desnoyers Cc: Shuah Khan Cc: Steven Rostedt Signed-off-by: Andrew Morton --- mm/damon/core.c | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/mm/damon/core.c b/mm/damon/core.c index aa0b76ccac4c..d2b7609dcf5a 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -3140,20 +3140,6 @@ static void damon_merge_two_regions(struct damon_target *t, damon_destroy_region(r, t); } -#ifdef CONFIG_DAMON_DEBUG_SANITY -static void damon_verify_merge_regions_of(struct damon_region *r) -{ - WARN_ONCE(r->nr_accesses != r->nr_accesses_bp / 10000, - "nr_accesses (%u) != nr_accesses_bp (%u)\n", - r->nr_accesses, r->nr_accesses_bp); -} -#else -static void damon_verify_merge_regions_of(struct damon_region *r) -{ -} -#endif - - /* * Merge adjacent regions having similar access frequencies * @@ -3167,7 +3153,6 @@ static void damon_merge_regions_of(struct damon_target *t, unsigned int thres, struct damon_region *r, *prev = NULL, *next; damon_for_each_region_safe(r, next, t) { - damon_verify_merge_regions_of(r); if (abs(r->nr_accesses - r->last_nr_accesses) > thres) r->age = 0; else if ((r->nr_accesses == 0) != (r->last_nr_accesses == 0)) From c1e75201de07c8369e54c5aaa4240f0892b5fe4f Mon Sep 17 00:00:00 2001 From: SJ Park Date: Mon, 29 Jun 2026 21:08:04 -0700 Subject: [PATCH 331/562] mm/damon/tests/core-kunit: remove nr_accesses_bp setup and tests DAMON core unit tests set up nr_accesses_bp for representing realistic damon_region, and also test the field. nr_acceses_bp is no longer being used for a real use case. Remove the setup and tests. Link: https://lore.kernel.org/20260630040812.149729-12-sj@kernel.org Signed-off-by: SJ Park Cc: Brendan Higgins Cc: "Masami Hiramatsu (Google)" Cc: Mathieu Desnoyers Cc: Shuah Khan Cc: Steven Rostedt Signed-off-by: Andrew Morton --- mm/damon/tests/core-kunit.h | 8 -------- 1 file changed, 8 deletions(-) diff --git a/mm/damon/tests/core-kunit.h b/mm/damon/tests/core-kunit.h index c45377dcc97e..0566fc3dc067 100644 --- a/mm/damon/tests/core-kunit.h +++ b/mm/damon/tests/core-kunit.h @@ -114,7 +114,6 @@ static void damon_test_aggregate(struct kunit *test) kunit_skip(test, "region alloc fail"); } r->nr_accesses = accesses[it][ir]; - r->nr_accesses_bp = accesses[it][ir] * 10000; damon_add_region(r, t); } it++; @@ -151,7 +150,6 @@ static void damon_test_split_at(struct kunit *test) damon_free_target(t); kunit_skip(test, "region alloc fail"); } - r->nr_accesses_bp = 420000; r->nr_accesses = 42; r->last_nr_accesses = 15; r->age = 10; @@ -164,7 +162,6 @@ static void damon_test_split_at(struct kunit *test) KUNIT_EXPECT_EQ(test, r_new->ar.start, 25ul); KUNIT_EXPECT_EQ(test, r_new->ar.end, 100ul); - KUNIT_EXPECT_EQ(test, r->nr_accesses_bp, r_new->nr_accesses_bp); KUNIT_EXPECT_EQ(test, r->nr_accesses, r_new->nr_accesses); KUNIT_EXPECT_EQ(test, r->last_nr_accesses, r_new->last_nr_accesses); KUNIT_EXPECT_EQ(test, r->age, r_new->age); @@ -187,7 +184,6 @@ static void damon_test_merge_two(struct kunit *test) kunit_skip(test, "region alloc fail"); } r->nr_accesses = 10; - r->nr_accesses_bp = 100000; r->age = 9; damon_add_region(r, t); r2 = damon_new_region(100, 300); @@ -196,7 +192,6 @@ static void damon_test_merge_two(struct kunit *test) kunit_skip(test, "second region alloc fail"); } r2->nr_accesses = 20; - r2->nr_accesses_bp = 200000; r2->age = 21; damon_add_region(r2, t); @@ -204,7 +199,6 @@ static void damon_test_merge_two(struct kunit *test) KUNIT_EXPECT_EQ(test, r->ar.start, 0ul); KUNIT_EXPECT_EQ(test, r->ar.end, 300ul); KUNIT_EXPECT_EQ(test, r->nr_accesses, 16u); - KUNIT_EXPECT_EQ(test, r->nr_accesses_bp, 160000u); KUNIT_EXPECT_EQ(test, r->age, 17u); i = 0; @@ -252,7 +246,6 @@ static void damon_test_merge_regions_of(struct kunit *test) kunit_skip(test, "region alloc fail"); } r->nr_accesses = nrs[i]; - r->nr_accesses_bp = nrs[i] * 10000; damon_add_region(r, t); } @@ -615,7 +608,6 @@ static void damon_test_update_monitoring_result(struct kunit *test) kunit_skip(test, "region alloc fail"); r->nr_accesses = 15; - r->nr_accesses_bp = 150000; r->age = 20; new_attrs = (struct damon_attrs){ From 84e286115daa82bc3fcbd66b041bccfca5fe908e Mon Sep 17 00:00:00 2001 From: SJ Park Date: Mon, 29 Jun 2026 21:08:05 -0700 Subject: [PATCH 332/562] selftests/damon/drgn_dump_damon_status: do not dump nr_accesses_bp drgn_dump_damon_status is dumping nr_accesses_bp field for future use case. nr_accesses_bp is not being used for a real purpose, though. Hence there will be no future test for it. Do not dump it. Link: https://lore.kernel.org/20260630040812.149729-13-sj@kernel.org Signed-off-by: SJ Park Cc: Brendan Higgins Cc: "Masami Hiramatsu (Google)" Cc: Mathieu Desnoyers Cc: Shuah Khan Cc: Steven Rostedt Signed-off-by: Andrew Morton --- tools/testing/selftests/damon/drgn_dump_damon_status.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/testing/selftests/damon/drgn_dump_damon_status.py b/tools/testing/selftests/damon/drgn_dump_damon_status.py index 26b207e44268..09552e91bc78 100755 --- a/tools/testing/selftests/damon/drgn_dump_damon_status.py +++ b/tools/testing/selftests/damon/drgn_dump_damon_status.py @@ -59,7 +59,6 @@ def region_to_dict(region): ['ar', addr_range_to_dict], ['sampling_addr', int], ['nr_accesses', int], - ['nr_accesses_bp', int], ['age', int], ]) From e56521fd38b998604fe28e108b5d1bce10d83920 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Mon, 29 Jun 2026 21:08:06 -0700 Subject: [PATCH 333/562] mm/damon/core: remove nr_accesses_bp setups and updates DAMON core sets and updates nr_accesses_bp in multiple places. It explains how delicate it is. The field is no more being used for any real purpose, and replaced by a simpler function. Remove the setups and updates. Link: https://lore.kernel.org/20260630040812.149729-14-sj@kernel.org Signed-off-by: SJ Park Cc: Brendan Higgins Cc: "Masami Hiramatsu (Google)" Cc: Mathieu Desnoyers Cc: Shuah Khan Cc: Steven Rostedt Signed-off-by: Andrew Morton --- mm/damon/core.c | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/mm/damon/core.c b/mm/damon/core.c index d2b7609dcf5a..ef0b87f2c035 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -302,7 +302,6 @@ struct damon_region *damon_new_region(unsigned long start, unsigned long end) region->ar.start = start; region->ar.end = end; region->nr_accesses = 0; - region->nr_accesses_bp = 0; for (i = 0; i < DAMON_MAX_PROBES; i++) region->probe_hits[i] = 0; INIT_LIST_HEAD(®ion->list); @@ -889,20 +888,17 @@ static void damon_update_monitoring_result(struct damon_region *r, { r->last_nr_accesses = damon_nr_accesses_for_new_attrs( r->last_nr_accesses, old_attrs, new_attrs); - if (!aggregating) { + if (!aggregating) r->nr_accesses = damon_nr_accesses_for_new_attrs( r->nr_accesses, old_attrs, new_attrs); - r->nr_accesses_bp = r->nr_accesses * 10000; - } else { + else /* * if this is called in the middle of the aggregation, reset * the aggregations we made so far for this aggregation * interval. In other words, make the status like * kdamond_reset_aggregated() is called. */ - r->nr_accesses_bp = r->last_nr_accesses * 10000; r->nr_accesses = 0; - } r->age = damon_age_for_new_attrs(r->age, old_attrs, new_attrs); } @@ -3129,7 +3125,6 @@ static void damon_merge_two_regions(struct damon_target *t, l->nr_accesses = (l->nr_accesses * sz_l + r->nr_accesses * sz_r) / (sz_l + sz_r); - l->nr_accesses_bp = l->nr_accesses * 10000; l->age = (l->age * sz_l + r->age * sz_r) / (sz_l + sz_r); l->ar.end = r->ar.end; /* todo: do this for only installed probes */ @@ -3241,7 +3236,6 @@ static void damon_split_region_at(struct damon_target *t, new->age = r->age; new->last_nr_accesses = r->last_nr_accesses; - new->nr_accesses_bp = r->nr_accesses_bp; new->nr_accesses = r->nr_accesses; /* todo: do this for only installed probes */ memcpy(new->probe_hits, r->probe_hits, sizeof(r->probe_hits)); @@ -3849,18 +3843,6 @@ static unsigned int damon_moving_sum(unsigned int mvsum, unsigned int nomvsum, void damon_update_region_access_rate(struct damon_region *r, bool accessed, struct damon_attrs *attrs) { - unsigned int len_window = 1; - - /* - * sample_interval can be zero, but cannot be larger than - * aggr_interval, owing to validation of damon_set_attrs(). - */ - if (attrs->sample_interval) - len_window = damon_max_nr_accesses(attrs); - r->nr_accesses_bp = damon_moving_sum(r->nr_accesses_bp, - r->last_nr_accesses * 10000, len_window, - accessed ? 10000 : 0); - if (accessed) r->nr_accesses++; } From 7e47e042b778efe225e33213919df27a824ea833 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Mon, 29 Jun 2026 21:08:07 -0700 Subject: [PATCH 334/562] mm/damon/core: remove attrs param from damon_update_region_access_rate() damon_update_region_access_rate() is not using attrs parameter. Remove it. Link: https://lore.kernel.org/20260630040812.149729-15-sj@kernel.org Signed-off-by: SJ Park Cc: Brendan Higgins Cc: "Masami Hiramatsu (Google)" Cc: Mathieu Desnoyers Cc: Shuah Khan Cc: Steven Rostedt Signed-off-by: Andrew Morton --- include/linux/damon.h | 3 +-- mm/damon/core.c | 4 +--- mm/damon/paddr.c | 4 ++-- mm/damon/vaddr.c | 6 +++--- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/include/linux/damon.h b/include/linux/damon.h index 87c1ff479da8..02ed47c558cc 100644 --- a/include/linux/damon.h +++ b/include/linux/damon.h @@ -1016,8 +1016,7 @@ unsigned int damon_nr_accesses_mvsum(struct damon_region *r, int damon_set_regions(struct damon_target *t, struct damon_addr_range *ranges, unsigned int nr_ranges, unsigned long min_region_sz); -void damon_update_region_access_rate(struct damon_region *r, bool accessed, - struct damon_attrs *attrs); +void damon_update_region_access_rate(struct damon_region *r, bool accessed); struct damos_filter *damos_new_filter(enum damos_filter_type type, bool matching, bool allow); diff --git a/mm/damon/core.c b/mm/damon/core.c index ef0b87f2c035..ce14e2929e9c 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -3833,15 +3833,13 @@ static unsigned int damon_moving_sum(unsigned int mvsum, unsigned int nomvsum, * damon_update_region_access_rate() - Update the access rate of a region. * @r: The DAMON region to update for its access check result. * @accessed: Whether the region has accessed during last sampling interval. - * @attrs: The damon_attrs of the DAMON context. * * Update the access rate of a region with the region's last sampling interval * access check result. * * Usually this will be called by &damon_operations->check_accesses callback. */ -void damon_update_region_access_rate(struct damon_region *r, bool accessed, - struct damon_attrs *attrs) +void damon_update_region_access_rate(struct damon_region *r, bool accessed) { if (accessed) r->nr_accesses++; diff --git a/mm/damon/paddr.c b/mm/damon/paddr.c index 5c2da45f988c..853021308669 100644 --- a/mm/damon/paddr.c +++ b/mm/damon/paddr.c @@ -91,12 +91,12 @@ static void __damon_pa_check_access(struct damon_region *r, /* If the region is in the last checked page, reuse the result */ if (ALIGN_DOWN(last_addr, last_folio_sz) == ALIGN_DOWN(sampling_addr, last_folio_sz)) { - damon_update_region_access_rate(r, last_accessed, attrs); + damon_update_region_access_rate(r, last_accessed); return; } last_accessed = damon_pa_young(sampling_addr, &last_folio_sz); - damon_update_region_access_rate(r, last_accessed, attrs); + damon_update_region_access_rate(r, last_accessed); last_addr = sampling_addr; } diff --git a/mm/damon/vaddr.c b/mm/damon/vaddr.c index e73ec1ce016e..2eaced0765e2 100644 --- a/mm/damon/vaddr.c +++ b/mm/damon/vaddr.c @@ -501,19 +501,19 @@ static void __damon_va_check_access(struct mm_struct *mm, static bool last_accessed; if (!mm) { - damon_update_region_access_rate(r, false, attrs); + damon_update_region_access_rate(r, false); return; } /* If the region is in the last checked page, reuse the result */ if (same_target && (ALIGN_DOWN(last_addr, last_folio_sz) == ALIGN_DOWN(r->sampling_addr, last_folio_sz))) { - damon_update_region_access_rate(r, last_accessed, attrs); + damon_update_region_access_rate(r, last_accessed); return; } last_accessed = damon_va_young(mm, r->sampling_addr, &last_folio_sz); - damon_update_region_access_rate(r, last_accessed, attrs); + damon_update_region_access_rate(r, last_accessed); last_addr = r->sampling_addr; } From 04cc06388fbafc65569e98027ace8e8c5780b8fb Mon Sep 17 00:00:00 2001 From: SJ Park Date: Mon, 29 Jun 2026 21:08:08 -0700 Subject: [PATCH 335/562] mm/damon/paddr: remove attrs param from __damon_pa_check_access() The function is not using the parameter. Remove it. Link: https://lore.kernel.org/20260630040812.149729-16-sj@kernel.org Signed-off-by: SJ Park Cc: Brendan Higgins Cc: "Masami Hiramatsu (Google)" Cc: Mathieu Desnoyers Cc: Shuah Khan Cc: Steven Rostedt Signed-off-by: Andrew Morton --- mm/damon/paddr.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mm/damon/paddr.c b/mm/damon/paddr.c index 853021308669..617246498173 100644 --- a/mm/damon/paddr.c +++ b/mm/damon/paddr.c @@ -80,7 +80,7 @@ static bool damon_pa_young(phys_addr_t paddr, unsigned long *folio_sz) } static void __damon_pa_check_access(struct damon_region *r, - struct damon_attrs *attrs, unsigned long addr_unit) + unsigned long addr_unit) { static phys_addr_t last_addr; static unsigned long last_folio_sz = PAGE_SIZE; @@ -109,8 +109,7 @@ static unsigned int damon_pa_check_accesses(struct damon_ctx *ctx) damon_for_each_target(t, ctx) { damon_for_each_region(r, t) { - __damon_pa_check_access( - r, &ctx->attrs, ctx->addr_unit); + __damon_pa_check_access(r, ctx->addr_unit); max_nr_accesses = max(r->nr_accesses, max_nr_accesses); } } From fe88f482925a6e44a140e4956c1b3be19e3a41ee Mon Sep 17 00:00:00 2001 From: SJ Park Date: Mon, 29 Jun 2026 21:08:09 -0700 Subject: [PATCH 336/562] mm/damon/vaddr: remove attrs param from __damon_va_check_access() The function is not using attrs parameter. Remove it. Link: https://lore.kernel.org/20260630040812.149729-17-sj@kernel.org Signed-off-by: SJ Park Cc: Brendan Higgins Cc: "Masami Hiramatsu (Google)" Cc: Mathieu Desnoyers Cc: Shuah Khan Cc: Steven Rostedt Signed-off-by: Andrew Morton --- mm/damon/vaddr.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/mm/damon/vaddr.c b/mm/damon/vaddr.c index 2eaced0765e2..2058db9c01d5 100644 --- a/mm/damon/vaddr.c +++ b/mm/damon/vaddr.c @@ -493,8 +493,7 @@ static bool damon_va_young(struct mm_struct *mm, unsigned long addr, * r the region to be checked */ static void __damon_va_check_access(struct mm_struct *mm, - struct damon_region *r, bool same_target, - struct damon_attrs *attrs) + struct damon_region *r, bool same_target) { static unsigned long last_addr; static unsigned long last_folio_sz = PAGE_SIZE; @@ -530,8 +529,7 @@ static unsigned int damon_va_check_accesses(struct damon_ctx *ctx) mm = damon_get_mm(t); same_target = false; damon_for_each_region(r, t) { - __damon_va_check_access(mm, r, same_target, - &ctx->attrs); + __damon_va_check_access(mm, r, same_target); max_nr_accesses = max(r->nr_accesses, max_nr_accesses); same_target = true; } From 1cc490df645a57422476d0d289c9023947e3754c Mon Sep 17 00:00:00 2001 From: SJ Park Date: Mon, 29 Jun 2026 21:08:10 -0700 Subject: [PATCH 337/562] mm/damon/core: remove damon_moving_sum() and its unit test damon_moving_sum() is no longer being called for real purpose but its unit test. Testing a function that is not being used for real users makes no sense. Remove the test and the function. Link: https://lore.kernel.org/20260630040812.149729-18-sj@kernel.org Signed-off-by: SJ Park Cc: Brendan Higgins Cc: "Masami Hiramatsu (Google)" Cc: Mathieu Desnoyers Cc: Shuah Khan Cc: Steven Rostedt Signed-off-by: Andrew Morton --- mm/damon/core.c | 40 ------------------------------------- mm/damon/tests/core-kunit.h | 16 --------------- 2 files changed, 56 deletions(-) diff --git a/mm/damon/core.c b/mm/damon/core.c index ce14e2929e9c..0c32219fb1f8 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -3789,46 +3789,6 @@ int damon_set_region_system_rams_default(struct damon_target *t, return damon_set_regions(t, &addr_range, 1, min_region_sz); } -/* - * damon_moving_sum() - Calculate an inferred moving sum value. - * @mvsum: Inferred sum of the last @len_window values. - * @nomvsum: Non-moving sum of the last discrete @len_window window values. - * @len_window: The number of last values to take care of. - * @new_value: New value that will be added to the pseudo moving sum. - * - * Moving sum (moving average * window size) is good for handling noise, but - * the cost of keeping past values can be high for arbitrary window size. This - * function implements a lightweight pseudo moving sum function that doesn't - * keep the past window values. - * - * It simply assumes there was no noise in the past, and get the no-noise - * assumed past value to drop from @nomvsum and @len_window. @nomvsum is a - * non-moving sum of the last window. For example, if @len_window is 10 and we - * have 25 values, @nomvsum is the sum of the 11th to 20th values of the 25 - * values. Hence, this function simply drops @nomvsum / @len_window from - * given @mvsum and add @new_value. - * - * For example, if @len_window is 10 and @nomvsum is 50, the last 10 values for - * the last window could be vary, e.g., 0, 10, 0, 10, 0, 10, 0, 0, 0, 20. For - * calculating next moving sum with a new value, we should drop 0 from 50 and - * add the new value. However, this function assumes it got value 5 for each - * of the last ten times. Based on the assumption, when the next value is - * measured, it drops the assumed past value, 5 from the current sum, and add - * the new value to get the updated pseduo-moving average. - * - * This means the value could have errors, but the errors will be disappeared - * for every @len_window aligned calls. For example, if @len_window is 10, the - * pseudo moving sum with 11th value to 19th value would have an error. But - * the sum with 20th value will not have the error. - * - * Return: Pseudo-moving average after getting the @new_value. - */ -static unsigned int damon_moving_sum(unsigned int mvsum, unsigned int nomvsum, - unsigned int len_window, unsigned int new_value) -{ - return mvsum - nomvsum / len_window + new_value; -} - /** * damon_update_region_access_rate() - Update the access rate of a region. * @r: The DAMON region to update for its access check result. diff --git a/mm/damon/tests/core-kunit.h b/mm/damon/tests/core-kunit.h index 0566fc3dc067..0124f83b39b8 100644 --- a/mm/damon/tests/core-kunit.h +++ b/mm/damon/tests/core-kunit.h @@ -659,21 +659,6 @@ static void damon_test_set_attrs(struct kunit *test) damon_destroy_ctx(c); } -static void damon_test_moving_sum(struct kunit *test) -{ - unsigned int mvsum = 50000, nomvsum = 50000, len_window = 10; - unsigned int new_values[] = {10000, 0, 10000, 0, 0, 0, 10000, 0, 0, 0}; - unsigned int expects[] = {55000, 50000, 55000, 50000, 45000, 40000, - 45000, 40000, 35000, 30000}; - int i; - - for (i = 0; i < ARRAY_SIZE(new_values); i++) { - mvsum = damon_moving_sum(mvsum, nomvsum, len_window, - new_values[i]); - KUNIT_EXPECT_EQ(test, mvsum, expects[i]); - } -} - static void damon_test_mvsum(struct kunit *test) { unsigned long input_expects[] = { @@ -1590,7 +1575,6 @@ static struct kunit_case damon_test_cases[] = { KUNIT_CASE(damon_test_nr_accesses_to_accesses_bp), KUNIT_CASE(damon_test_update_monitoring_result), KUNIT_CASE(damon_test_set_attrs), - KUNIT_CASE(damon_test_moving_sum), KUNIT_CASE(damon_test_mvsum), KUNIT_CASE(damos_test_new_filter), KUNIT_CASE(damos_test_commit_quota_goal), From a52188bb6475887cdbfa3462c6d3c7e4c2e80030 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Mon, 29 Jun 2026 21:08:11 -0700 Subject: [PATCH 338/562] mm/damon/core: remove damon_region->nr_accesses_bp No code touches damon_region->nr_accesses_bp field. Remove it. Link: https://lore.kernel.org/20260630040812.149729-19-sj@kernel.org Signed-off-by: SJ Park Cc: Brendan Higgins Cc: "Masami Hiramatsu (Google)" Cc: Mathieu Desnoyers Cc: Shuah Khan Cc: Steven Rostedt Signed-off-by: Andrew Morton --- include/linux/damon.h | 10 ---------- mm/damon/core.c | 3 +-- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/include/linux/damon.h b/include/linux/damon.h index 02ed47c558cc..805e089ff4f2 100644 --- a/include/linux/damon.h +++ b/include/linux/damon.h @@ -45,8 +45,6 @@ struct damon_size_range { * @ar: The address range of the region. * @sampling_addr: Address of the sample for the next access check. * @nr_accesses: Access frequency of this region. - * @nr_accesses_bp: @nr_accesses in basis point (0.01%) that updated for - * each sampling interval. * @probe_hits: Number of probe-positive region samples. * @list: List head for siblings. * @age: Age of this region. @@ -59,13 +57,6 @@ struct damon_size_range { * not be done with direct access but with the helper function, * damon_update_region_access_rate(). * - * @nr_accesses_bp is another representation of @nr_accesses in basis point - * (1 in 10,000) that updated for every &damon_attrs->sample_interval in a - * manner similar to moving sum. By the algorithm, this value becomes - * @nr_accesses * 10000 for every &struct damon_attrs->aggr_interval. This can - * be used when the aggregation interval is too huge and therefore cannot wait - * for it before getting the access monitoring results. - * * @age is initially zero, increased for each aggregation interval, and reset * to zero again if the access frequency is significantly changed. If two * regions are merged into a new region, both @nr_accesses and @age of the new @@ -75,7 +66,6 @@ struct damon_region { struct damon_addr_range ar; unsigned long sampling_addr; unsigned int nr_accesses; - unsigned int nr_accesses_bp; unsigned char probe_hits[DAMON_MAX_PROBES]; struct list_head list; diff --git a/mm/damon/core.c b/mm/damon/core.c index 0c32219fb1f8..b2fc15a3804f 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -3649,8 +3649,7 @@ static int kdamond_fn(void *data) * aggregation, and make aggregation * information reset for all regions. Then, * following kdamond_reset_aggregated() call - * will make the region information invalid, - * particularly for ->nr_accesses_bp. + * will make the region information invalid. * * Reset ->next_aggregation_sis to avoid that. * It will anyway correctly updated after this From 4b9677c3e97ed9ab56bc8612884b7e9a8799ade9 Mon Sep 17 00:00:00 2001 From: Zhen Yan Date: Tue, 30 Jun 2026 20:50:47 +0800 Subject: [PATCH 339/562] mm: fix mapping_seek_hole_data() overflow on last page A local unprivileged process can create a shmem/tmpfs file with i_size == LLONG_MAX using memfd_create() and fallocate(). If the last page is present in the page cache, lseek(SEEK_HOLE) on that page returns 0x8000000000000000 as a successful offset, which is LLONG_MIN when stored in loff_t. The same file has readable data at the last byte, but SEEK_DATA from that offset returns ENXIO. The overflow is in mapping_seek_hole_data(): pos = round_up((u64)pos + 1, seek_size); For the final page below LLONG_MAX, the next page boundary is 0x8000000000000000, which is then used as a signed file offset. When assigned to the loff_t pos, this overflows to LLONG_MIN, so a subsequent "pos > end" comparison does not catch it. Keep mapping_seek_hole_data() inside its documented [start, end) search range: compute round_up() into a u64 variable and compare against (u64)end so the overflow is detected, then clamp pos to end when the rounded-up value goes past the search limit. Link: https://lore.kernel.org/20260630125047.703170-1-yanzhen20011121@163.com Signed-off-by: Zhen Yan Cc: Christian Brauner Cc: Hugh Dickins Cc: Jan Kara Cc: Matthew Wilcox (Oracle) Signed-off-by: Andrew Morton --- mm/filemap.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mm/filemap.c b/mm/filemap.c index 6e40f36c2bff..b39111abdc4b 100644 --- a/mm/filemap.c +++ b/mm/filemap.c @@ -3229,6 +3229,7 @@ loff_t mapping_seek_hole_data(struct address_space *mapping, loff_t start, while ((folio = find_get_entry(&xas, max, XA_PRESENT))) { loff_t pos = (u64)xas.xa_index << PAGE_SHIFT; size_t seek_size; + u64 next; if (start < pos) { if (!seek_data) @@ -3237,7 +3238,11 @@ loff_t mapping_seek_hole_data(struct address_space *mapping, loff_t start, } seek_size = seek_folio_size(&xas, folio); - pos = round_up((u64)pos + 1, seek_size); + next = round_up((u64)pos + 1, seek_size); + if (next > (u64)end) + pos = end; + else + pos = next; start = folio_seek_hole_data(&xas, mapping, folio, start, pos, seek_data); if (start < pos) From cac46029f1f83a18ec2a78d21768e303a8831dd2 Mon Sep 17 00:00:00 2001 From: Yunzhao Li Date: Thu, 2 Jul 2026 11:07:35 -0700 Subject: [PATCH 340/562] mm/zswap: use ratelimited stats flush in zswap_shrinker_count() zswap_shrinker_count() calls mem_cgroup_flush_stats(), which takes the global cgroup rstat lock synchronously. On machines with many CPUs and NUMA nodes, this creates severe lock contention in the kswapd reclaim path: - Multiple kswapd threads (one per NUMA node) run concurrently. - do_shrink_slab() invokes zswap_shrinker_count() for each memcg-aware shrinker pass. - Each call flushes the full cgroup rstat hierarchy under the global lock. On AMD EPYC 9684X machines (96 cores, 192 threads, 12 NUMA nodes) running production workloads with zswap enabled, perf shows 2.88% of kernel cycles in osq_lock contention from this path: 2.88% [k] osq_lock --__mutex_lock.constprop.0 --__cgroup_rstat_lock --cgroup_rstat_flush_locked --cgroup_rstat_flush --zswap_shrinker_count do_shrink_slab shrink_slab shrink_node balance_pgdat kswapd 84% of kswapd kernel cycles are spent in shrink_slab -> zswap_shrinker_count -> cgroup_rstat_flush, not in actual page reclaim (shrink_lruvec). Controlled A/B on identical hardware and workload: shrinker=Y: 2.88% osq_lock, memory PSI 1.58% shrinker=N: 0.00% osq_lock, memory PSI 0.57% eBPF-based rstat lock wait measurement across 8 production metals confirms the contention splits cleanly along shrinker enablement: shrinker=Y: 50-250x more contended lock acquisitions (248/s vs 1.1/s) shrinker=N: baseline lock wait (0.0017 s/s vs 1.04 s/s) zswap_shrinker_count() only produces a heuristic estimate, scaled by compression ratio via mult_frac(). The actual writeback happens in zswap_shrinker_scan(). Slightly stale stats are acceptable here. Switch to mem_cgroup_flush_stats_ratelimited(), which only flushes if the periodic 2-second flusher is one full cycle late. This matches the approach already used in prepare_scan_control() (mm/vmscan.c) for the same reclaim path. After applying this patch, rstat flush latency and lock wait time on shrinker=Y machines dropped to the same level as shrinker=N controls, while the zswap shrinker continues to function (pool size remains bounded under the max_pool_percent cap). Previously discussed: - Chengming Zhou (Dec 2023): rstat contention from zswap_shrinker_count [1] - Shakeel Butt (Aug 2024): zswap_shrinker_count still uses sync flush [2] - Yosry Ahmed (Aug 2024): suggested eliminating in-kernel flushers [3] - Jesper Dangaard Brouer (Sep 2024): cgroup/rstat V11 patch [4] Link: https://lore.kernel.org/20260702180908.150136-1-yunzhao@cloudflare.com Link: https://lore.kernel.org/linux-mm/20231206103935.3440502-1-zhouchengming@bytedance.com/ [1] Link: https://lore.kernel.org/linux-mm/CALvZod7LFxLCxVpOFH8b2Ppm8T40HPGMKQwX_=NPCWB_mFW+oQ@mail.gmail.com/ [2] Link: https://lore.kernel.org/linux-mm/CAJD7tkYvFyOSX+rP_FKGBhxvZiCDxtpsNp-c5CGOA-4Bq9oXSg@mail.gmail.com/ [3] Link: https://lore.kernel.org/linux-mm/172616070094.2055617.17676042522679701515.stgit@firesoul/ [4] Suggested-by: Jesper Dangaard Brouer Signed-off-by: Jesper Dangaard Brouer Signed-off-by: Yunzhao Li Tested-by: Yunzhao Li Acked-by: Johannes Weiner Acked-by: Jesper Dangaard Brouer Cc: Chengming Zhou Cc: Nhat Pham Cc: Shakeel Butt Cc: Yosry Ahmed Cc: Yunzhao Li Cc: Sourav Panda Signed-off-by: Andrew Morton --- mm/zswap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/zswap.c b/mm/zswap.c index 761cd699e0a3..b5a17ea20237 100644 --- a/mm/zswap.c +++ b/mm/zswap.c @@ -1217,7 +1217,7 @@ static unsigned long zswap_shrinker_count(struct shrinker *shrinker, * Without memcg, use the zswap pool-wide metrics. */ if (!mem_cgroup_disabled()) { - mem_cgroup_flush_stats(memcg); + mem_cgroup_flush_stats_ratelimited(memcg); nr_backing = memcg_page_state(memcg, MEMCG_ZSWAP_B) >> PAGE_SHIFT; nr_stored = memcg_page_state(memcg, MEMCG_ZSWAPPED); } else { From 4c67986d479368fe2e77449a39fa01c9a725b2b6 Mon Sep 17 00:00:00 2001 From: Ackerley Tng Date: Thu, 2 Jul 2026 09:21:45 -0700 Subject: [PATCH 341/562] mm: hugetlb: consolidate interpretation of gbl_chg within alloc_hugetlb_folio() Patch series "Open HugeTLB allocation routine for more generic use", v4. The motivation for this patch series is guest_memfd, which would like to use HugeTLB as a generic source of huge pages but not adopt HugeTLB's reservation at mmap() time. By refactoring alloc_hugetlb_folio() and some dependent functions, there is now an option to allocate HugeTLB folios without providing a VMA. Specifically, HugeTLB allocation used to be dependent on the VMA to 1. Look up reservations in the resv_map 2. Get mpol, stored at vma->vm_policy This refactoring provides hugetlb_alloc_folio(), which focuses on just the allocation itself, and associated memory and HugeTLB charging (cgroups). alloc_hugetlb_folio() still handles reservations in the resv_map and subpools. Regarding naming, I'm definitely open to alternative names :) I chose hugetlb_alloc_folio() because I'm seeing this function as a general allocation function that is provided by the HugeTLB subsystem (hence the hugetlb_ prefix). I'm intending for alloc_hugetlb_folio() to be later refactored as a static function for use just by HugeTLB, and HugeTLBfs should probably use hugetlb_alloc_folio() directly. To see how hugetlb_alloc_folio() is used by guest_memfd, the most recent patch series that uses this more generic HugeTLB allocation routine is at [1], and a newer revision of that patch series is at [2]. Independently of guest_memfd, I believe this change is useful in simplifying alloc_hugetlb_folio(). alloc_hugetlb_folio() was so coupled to a VMA that even HugeTLBfs allocates HugeTLB folios using a pseudo-VMA. This patch (of 6): The dequeue_hugetlb_folio_vma() function currently handles the gbl_chg parameter to determine if a folio can be dequeued based on global page availability. This leaks reservation-specific logic into the dequeueing path. Relocate this logic to alloc_hugetlb_folio() so that dequeue_hugetlb_folio_vma() focuses solely on selecting and dequeuing a folio. In alloc_hugetlb_folio(), only attempt to dequeue a folio if a reservation exists (gbl_chg == 0) or if there are available huge pages in the global pool. No functional change intended. Link: https://lore.kernel.org/20260702-hugetlb-open-up-v4-0-d53cefcccf34@google.com Link: https://lore.kernel.org/20260702-hugetlb-open-up-v4-1-d53cefcccf34@google.com Link: https://lore.kernel.org/all/cover.1747264138.git.ackerleytng@google.com/T/ [1] Link: https://github.com/googleprodkernel/linux-cc/tree/wip-gmem-conversions-hugetlb-restructuring-12-08-25 [2] Link: https://lore.kernel.org/all/agqaUcVp_hwH-VXr@localhost.localdomain/ [3] Link: https://sashiko.dev/#/patchset/20260518-hugetlb-open-up-v3-0-e14b302477f8@google.com [4] Signed-off-by: Ackerley Tng Reviewed-by: James Houghton Acked-by: Oscar Salvador Reviewed-by: Joshua Hahn Cc: Qi Zheng Cc: Alistair Popple Cc: Byungchul Park Cc: David Hildenbrand Cc: David Rientjes Cc: "Edgecombe, Rick P" Cc: Frank van der Linden Cc: Gregory Price Cc: "Huang, Ying" Cc: Jason Gunthorpe Cc: Jiaqi Yan Cc: Matthew Brost Cc: Michael Roth Cc: Michal Hocko Cc: Muchun Song Cc: Paolo Bonzini Cc: Pasha Tatashin Cc: Peter Xu Cc: Pratyush Yadav Cc: Rakie Kim Cc: Roman Gushchin Cc: Sean Christopherson Cc: Shakeel Butt Cc: Shivank Garg Cc: Vishal Annapurve Cc: Yan Zhao Cc: Zi Yan Signed-off-by: Andrew Morton --- mm/hugetlb.c | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/mm/hugetlb.c b/mm/hugetlb.c index 5d6b15692e3b..9a4b13a1fd4b 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -1318,7 +1318,7 @@ static unsigned long available_huge_pages(struct hstate *h) static struct folio *dequeue_hugetlb_folio_vma(struct hstate *h, struct vm_area_struct *vma, - unsigned long address, long gbl_chg) + unsigned long address) { struct folio *folio = NULL; struct mempolicy *mpol; @@ -1326,13 +1326,6 @@ static struct folio *dequeue_hugetlb_folio_vma(struct hstate *h, nodemask_t *nodemask; int nid; - /* - * gbl_chg==1 means the allocation requires a new page that was not - * reserved before. Making sure there's at least one free page. - */ - if (gbl_chg && !available_huge_pages(h)) - goto err; - gfp_mask = htlb_alloc_mask(h); nid = huge_node(vma, address, gfp_mask, &mpol, &nodemask); @@ -1350,9 +1343,6 @@ static struct folio *dequeue_hugetlb_folio_vma(struct hstate *h, mpol_cond_put(mpol); return folio; - -err: - return NULL; } #if defined(CONFIG_ARCH_HAS_GIGANTIC_PAGE) && defined(CONFIG_CONTIG_ALLOC) @@ -2934,12 +2924,17 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma, goto out_uncharge_cgroup_reservation; spin_lock_irq(&hugetlb_lock); + /* - * glb_chg is passed to indicate whether or not a page must be taken - * from the global free pool (global change). gbl_chg == 0 indicates - * a reservation exists for the allocation. + * gbl_chg == 0 indicates a reservation exists for the + * allocation, so try dequeuing a page. In case there was no + * reservation, try dequeuing a page if there are available + * pages in the global pool. */ - folio = dequeue_hugetlb_folio_vma(h, vma, addr, gbl_chg); + folio = NULL; + if (!gbl_chg || available_huge_pages(h)) + folio = dequeue_hugetlb_folio_vma(h, vma, addr); + if (!folio) { spin_unlock_irq(&hugetlb_lock); folio = alloc_buddy_hugetlb_folio_with_mpol(h, vma, addr); From 6d1c1e8350b5d1aaf4249f3aae6803f16818c3da Mon Sep 17 00:00:00 2001 From: Ackerley Tng Date: Thu, 2 Jul 2026 09:21:46 -0700 Subject: [PATCH 342/562] mm: hugetlb: move mpol interpretation out of alloc_buddy_hugetlb_folio_with_mpol() Move memory policy interpretation out of alloc_buddy_hugetlb_folio_with_mpol() and into alloc_hugetlb_folio() to separate reading and interpretation of memory policy from actual allocation. This will later allow memory policy to be interpreted outside of the process of allocating a hugetlb folio entirely. This opens doors for other callers of the HugeTLB folio allocation function, such as guest_memfd, where memory may not always be mapped and hence may not have an associated vma. Introduce struct mempolicy_interpreted to hold all the components of an interpreted memory policy. Rename alloc_buddy_hugetlb_folio_with_mpol() to alloc_buddy_hugetlb_folio() since the function no longer interprets memory policy. No functional change intended. Link: https://lore.kernel.org/20260702-hugetlb-open-up-v4-2-d53cefcccf34@google.com Signed-off-by: Ackerley Tng Reviewed-by: James Houghton Acked-by: Oscar Salvador Cc: Alistair Popple Cc: Byungchul Park Cc: David Hildenbrand Cc: David Rientjes Cc: "Edgecombe, Rick P" Cc: Frank van der Linden Cc: Gregory Price Cc: "Huang, Ying" Cc: Jason Gunthorpe Cc: Jiaqi Yan Cc: Joshua Hahn Cc: Matthew Brost Cc: Michael Roth Cc: Michal Hocko Cc: Muchun Song Cc: Paolo Bonzini Cc: Pasha Tatashin Cc: Peter Xu Cc: Pratyush Yadav Cc: Qi Zheng Cc: Rakie Kim Cc: Roman Gushchin Cc: Sean Christopherson Cc: Shakeel Butt Cc: Shivank Garg Cc: Vishal Annapurve Cc: Yan Zhao Cc: Zi Yan Signed-off-by: Andrew Morton --- include/uapi/linux/mempolicy.h | 2 +- mm/hugetlb.c | 54 ++++++++++++++++++++++------------ 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/include/uapi/linux/mempolicy.h b/include/uapi/linux/mempolicy.h index 6c962d866e86..7f6fc9599693 100644 --- a/include/uapi/linux/mempolicy.h +++ b/include/uapi/linux/mempolicy.h @@ -16,7 +16,7 @@ */ /* Policies */ -enum { +enum mempolicy_mode { MPOL_DEFAULT, MPOL_PREFERRED, MPOL_BIND, diff --git a/mm/hugetlb.c b/mm/hugetlb.c index 9a4b13a1fd4b..59c75a799b30 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -1316,6 +1316,12 @@ static unsigned long available_huge_pages(struct hstate *h) return h->free_huge_pages - h->resv_huge_pages; } +struct mempolicy_interpreted { + int nid; + nodemask_t *nodemask; + enum mempolicy_mode mode; +}; + static struct folio *dequeue_hugetlb_folio_vma(struct hstate *h, struct vm_area_struct *vma, unsigned long address) @@ -2149,32 +2155,28 @@ static struct folio *alloc_migrate_hugetlb_folio(struct hstate *h, gfp_t gfp_mas return folio; } -/* - * Use the VMA's mpolicy to allocate a huge page from the buddy. - */ static -struct folio *alloc_buddy_hugetlb_folio_with_mpol(struct hstate *h, - struct vm_area_struct *vma, unsigned long addr) +struct folio *alloc_buddy_hugetlb_folio(struct hstate *h, + gfp_t gfp_mask, struct mempolicy_interpreted *mpoli) { struct folio *folio = NULL; - struct mempolicy *mpol; - gfp_t gfp_mask = htlb_alloc_mask(h); - int nid; - nodemask_t *nodemask; + nodemask_t *nodemask = mpoli->nodemask; - nid = huge_node(vma, addr, gfp_mask, &mpol, &nodemask); - if (mpol_is_preferred_many(mpol)) { + if (mpoli->mode == MPOL_PREFERRED_MANY) { gfp_t gfp = gfp_mask & ~(__GFP_DIRECT_RECLAIM | __GFP_NOFAIL); - folio = alloc_surplus_hugetlb_folio(h, gfp, nid, nodemask); + folio = alloc_surplus_hugetlb_folio(h, gfp, mpoli->nid, + nodemask); /* Fallback to all nodes if page==NULL */ nodemask = NULL; } - if (!folio) - folio = alloc_surplus_hugetlb_folio(h, gfp_mask, nid, nodemask); - mpol_cond_put(mpol); + if (!folio) { + folio = alloc_surplus_hugetlb_folio(h, gfp_mask, mpoli->nid, + nodemask); + } + return folio; } @@ -2864,7 +2866,7 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma, int ret, idx; struct hugetlb_cgroup *h_cg = NULL; struct hugetlb_cgroup *h_cg_rsvd = NULL; - gfp_t gfp = htlb_alloc_mask(h) | __GFP_RETRY_MAYFAIL; + gfp_t gfp = htlb_alloc_mask(h); idx = hstate_index(h); @@ -2936,8 +2938,24 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma, folio = dequeue_hugetlb_folio_vma(h, vma, addr); if (!folio) { + struct mempolicy_interpreted mpoli; + struct mempolicy *mpol; + nodemask_t *nodemask; + int nid; + spin_unlock_irq(&hugetlb_lock); - folio = alloc_buddy_hugetlb_folio_with_mpol(h, vma, addr); + nid = huge_node(vma, addr, gfp, &mpol, &nodemask); + mpoli = (struct mempolicy_interpreted){ + .nid = nid, +#ifdef CONFIG_NUMA + .mode = mpol ? mpol->mode : MPOL_DEFAULT, +#else + .mode = MPOL_DEFAULT, +#endif + .nodemask = nodemask, + }; + folio = alloc_buddy_hugetlb_folio(h, gfp, &mpoli); + mpol_cond_put(mpol); if (!folio) goto out_uncharge_cgroup; spin_lock_irq(&hugetlb_lock); @@ -2993,7 +3011,7 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma, } } - ret = mem_cgroup_charge_hugetlb(folio, gfp); + ret = mem_cgroup_charge_hugetlb(folio, gfp | __GFP_RETRY_MAYFAIL); /* * Unconditionally increment NR_HUGETLB here. If it turns out that * mem_cgroup_charge_hugetlb failed, then immediately free the page and From cb12cbab42e48ee10a269203a7d1eaf6af099000 Mon Sep 17 00:00:00 2001 From: Ackerley Tng Date: Thu, 2 Jul 2026 09:21:47 -0700 Subject: [PATCH 343/562] mm: hugetlb: move mpol interpretation out of dequeue_hugetlb_folio_vma() Move memory policy interpretation out of dequeue_hugetlb_folio_vma() and into alloc_hugetlb_folio() to separate reading and interpretation of memory policy from actual allocation. Also rename dequeue_hugetlb_folio_vma() to dequeue_hugetlb_folio_with_mpol() to remove association with vma and to align with alloc_buddy_hugetlb_folio_with_mpol(). This will later allow memory policy to be interpreted outside of the process of allocating a hugetlb folio entirely. This opens doors for other callers of the HugeTLB folio allocation function, such as guest_memfd, where memory may not always be mapped and hence may not have an associated vma. No functional change intended. Link: https://lore.kernel.org/20260702-hugetlb-open-up-v4-3-d53cefcccf34@google.com Signed-off-by: Ackerley Tng Reviewed-by: James Houghton Cc: Alistair Popple Cc: Byungchul Park Cc: David Hildenbrand Cc: David Rientjes Cc: "Edgecombe, Rick P" Cc: Frank van der Linden Cc: Gregory Price Cc: "Huang, Ying" Cc: Jason Gunthorpe Cc: Jiaqi Yan Cc: Joshua Hahn Cc: Matthew Brost Cc: Michael Roth Cc: Michal Hocko Cc: Muchun Song Cc: Oscar Salvador Cc: Paolo Bonzini Cc: Pasha Tatashin Cc: Peter Xu Cc: Pratyush Yadav Cc: Qi Zheng Cc: Rakie Kim Cc: Roman Gushchin Cc: Sean Christopherson Cc: Shakeel Butt Cc: Shivank Garg Cc: Vishal Annapurve Cc: Yan Zhao Cc: Zi Yan Signed-off-by: Andrew Morton --- mm/hugetlb.c | 66 +++++++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/mm/hugetlb.c b/mm/hugetlb.c index 59c75a799b30..24e75a633dfb 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -1322,32 +1322,26 @@ struct mempolicy_interpreted { enum mempolicy_mode mode; }; -static struct folio *dequeue_hugetlb_folio_vma(struct hstate *h, - struct vm_area_struct *vma, - unsigned long address) +static struct folio *dequeue_hugetlb_folio(struct hstate *h, gfp_t gfp_mask, + struct mempolicy_interpreted *mpoli) { + nodemask_t *nodemask = mpoli->nodemask; struct folio *folio = NULL; - struct mempolicy *mpol; - gfp_t gfp_mask; - nodemask_t *nodemask; - int nid; - - gfp_mask = htlb_alloc_mask(h); - nid = huge_node(vma, address, gfp_mask, &mpol, &nodemask); - if (mpol_is_preferred_many(mpol)) { + if (mpoli->mode == MPOL_PREFERRED_MANY) { folio = dequeue_hugetlb_folio_nodemask(h, gfp_mask, - nid, nodemask); + mpoli->nid, + nodemask); /* Fallback to all nodes if page==NULL */ nodemask = NULL; } - if (!folio) + if (!folio) { folio = dequeue_hugetlb_folio_nodemask(h, gfp_mask, - nid, nodemask); - - mpol_cond_put(mpol); + mpoli->nid, + nodemask); + } return folio; } @@ -2866,7 +2860,11 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma, int ret, idx; struct hugetlb_cgroup *h_cg = NULL; struct hugetlb_cgroup *h_cg_rsvd = NULL; + struct mempolicy_interpreted mpoli; gfp_t gfp = htlb_alloc_mask(h); + struct mempolicy *mpol; + nodemask_t *nodemask; + int nid; idx = hstate_index(h); @@ -2925,6 +2923,18 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma, if (ret) goto out_uncharge_cgroup_reservation; + /* Takes reference on mpol. */ + nid = huge_node(vma, addr, gfp, &mpol, &nodemask); + mpoli = (struct mempolicy_interpreted){ + .nid = nid, +#ifdef CONFIG_NUMA + .mode = mpol ? mpol->mode : MPOL_DEFAULT, +#else + .mode = MPOL_DEFAULT, +#endif + .nodemask = nodemask, + }; + spin_lock_irq(&hugetlb_lock); /* @@ -2935,35 +2945,23 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma, */ folio = NULL; if (!gbl_chg || available_huge_pages(h)) - folio = dequeue_hugetlb_folio_vma(h, vma, addr); + folio = dequeue_hugetlb_folio(h, gfp, &mpoli); if (!folio) { - struct mempolicy_interpreted mpoli; - struct mempolicy *mpol; - nodemask_t *nodemask; - int nid; - spin_unlock_irq(&hugetlb_lock); - nid = huge_node(vma, addr, gfp, &mpol, &nodemask); - mpoli = (struct mempolicy_interpreted){ - .nid = nid, -#ifdef CONFIG_NUMA - .mode = mpol ? mpol->mode : MPOL_DEFAULT, -#else - .mode = MPOL_DEFAULT, -#endif - .nodemask = nodemask, - }; folio = alloc_buddy_hugetlb_folio(h, gfp, &mpoli); - mpol_cond_put(mpol); - if (!folio) + if (!folio) { + mpol_cond_put(mpol); goto out_uncharge_cgroup; + } spin_lock_irq(&hugetlb_lock); list_add(&folio->lru, &h->hugepage_activelist); folio_ref_unfreeze(folio, 1); /* Fall through */ } + mpol_cond_put(mpol); + /* * Either dequeued or buddy-allocated folio needs to add special * mark to the folio when it consumes a global reservation. From aaff31861053e579d25e4f722df3449d64b67c27 Mon Sep 17 00:00:00 2001 From: Ackerley Tng Date: Thu, 2 Jul 2026 09:21:48 -0700 Subject: [PATCH 344/562] mm: hugetlb: use error variable in alloc_hugetlb_folio Refactor alloc_hugetlb_folio to use a local variable for returning error codes. Instead of returning ERR_PTR(-ENOSPC) at the end of the error path, assign -ENOSPC to a return variable at each failure point and return that variable at the end. This allows the cleanup goto targets to be used with other errors in a later patch. No functional change intended. Link: https://lore.kernel.org/20260702-hugetlb-open-up-v4-4-d53cefcccf34@google.com Signed-off-by: Ackerley Tng Cc: Alistair Popple Cc: Byungchul Park Cc: David Hildenbrand Cc: David Rientjes Cc: "Edgecombe, Rick P" Cc: Frank van der Linden Cc: Gregory Price Cc: "Huang, Ying" Cc: James Houghton Cc: Jason Gunthorpe Cc: Jiaqi Yan Cc: Joshua Hahn Cc: Matthew Brost Cc: Michael Roth Cc: Michal Hocko Cc: Muchun Song Cc: Oscar Salvador Cc: Paolo Bonzini Cc: Pasha Tatashin Cc: Peter Xu Cc: Pratyush Yadav Cc: Qi Zheng Cc: Rakie Kim Cc: Roman Gushchin Cc: Sean Christopherson Cc: Shakeel Butt Cc: Shivank Garg Cc: Vishal Annapurve Cc: Yan Zhao Cc: Zi Yan Signed-off-by: Andrew Morton --- mm/hugetlb.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/mm/hugetlb.c b/mm/hugetlb.c index 24e75a633dfb..d65b7e31fccd 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -2898,8 +2898,10 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma, */ if (map_chg) { gbl_chg = hugepage_subpool_get_pages(spool, 1); - if (gbl_chg < 0) + if (gbl_chg < 0) { + ret = -ENOSPC; goto out_end_reservation; + } } else { /* * If we have the vma reservation ready, no need for extra @@ -2915,13 +2917,17 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma, if (map_chg) { ret = hugetlb_cgroup_charge_cgroup_rsvd( idx, pages_per_huge_page(h), &h_cg_rsvd); - if (ret) + if (ret) { + ret = -ENOSPC; goto out_subpool_put; + } } ret = hugetlb_cgroup_charge_cgroup(idx, pages_per_huge_page(h), &h_cg); - if (ret) + if (ret) { + ret = -ENOSPC; goto out_uncharge_cgroup_reservation; + } /* Takes reference on mpol. */ nid = huge_node(vma, addr, gfp, &mpol, &nodemask); @@ -2952,6 +2958,7 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma, folio = alloc_buddy_hugetlb_folio(h, gfp, &mpoli); if (!folio) { mpol_cond_put(mpol); + ret = -ENOSPC; goto out_uncharge_cgroup; } spin_lock_irq(&hugetlb_lock); @@ -3044,7 +3051,7 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma, out_end_reservation: if (map_chg != MAP_CHG_ENFORCED) vma_end_reservation(h, vma, addr); - return ERR_PTR(-ENOSPC); + return ERR_PTR(ret); } static __init void *alloc_bootmem(struct hstate *h, int nid, bool node_exact) From af2dec74c4b42f7855242dc87528d5f958370467 Mon Sep 17 00:00:00 2001 From: Ackerley Tng Date: Thu, 2 Jul 2026 09:21:49 -0700 Subject: [PATCH 345/562] mm: hugetlb: move mem_cgroup_charge_hugetlb() earlier in allocation Move mem_cgroup_charge_hugetlb() earlier in the folio allocation process. This change draws a cleaner line between memcg charging and the subsequent hugetlb-specific reservation logic for VMAs and subpools. While it would be ideal to make all accounting and reservations perfectly symmetric, mem_cgroup_charge_hugetlb() is a complex operation that cannot be performed under the hugetlb_lock. Moving the charge to this earlier point ensures that memcg charging is handled before the code begins manipulating subpool and VMA-specific state. These two types of accounting will be separated in a future patch. If mem_cgroup_charge_hugetlb() fails, the code now branches to out_subpool_put to ensure the folio is freed and the subpool references are handled correctly. Link: https://lore.kernel.org/20260702-hugetlb-open-up-v4-5-d53cefcccf34@google.com Signed-off-by: Ackerley Tng Cc: Alistair Popple Cc: Byungchul Park Cc: David Hildenbrand Cc: David Rientjes Cc: "Edgecombe, Rick P" Cc: Frank van der Linden Cc: Gregory Price Cc: "Huang, Ying" Cc: James Houghton Cc: Jason Gunthorpe Cc: Jiaqi Yan Cc: Joshua Hahn Cc: Matthew Brost Cc: Michael Roth Cc: Michal Hocko Cc: Muchun Song Cc: Oscar Salvador Cc: Paolo Bonzini Cc: Pasha Tatashin Cc: Peter Xu Cc: Pratyush Yadav Cc: Qi Zheng Cc: Rakie Kim Cc: Roman Gushchin Cc: Sean Christopherson Cc: Shakeel Butt Cc: Shivank Garg Cc: Vishal Annapurve Cc: Yan Zhao Cc: Zi Yan Signed-off-by: Andrew Morton --- mm/hugetlb.c | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/mm/hugetlb.c b/mm/hugetlb.c index d65b7e31fccd..93d265fd36fb 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -2989,6 +2989,24 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma, spin_unlock_irq(&hugetlb_lock); + ret = mem_cgroup_charge_hugetlb(folio, gfp | __GFP_RETRY_MAYFAIL); + /* + * Unconditionally increment NR_HUGETLB here. If it turns out that + * mem_cgroup_charge_hugetlb failed, then immediately free the page and + * decrement NR_HUGETLB. + */ + lruvec_stat_mod_folio(folio, NR_HUGETLB, pages_per_huge_page(h)); + + if (ret == -ENOMEM) { + free_huge_folio(folio); + /* + * Skip uncharging hugetlb_cgroup since the charges + * were committed to the folio and freeing the folio + * would have cleared those up. + */ + goto out_subpool_put; + } + hugetlb_set_folio_subpool(folio, spool); if (map_chg != MAP_CHG_ENFORCED) { @@ -3016,19 +3034,6 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma, } } - ret = mem_cgroup_charge_hugetlb(folio, gfp | __GFP_RETRY_MAYFAIL); - /* - * Unconditionally increment NR_HUGETLB here. If it turns out that - * mem_cgroup_charge_hugetlb failed, then immediately free the page and - * decrement NR_HUGETLB. - */ - lruvec_stat_mod_folio(folio, NR_HUGETLB, pages_per_huge_page(h)); - - if (ret == -ENOMEM) { - free_huge_folio(folio); - return ERR_PTR(-ENOMEM); - } - return folio; out_uncharge_cgroup: From 8cd87ff9ac66e01646a9131cdac8430fe94751b1 Mon Sep 17 00:00:00 2001 From: Ackerley Tng Date: Thu, 2 Jul 2026 09:21:50 -0700 Subject: [PATCH 346/562] mm: hugetlb: refactor out hugetlb_alloc_folio() Refactor out hugetlb_alloc_folio() from alloc_hugetlb_folio(), which handles allocation of a folio and memory and HugeTLB charging to cgroups. This refactoring decouples the HugeTLB page allocation from VMAs, specifically: 1. Reservations (as in resv_map) are stored in the vma 2. mpol is stored at vma->vm_policy 3. A vma must be used for allocation even if the pages are not meant to be used by host process. Without this coupling, VMAs are no longer a requirement for allocation. This opens up the allocation routine for usage without VMAs, which will allow guest_memfd to use HugeTLB as a more generic allocator of huge pages, since guest_memfd memory may not have any associated VMAs by design. In addition, direct allocations from HugeTLB could possibly be refactored to avoid the use of a pseudo-VMA. Also, this decouples HugeTLB page allocation from HugeTLBfs, where the subpool is stored at the fs mount. This is also a requirement for guest_memfd, where the plan is to have a subpool created per-fd and stored on the inode. Provide and use alloc_flags to allow more allocation knobs in future without expanding the number of parameters in hugetlb_alloc_folio(). No functional change intended. Link: https://lore.kernel.org/20260702-hugetlb-open-up-v4-6-d53cefcccf34@google.com Signed-off-by: Ackerley Tng Cc: Alistair Popple Cc: Byungchul Park Cc: David Hildenbrand Cc: David Rientjes Cc: "Edgecombe, Rick P" Cc: Frank van der Linden Cc: Gregory Price Cc: "Huang, Ying" Cc: James Houghton Cc: Jason Gunthorpe Cc: Jiaqi Yan Cc: Joshua Hahn Cc: Matthew Brost Cc: Michael Roth Cc: Michal Hocko Cc: Muchun Song Cc: Oscar Salvador Cc: Paolo Bonzini Cc: Pasha Tatashin Cc: Peter Xu Cc: Pratyush Yadav Cc: Rakie Kim Cc: Roman Gushchin Cc: Sean Christopherson Cc: Shakeel Butt Cc: Shivank Garg Cc: Vishal Annapurve Cc: Yan Zhao Cc: Zi Yan Cc: Qi Zheng Signed-off-by: Andrew Morton --- include/linux/hugetlb.h | 18 ++++ mm/hugetlb.c | 206 ++++++++++++++++++++++------------------ 2 files changed, 131 insertions(+), 93 deletions(-) diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h index 7bc3cafa7f61..3ebdbc246857 100644 --- a/include/linux/hugetlb.h +++ b/include/linux/hugetlb.h @@ -2,6 +2,7 @@ #ifndef _LINUX_HUGETLB_H #define _LINUX_HUGETLB_H +#include #include #include #include @@ -682,6 +683,23 @@ struct hstate { int isolate_or_dissolve_huge_folio(struct folio *folio, struct list_head *list); int replace_free_hugepage_folios(unsigned long start_pfn, unsigned long end_pfn); void wait_for_freed_hugetlb_folios(void); + +struct mempolicy_interpreted { + int nid; + nodemask_t *nodemask; + enum mempolicy_mode mode; +}; + +enum hugetlb_alloc_flag { + HUGETLB_ALLOC_CHARGE_CGROUP_RSVD_BIT = 0, + HUGETLB_ALLOC_USE_GLOBAL_RESERVATIONS_BIT, +}; + +#define HUGETLB_ALLOC_CHARG_CGROUP_RSVD BIT(HUGETLB_ALLOC_CHARGE_CGROUP_RSVD_BIT) +#define HUGETLB_ALLOC_USE_GLOBAL_RESERVATIONS BIT(HUGETLB_ALLOC_USE_GLOBAL_RESERVATIONS_BIT) + +struct folio *hugetlb_alloc_folio(struct hstate *h, + struct mempolicy_interpreted *mpoli, u8 alloc_flags); struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma, unsigned long addr, bool cow_from_owner); struct folio *alloc_hugetlb_folio_nodemask(struct hstate *h, int preferred_nid, diff --git a/mm/hugetlb.c b/mm/hugetlb.c index 93d265fd36fb..4cb8412196fa 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -1316,12 +1316,6 @@ static unsigned long available_huge_pages(struct hstate *h) return h->free_huge_pages - h->resv_huge_pages; } -struct mempolicy_interpreted { - int nid; - nodemask_t *nodemask; - enum mempolicy_mode mode; -}; - static struct folio *dequeue_hugetlb_folio(struct hstate *h, gfp_t gfp_mask, struct mempolicy_interpreted *mpoli) { @@ -2823,6 +2817,104 @@ void wait_for_freed_hugetlb_folios(void) flush_work(&free_hpage_work); } +/** + * hugetlb_alloc_folio - Allocate a hugetlb folio. + * @h: Hugetlb state control block. + * @mpoli: Interpreted memory policy to use for allocation. + * @alloc_flags: Flags controlling the allocation behavior. + * + * Allocates a hugetlb folio and handles cgroup charging and global hstate + * reservations. + * + * Return: A pointer to the allocated folio, or an ERR_PTR on failure. + * -ENOSPC if cgroup charging fails or no folio is available. + * -ENOMEM if mem cgroup charging fails. + */ +struct folio *hugetlb_alloc_folio(struct hstate *h, + struct mempolicy_interpreted *mpoli, u8 alloc_flags) +{ + bool charge_hugetlb_cgroup_rsvd = alloc_flags & + HUGETLB_ALLOC_CHARG_CGROUP_RSVD; + bool use_global_reservation = alloc_flags & + HUGETLB_ALLOC_USE_GLOBAL_RESERVATIONS; + size_t nr_pages = pages_per_huge_page(h); + struct hugetlb_cgroup *h_cg_rsvd = NULL; + struct hugetlb_cgroup *h_cg = NULL; + gfp_t gfp = htlb_alloc_mask(h); + int idx = hstate_index(h); + struct folio *folio; + int ret; + + if (charge_hugetlb_cgroup_rsvd && + hugetlb_cgroup_charge_cgroup_rsvd(idx, nr_pages, &h_cg_rsvd)) + return ERR_PTR(-ENOSPC); + + if (hugetlb_cgroup_charge_cgroup(idx, nr_pages, &h_cg)) { + ret = -ENOSPC; + goto err_uncharge_hugetlb_cgroup_rsvd; + } + + spin_lock_irq(&hugetlb_lock); + + folio = NULL; + if (use_global_reservation || available_huge_pages(h)) + folio = dequeue_hugetlb_folio(h, gfp, mpoli); + + if (!folio) { + spin_unlock_irq(&hugetlb_lock); + folio = alloc_buddy_hugetlb_folio(h, gfp, mpoli); + if (!folio) { + ret = -ENOSPC; + goto err_uncharge_hugetlb_cgroup; + } + spin_lock_irq(&hugetlb_lock); + list_add(&folio->lru, &h->hugepage_activelist); + folio_ref_unfreeze(folio, 1); + } + + if (use_global_reservation) { + folio_set_hugetlb_restore_reserve(folio); + h->resv_huge_pages--; + } + + hugetlb_cgroup_commit_charge(idx, nr_pages, h_cg, folio); + + if (charge_hugetlb_cgroup_rsvd) { + hugetlb_cgroup_commit_charge_rsvd(idx, nr_pages, h_cg_rsvd, + folio); + } + + spin_unlock_irq(&hugetlb_lock); + + ret = mem_cgroup_charge_hugetlb(folio, gfp | __GFP_RETRY_MAYFAIL); + /* + * Unconditionally increment NR_HUGETLB here because if + * mem_cgroup_charge_hugetlb failed, freeing the page will + * decrement NR_HUGETLB. + */ + lruvec_stat_mod_folio(folio, NR_HUGETLB, nr_pages); + + if (ret == -ENOMEM) { + free_huge_folio(folio); + /* + * Skip uncharging hugetlb_cgroup since the charges + * were committed to the folio and freeing the folio + * would have cleared those up. + */ + return ERR_PTR(ret); + } + + return folio; + + err_uncharge_hugetlb_cgroup: + hugetlb_cgroup_uncharge_cgroup(idx, nr_pages, h_cg); + err_uncharge_hugetlb_cgroup_rsvd: + if (charge_hugetlb_cgroup_rsvd) + hugetlb_cgroup_uncharge_cgroup_rsvd(idx, nr_pages, h_cg_rsvd); + + return ERR_PTR(ret); +} + typedef enum { /* * For either 0/1: we checked the per-vma resv map, and one resv @@ -2857,16 +2949,13 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma, struct folio *folio; long retval, gbl_chg, gbl_reserve; map_chg_state map_chg; - int ret, idx; - struct hugetlb_cgroup *h_cg = NULL; - struct hugetlb_cgroup *h_cg_rsvd = NULL; struct mempolicy_interpreted mpoli; gfp_t gfp = htlb_alloc_mask(h); struct mempolicy *mpol; nodemask_t *nodemask; + u8 alloc_flags = 0; int nid; - - idx = hstate_index(h); + int ret; /* Whether we need a separate per-vma reservation? */ if (cow_from_owner) { @@ -2911,23 +3000,18 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma, } /* - * If this allocation is not consuming a per-vma reservation, - * charge the hugetlb cgroup now. + * If allocation doesn't reuse a reservation in the resv_map, + * charge for the reservation. */ - if (map_chg) { - ret = hugetlb_cgroup_charge_cgroup_rsvd( - idx, pages_per_huge_page(h), &h_cg_rsvd); - if (ret) { - ret = -ENOSPC; - goto out_subpool_put; - } - } + if (map_chg != MAP_CHG_REUSE) + alloc_flags |= HUGETLB_ALLOC_CHARG_CGROUP_RSVD; - ret = hugetlb_cgroup_charge_cgroup(idx, pages_per_huge_page(h), &h_cg); - if (ret) { - ret = -ENOSPC; - goto out_uncharge_cgroup_reservation; - } + /* + * gbl_chg == 0 indicates a reservation exists for this + * allocation, so try to use it. + */ + if (gbl_chg == 0) + alloc_flags |= HUGETLB_ALLOC_USE_GLOBAL_RESERVATIONS; /* Takes reference on mpol. */ nid = huge_node(vma, addr, gfp, &mpol, &nodemask); @@ -2941,69 +3025,12 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma, .nodemask = nodemask, }; - spin_lock_irq(&hugetlb_lock); - - /* - * gbl_chg == 0 indicates a reservation exists for the - * allocation, so try dequeuing a page. In case there was no - * reservation, try dequeuing a page if there are available - * pages in the global pool. - */ - folio = NULL; - if (!gbl_chg || available_huge_pages(h)) - folio = dequeue_hugetlb_folio(h, gfp, &mpoli); - - if (!folio) { - spin_unlock_irq(&hugetlb_lock); - folio = alloc_buddy_hugetlb_folio(h, gfp, &mpoli); - if (!folio) { - mpol_cond_put(mpol); - ret = -ENOSPC; - goto out_uncharge_cgroup; - } - spin_lock_irq(&hugetlb_lock); - list_add(&folio->lru, &h->hugepage_activelist); - folio_ref_unfreeze(folio, 1); - /* Fall through */ - } + folio = hugetlb_alloc_folio(h, &mpoli, alloc_flags); mpol_cond_put(mpol); - /* - * Either dequeued or buddy-allocated folio needs to add special - * mark to the folio when it consumes a global reservation. - */ - if (!gbl_chg) { - folio_set_hugetlb_restore_reserve(folio); - h->resv_huge_pages--; - } - - hugetlb_cgroup_commit_charge(idx, pages_per_huge_page(h), h_cg, folio); - /* If allocation is not consuming a reservation, also store the - * hugetlb_cgroup pointer on the page. - */ - if (map_chg) { - hugetlb_cgroup_commit_charge_rsvd(idx, pages_per_huge_page(h), - h_cg_rsvd, folio); - } - - spin_unlock_irq(&hugetlb_lock); - - ret = mem_cgroup_charge_hugetlb(folio, gfp | __GFP_RETRY_MAYFAIL); - /* - * Unconditionally increment NR_HUGETLB here. If it turns out that - * mem_cgroup_charge_hugetlb failed, then immediately free the page and - * decrement NR_HUGETLB. - */ - lruvec_stat_mod_folio(folio, NR_HUGETLB, pages_per_huge_page(h)); - - if (ret == -ENOMEM) { - free_huge_folio(folio); - /* - * Skip uncharging hugetlb_cgroup since the charges - * were committed to the folio and freeing the folio - * would have cleared those up. - */ + if (IS_ERR(folio)) { + ret = PTR_ERR(folio); goto out_subpool_put; } @@ -3036,12 +3063,6 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma, return folio; -out_uncharge_cgroup: - hugetlb_cgroup_uncharge_cgroup(idx, pages_per_huge_page(h), h_cg); -out_uncharge_cgroup_reservation: - if (map_chg) - hugetlb_cgroup_uncharge_cgroup_rsvd(idx, pages_per_huge_page(h), - h_cg_rsvd); out_subpool_put: /* * put page to subpool iff the quota of subpool's rsv_hpages is used @@ -3052,7 +3073,6 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma, hugetlb_acct_memory(h, -gbl_reserve); } - out_end_reservation: if (map_chg != MAP_CHG_ENFORCED) vma_end_reservation(h, vma, addr); From 16f8de0b876bcf200e807212fa44959c0a3a6bc7 Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Thu, 2 Jul 2026 20:02:27 +0800 Subject: [PATCH 347/562] memcg: bail out memory.high when memcg is dying Patch series "memcg: bail out reclaim when memcg is dying", v3. This series mitigates a system-wide stall we hit when a cgroup is removed while one of its memory control files is doing synchronous reclaim. Problem Description =================== Writing to memory.high, memory.max or memory.reclaim runs reclaim synchronously in the writer's context, looping until the usage drops below the target (or, for memory.reclaim, until the requested amount has been reclaimed). On a large cgroup this can take a long time. The latency is especially bad when reclaim has to perform swap I/O, where it is bound by the swap device write bandwidth, and under thrashing it is effectively unbounded - each round reclaims a few pages that the workload immediately faults back in, so the loop keeps making "progress" and never converges. The legacy (v1) reclaim loops in memory.limit_in_bytes, memory.memsw.limit_in_bytes and memory.force_empty share the same pattern. These writes go through cgroup_file_write(), which does not take cgroup_mutex and does not pin the css. Instead, kernfs guarantees the node (and thus the css) stays alive for the duration of the operation by holding an active reference. So while the reclaim loop runs, the active reference on the file is held. If another task removes the same cgroup in parallel, cgroup_rmdir() takes cgroup_mutex and then blocks in kernfs_drain() waiting for that active reference to drain. Because cgroup_mutex is held throughout the wait, every other task that needs it piles up behind the remover - in our case the whole machine ground to a halt, with hung_task reports for the remover and for unrelated tasks merely reading /proc//cgroup: INFO: task cgdelete:366634 blocked for more than 159 seconds. Not tainted 6.6.102+ #1 "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message. Call Trace: __schedule+0x3da/0x1650 schedule+0x58/0x100 kernfs_drain+0xe6/0x150 __kernfs_remove.part.0+0xd0/0x200 kernfs_remove_by_name_ns+0x75/0xd0 cgroup_addrm_files+0x325/0x410 css_clear_dir+0x50/0xf0 cgroup_destroy_locked+0xdf/0x1e0 cgroup_rmdir+0x2d/0xd0 kernfs_iop_rmdir+0x53/0x90 vfs_rmdir+0x98/0x240 do_rmdir+0x172/0x1b0 __x64_sys_rmdir+0x42/0x70 x64_sys_call+0xeb0/0x2210 do_syscall_64+0x56/0x90 entry_SYSCALL_64_after_hwframe+0x78/0xe2 INFO: task systemd-journal:2352 blocked for more than 182 seconds. Not tainted 6.6.102+ #1 "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message. Call Trace: __schedule+0x3da/0x1650 schedule+0x58/0x100 schedule_preempt_disabled+0xe/0x20 __mutex_lock.constprop.0+0x3bb/0x640 __mutex_lock_slowpath+0x13/0x20 mutex_lock+0x3c/0x50 proc_cgroup_show+0x4d/0x380 proc_single_show+0x53/0xe0 seq_read_iter+0x12f/0x4b0 seq_read+0xcd/0x110 vfs_read+0xb1/0x360 ? __seccomp_filter+0x368/0x590 ksys_read+0x73/0x100 __x64_sys_read+0x19/0x30 x64_sys_call+0x18d3/0x2210 do_syscall_64+0x56/0x90 entry_SYSCALL_64_after_hwframe+0x78/0xe2 The system recovers only once the reclaim finally finishes and releases the active reference. The reclaim itself is pointless here: the cgroup is being torn down and its remaining pages will be reparented to the parent anyway. Even though we check signal_pending(current) in the reclaim loop, the typical symptom is that cat /proc//cgroup gets stuck. By the time someone looks for which task is actually stuck in reclaim, the hung task timeout has already been hit. This makes the problem particularly nasty to debug from a hung-task report alone, because the blocked tasks shown are often the victims, not the reclaim writer itself. Our Mitigation ============== cgroup destruction sets CSS_DYING in kill_css_sync() *before* css_clear_dir() triggers the kernfs_drain() that blocks the remover. The in-flight reclaim loop is therefore guaranteed to observe it before starting another reclaim iteration. This series checks memcg_is_dying() in the v2 reclaim loops (memory.high, memory.max and proactive reclaim) and the v1 reclaim loops (memory.limit_in_bytes, memory.memsw.limit_in_bytes and memory.force_empty), and bails out early, so the writer drops the active reference promptly and the remover can make progress. Unlike the no-progress guard (MAX_RECLAIM_RETRIES), which only fires when reclaim makes zero progress, the dying check also covers the slow swap I/O and thrashing cases, where reclaim keeps succeeding a little and the loop would otherwise never converge. For memory.reclaim, bailing out because the memcg is dying means the requested reclaim amount was not satisfied, so the write returns -EAGAIN. This is orthogonal to commit c8e6002bd611 ("memcg: introduce non-blocking limit setting option"): O_NONBLOCK lets a caller avoid the synchronous reclaim up front, while this series handles the case where reclaim is already running when the cgroup starts being removed. This patch (of 4): memory.high reclaims synchronously in the writer's context, and the latency can be very high - especially when reclaim performs swap I/O, or under thrashing where the loop may not converge for a long time. While this runs the kernfs active reference on the file is held, so a concurrent removal of the same cgroup blocks in kernfs_drain() under cgroup_mutex until it finishes. Reclaiming a dying cgroup is pointless, as its pages are reparented to the parent anyway. Mitigate this by bailing out of the reclaim loop once memcg_is_dying(). Link: https://lore.kernel.org/20260702120235.376752-1-jiayuan.chen@linux.dev Link: https://lore.kernel.org/20260702120235.376752-2-jiayuan.chen@linux.dev Signed-off-by: Jiayuan Chen Reported-by: Zhou Yingfu Acked-by: Johannes Weiner Cc: Axel Rasmussen Cc: Barry Song Cc: David Hildenbrand Cc: Kairui Song Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Muchun Song Cc: Roman Gushchin Cc: Shakeel Butt Cc: Wei Xu Cc: Yuanchu Xie Signed-off-by: Andrew Morton --- mm/memcontrol.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mm/memcontrol.c b/mm/memcontrol.c index d20ffc827306..4519dc9eae33 100644 --- a/mm/memcontrol.c +++ b/mm/memcontrol.c @@ -4794,6 +4794,10 @@ static ssize_t memory_high_write(struct kernfs_open_file *of, if (signal_pending(current)) break; + /* cgroup_rmdir() waits for us with cgroup_mutex held. */ + if (memcg_is_dying(memcg)) + break; + if (!drained) { drain_all_stock(memcg); drained = true; From b1a035248b4ea0bbc50401850abb6a31538f828f Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Thu, 2 Jul 2026 20:02:28 +0800 Subject: [PATCH 348/562] memcg: bail out memory.max when memcg is dying memory.max has the same high-latency reclaim loop as memory.high, and may additionally invoke the OOM killer on a cgroup that is already going away, further delaying its removal. Mitigate this by bailing out of the loop once memcg_is_dying(). Link: https://lore.kernel.org/20260702120235.376752-3-jiayuan.chen@linux.dev Signed-off-by: Jiayuan Chen Reported-by: Zhou Yingfu Acked-by: Johannes Weiner Cc: Jiayuan Chen Cc: Axel Rasmussen Cc: Barry Song Cc: David Hildenbrand Cc: Kairui Song Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Muchun Song Cc: Roman Gushchin Cc: Shakeel Butt Cc: Wei Xu Cc: Yuanchu Xie Signed-off-by: Andrew Morton --- mm/memcontrol.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mm/memcontrol.c b/mm/memcontrol.c index 4519dc9eae33..938f190a98fe 100644 --- a/mm/memcontrol.c +++ b/mm/memcontrol.c @@ -4849,6 +4849,10 @@ static ssize_t memory_max_write(struct kernfs_open_file *of, if (signal_pending(current)) break; + /* cgroup_rmdir() waits for us with cgroup_mutex held. */ + if (memcg_is_dying(memcg)) + break; + if (!drained) { drain_all_stock(memcg); drained = true; From 68beb6bfe5445ded8eb40bb956a736e712336680 Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Thu, 2 Jul 2026 20:02:29 +0800 Subject: [PATCH 349/562] memcg: bail out proactive reclaim when memcg is dying Proactive reclaim via memory.reclaim can run for a long time - swap I/O or thrashing again dominating the latency - and delays cgroup removal in the same way. Mitigate this by stopping the reclaim once memcg_is_dying(). Link: https://lore.kernel.org/20260702120235.376752-4-jiayuan.chen@linux.dev Signed-off-by: Jiayuan Chen Reported-by: Zhou Yingfu Acked-by: Johannes Weiner Cc: Jiayuan Chen Cc: Axel Rasmussen Cc: Barry Song Cc: David Hildenbrand Cc: Kairui Song Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Muchun Song Cc: Roman Gushchin Cc: Shakeel Butt Cc: Wei Xu Cc: Yuanchu Xie Signed-off-by: Andrew Morton --- mm/vmscan.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mm/vmscan.c b/mm/vmscan.c index f40cfe9d703b..56fe5393f30f 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -7904,6 +7904,10 @@ int user_proactive_reclaim(char *buf, if (signal_pending(current)) return -EINTR; + /* cgroup_rmdir() waits for us with cgroup_mutex held. */ + if (memcg && memcg_is_dying(memcg)) + return -EAGAIN; + /* * This is the final attempt, drain percpu lru caches in the * hope of introducing more evictable pages. From ae4735a8d562eb12716c5bfbb3f1352b69c18cb3 Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Thu, 2 Jul 2026 20:02:30 +0800 Subject: [PATCH 350/562] memcg-v1: bail out reclaim when memcg is dying The legacy memory.limit_in_bytes and memory.memsw.limit_in_bytes writers retry page_counter_set_max() by reclaiming synchronously in the writer context. memory.force_empty similarly loops in synchronous reclaim until the cgroup is empty or reclaim stops making progress. These writes hold a kernfs active reference on the file. If cgroup removal starts in parallel, the remover sets CSS_DYING and then waits in kernfs_drain() under cgroup_mutex for the active reference to drain. Continuing reclaim after the memcg is dying can therefore delay cgroup removal and keep cgroup_mutex held for a long time. Stop the v1 reclaim loops once the memcg is dying. For limit resizing, keep the existing -EBUSY semantics when the new limit could not be installed. For memory.force_empty, keep the existing best-effort success semantics. Link: https://lore.kernel.org/20260702120235.376752-5-jiayuan.chen@linux.dev Signed-off-by: Jiayuan Chen Reported-by: Zhou Yingfu Acked-by: Johannes Weiner Cc: Jiayuan Chen Cc: Axel Rasmussen Cc: Barry Song Cc: David Hildenbrand Cc: Kairui Song Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Muchun Song Cc: Roman Gushchin Cc: Shakeel Butt Cc: Wei Xu Cc: Yuanchu Xie Signed-off-by: Andrew Morton --- mm/memcontrol-v1.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mm/memcontrol-v1.c b/mm/memcontrol-v1.c index 135622b6172b..e8b6e1560278 100644 --- a/mm/memcontrol-v1.c +++ b/mm/memcontrol-v1.c @@ -1805,6 +1805,10 @@ static int mem_cgroup_resize_max(struct mem_cgroup *memcg, if (!ret) break; + /* cgroup_rmdir() waits for us with cgroup_mutex held. */ + if (memcg_is_dying(memcg)) + break; + if (!drained) { drain_all_stock(memcg); drained = true; @@ -1843,6 +1847,10 @@ static int mem_cgroup_force_empty(struct mem_cgroup *memcg) if (signal_pending(current)) return -EINTR; + /* cgroup_rmdir() waits for us with cgroup_mutex held. */ + if (memcg_is_dying(memcg)) + break; + if (!try_to_free_mem_cgroup_pages(memcg, 1, GFP_KERNEL, MEMCG_RECLAIM_MAY_SWAP, NULL)) nr_retries--; From 2ddd1ad9674c5a5c59d329f0553b5b2346536402 Mon Sep 17 00:00:00 2001 From: Feng Tang Date: Thu, 2 Jul 2026 19:26:10 +0800 Subject: [PATCH 351/562] mm/vmalloc: add alignment info in warning print as possible failure reason When running 'fix_align_alloc_test' case of test_vmalloc module with command: insmod ./test_vmalloc.ko run_test_mask=64 It will fail, which is the expected result, as the case increment the alignment parameter gradually to 64bit limit. And the dmesg has warning msg: "vmalloc_test/0: vmalloc error: size 4096, vm_struct allocation failed, mode:0xdc0(GFP_KERNEL|__GFP_ZERO), nodemask=(null),cpuset=/,mems_allowed=0" It doesn't give the alignment info, which is the real reason for the failure (not the 'size'). Add alignment info to the warning print to give the necessary hint for possible failure reason, and the message will be: "vmalloc_test/0: vmalloc error: size 4096, align 0x800000000000, vm_struct allocation failed, mode:0xdc0(GFP_KERNEL|__GFP_ZERO), nodemask=(null),cpuset=/,mems_allowed=0" Link: https://lore.kernel.org/20260702112610.21589-1-feng.tang@linux.alibaba.com Signed-off-by: Feng Tang Reviewed-by: Uladzislau Rezki (Sony) Signed-off-by: Andrew Morton --- mm/vmalloc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mm/vmalloc.c b/mm/vmalloc.c index 12f4a39fdd0b..1191cda3b4e8 100644 --- a/mm/vmalloc.c +++ b/mm/vmalloc.c @@ -4045,8 +4045,8 @@ void *__vmalloc_node_range_noprof(unsigned long size, unsigned long align, if (!area) { bool nofail = gfp_mask & __GFP_NOFAIL; warn_alloc(gfp_mask, NULL, - "vmalloc error: size %lu, vm_struct allocation failed%s", - size, (nofail) ? ". Retrying." : ""); + "vmalloc error: size %lu, align 0x%lx, vm_struct allocation failed%s", + size, align, (nofail) ? ". Retrying." : ""); if (nofail) { schedule_timeout_uninterruptible(1); goto again; From d148260a31fddf6d59cc0ea4980bd78ebe301a91 Mon Sep 17 00:00:00 2001 From: Ethan Nelson-Moore Date: Wed, 10 Jun 2026 18:22:44 -0700 Subject: [PATCH 352/562] mm: fix CONFIG_STACK_GROWSUP typo in tools/testing/vma/include/dup.h Commit 2b6a3f061f11 ("mm: declare VMA flags by bit") significantly refactored the header file include/linux/mm.h. In that step, it introduced a typo in an ifdef, referring to a non-existing config option STACK_GROWS_UP, whereas the actual config option is called STACK_GROWSUP. Commit 40a4af52e047 ("mm: fix CONFIG_STACK_GROWSUP typo in mm.h") fixed this typo in the mm.h header file, but did not update the copy of the code in tools/testing/vma/include/dup.h. Update this copy as well. Commit message adapted from the above-referenced fix to mm.h. Link: https://lore.kernel.org/20260611012258.432043-1-enelsonmoore@gmail.com Signed-off-by: Ethan Nelson-Moore Reviewed-by: Lorenzo Stoakes Cc: Alice Ryhl Cc: Jann Horn Cc: Liam R. Howlett Cc: Pedro Falcato Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- tools/testing/vma/include/dup.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/vma/include/dup.h b/tools/testing/vma/include/dup.h index bf26b3f48d3a..cf73bcd9bb9d 100644 --- a/tools/testing/vma/include/dup.h +++ b/tools/testing/vma/include/dup.h @@ -243,7 +243,7 @@ enum { #define VM_NOHUGEPAGE INIT_VM_FLAG(NOHUGEPAGE) #define VM_MERGEABLE INIT_VM_FLAG(MERGEABLE) #define VM_STACK INIT_VM_FLAG(STACK) -#ifdef CONFIG_STACK_GROWS_UP +#ifdef CONFIG_STACK_GROWSUP #define VM_STACK_EARLY INIT_VM_FLAG(STACK_EARLY) #else #define VM_STACK_EARLY VM_NONE From 7dc739f38c02e1a62555510a7f70e30f679d74a9 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Tue, 9 Jun 2026 18:00:28 +0300 Subject: [PATCH 353/562] lib/xz: replace min_t with min Use the simpler min() macro since the values are unsigned and compatible. Link: https://lore.kernel.org/20260609150030.634570-1-lasse.collin@tukaani.org Signed-off-by: Thorsten Blum Reviewed-by: Lasse Collin Signed-off-by: Lasse Collin Signed-off-by: Andrew Morton --- lib/xz/xz_dec_bcj.c | 2 +- lib/xz/xz_dec_lzma2.c | 11 +++++------ lib/xz/xz_dec_stream.c | 4 ++-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/xz/xz_dec_bcj.c b/lib/xz/xz_dec_bcj.c index cc49a300a5b2..88922323f96e 100644 --- a/lib/xz/xz_dec_bcj.c +++ b/lib/xz/xz_dec_bcj.c @@ -466,7 +466,7 @@ static void bcj_flush(struct xz_dec_bcj *s, struct xz_buf *b) { size_t copy_size; - copy_size = min_t(size_t, s->temp.filtered, b->out_size - b->out_pos); + copy_size = min(s->temp.filtered, b->out_size - b->out_pos); memcpy(b->out + b->out_pos, s->temp.buf, copy_size); b->out_pos += copy_size; diff --git a/lib/xz/xz_dec_lzma2.c b/lib/xz/xz_dec_lzma2.c index 4b783ac94e71..9d80342b9c6b 100644 --- a/lib/xz/xz_dec_lzma2.c +++ b/lib/xz/xz_dec_lzma2.c @@ -354,7 +354,7 @@ static bool dict_repeat(struct dictionary *dict, uint32_t *len, uint32_t dist) if (dist >= dict->full || dist >= dict->size) return false; - left = min_t(size_t, dict->limit - dict->pos, *len); + left = min(dict->limit - dict->pos, *len); *len -= left; back = dict->pos - dist - 1; @@ -1098,9 +1098,8 @@ enum xz_ret xz_dec_lzma2_run(struct xz_dec_lzma2 *s, struct xz_buf *b) * the output buffer yet, we may run this loop * multiple times without changing s->lzma2.sequence. */ - dict_limit(&s->dict, min_t(size_t, - b->out_size - b->out_pos, - s->lzma2.uncompressed)); + dict_limit(&s->dict, min(b->out_size - b->out_pos, + s->lzma2.uncompressed)); if (!lzma2_lzma(s, b)) return XZ_DATA_ERROR; @@ -1260,8 +1259,8 @@ enum xz_ret xz_dec_microlzma_run(struct xz_dec_microlzma *s_ptr, s->dict.end = b->out_size - b->out_pos; while (true) { - dict_limit(&s->dict, min_t(size_t, b->out_size - b->out_pos, - s->lzma2.uncompressed)); + dict_limit(&s->dict, min(b->out_size - b->out_pos, + s->lzma2.uncompressed)); if (!lzma2_lzma(s, b)) return XZ_DATA_ERROR; diff --git a/lib/xz/xz_dec_stream.c b/lib/xz/xz_dec_stream.c index 59bfd54ffee7..0bed6daefac2 100644 --- a/lib/xz/xz_dec_stream.c +++ b/lib/xz/xz_dec_stream.c @@ -155,8 +155,8 @@ static const uint8_t check_sizes[16] = { */ static bool fill_temp(struct xz_dec *s, struct xz_buf *b) { - size_t copy_size = min_t(size_t, - b->in_size - b->in_pos, s->temp.size - s->temp.pos); + size_t copy_size = min(b->in_size - b->in_pos, + s->temp.size - s->temp.pos); memcpy(s->temp.buf + s->temp.pos, b->in + b->in_pos, copy_size); b->in_pos += copy_size; From 3ac643bc6b0aed8893606bca3d65017963038288 Mon Sep 17 00:00:00 2001 From: Manuel Quintero Fonseca Date: Fri, 22 May 2026 17:01:31 -0700 Subject: [PATCH 354/562] resource: downgrade "resource sanity check" warning to debug level The "resource sanity check" warning currently triggers on certain systems, causing unnecessary noise in the kernel logs for users. This patch downgrades the log level from pr_warn to pr_debug. This change reduces log clutter while keeping the diagnostic information available for debugging purposes if needed. Link: https://lore.kernel.org/20260523000131.7086-1-sakunix@yahoo.com Signed-off-by: Manuel Quintero Fonseca Cc: Andriy Shevchenko Cc: Bjorn Helgaas Cc: Hans de Goede Cc: Mika Westeberg Cc: "Rafael J. Wysocki" Signed-off-by: Andrew Morton --- kernel/resource.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/resource.c b/kernel/resource.c index 3d17e3196a3e..e60539a55541 100644 --- a/kernel/resource.c +++ b/kernel/resource.c @@ -1859,7 +1859,7 @@ int iomem_map_sanity_check(resource_size_t addr, unsigned long size) if (p->flags & IORESOURCE_BUSY) continue; - pr_warn("resource sanity check: requesting [mem %pa-%pa], which spans more than %s %pR\n", + pr_debug("resource sanity check: requesting [mem %pa-%pa], which spans more than %s %pR\n", &addr, &end, p->name, p); err = -1; break; From 7288f452e7bae6845e38f2a995e4f80fba702087 Mon Sep 17 00:00:00 2001 From: Andrew Morton Date: Tue, 26 May 2026 15:14:09 -0700 Subject: [PATCH 355/562] drivers/media/v4l2-core/v4l2-vp9.c: reduce inlining csky allmodconfig, gcc-15.2.0: drivers/media/v4l2-core/v4l2-vp9.c: In function 'v4l2_vp9_adapt_noncoef_probs': drivers/media/v4l2-core/v4l2-vp9.c:1834:1: error: the frame size of 1436 bytes is larger than 1280 bytes [-Werror=frame-larger-than=] The amount of inlining in there is simply nuts. This patch semi-randomly uninlines various things and fixes the above. Ad the .text size reduction is tremendous: ts:/usr/src/25> size drivers/media/v4l2-core/v4l2-vp9.o text data bss dec hex filename 22450 36 0 22486 57d6 drivers/media/v4l2-core/v4l2-vp9.o-before 16144 36 0 16180 3f34 drivers/media/v4l2-core/v4l2-vp9.o-after Reviewed-by: Daniel Almeida Cc: Mauro Carvalho Chehab Signed-off-by: Andrew Morton --- drivers/media/v4l2-core/v4l2-vp9.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/drivers/media/v4l2-core/v4l2-vp9.c b/drivers/media/v4l2-core/v4l2-vp9.c index 859589f1fd35..e965ffbd9b8a 100644 --- a/drivers/media/v4l2-core/v4l2-vp9.c +++ b/drivers/media/v4l2-core/v4l2-vp9.c @@ -1582,25 +1582,25 @@ static inline u8 noncoef_merge_prob(u8 pre_prob, u32 ct0, u32 ct1) * merge_prob(p[9], c[9], [10]) */ -static inline void merge_probs_variant_a(u8 *p, const u32 *c, u16 count_sat, u32 update_factor) +static noinline_for_stack void merge_probs_variant_a(u8 *p, const u32 *c, u16 count_sat, u32 update_factor) { p[1] = merge_prob(p[1], c[0], c[1] + c[2], count_sat, update_factor); p[2] = merge_prob(p[2], c[1], c[2], count_sat, update_factor); } -static inline void merge_probs_variant_b(u8 *p, const u32 *c, u16 count_sat, u32 update_factor) +static noinline_for_stack void merge_probs_variant_b(u8 *p, const u32 *c, u16 count_sat, u32 update_factor) { p[0] = merge_prob(p[0], c[0], c[1], count_sat, update_factor); } -static inline void merge_probs_variant_c(u8 *p, const u32 *c) +static noinline_for_stack void merge_probs_variant_c(u8 *p, const u32 *c) { p[0] = noncoef_merge_prob(p[0], c[2], c[1] + c[0] + c[3]); p[1] = noncoef_merge_prob(p[1], c[0], c[1] + c[3]); p[2] = noncoef_merge_prob(p[2], c[1], c[3]); } -static void merge_probs_variant_d(u8 *p, const u32 *c) +static noinline_for_stack void merge_probs_variant_d(u8 *p, const u32 *c) { u32 sum = 0, s2; @@ -1624,20 +1624,20 @@ static void merge_probs_variant_d(u8 *p, const u32 *c) p[8] = noncoef_merge_prob(p[8], c[6], c[7]); } -static inline void merge_probs_variant_e(u8 *p, const u32 *c) +static noinline_for_stack void merge_probs_variant_e(u8 *p, const u32 *c) { p[0] = noncoef_merge_prob(p[0], c[0], c[1] + c[2] + c[3]); p[1] = noncoef_merge_prob(p[1], c[1], c[2] + c[3]); p[2] = noncoef_merge_prob(p[2], c[2], c[3]); } -static inline void merge_probs_variant_f(u8 *p, const u32 *c) +static noinline_for_stack void merge_probs_variant_f(u8 *p, const u32 *c) { p[0] = noncoef_merge_prob(p[0], c[0], c[1] + c[2]); p[1] = noncoef_merge_prob(p[1], c[1], c[2]); } -static void merge_probs_variant_g(u8 *p, const u32 *c) +static noinline_for_stack void merge_probs_variant_g(u8 *p, const u32 *c) { u32 sum; @@ -1659,12 +1659,12 @@ static void merge_probs_variant_g(u8 *p, const u32 *c) } /* 8.4.3 Coefficient probability adaptation process */ -static inline void adapt_probs_variant_a_coef(u8 *p, const u32 *c, u32 update_factor) +static noinline_for_stack void adapt_probs_variant_a_coef(u8 *p, const u32 *c, u32 update_factor) { merge_probs_variant_a(p, c, 24, update_factor); } -static inline void adapt_probs_variant_b_coef(u8 *p, const u32 *c, u32 update_factor) +static noinline_for_stack void adapt_probs_variant_b_coef(u8 *p, const u32 *c, u32 update_factor) { merge_probs_variant_b(p, c, 24, update_factor); } @@ -1724,33 +1724,33 @@ static inline void adapt_probs_variant_b(u8 *p, const u32 *c) merge_probs_variant_b(p, c, 20, 128); } -static inline void adapt_probs_variant_c(u8 *p, const u32 *c) +static noinline_for_stack void adapt_probs_variant_c(u8 *p, const u32 *c) { merge_probs_variant_c(p, c); } -static inline void adapt_probs_variant_d(u8 *p, const u32 *c) +static noinline_for_stack void adapt_probs_variant_d(u8 *p, const u32 *c) { merge_probs_variant_d(p, c); } -static inline void adapt_probs_variant_e(u8 *p, const u32 *c) +static noinline_for_stack void adapt_probs_variant_e(u8 *p, const u32 *c) { merge_probs_variant_e(p, c); } -static inline void adapt_probs_variant_f(u8 *p, const u32 *c) +static noinline_for_stack void adapt_probs_variant_f(u8 *p, const u32 *c) { merge_probs_variant_f(p, c); } -static inline void adapt_probs_variant_g(u8 *p, const u32 *c) +static noinline_for_stack void adapt_probs_variant_g(u8 *p, const u32 *c) { merge_probs_variant_g(p, c); } /* 8.4.4 Non coefficient probability adaptation process, adapt_prob() */ -static inline u8 adapt_prob(u8 prob, const u32 counts[2]) +static noinline_for_stack u8 adapt_prob(u8 prob, const u32 counts[2]) { return noncoef_merge_prob(prob, counts[0], counts[1]); } From 04998454de927f7f1a690b0c723a54ea238f949c Mon Sep 17 00:00:00 2001 From: Josh Law Date: Fri, 6 Mar 2026 20:30:47 +0000 Subject: [PATCH 356/562] lib/idr: fix ida_find_first_range() missing IDs across chunk boundaries ida_find_first_range() only examines the first XArray entry returned by xa_find(). If that entry does not contain a set bit at or above the requested offset, the function returns -ENOENT without searching subsequent entries, even though later chunks may contain allocated IDs within the requested range. For example, a DRM driver using IDA to manage connector IDs may allocate IDs across multiple 1024-bit IDA chunks. If early IDs are freed and the driver calls ida_find_first_range() with a min that falls into a sparsely populated first chunk, valid IDs in higher chunks are silently missed. This can cause the driver to incorrectly conclude no connectors exist in the queried range, leading to stale connector state or failed hotplug detection. Fix this by looping over xa_find()/xa_find_after() to continue searching subsequent entries when the current one has no matching bit. Link: https://lore.kernel.org/20260306203047.2821852-1-objecting@objecting.org Fixes: 7fe6b987166b ("ida: Add ida_find_first_range()") Signed-off-by: Josh Law Cc: Yi Liu Cc: Jason Gunthorpe Cc: Jason Gunthorpe Cc: Kevin Tian Cc: Liu Yi L Cc: Matthew Wilcox Signed-off-by: Andrew Morton --- lib/idr.c | 55 ++++++++++++++++++++++---------------------------- lib/test_ida.c | 14 +++++++++++++ 2 files changed, 38 insertions(+), 31 deletions(-) diff --git a/lib/idr.c b/lib/idr.c index 69bee5369670..1649f41016e7 100644 --- a/lib/idr.c +++ b/lib/idr.c @@ -495,10 +495,9 @@ int ida_find_first_range(struct ida *ida, unsigned int min, unsigned int max) unsigned long index = min / IDA_BITMAP_BITS; unsigned int offset = min % IDA_BITMAP_BITS; unsigned long *addr, size, bit; - unsigned long tmp = 0; + unsigned long tmp; unsigned long flags; void *entry; - int ret; if ((int)min < 0) return -EINVAL; @@ -508,40 +507,34 @@ int ida_find_first_range(struct ida *ida, unsigned int min, unsigned int max) xa_lock_irqsave(&ida->xa, flags); entry = xa_find(&ida->xa, &index, max / IDA_BITMAP_BITS, XA_PRESENT); - if (!entry) { - ret = -ENOENT; - goto err_unlock; - } - - if (index > min / IDA_BITMAP_BITS) - offset = 0; - if (index * IDA_BITMAP_BITS + offset > max) { - ret = -ENOENT; - goto err_unlock; - } - - if (xa_is_value(entry)) { - tmp = xa_to_value(entry); - addr = &tmp; - size = BITS_PER_XA_VALUE; - } else { - addr = ((struct ida_bitmap *)entry)->bitmap; - size = IDA_BITMAP_BITS; - } - - bit = find_next_bit(addr, size, offset); + while (entry) { + if (index > min / IDA_BITMAP_BITS) + offset = 0; + if (index * IDA_BITMAP_BITS + offset > max) + break; - xa_unlock_irqrestore(&ida->xa, flags); + if (xa_is_value(entry)) { + tmp = xa_to_value(entry); + addr = &tmp; + size = BITS_PER_XA_VALUE; + } else { + addr = ((struct ida_bitmap *)entry)->bitmap; + size = IDA_BITMAP_BITS; + } - if (bit == size || - index * IDA_BITMAP_BITS + bit > max) - return -ENOENT; + bit = find_next_bit(addr, size, offset); + if (bit < size && + index * IDA_BITMAP_BITS + bit <= max) { + xa_unlock_irqrestore(&ida->xa, flags); + return index * IDA_BITMAP_BITS + bit; + } - return index * IDA_BITMAP_BITS + bit; + entry = xa_find_after(&ida->xa, &index, + max / IDA_BITMAP_BITS, XA_PRESENT); + } -err_unlock: xa_unlock_irqrestore(&ida->xa, flags); - return ret; + return -ENOENT; } EXPORT_SYMBOL(ida_find_first_range); diff --git a/lib/test_ida.c b/lib/test_ida.c index 63078f8dc13f..c400b24f88b6 100644 --- a/lib/test_ida.c +++ b/lib/test_ida.c @@ -256,6 +256,20 @@ static void ida_check_find_first(struct ida *ida) ida_free(ida, (1 << 20) - 1); IDA_BUG_ON(ida, !ida_is_empty(ida)); + + /* + * Test cross-chunk search. + * Allocate ID in chunk 0 and ID in chunk 1. + * Search for ID >= 1. min=1 maps to chunk 0. Chunk 0 has no IDs >= 1. + * It should continue to chunk 1 and return 1024. + */ + IDA_BUG_ON(ida, ida_alloc_min(ida, 0, GFP_KERNEL) != 0); + IDA_BUG_ON(ida, ida_alloc_min(ida, 1024, GFP_KERNEL) != 1024); + IDA_BUG_ON(ida, ida_find_first_range(ida, 1, INT_MAX) != 1024); + ida_free(ida, 0); + ida_free(ida, 1024); + + IDA_BUG_ON(ida, !ida_is_empty(ida)); } static DEFINE_IDA(ida); From 2a9fd96c8eba313e06e176dbaa0dee1c7059e432 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Tue, 16 Jun 2026 15:49:31 +0800 Subject: [PATCH 357/562] ocfs2/cluster: keep heartbeat local node stable o2nm_node_local_store() handles local=0 by stopping o2net and setting cl_local_node to O2NM_INVALID_NODE_NUM, but it leaves cl_has_local set. That stale state makes o2nm_this_node() return 255, blocks a later local=1 attempt with -EBUSY, and can feed 255 to heartbeat users that call o2nm_this_node() dynamically. Clearing cl_has_local is required when the local node is reset. But heartbeat threads can still be running at that point. They pin the local node config item at startup, yet o2hb_do_disk_heartbeat() and thread teardown re-read o2nm_this_node() for the local slot and for o2nm_undepend_this_node(). Once local=0 has cleared the live local-node state, those dynamic reads return O2NM_MAX_NODES, which is also the invalid node number 255. Store the local node number in the heartbeat region when the region starts. Use that stable node for heartbeat slot writes/checks, negotiation messages, and the final configfs undepend. Stop the heartbeat loop when the current local node no longer matches the stored node, and clear cl_has_local together with cl_local_node in the local=0 path so nodemanager state matches node removal. Validation reproduced this kernel report: KASAN slab-out-of-bounds in o2hb_do_disk_heartbeat+0x372/0xb30 RIP: 0010:memset+0xf/0x20 Read of size 8 Call trace: dump_stack_lvl+0x66/0xa0 print_report+0xd0/0x630 o2hb_do_disk_heartbeat+0x372/0xb30 (fs/ocfs2/cluster/heartbeat.c:1079) srso_alias_return_thunk+0x5/0xfbef5 __virt_addr_valid+0x188/0x2f0 kasan_report+0xe4/0x120 o2hb_do_disk_heartbeat+0x5/0xb30 (fs/ocfs2/cluster/heartbeat.c:1079) o2hb_thread+0x14e/0x770 kthread_affine_node+0x139/0x180 lockdep_hardirqs_on_prepare+0xda/0x190 trace_hardirqs_on+0x18/0x130 kthread+0x19d/0x1e0 ret_from_fork+0x37a/0x4d0 __switch_to+0x2d5/0x6f0 ret_from_fork_asm+0x1a/0x30 Link: https://lore.kernel.org/20260616074931.3774929-1-zzzccc427@gmail.com Fixes: a7f6a5fb4bde ("[PATCH] OCFS2: The Second Oracle Cluster Filesystem") Assisted-by: Codex:gpt-5.5 Signed-off-by: Cen Zhang Suggested-by: Joseph Qi Reviewed-by: Joseph Qi Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao Signed-off-by: Andrew Morton --- fs/ocfs2/cluster/heartbeat.c | 43 +++++++++++++++++++++++----------- fs/ocfs2/cluster/nodemanager.c | 19 +++++++++++---- fs/ocfs2/cluster/nodemanager.h | 2 ++ 3 files changed, 46 insertions(+), 18 deletions(-) diff --git a/fs/ocfs2/cluster/heartbeat.c b/fs/ocfs2/cluster/heartbeat.c index d12784aaaa4b..6da96a374fcd 100644 --- a/fs/ocfs2/cluster/heartbeat.c +++ b/fs/ocfs2/cluster/heartbeat.c @@ -203,6 +203,7 @@ struct o2hb_region { /* protected by the hr_callback_sem */ struct task_struct *hr_task; + u8 hr_node_num; unsigned int hr_blocks; unsigned long long hr_start_block; @@ -350,12 +351,12 @@ static void o2hb_disarm_timeout(struct o2hb_region *reg) cancel_delayed_work_sync(®->hr_nego_timeout_work); } -static int o2hb_send_nego_msg(int key, int type, u8 target) +static int o2hb_send_nego_msg(int key, int type, u8 target, u8 node_num) { struct o2hb_nego_msg msg; int status, ret; - msg.node_num = o2nm_this_node(); + msg.node_num = node_num; again: ret = o2net_send_message(type, key, &msg, sizeof(msg), target, &status); @@ -373,8 +374,10 @@ static void o2hb_nego_timeout(struct work_struct *work) unsigned long live_node_bitmap[BITS_TO_LONGS(O2NM_MAX_NODES)]; int master_node, i, ret; struct o2hb_region *reg; + u8 node_num; reg = container_of(work, struct o2hb_region, hr_nego_timeout_work.work); + node_num = reg->hr_node_num; /* don't negotiate timeout if last hb failed since it is very * possible io failed. Should let write timeout fence self. */ @@ -385,10 +388,10 @@ static void o2hb_nego_timeout(struct work_struct *work) /* lowest node as master node to make negotiate decision. */ master_node = find_first_bit(live_node_bitmap, O2NM_MAX_NODES); - if (master_node == o2nm_this_node()) { + if (master_node == node_num) { if (!test_bit(master_node, reg->hr_nego_node_bitmap)) { printk(KERN_NOTICE "o2hb: node %d hb write hung for %ds on region %s (%pg).\n", - o2nm_this_node(), O2HB_NEGO_TIMEOUT_MS/1000, + node_num, O2HB_NEGO_TIMEOUT_MS / 1000, config_item_name(®->hr_item), reg_bdev(reg)); set_bit(master_node, reg->hr_nego_node_bitmap); } @@ -417,7 +420,7 @@ static void o2hb_nego_timeout(struct work_struct *work) mlog(ML_HEARTBEAT, "send NEGO_APPROVE msg to node %d\n", i); ret = o2hb_send_nego_msg(reg->hr_key, - O2HB_NEGO_APPROVE_MSG, i); + O2HB_NEGO_APPROVE_MSG, i, node_num); if (ret) mlog(ML_ERROR, "send NEGO_APPROVE msg to node %d fail %d\n", i, ret); @@ -425,10 +428,10 @@ static void o2hb_nego_timeout(struct work_struct *work) } else { /* negotiate timeout with master node. */ printk(KERN_NOTICE "o2hb: node %d hb write hung for %ds on region %s (%pg), negotiate timeout with node %d.\n", - o2nm_this_node(), O2HB_NEGO_TIMEOUT_MS/1000, config_item_name(®->hr_item), + node_num, O2HB_NEGO_TIMEOUT_MS / 1000, config_item_name(®->hr_item), reg_bdev(reg), master_node); ret = o2hb_send_nego_msg(reg->hr_key, O2HB_NEGO_TIMEOUT_MSG, - master_node); + master_node, node_num); if (ret) mlog(ML_ERROR, "send NEGO_TIMEOUT msg to node %d fail %d\n", master_node, ret); @@ -601,7 +604,9 @@ static int o2hb_issue_node_write(struct o2hb_region *reg, o2hb_bio_wait_init(write_wc); - slot = o2nm_this_node(); + slot = reg->hr_node_num; + if (slot >= O2NM_MAX_NODES) + return -EINVAL; bio = o2hb_setup_one_bio(reg, write_wc, &slot, slot+1, REQ_OP_WRITE | REQ_SYNC); @@ -670,8 +675,12 @@ static int o2hb_check_own_slot(struct o2hb_region *reg) struct o2hb_disk_slot *slot; struct o2hb_disk_heartbeat_block *hb_block; char *errstr; + u8 node_num = reg->hr_node_num; + + if (node_num >= O2NM_MAX_NODES) + return 0; - slot = ®->hr_slots[o2nm_this_node()]; + slot = ®->hr_slots[node_num]; /* Don't check on our 1st timestamp */ if (!slot->ds_last_time) return 0; @@ -712,7 +721,10 @@ static inline void o2hb_prepare_block(struct o2hb_region *reg, struct o2hb_disk_slot *slot; struct o2hb_disk_heartbeat_block *hb_block; - node_num = o2nm_this_node(); + node_num = reg->hr_node_num; + if (node_num >= O2NM_MAX_NODES) + return; + slot = ®->hr_slots[node_num]; hb_block = (struct o2hb_disk_heartbeat_block *)slot->ds_raw_block; @@ -1206,7 +1218,7 @@ static int o2hb_thread(void *data) set_user_nice(current, MIN_NICE); /* Pin node */ - ret = o2nm_depend_this_node(); + ret = o2nm_depend_node(reg->hr_node_num); if (ret) { mlog(ML_ERROR, "Node has been deleted, ret = %d\n", ret); reg->hr_node_deleted = 1; @@ -1215,7 +1227,8 @@ static int o2hb_thread(void *data) } while (!kthread_should_stop() && - !reg->hr_unclean_stop && !reg->hr_aborted_start) { + !reg->hr_unclean_stop && !reg->hr_aborted_start && + o2nm_this_node() == reg->hr_node_num) { /* We track the time spent inside * o2hb_do_disk_heartbeat so that we avoid more than * hr_timeout_ms between disk writes. On busy systems @@ -1264,7 +1277,7 @@ static int o2hb_thread(void *data) } /* Unpin node */ - o2nm_undepend_this_node(); + o2nm_undepend_node(reg->hr_node_num); mlog(ML_HEARTBEAT|ML_KTHREAD, "o2hb thread exiting\n"); @@ -1791,7 +1804,8 @@ static ssize_t o2hb_region_dev_store(struct config_item *item, /* We can't heartbeat without having had our node number * configured yet. */ - if (o2nm_this_node() == O2NM_MAX_NODES) + reg->hr_node_num = o2nm_this_node(); + if (reg->hr_node_num == O2NM_MAX_NODES) return -EINVAL; ret = kstrtol(p, 0, &fd); @@ -2036,6 +2050,7 @@ static struct config_item *o2hb_heartbeat_group_make_item(struct config_group *g ret = -ENAMETOOLONG; goto free; } + reg->hr_node_num = O2NM_MAX_NODES; spin_lock(&o2hb_live_lock); reg->hr_region_num = 0; diff --git a/fs/ocfs2/cluster/nodemanager.c b/fs/ocfs2/cluster/nodemanager.c index 402563154550..e1f8f577ce5d 100644 --- a/fs/ocfs2/cluster/nodemanager.c +++ b/fs/ocfs2/cluster/nodemanager.c @@ -367,6 +367,7 @@ static ssize_t o2nm_node_local_store(struct config_item *item, const char *page, if (!tmp && cluster->cl_has_local && cluster->cl_local_node == node->nd_num) { o2net_stop_listening(node); + cluster->cl_has_local = 0; cluster->cl_local_node = O2NM_INVALID_NODE_NUM; } @@ -782,12 +783,12 @@ void o2nm_undepend_item(struct config_item *item) configfs_undepend_item(item); } -int o2nm_depend_this_node(void) +int o2nm_depend_node(u8 node_num) { int ret = 0; struct o2nm_node *local_node; - local_node = o2nm_get_node_by_num(o2nm_this_node()); + local_node = o2nm_get_node_by_num(node_num); if (!local_node) { ret = -EINVAL; goto out; @@ -800,17 +801,27 @@ int o2nm_depend_this_node(void) return ret; } -void o2nm_undepend_this_node(void) +void o2nm_undepend_node(u8 node_num) { struct o2nm_node *local_node; - local_node = o2nm_get_node_by_num(o2nm_this_node()); + local_node = o2nm_get_node_by_num(node_num); BUG_ON(!local_node); o2nm_undepend_item(&local_node->nd_item); o2nm_node_put(local_node); } +int o2nm_depend_this_node(void) +{ + return o2nm_depend_node(o2nm_this_node()); +} + +void o2nm_undepend_this_node(void) +{ + o2nm_undepend_node(o2nm_this_node()); +} + static void __exit exit_o2nm(void) { diff --git a/fs/ocfs2/cluster/nodemanager.h b/fs/ocfs2/cluster/nodemanager.h index 3490e77a952d..39006005427b 100644 --- a/fs/ocfs2/cluster/nodemanager.h +++ b/fs/ocfs2/cluster/nodemanager.h @@ -65,6 +65,8 @@ void o2nm_node_put(struct o2nm_node *node); int o2nm_depend_item(struct config_item *item); void o2nm_undepend_item(struct config_item *item); +int o2nm_depend_node(u8 node_num); +void o2nm_undepend_node(u8 node_num); int o2nm_depend_this_node(void); void o2nm_undepend_this_node(void); From a47940aad506e7beadcad62731c98d4d35569a14 Mon Sep 17 00:00:00 2001 From: Deepanshu Kartikey Date: Sun, 21 Jun 2026 04:42:23 +0530 Subject: [PATCH 358/562] ocfs2: use inode_lock_nested() for orphan dir locking PREEMPT_RT's rtmutex PI chain walker warns about a lock dependency cycle when inode_lock(orphan_dir_inode) is called while holding inode_lock(file_inode): ocfs2_file_write_iter() inode_lock(file_inode) [class 0] ocfs2_dio_end_io_write() ocfs2_del_inode_from_orphan() inode_lock(orphan_dir_inode) [class 0] <- warning! However this is a false positive. write_iter() is never called on a directory, and orphan_dir is always a directory, so these two locks can never actually conflict in practice. Fix by using inode_lock_nested(orphan_dir_inode, I_MUTEX_NONDIR2) in all three places where orphan_dir_inode is locked in namei.c, placing it in a separate lock class so the rtmutex PI chain walker understands these locks have distinct roles and does not warn about their ordering. Link: https://lore.kernel.org/20260620231223.46588-1-kartikey406@gmail.com Signed-off-by: Deepanshu Kartikey Suggested-by: Matthew Wilcox Reported-by: syzbot+ce129763ce7d7e914739@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=ce129763ce7d7e914739 Reviewed-by: Joseph Qi Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao Signed-off-by: Andrew Morton --- fs/ocfs2/namei.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c index 1277666c77cd..4cfd7b3d3e1a 100644 --- a/fs/ocfs2/namei.c +++ b/fs/ocfs2/namei.c @@ -2126,7 +2126,7 @@ static int ocfs2_lookup_lock_orphan_dir(struct ocfs2_super *osb, return ret; } - inode_lock(orphan_dir_inode); + inode_lock_nested(orphan_dir_inode, I_MUTEX_NONDIR2); ret = ocfs2_inode_lock(orphan_dir_inode, &orphan_dir_bh, 1); if (ret < 0) { @@ -2725,7 +2725,7 @@ int ocfs2_del_inode_from_orphan(struct ocfs2_super *osb, goto bail; } - inode_lock(orphan_dir_inode); + inode_lock_nested(orphan_dir_inode, I_MUTEX_NONDIR2); status = ocfs2_inode_lock(orphan_dir_inode, &orphan_dir_bh, 1); if (status < 0) { inode_unlock(orphan_dir_inode); @@ -2838,7 +2838,7 @@ int ocfs2_mv_orphaned_inode_to_new(struct inode *dir, goto leave; } - inode_lock(orphan_dir_inode); + inode_lock_nested(orphan_dir_inode, I_MUTEX_NONDIR2); status = ocfs2_inode_lock(orphan_dir_inode, &orphan_dir_bh, 1); if (status < 0) { From cb35f1ae68dd5341ab07c25628294b326f14dbc9 Mon Sep 17 00:00:00 2001 From: Bradley Morgan Date: Sun, 21 Jun 2026 12:11:33 +0000 Subject: [PATCH 359/562] lib/string: fix memchr_inv() for large ranges memchr_inv() takes a size_t length but counts 8 byte words in an unsigned int. At 32GiB that count wraps, so the scan can quietly miss most of the range. Use size_t for the word count. Link: https://lore.kernel.org/20260621121133.16460-1-include@grrlz.net Fixes: 798248206b59 ("lib/string.c: introduce memchr_inv()") Signed-off-by: Bradley Morgan Cc: Akinbou Mita Cc: Andy Shevchenko Cc: Christoph Lameer Cc: Joern Engel Cc: Kees Cook Cc: Pekka Enberg Signed-off-by: Andrew Morton --- lib/string.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/string.c b/lib/string.c index a4e8ad23577d..a3778d5aab4a 100644 --- a/lib/string.c +++ b/lib/string.c @@ -821,7 +821,8 @@ void *memchr_inv(const void *start, int c, size_t bytes) { u8 value = c; u64 value64; - unsigned int words, prefix; + size_t words; + unsigned int prefix; if (bytes <= 16) return check_bytes8(start, value, bytes); From 458131aa44a1e6f3c7dfc7004c2a208d222c3d39 Mon Sep 17 00:00:00 2001 From: Jim Cromie Date: Thu, 18 Jun 2026 13:07:15 -0600 Subject: [PATCH 360/562] kernel/params: fix a pr_debug(" %p ") in parse_one() Inside parse_one(), the core parameter-parsing engine prints the address of the parameter-set callback function using %p: pr_debug("handling %s with %p\n", param, params[i].ops->set); Since the string value of the parameter being parsed (val) is already available, print the parameter name and its value instead, and avoid tainting the kernel by exposing a kernel-ptr. Link: https://lore.kernel.org/20260618190715.3563047-1-jim.cromie@gmail.com Signed-off-by: Jim Cromie Cc: Greg Kroah-Hartman Signed-off-by: Andrew Morton --- kernel/params.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/params.c b/kernel/params.c index a668863a4bb6..5c8a05921e28 100644 --- a/kernel/params.c +++ b/kernel/params.c @@ -136,8 +136,8 @@ static int parse_one(char *param, if (!val && !(params[i].ops->flags & KERNEL_PARAM_OPS_FL_NOARG)) return -EINVAL; - pr_debug("handling %s with %p\n", param, - params[i].ops->set); + pr_debug("handling %s with value '%s'\n", param, + val ? val : "no-arg"); kernel_param_lock(params[i].mod); if (param_check_unsafe(¶ms[i])) err = params[i].ops->set(val, ¶ms[i]); From 1dde4d203d1a5bc4f967313a4487ae461e82331a Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 16 Jun 2026 01:45:57 +0800 Subject: [PATCH 361/562] watchdog/softlockup: fix softlockup typos Fix misspellings of "softlockup" in the watchdog enabled bit definitions and related comments. Also fix a nearby "successful" typo. No functional change. Link: https://lore.kernel.org/20260615174557.1836562-1-edcr1790@gmail.com Signed-off-by: Matthew Chen Reviewed-by: Douglas Anderson Reviewed-by: Petr Mladek Signed-off-by: Andrew Morton --- include/linux/nmi.h | 4 ++-- kernel/watchdog.c | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/include/linux/nmi.h b/include/linux/nmi.h index bc1162895f35..89c5465e2524 100644 --- a/include/linux/nmi.h +++ b/include/linux/nmi.h @@ -77,9 +77,9 @@ static inline void reset_hung_task_detector(void) { } * detectors are 'suspended' while 'watchdog_thresh' is equal zero. */ #define WATCHDOG_HARDLOCKUP_ENABLED_BIT 0 -#define WATCHDOG_SOFTOCKUP_ENABLED_BIT 1 +#define WATCHDOG_SOFTLOCKUP_ENABLED_BIT 1 #define WATCHDOG_HARDLOCKUP_ENABLED (1 << WATCHDOG_HARDLOCKUP_ENABLED_BIT) -#define WATCHDOG_SOFTOCKUP_ENABLED (1 << WATCHDOG_SOFTOCKUP_ENABLED_BIT) +#define WATCHDOG_SOFTLOCKUP_ENABLED (1 << WATCHDOG_SOFTLOCKUP_ENABLED_BIT) #if defined(CONFIG_HARDLOCKUP_DETECTOR) extern void hardlockup_detector_disable(void); diff --git a/kernel/watchdog.c b/kernel/watchdog.c index 87dd5e0f6968..e5134ad7b663 100644 --- a/kernel/watchdog.c +++ b/kernel/watchdog.c @@ -359,14 +359,14 @@ static void lockup_detector_update_enable(void) if (watchdog_hardlockup_available && watchdog_hardlockup_user_enabled) watchdog_enabled |= WATCHDOG_HARDLOCKUP_ENABLED; if (watchdog_softlockup_user_enabled) - watchdog_enabled |= WATCHDOG_SOFTOCKUP_ENABLED; + watchdog_enabled |= WATCHDOG_SOFTLOCKUP_ENABLED; } #ifdef CONFIG_SOFTLOCKUP_DETECTOR /* - * Delay the soflockup report when running a known slow code. - * It does _not_ affect the timestamp of the last successdul reschedule. + * Delay the softlockup report when running a known slow code. + * It does _not_ affect the timestamp of the last successful reschedule. */ #define SOFTLOCKUP_DELAY_REPORT ULONG_MAX @@ -742,7 +742,7 @@ static int is_softlockup(unsigned long touch_ts, unsigned long period_ts, unsigned long now) { - if ((watchdog_enabled & WATCHDOG_SOFTOCKUP_ENABLED) && watchdog_thresh) { + if ((watchdog_enabled & WATCHDOG_SOFTLOCKUP_ENABLED) && watchdog_thresh) { /* * If period_ts has not been updated during a sample_period, then * in the subsequent few sample_periods, period_ts might also not @@ -1098,11 +1098,11 @@ static void proc_watchdog_update(bool thresh_changed) * caller | table->data points to | 'which' * -------------------|----------------------------------|------------------------------- * proc_watchdog | watchdog_user_enabled | WATCHDOG_HARDLOCKUP_ENABLED | - * | | WATCHDOG_SOFTOCKUP_ENABLED + * | | WATCHDOG_SOFTLOCKUP_ENABLED * -------------------|----------------------------------|------------------------------- * proc_nmi_watchdog | watchdog_hardlockup_user_enabled | WATCHDOG_HARDLOCKUP_ENABLED * -------------------|----------------------------------|------------------------------- - * proc_soft_watchdog | watchdog_softlockup_user_enabled | WATCHDOG_SOFTOCKUP_ENABLED + * proc_soft_watchdog | watchdog_softlockup_user_enabled | WATCHDOG_SOFTLOCKUP_ENABLED */ static int proc_watchdog_common(int which, const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) @@ -1136,7 +1136,7 @@ static int proc_watchdog(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) { return proc_watchdog_common(WATCHDOG_HARDLOCKUP_ENABLED | - WATCHDOG_SOFTOCKUP_ENABLED, + WATCHDOG_SOFTLOCKUP_ENABLED, table, write, buffer, lenp, ppos); } @@ -1159,7 +1159,7 @@ static int proc_nmi_watchdog(const struct ctl_table *table, int write, static int proc_soft_watchdog(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) { - return proc_watchdog_common(WATCHDOG_SOFTOCKUP_ENABLED, + return proc_watchdog_common(WATCHDOG_SOFTLOCKUP_ENABLED, table, write, buffer, lenp, ppos); } #endif From 6bba12049153539c67396e1380f0a12d6f2ff37d Mon Sep 17 00:00:00 2001 From: Jianlin Shi Date: Mon, 15 Jun 2026 10:27:41 +0800 Subject: [PATCH 362/562] ipc: only destroy orphaned shm segments on sysctl write proc_ipc_dointvec_minmax_orphans() currently calls shm_destroy_orphaned() whenever shm_rmid_forced is set, including on sysctl reads. Reading /proc/sys/kernel/shm_rmid_forced should not take shm_ids rwsem for write and walk all segments. Only run the cleanup when the sysctl is written and the forced RMID policy is enabled. When shm_rmid_forced=1, monitoring tools that read /proc/sys/kernel/shm_rmid_forced trigger the cleanup on every read. Link: https://lore.kernel.org/all/?q=only+destroy+orphaned+shm+segments+on+sysctl+write Link: https://lore.kernel.org/tencent_738A8BC6E9EA205F555E4B0DAA154D4F8E0A@qq.com Signed-off-by: Jianlin Shi Cc: "Eric W. Biederman" Cc: Davidlohr Bueso Signed-off-by: Andrew Morton --- ipc/ipc_sysctl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipc/ipc_sysctl.c b/ipc/ipc_sysctl.c index 9b087ebeb643..d038d944257f 100644 --- a/ipc/ipc_sysctl.c +++ b/ipc/ipc_sysctl.c @@ -28,7 +28,7 @@ static int proc_ipc_dointvec_minmax_orphans(const struct ctl_table *table, int w if (err < 0) return err; - if (ns->shm_rmid_forced) + if (write && ns->shm_rmid_forced) shm_destroy_orphaned(ns); return err; } From e65c8d44c5f80796671d8b1422a1cfad4c6a8470 Mon Sep 17 00:00:00 2001 From: Lasse Collin Date: Sun, 14 Jun 2026 19:05:17 +0300 Subject: [PATCH 363/562] lib/xz: use size_t instead of uint32_t in a few places Reduce the number of uint32_t <-> size_t conversions a little. Eliminating such conversions entirely would require changing almost all uint32_t to size_t, which would look confusing and increase the sizes of the structs even more. Going the other way, converting everything to uint32_t, isn't possible because the input and output buffers use size_t in struct xz_buf. Now both arguments to min() have the same type. This is required to for compatibility with PowerPC boot code[1] whose min() is strict like include/linux/minmax.h was before the commit d03eba99f5bf ("minmax: allow min()/max()/clamp() if the arguments have the same signedness."). Swap the order of the "state" and "len" in struct lzma_dec to avoid padding in the middle of the struct when size_t is 64 bits. The reordering doesn't change the size of the struct; the padding just appears at the end instead. dict_flush() used to truncate size_t to uint32_t when returning. This wasn't a bug; the value is always small enough. Link: https://lore.kernel.org/20260614160521.924710-1-lasse.collin@tukaani.org Signed-off-by: Lasse Collin Reported-by: Nathan Chancellor Closes: https://lore.kernel.org/lkml/20260610232323.GA1071374@ax162/ [1] Reviewed-by: Thorsten Blum Cc: David Laight Signed-off-by: Andrew Morton --- lib/xz/xz_dec_lzma2.c | 40 ++++++++++++++++++++-------------------- lib/xz/xz_lzma2.h | 2 +- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/lib/xz/xz_dec_lzma2.c b/lib/xz/xz_dec_lzma2.c index 9d80342b9c6b..68ae9c33b6a8 100644 --- a/lib/xz/xz_dec_lzma2.c +++ b/lib/xz/xz_dec_lzma2.c @@ -135,14 +135,16 @@ struct lzma_dec { uint32_t rep2; uint32_t rep3; - /* Types of the most recently seen LZMA symbols */ - enum lzma_state state; - /* * Length of a match. This is updated so that dict_repeat can - * be called again to finish repeating the whole match. + * be called again to finish repeating the whole match. This is + * size_t because a pointer to this is passed to dict_repeat, + * and there it's nicer to have size_t instead of uint32_t. */ - uint32_t len; + size_t len; + + /* Types of the most recently seen LZMA symbols */ + enum lzma_state state; /* * LZMA properties or related bit masks (number of literal @@ -228,13 +230,13 @@ struct lzma2_dec { enum lzma2_seq next_sequence; /* Uncompressed size of LZMA chunk (2 MiB at maximum) */ - uint32_t uncompressed; + size_t uncompressed; /* * Compressed size of LZMA chunk or compressed/uncompressed * size of uncompressed chunk (64 KiB at maximum) */ - uint32_t compressed; + size_t compressed; /* * True if dictionary reset is needed. This is false before @@ -273,7 +275,7 @@ struct xz_dec_lzma2 { * decoder calls. See lzma2_lzma() for details. */ struct { - uint32_t size; + size_t size; uint8_t buf[3 * LZMA_IN_REQUIRED]; } temp; }; @@ -320,7 +322,7 @@ static inline bool dict_has_space(const struct dictionary *dict) * still empty. This special case is needed for single-call decoding to * avoid writing a '\0' to the end of the destination buffer. */ -static inline uint32_t dict_get(const struct dictionary *dict, uint32_t dist) +static inline uint32_t dict_get(const struct dictionary *dict, size_t dist) { size_t offset = dict->pos - dist - 1; @@ -346,10 +348,10 @@ static inline void dict_put(struct dictionary *dict, uint8_t byte) * invalid, false is returned. On success, true is returned and *len is * updated to indicate how many bytes were left to be repeated. */ -static bool dict_repeat(struct dictionary *dict, uint32_t *len, uint32_t dist) +static bool dict_repeat(struct dictionary *dict, size_t *len, size_t dist) { size_t back; - uint32_t left; + size_t left; if (dist >= dict->full || dist >= dict->size) return false; @@ -375,7 +377,7 @@ static bool dict_repeat(struct dictionary *dict, uint32_t *len, uint32_t dist) /* Copy uncompressed data as is from input to dictionary and output buffers. */ static void dict_uncompressed(struct dictionary *dict, struct xz_buf *b, - uint32_t *left) + size_t *left) { size_t copy_size; @@ -433,7 +435,7 @@ static void dict_uncompressed(struct dictionary *dict, struct xz_buf *b, * enough space in b->out. This is guaranteed because caller uses dict_limit() * before decoding data into the dictionary. */ -static uint32_t dict_flush(struct dictionary *dict, struct xz_buf *b) +static size_t dict_flush(struct dictionary *dict, struct xz_buf *b) { size_t copy_size = dict->pos - dict->start; @@ -878,7 +880,7 @@ static bool lzma_props(struct xz_dec_lzma2 *s, uint8_t props) static bool lzma2_lzma(struct xz_dec_lzma2 *s, struct xz_buf *b) { size_t in_avail; - uint32_t tmp; + size_t tmp; in_avail = b->in_size - b->in_pos; if (s->temp.size > 0 || s->lzma2.compressed == 0) { @@ -1046,25 +1048,23 @@ enum xz_ret xz_dec_lzma2_run(struct xz_dec_lzma2 *s, struct xz_buf *b) case SEQ_UNCOMPRESSED_1: s->lzma2.uncompressed - += (uint32_t)b->in[b->in_pos++] << 8; + += (size_t)b->in[b->in_pos++] << 8; s->lzma2.sequence = SEQ_UNCOMPRESSED_2; break; case SEQ_UNCOMPRESSED_2: s->lzma2.uncompressed - += (uint32_t)b->in[b->in_pos++] + 1; + += (size_t)b->in[b->in_pos++] + 1; s->lzma2.sequence = SEQ_COMPRESSED_0; break; case SEQ_COMPRESSED_0: - s->lzma2.compressed - = (uint32_t)b->in[b->in_pos++] << 8; + s->lzma2.compressed = (size_t)b->in[b->in_pos++] << 8; s->lzma2.sequence = SEQ_COMPRESSED_1; break; case SEQ_COMPRESSED_1: - s->lzma2.compressed - += (uint32_t)b->in[b->in_pos++] + 1; + s->lzma2.compressed += (size_t)b->in[b->in_pos++] + 1; s->lzma2.sequence = s->lzma2.next_sequence; break; diff --git a/lib/xz/xz_lzma2.h b/lib/xz/xz_lzma2.h index d2632b7dfb9c..a612ce4fd450 100644 --- a/lib/xz/xz_lzma2.h +++ b/lib/xz/xz_lzma2.h @@ -143,7 +143,7 @@ static inline bool lzma_state_is_literal(enum lzma_state state) * Get the index of the appropriate probability array for decoding * the distance slot. */ -static inline uint32_t lzma_get_dist_state(uint32_t len) +static inline size_t lzma_get_dist_state(size_t len) { return len < DIST_STATES + MATCH_LEN_MIN ? len - MATCH_LEN_MIN : DIST_STATES - 1; From 1850510c6c3d4e36c488f2ba5c4d7fabdd5909b0 Mon Sep 17 00:00:00 2001 From: Lasse Collin Date: Sun, 14 Jun 2026 19:05:18 +0300 Subject: [PATCH 364/562] lib/xz: fix comments Link: https://lore.kernel.org/20260614160521.924710-2-lasse.collin@tukaani.org Signed-off-by: Lasse Collin Cc: David Laight Cc: Nathan Chancellor Cc: Thorsten Blum Signed-off-by: Andrew Morton --- lib/xz/xz_dec_lzma2.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/xz/xz_dec_lzma2.c b/lib/xz/xz_dec_lzma2.c index 68ae9c33b6a8..46c2df6ad6f5 100644 --- a/lib/xz/xz_dec_lzma2.c +++ b/lib/xz/xz_dec_lzma2.c @@ -271,7 +271,7 @@ struct xz_dec_lzma2 { struct lzma_dec lzma; /* - * Temporary buffer which holds small number of input bytes between + * Temporary buffer which holds a small number of input bytes between * decoder calls. See lzma2_lzma() for details. */ struct { @@ -757,8 +757,8 @@ static bool lzma_main(struct xz_dec_lzma2 *s) uint32_t pos_state; /* - * If the dictionary was reached during the previous call, try to - * finish the possibly pending repeat in the dictionary. + * If the dictionary write limit was reached during the previous call, + * try to finish the possibly pending repeat in the dictionary. */ if (dict_has_space(&s->dict) && s->lzma.len > 0) dict_repeat(&s->dict, &s->lzma.len, s->lzma.rep0); @@ -978,7 +978,7 @@ enum xz_ret xz_dec_lzma2_run(struct xz_dec_lzma2 *s, struct xz_buf *b) * an uncompressed chunk * 0x02 Uncompressed chunk (no dictionary reset) * - * Highest three bits (s->control & 0xE0): + * Highest three bits (tmp & 0xE0): * 0xE0 Dictionary reset, new properties and state * reset, followed by LZMA compressed chunk * 0xC0 New properties and state reset, followed @@ -990,7 +990,7 @@ enum xz_ret xz_dec_lzma2_run(struct xz_dec_lzma2 *s, struct xz_buf *b) * 0x80 LZMA chunk (no dictionary or state reset) * * For LZMA compressed chunks, the lowest five bits - * (s->control & 1F) are the highest bits of the + * (tmp & 1F) are the highest bits of the * uncompressed size (bits 16-20). * * A new LZMA2 stream must begin with a dictionary @@ -1091,7 +1091,7 @@ enum xz_ret xz_dec_lzma2_run(struct xz_dec_lzma2 *s, struct xz_buf *b) case SEQ_LZMA_RUN: /* * Set dictionary limit to indicate how much we want - * to be encoded at maximum. Decode new data into the + * to be decoded at maximum. Decode new data into the * dictionary. Flush the new data from dictionary to * b->out. Check if we finished decoding this chunk. * In case the dictionary got full but we didn't fill From 9f8f36db924f7c44b367560f5da86afe3af875ee Mon Sep 17 00:00:00 2001 From: Bradley Morgan Date: Mon, 22 Jun 2026 20:25:08 +0000 Subject: [PATCH 365/562] signal: avoid shared siginfo namespace rewrites send_signal_locked() rewrites sender ids for the target namespace. Group sends reuse the same siginfo, so one recipient can affect the next. Copy the siginfo before changing it. Link: https://lore.kernel.org/86a8857d58d43ee26a8b365b837fd24830343494.1782159692.git.include@grrlz.net Fixes: 7a0cf094944e ("signal: Correct namespace fixups of si_pid and si_uid") Signed-off-by: Bradley Morgan Acked-by: Oleg Nesterov Cc: "Eric W. Biederman" Cc: Adrian Huang Cc: Aleksandr Nogikh Cc: Christian Brauner Cc: Marco Elver Cc: "Masami Hiramatsu (Google)" Cc: Mathieu Desnoyers Cc: Peter Zijlstra Cc: Steven Rostedt Cc: Signed-off-by: Andrew Morton --- kernel/signal.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/kernel/signal.c b/kernel/signal.c index 9c2b32c4d755..9de9a3343f66 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -1181,6 +1181,7 @@ static inline bool has_si_pid_and_uid(struct kernel_siginfo *info) int send_signal_locked(int sig, struct kernel_siginfo *info, struct task_struct *t, enum pid_type type) { + struct kernel_siginfo rewritten; /* Should SIGKILL or SIGSTOP be received by a pid namespace init? */ bool force = false; @@ -1194,6 +1195,9 @@ int send_signal_locked(int sig, struct kernel_siginfo *info, /* SIGKILL and SIGSTOP is special or has ids */ struct user_namespace *t_user_ns; + rewritten = *info; + info = &rewritten; + rcu_read_lock(); t_user_ns = task_cred_xxx(t, user_ns); if (current_user_ns() != t_user_ns) { From f811226eb1e130ea488441e2ca7deadf97473fee Mon Sep 17 00:00:00 2001 From: Oleg Nesterov Date: Fri, 26 Jun 2026 17:33:08 +0200 Subject: [PATCH 366/562] signal: change sys_kill() to use SEND_SIG_NOINFO prepare_kill_siginfo(PIDTYPE_TGID) fills si_code = SI_USER and sets si_pid/si_uid in the sender's namespace. Then send_signal_locked() translates si_pid/si_uid to the target's namespace. SEND_SIG_NOINFO exists precisely for the case when si_code == SI_USER and si_pid/si_uid are the sender's ids; this is exactly what sys_kill() does via prepare_kill_siginfo(PIDTYPE_TGID). Change sys_kill() to use it directly. SEND_SIG_NOINFO produces the same result: si_code = SI_USER, and __send_signal_locked() computes si_pid/si_uid directly in the target's namespace. The force computation is also the same: both check if the sender is visible in the target's pid namespace. This is just a cleanup and microoptimization (especially with [1]), this skips the has_si_pid_and_uid() block in send_signal_locked() and offloads the namespace translation logic to __send_signal_locked(SEND_SIG_NOINFO) which uses the simpler computations. NOTE: As a "side effect" this also fixes the kill(pid < 0, sig) case where send_signal_locked() rewrites si_pid/si_uid in the shared siginfo, corrupting it for subsequent recipients. But for other group senders like __kill_pgrp_info() we still need the fix from Bradley Morgan [1] who found this problem. TODO: kill prepare_kill_siginfo() and change other users to use SEND_SIG_NOINFO too. This needs trivial changes in __send_signal_locked() and TP_STORE_SIGINFO(). Link: https://lore.kernel.org/aj6btAZqYuv59a8w@redhat.com Link: https://lore.kernel.org/all/20260622164029.11474-1-include@grrlz.net/ [1] Signed-off-by: Oleg Nesterov Reviewed-by: Bradley Morgan Cc: Eric Biederman Signed-off-by: Andrew Morton --- kernel/signal.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/kernel/signal.c b/kernel/signal.c index 9de9a3343f66..4ab7d91901d0 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -3954,11 +3954,7 @@ static void prepare_kill_siginfo(int sig, struct kernel_siginfo *info, */ SYSCALL_DEFINE2(kill, pid_t, pid, int, sig) { - struct kernel_siginfo info; - - prepare_kill_siginfo(sig, &info, PIDTYPE_TGID); - - return kill_something_info(sig, &info, pid); + return kill_something_info(sig, SEND_SIG_NOINFO, pid); } /* From 219ad178e24d045acdc9df6219006edba655a86b Mon Sep 17 00:00:00 2001 From: Oleg Nesterov Date: Sat, 4 Jul 2026 16:34:42 +0200 Subject: [PATCH 367/562] signal: avoid unconditional siginfo copy in send_signal_locked() send_signal_locked() unconditionally copies siginfo before the namespace translation to avoid corrupting a shared siginfo. Not that I think this can actually hurt performance-wise, just it doesn't look clean to me; the copy is only needed in the unlikely case when the translation will actually change something. Defer it to the two cases where si_pid/si_uid are rewritten, and while at it add #ifdef's just for completeness. Link: https://lore.kernel.org/akkaAgNfUby5_3nM@redhat.com Signed-off-by: Oleg Nesterov Reviewed-by: Bradley Morgan Cc: Christian Brauner Cc: Eric Biederman Signed-off-by: Andrew Morton --- kernel/signal.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/kernel/signal.c b/kernel/signal.c index 4ab7d91901d0..da41e89d2bce 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -1181,7 +1181,7 @@ static inline bool has_si_pid_and_uid(struct kernel_siginfo *info) int send_signal_locked(int sig, struct kernel_siginfo *info, struct task_struct *t, enum pid_type type) { - struct kernel_siginfo rewritten; + struct kernel_siginfo __maybe_unused rewritten; /* Should SIGKILL or SIGSTOP be received by a pid namespace init? */ bool force = false; @@ -1193,27 +1193,34 @@ int send_signal_locked(int sig, struct kernel_siginfo *info, force = true; } else if (has_si_pid_and_uid(info)) { /* SIGKILL and SIGSTOP is special or has ids */ +#ifdef CONFIG_USER_NS struct user_namespace *t_user_ns; - - rewritten = *info; - info = &rewritten; + kuid_t uid; rcu_read_lock(); t_user_ns = task_cred_xxx(t, user_ns); if (current_user_ns() != t_user_ns) { - kuid_t uid = make_kuid(current_user_ns(), info->si_uid); - info->si_uid = from_kuid_munged(t_user_ns, uid); + rewritten = *info; + info = &rewritten; + uid = make_kuid(current_user_ns(), info->si_uid); + rewritten.si_uid = from_kuid_munged(t_user_ns, uid); } rcu_read_unlock(); - +#endif /* A kernel generated signal? */ force = (info->si_code == SI_KERNEL); +#ifdef CONFIG_PID_NS /* From an ancestor pid namespace? */ if (!task_pid_nr_ns(current, task_active_pid_ns(t))) { - info->si_pid = 0; + if (info != &rewritten) { + rewritten = *info; + info = &rewritten; + } + rewritten.si_pid = 0; force = true; } +#endif } return __send_signal_locked(sig, info, t, type, force); } From b3714f2ec1496d2a76254c805be05be71e7470a0 Mon Sep 17 00:00:00 2001 From: Adi Nata Date: Sat, 6 Jun 2026 11:03:09 +0800 Subject: [PATCH 368/562] lib/math: add KUnit test suite for polynomial_calc() Add a KUnit test suite for the polynomial_calc() function, which had no in-kernel test coverage. The tests verify correct evaluation of constant, linear, quadratic, and cubic polynomials, including negative coefficients, negative input data, zero-coefficient terms. The Kconfig entry uses 'select POLYNOMIAL' rather than 'depends on POLYNOMIAL' because POLYNOMIAL is a promptless tristate that cannot be manually enabled on UML without an explicit selector. Link: https://lore.kernel.org/20260606030319.316752-1-adinata.softwareengineer@gmail.com Signed-off-by: Adi Nata Cc: Maxim Kaurkin Cc: Serge Semin Cc: Guenter Roeck Cc: Brendan Higgins Cc: David Gow Cc: Rae Moar Signed-off-by: Andrew Morton --- lib/Kconfig.debug | 17 ++ lib/math/tests/Makefile | 1 + lib/math/tests/polynomial_kunit.c | 270 ++++++++++++++++++++++++++++++ 3 files changed, 288 insertions(+) create mode 100644 lib/math/tests/polynomial_kunit.c diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 1244dcac2294..311b99790416 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -3504,6 +3504,23 @@ config GCD_KUNIT_TEST If unsure, say N +config POLYNOMIAL_KUNIT_TEST + tristate "Polynomial calculation (polynomial_calc) test" if !KUNIT_ALL_TESTS + depends on KUNIT + select POLYNOMIAL + default KUNIT_ALL_TESTS + help + This option enables the KUnit test suite for the polynomial_calc() + function, which evaluates integer polynomials using factor + redistribution to avoid overflow. + + The test suite verifies correctness for constant, linear, and + quadratic polynomials, negative coefficients, per-step dividers, + divider_leftover, total_divider scaling, and a real sensor + N-to-temperature conversion polynomial. + + If unsure, say N + config PRIME_NUMBERS_KUNIT_TEST tristate "Prime number generator test" if !KUNIT_ALL_TESTS depends on KUNIT diff --git a/lib/math/tests/Makefile b/lib/math/tests/Makefile index 13dc96e48408..85e1ad59f29d 100644 --- a/lib/math/tests/Makefile +++ b/lib/math/tests/Makefile @@ -4,5 +4,6 @@ obj-$(CONFIG_GCD_KUNIT_TEST) += gcd_kunit.o obj-$(CONFIG_INT_LOG_KUNIT_TEST) += int_log_kunit.o obj-$(CONFIG_INT_POW_KUNIT_TEST) += int_pow_kunit.o obj-$(CONFIG_INT_SQRT_KUNIT_TEST) += int_sqrt_kunit.o +obj-$(CONFIG_POLYNOMIAL_KUNIT_TEST) += polynomial_kunit.o obj-$(CONFIG_PRIME_NUMBERS_KUNIT_TEST) += prime_numbers_kunit.o obj-$(CONFIG_RATIONAL_KUNIT_TEST) += rational_kunit.o diff --git a/lib/math/tests/polynomial_kunit.c b/lib/math/tests/polynomial_kunit.c new file mode 100644 index 000000000000..ef443b57fc12 --- /dev/null +++ b/lib/math/tests/polynomial_kunit.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include + +struct polynomial_test_param { + const struct polynomial *poly; + long data; + long expected; + const char *name; +}; + +/* f(x) = 5 */ +static const struct polynomial poly_constant = { + .total_divider = 1, + .terms = { + {0, 5, 1, 1}, + } +}; + +/* f(x) = 2x^2 + 3x + 5 */ +static const struct polynomial poly_simple = { + .total_divider = 1, + .terms = { + {2, 2, 1, 1}, + {1, 3, 1, 1}, + {0, 5, 1, 1}, + } +}; + +/* f(x) = -5x + 100 */ +static const struct polynomial poly_negative_coef = { + .total_divider = 1, + .terms = { + {1, -5, 1, 1}, + {0, 100, 1, 1}, + } +}; + +/* f(x) = (150x + 50) / 10 */ +static const struct polynomial poly_total_divider = { + .total_divider = 10, + .terms = { + {1, 150, 1, 1}, + {0, 50, 1, 1}, + } +}; + +/* + * f(x) = x / 2 + * divider=2 applied once per multiply: mult_frac(coef, data, 2) = coef*data/2 + */ +static const struct polynomial poly_step_divider = { + .total_divider = 1, + .terms = { + {1, 1, 2, 1}, + {0, 0, 1, 1}, + } +}; + +/* + * f(x) = (100/500) * x^2 = 0.2 * x^2 + * Encoded as coef=100, divider=10, divider_leftover=5: + * denom = 10^2 * 5 = 500 + */ +static const struct polynomial poly_leftover = { + .total_divider = 1, + .terms = { + {2, 100, 10, 5}, + {0, 0, 1, 1}, + } +}; + +/* + * f(x) = 2x^3 (single high-degree term, no constant) + * Used to exercise the power loop alone. + */ +static const struct polynomial poly_cubic = { + .total_divider = 1, + .terms = { + {3, 2, 1, 1}, + {0, 0, 1, 1}, + } +}; + +/* + * f(x) = 4x + 1 with a zero-coefficient quadratic term. + * The deg-2 term contributes nothing regardless of input. + */ +static const struct polynomial poly_zero_coef = { + .total_divider = 1, + .terms = { + {2, 0, 1, 1}, + {1, 4, 1, 1}, + {0, 1, 1, 1}, + } +}; + +/* + * f(x) = 9 with total_divider = 0. + * The implementation treats 0 as 1 via `total_divider ?: 1`, so the + * result must equal the constant term unchanged. + */ +static const struct polynomial poly_zero_total_divider = { + .total_divider = 0, + .terms = { + {0, 9, 1, 1}, + } +}; + + +static const struct polynomial_test_param test_params[] = { + { + .poly = &poly_constant, + .data = 0, + .expected = 5, + .name = "Constant polynomial at x=0", + }, + { + .poly = &poly_constant, + .data = 42, + .expected = 5, + .name = "Constant polynomial is independent of input", + }, + { + .poly = &poly_simple, + .data = 0, + .expected = 5, /* zero input collapses all power terms */ + .name = "Zero input yields constant term only", + }, + { + .poly = &poly_simple, + .data = 10, + .expected = 235, /* 2*100 + 3*10 + 5 */ + .name = "Simple quadratic at x=10", + }, + { + .poly = &poly_negative_coef, + .data = 10, + .expected = 50, /* -5*10 + 100 */ + .name = "Negative coefficient at x=10", + }, + { + .poly = &poly_negative_coef, + .data = 20, + .expected = 0, /* -5*20 + 100 = 0 */ + .name = "Negative coefficient result is zero", + }, + { + .poly = &poly_total_divider, + .data = 3, + .expected = 50, /* (150*3 + 50) / 10 = 500/10 */ + .name = "total_divider scales the final sum", + }, + { + .poly = &poly_step_divider, + .data = 100, + .expected = 50, /* 1*100/2 */ + .name = "Per-step divider halves input", + }, + { + .poly = &poly_leftover, + .data = 30, + .expected = 180, /* 100*30^2 / (10^2 * 5) = 90000/500 */ + .name = "divider_leftover with quadratic term", + }, + /* Boundary: unit and negative-unit input */ + { + /* + * data=1: each mult_frac(tmp, 1, divider) strips one factor of + * divider from coef per degree, so coef is left-shifted right + * until intermediate precision is exhausted. + * 2*1 + 3*1 + 5 = 10 + */ + .poly = &poly_simple, + .data = 1, + .expected = 10, + .name = "Boundary: data=1 (unit input)", + }, + { + /* + * data=-1: even degrees produce positive contributions, + * odd degrees produce negative ones. + * 2*(-1)^2 + 3*(-1) + 5 = 2 - 3 + 5 = 4 + */ + .poly = &poly_simple, + .data = -1, + .expected = 4, + .name = "Boundary: data=-1 (negative unit input)", + }, + + /* Boundary: negative non-trivial input */ + { + /* + * 2*(-3)^2 + 3*(-3) + 5 = 18 - 9 + 5 = 14 + * Verifies sign handling for negative data across all degrees. + */ + .poly = &poly_simple, + .data = -3, + .expected = 14, + .name = "Boundary: negative data with quadratic", + }, + + /* Boundary: total_divider = 0 is treated as 1 */ + { + .poly = &poly_zero_total_divider, + .data = 42, + .expected = 9, + .name = "Boundary: total_divider=0 defaults to 1", + }, + + /* Boundary: zero-coefficient high-degree term */ + { + /* + * The deg-2 term has coef=0, so it contributes 0 regardless + * of data. Result: 0 + 4*10 + 1 = 41 + */ + .poly = &poly_zero_coef, + .data = 10, + .expected = 41, + .name = "Boundary: zero-coefficient term is inert", + }, + + /* Boundary: single high-degree term, no constant */ + { + /* 2 * 5^3 = 250; also verifies the loop terminates on deg-0 */ + .poly = &poly_cubic, + .data = 5, + .expected = 250, + .name = "Boundary: single cubic term", + }, + { + /* 2 * (-2)^3 = -16; odd power preserves sign of negative data */ + .poly = &poly_cubic, + .data = -2, + .expected = -16, + .name = "Boundary: single cubic term, negative data", + }, + +}; + +static void get_desc(const struct polynomial_test_param *param, char *desc) +{ + strscpy(desc, param->name, KUNIT_PARAM_DESC_SIZE); +} + +KUNIT_ARRAY_PARAM(polynomial, test_params, get_desc); + +static void polynomial_calc_test(struct kunit *test) +{ + const struct polynomial_test_param *param = test->param_value; + + KUNIT_EXPECT_EQ(test, polynomial_calc(param->poly, param->data), + param->expected); +} + +static struct kunit_case polynomial_test_cases[] = { + KUNIT_CASE_PARAM(polynomial_calc_test, polynomial_gen_params), + {} +}; + +static struct kunit_suite polynomial_test_suite = { + .name = "math-polynomial", + .test_cases = polynomial_test_cases, +}; + +kunit_test_suites(&polynomial_test_suite); + +MODULE_DESCRIPTION("math.polynomial_calc KUnit test suite"); +MODULE_LICENSE("GPL"); From 1c200da61f915c20c81608ab77bb4da5db1aa846 Mon Sep 17 00:00:00 2001 From: Yemu Lu Date: Mon, 25 May 2026 16:56:49 +0800 Subject: [PATCH 369/562] fat: restore original value when fat_ent_write failed fat_ent_write() may have committed the new link to the primary FAT but then failed on the mirror copy, leaving the chain pointing to new_dclus even though the caller will free it. Restore the original value to keep the chain consistent. Link: https://lore.kernel.org/20260525085649.781643-1-n05ec@lzu.edu.cn Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Yemu Lu Signed-off-by: Ren Wei Reported-by: Yuan Tan Reported-by: Yifan Wu Reported-by: Juefei Pu Reported-by: Xin Liu Acked-by: OGAWA Hirofumi Cc: Christian Brauner Cc: Signed-off-by: Andrew Morton --- fs/fat/misc.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/fat/misc.c b/fs/fat/misc.c index 3027ef53af21..2a0fea26a99a 100644 --- a/fs/fat/misc.c +++ b/fs/fat/misc.c @@ -133,7 +133,11 @@ int fat_chain_add(struct inode *inode, int new_dclus, int nr_cluster) ret = fat_ent_read(inode, &fatent, last); if (ret >= 0) { int wait = inode_needs_sync(inode); + int old = ret; + ret = fat_ent_write(inode, &fatent, new_dclus, wait); + if (ret < 0) + fat_ent_write(inode, &fatent, old, wait); fatent_brelse(&fatent); } if (ret < 0) From a78d7ba6fe59ce24c8dde1dafb1b062cb8ad04a5 Mon Sep 17 00:00:00 2001 From: Daniel Palmer Date: Sat, 23 May 2026 22:04:45 +0900 Subject: [PATCH 370/562] tmpfs/ramfs: let memfd_create() work on nommu Currently trying to use memfd_create() on nommu returns an error with errno set to EFBIG. The manpage memfd_create() doesn't have EFBIG as a possible error value. Doing some digging this is coming from 0 getting passed as newsize to ramfs_nommu_expand_for_mapping() and that getting into get_order() and there "The result is undefined if the size is 0". Whatever comes out of get_order() is then used in the following logic and that results in the EFBIG that causes the syscall to fail and the errno in userspace. If newsize is 0 there is nothing to do so just return. Roughly tested on m68k nommu by creating a process, creating an memfd, forking another process, mmap()ing the memfd in the child, writing into the mapping, then mmap()ing in the parent and checking that the right data is there. Link: https://lore.kernel.org/20260523130445.1101818-1-daniel@thingy.jp Signed-off-by: Daniel Palmer Acked-by: Lorenzo Stoakes Cc: "Liam R. Howlett" Cc: Al Viro Cc: Christian Brauner Signed-off-by: Andrew Morton --- fs/ramfs/file-nommu.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/ramfs/file-nommu.c b/fs/ramfs/file-nommu.c index 2f79bcb89d2e..fb471bf88ab7 100644 --- a/fs/ramfs/file-nommu.c +++ b/fs/ramfs/file-nommu.c @@ -69,6 +69,9 @@ int ramfs_nommu_expand_for_mapping(struct inode *inode, size_t newsize) gfp_t gfp = mapping_gfp_mask(inode->i_mapping); /* make various checks */ + if (!newsize) + return 0; + order = get_order(newsize); if (unlikely(order > MAX_PAGE_ORDER)) return -EFBIG; From 75fd6789f7c153ed9688c4f5e56a46d08d6de742 Mon Sep 17 00:00:00 2001 From: Yunhui Cui Date: Sat, 23 May 2026 12:20:52 +0800 Subject: [PATCH 371/562] riscv: mm: exclude invalid THP PMDs from page table check RISC-V THP splitting uses a temporary invalid PMD state where pmd_mkinvalid() clears _PAGE_PRESENT and _PAGE_PROT_NONE but leaves _PAGE_LEAF set so the MM code can still recognize the PMD as a THP split in-progress entry. That temporary state no longer describes a user-accessible mapping, but page_table_check currently treats it as one because the RISC-V PMD user-accessibility test only checks whether the PMD is a leaf and has user permissions. As a result, when a PMD-sized anonymous THP is split during a COW fault, page_table_check can account the invalid intermediate PMD as a live PMD mapping, and then account the replacement PTE mappings again when the split installs the PTE table. This leaves stale PMD accounting behind and later triggers page_table_check failures such as a non-zero anon_map_count when the folio is freed. Fix this by tightening pmd_user_accessible_page() so PMD page-table-check accounting only considers leaf PMDs that still carry either _PAGE_PRESENT or _PAGE_PROT_NONE. This preserves the THP split semantics required by the MM code while preventing page_table_check from treating invalid split PMDs as live user mappings. With CONFIG_PAGE_TABLE_CHECK=y and CONFIG_PAGE_TABLE_CHECK_ENFORCED=y, tools/testing/selftests/mm/cow completes successfully on RISC-V after this change. Link: https://lore.kernel.org/20260523042052.35476-1-cuiyunhui@bytedance.com Fixes: 3fee229a8eb9 ("riscv/mm: enable ARCH_SUPPORTS_PAGE_TABLE_CHECK") Signed-off-by: Yunhui Cui Cc: Albert Ou Cc: Palmer Dabbelt Cc: Pasha Tatashin Cc: Paul Walmsley Cc: tongtiangen Cc: Signed-off-by: Andrew Morton --- arch/riscv/include/asm/pgtable.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/arch/riscv/include/asm/pgtable.h b/arch/riscv/include/asm/pgtable.h index 5d5756bda82e..1a9c14d71df4 100644 --- a/arch/riscv/include/asm/pgtable.h +++ b/arch/riscv/include/asm/pgtable.h @@ -986,7 +986,14 @@ static inline bool pte_user_accessible_page(struct mm_struct *mm, unsigned long static inline bool pmd_user_accessible_page(struct mm_struct *mm, unsigned long addr, pmd_t pmd) { - return pmd_leaf(pmd) && pmd_user(pmd); + /* + * page_table_check() must ignore THP split invalidation entries created by + * pmd_mkinvalid(). These retain _PAGE_LEAF so pmd_present()/pmd_leaf() stay + * true during the split, but they no longer describe a user-accessible + * mapping once both _PAGE_PRESENT and _PAGE_PROT_NONE are cleared. + */ + return (pmd_val(pmd) & (_PAGE_PRESENT | _PAGE_PROT_NONE)) && + (pmd_val(pmd) & _PAGE_LEAF) && pmd_user(pmd); } static inline bool pud_user_accessible_page(struct mm_struct *mm, unsigned long addr, pud_t pud) From d48c5469820ac58a34612bdef2537b8590ba2b11 Mon Sep 17 00:00:00 2001 From: Andrea Calabrese Date: Wed, 20 May 2026 08:28:50 +0200 Subject: [PATCH 372/562] kernel: refactor: shorten has_pending_signals In has_pending_signals there was a switch/case used for optimizations. However, today's compilers perform loop unrolling efficiently, thus it is not needed anymore. Put i inside the for declaration so we do not risk its escape from the scope. Moreover, i starts now from 0 and counts up, as it is a more usual pattern. Link: https://lore.kernel.org/20260520062849.183621-2-andrea.calabrese@amarulasolutions.com Signed-off-by: Andrea Calabrese Acked-by: Oleg Nesterov Cc: Adrian Huang Cc: Christian Brauner Cc: Marco Elver Cc: Peter Zijlstra Signed-off-by: Andrew Morton --- kernel/signal.c | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/kernel/signal.c b/kernel/signal.c index da41e89d2bce..b12878f3c578 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -130,28 +130,10 @@ static bool sig_ignored(struct task_struct *t, int sig, bool force) */ static inline bool has_pending_signals(sigset_t *signal, sigset_t *blocked) { - unsigned long ready; - long i; - - switch (_NSIG_WORDS) { - default: - for (i = _NSIG_WORDS, ready = 0; --i >= 0 ;) - ready |= signal->sig[i] &~ blocked->sig[i]; - break; - - case 4: ready = signal->sig[3] &~ blocked->sig[3]; - ready |= signal->sig[2] &~ blocked->sig[2]; - ready |= signal->sig[1] &~ blocked->sig[1]; - ready |= signal->sig[0] &~ blocked->sig[0]; - break; - - case 2: ready = signal->sig[1] &~ blocked->sig[1]; - ready |= signal->sig[0] &~ blocked->sig[0]; - break; - - case 1: ready = signal->sig[0] &~ blocked->sig[0]; - } - return ready != 0; + unsigned long ready = 0; + for (long i = 0; i < _NSIG_WORDS; i++) + ready |= signal->sig[i] & ~blocked->sig[i]; + return ready != 0; } #define PENDING(p,b) has_pending_signals(&(p)->signal, (b)) From 1cf12612816593ff5c8e9e3cbe11241d76b31d0b Mon Sep 17 00:00:00 2001 From: Sara Sena Date: Tue, 30 Jun 2026 22:11:53 -0300 Subject: [PATCH 373/562] tools/accounting/delaytop.c: fix typo in PSI header string Link: https://lore.kernel.org/20260701011153.93426-1-sarasena.adr@gmail.com Signed-off-by: Sara Sena Cc: Fan Yu Cc: Wang Yaxin Signed-off-by: Andrew Morton --- tools/accounting/delaytop.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/accounting/delaytop.c b/tools/accounting/delaytop.c index 72cc500b44b1..0a863dfeae8a 100644 --- a/tools/accounting/delaytop.c +++ b/tools/accounting/delaytop.c @@ -878,7 +878,7 @@ static void display_results(int psi_ret) suc &= BOOL_FPRINT(out, "\033[H\033[J"); /* PSI output (one-line, no cat style) */ - suc &= BOOL_FPRINT(out, "System Pressure Information: (avg10/avg60vg300/total)\n"); + suc &= BOOL_FPRINT(out, "System Pressure Information: (avg10/avg60/avg300/total)\n"); if (psi_ret) { suc &= BOOL_FPRINT(out, " PSI not found: check if psi=1 enabled in cmdline\n"); } else { From 9a1541524ef2e68dc14d4995844eb973a79de181 Mon Sep 17 00:00:00 2001 From: Vivian Wang Date: Mon, 29 Jun 2026 17:25:45 +0800 Subject: [PATCH 374/562] riscv: mm: fix concurrency in mark_new_valid_map() Turns out, the concurrency concerns [1] were justified - BOSC reported a spurious fault in KFENCE that still triggers despite previous fixes, which KFENCE reports as a false-positive. Fix the concurrency problems in mark_new_valid_map(): - Add smp_wmb() before filling the bitmap, to make sure page table writes are "received". - Use WRITE_ONCE() to fill the bitmap. This fixes the KFENCE false positives in internal testing. Also update comments in the assembly exception handler code to match. Link: https://lore.kernel.org/20260629-riscv-mm-new-valid-map-ordering-v1-1-60d8c10c6292@iscas.ac.cn Fixes: 26c171fc4853 ("riscv: mm: Use the bitmap API for new_valid_map_cpus") Signed-off-by: Vivian Wang Reported-by: Yaxing Guo Suggested-by: David Hildenbrand (Arm) Link: https://lore.kernel.org/linux-riscv/da19ffcf-8042-4f96-9c2d-649468dc6a0a@kernel.org/ # [1] Cc: Alexandre Ghiti Cc: David Hildenbrand Cc: Palmer Dabbelt Signed-off-by: Andrew Morton --- arch/riscv/include/asm/cacheflush.h | 15 ++++++++++++++- arch/riscv/kernel/entry.S | 11 ++++------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/arch/riscv/include/asm/cacheflush.h b/arch/riscv/include/asm/cacheflush.h index 8cfe59483a8f..1f191cf801a2 100644 --- a/arch/riscv/include/asm/cacheflush.h +++ b/arch/riscv/include/asm/cacheflush.h @@ -6,7 +6,9 @@ #ifndef _ASM_RISCV_CACHEFLUSH_H #define _ASM_RISCV_CACHEFLUSH_H +#include #include +#include static inline void local_flush_icache_all(void) { @@ -46,12 +48,23 @@ extern DECLARE_BITMAP(new_valid_map_cpus, NR_CPUS); extern char _end[]; static inline void mark_new_valid_map(void) { + /* + * Orders any previous page table writes before setting bits in + * new_valid_map_cpus. Pairs with the sfence.vma in + * new_valid_map_cpus_check. + */ + smp_wmb(); + /* * We don't care if concurrently a cpu resets this value since * the only place this can happen is in handle_exception() where * an sfence.vma is emitted. + * + * Not memset() or bitmap_fill() to avoid any possible compiler + * shenanigans. */ - bitmap_fill(new_valid_map_cpus, NR_CPUS); + for (size_t i = 0; i < ARRAY_SIZE(new_valid_map_cpus); i++) + WRITE_ONCE(new_valid_map_cpus[i], -1UL); } #define flush_cache_vmap flush_cache_vmap static inline void flush_cache_vmap(unsigned long start, unsigned long end) diff --git a/arch/riscv/kernel/entry.S b/arch/riscv/kernel/entry.S index c6988983cdf7..092f408edb7d 100644 --- a/arch/riscv/kernel/entry.S +++ b/arch/riscv/kernel/entry.S @@ -76,6 +76,8 @@ amoxor.d a0, a1, (a0) /* + * Pairs with the smp_wmb() in mark_new_valid_map() + * * A sfence.vma is required here. Even if we had Svvptc, there's no * guarantee that after returning we wouldn't just fault again. */ @@ -141,13 +143,8 @@ SYM_CODE_START(handle_exception) /* * The RISC-V kernel does not flush TLBs on all CPUS after each new * vmalloc mapping or kfence_unprotect(), which may result in - * exceptions: - * - * - if the uarch caches invalid entries, the new mapping would not be - * observed by the page table walker and an invalidation is needed. - * - if the uarch does not cache invalid entries, a reordered access - * could "miss" the new mapping and traps: in that case, we only need - * to retry the access, no sfence.vma is required. + * exceptions. In that case, we need to sfence.vma to "receive" the new + * mappings and retry, whether or not we have Svvptc. */ new_valid_map_cpus_check #endif From cdc9a8dd11a847e8daa732eabdedcb3ef244d2f4 Mon Sep 17 00:00:00 2001 From: Vivian Wang Date: Tue, 30 Jun 2026 15:51:59 +0800 Subject: [PATCH 375/562] riscv: mm: avoid spurious fault after hotplugging vmemmap section_activate() does not flush TLB after populating new vmemmap pages. On most architectures, this is okay. However it is a problem on RISC-V since there the TLB caching non-present entries is permitted, which causes spurious faults on some hardwares. This seems to be most easily reproduced with DEBUG_VM=y and PAGE_POISONING=y, which causes these newly mapped struct pages to be poisoned i.e. written to immediately after mapping. Add a hook vmemmap_populate_finalize() in __populate_section_memmap() after population, to allow architectures to handle such situations as needed. Then implement it on RISC-V to arrange for the existing exception handler code to deal with these faults if they happen. Link: https://lore.kernel.org/20260630-mark-after-vmemmap-populate-v4-1-febbc15da028@iscas.ac.cn Signed-off-by: Vivian Wang Cc: Alexandre Ghiti Cc: David Hildenbrand Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Palmer Dabbelt Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- arch/riscv/include/asm/pgtable.h | 4 ++++ arch/riscv/mm/init.c | 6 ++++++ mm/sparse-vmemmap.c | 8 ++++++++ 3 files changed, 18 insertions(+) diff --git a/arch/riscv/include/asm/pgtable.h b/arch/riscv/include/asm/pgtable.h index 1a9c14d71df4..98f57191f02b 100644 --- a/arch/riscv/include/asm/pgtable.h +++ b/arch/riscv/include/asm/pgtable.h @@ -1260,6 +1260,10 @@ static inline pte_t pte_swp_clear_exclusive(pte_t pte) #define TASK_SIZE FIXADDR_START #endif +/* Needed on SPARSEMEM_VMEMMAP */ +#define vmemmap_populate_finalize vmemmap_populate_finalize +void __meminit vmemmap_populate_finalize(void); + #else /* CONFIG_MMU */ #define PAGE_SHARED __pgprot(0) diff --git a/arch/riscv/mm/init.c b/arch/riscv/mm/init.c index 5b1b3c88b4d1..800cb5c007d1 100644 --- a/arch/riscv/mm/init.c +++ b/arch/riscv/mm/init.c @@ -1372,6 +1372,12 @@ int __meminit vmemmap_populate(unsigned long start, unsigned long end, int node, */ return vmemmap_populate_hugepages(start, end, node, altmap); } + +void __meminit vmemmap_populate_finalize(void) +{ + /* Avoid faults on cached non-present TLB entries. */ + mark_new_valid_map(); +} #endif #if defined(CONFIG_MMU) && defined(CONFIG_64BIT) diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c index 99e2be39671b..290cafcfd723 100644 --- a/mm/sparse-vmemmap.c +++ b/mm/sparse-vmemmap.c @@ -544,6 +544,12 @@ static int __meminit vmemmap_populate_compound_pages(unsigned long start_pfn, #endif +#ifndef vmemmap_populate_finalize +static void __meminit vmemmap_populate_finalize(void) +{ +} +#endif + struct page * __meminit __populate_section_memmap(unsigned long pfn, unsigned long nr_pages, int nid, struct vmem_altmap *altmap, struct dev_pagemap *pgmap) @@ -564,6 +570,8 @@ struct page * __meminit __populate_section_memmap(unsigned long pfn, if (r < 0) return NULL; + vmemmap_populate_finalize(); + return pfn_to_page(pfn); } From 8fe7a8846d6549f0742c80a974ff9dcf837a29af Mon Sep 17 00:00:00 2001 From: "Joy H.J. Lee" Date: Thu, 2 Jul 2026 05:06:35 +0900 Subject: [PATCH 376/562] tools/compiler: match glibc 2.42 definition of __attribute_const__ glibc 2.42 added __attribute_const__ to sys/cdefs.h: # define __attribute_const__ __attribute__ ((__const__)) GCC 15 warns when a macro is redefined to a different replacement list (-Wbuiltin-macro-redefined). Since host tool Makefiles (resolve_btfids, objtool) pass -Werror, this conflict becomes fatal. The warning is suppressed on standard native builds because GCC treats /usr/include as a system header path (-isystem), and macro-redefinition warnings from system headers are silently suppressed by GCC. It fires when glibc headers are on a regular include path (-I) instead, which is the case in cross-compilation setups such as NixOS, where the sysroot's glibc is passed explicitly via -I rather than -isystem. Per (C11 6.10.3), identical replacement lists are accepted silently. Match the glibc definition exactly, including the space before "((", so the redefinition is accepted without warning regardless of whether glibc headers are treated as system or non-system includes. Link: https://lore.kernel.org/20260701200635.3992767-1-rkr0k0r@gmail.com Signed-off-by: Joy H.J. Lee Cc: Nathan Chancellor Cc: David Laight Cc: Signed-off-by: Andrew Morton --- tools/include/linux/compiler.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/include/linux/compiler.h b/tools/include/linux/compiler.h index f40bd2b04c29..f2f54b038168 100644 --- a/tools/include/linux/compiler.h +++ b/tools/include/linux/compiler.h @@ -119,7 +119,7 @@ #define __read_mostly #ifndef __attribute_const__ -# define __attribute_const__ +# define __attribute_const__ __attribute__ ((__const__)) #endif #ifndef __maybe_unused From 9d4200705211ff2772c497312ecdba5ab939aac6 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Mon, 29 Jun 2026 00:01:43 -0500 Subject: [PATCH 377/562] ocfs2: bound namelen in dlm_migrate_request_handler Patch series "ocfs2/dlm: bound peer-controlled lengths in the o2dlm". The o2dlm receive handlers trust u8 length and count fields from the wire without bounding them, so a node in a DLM domain can corrupt or panic any other node with a malformed message. Three defects: - dlm_migrate_request_handler() passes migrate->namelen unchecked to dlm_init_mle(), which memcpy()s it into the 32-byte mname[] of an o2dlm_mle slab object: a heap out-of-bounds write of up to ~215 attacker-controlled bytes. - dlm_mig_lockres_handler() passes mres->lockname_len unchecked to dlm_init_lockres(), which memcpy()s it into the 32-byte o2dlm_lockname slab object: a heap out-of-bounds write of up to ~223 bytes. - the same handler trusts mres->num_locks without checking that the message is large enough to hold that many entries, so dlm_process_recovery_data() walks mres->ml[] past the kmalloc(data_len) copy and trips a BUG_ON (an out-of-bounds read ending in a panic). The other o2dlm receive handlers already reject an oversized name; the migration and recovery handlers have omitted it since the DLM was added (see the Fixes tags). Patch 1 bounds namelen; patch 2 validates lockname_len, num_locks, and the payload size. Conforming recovery and migration traffic is unaffected. o2net authenticates peers only by the DLM domain key, so any node that has joined the domain -- including a compromised or malicious member -- can send these messages. There is no local trigger; the attacker must already be a member of the cluster. Each sink was confirmed under KASAN with an out-of-tree module mirroring it exactly -- a kmem_cache/kmalloc of the real destination size, then the same unclamped memcpy/loop: slab-out-of-bounds Write for the two writes, Read for the recovery walk, and a panic. A userspace AddressSanitizer build faults identically under -m32 and -m64. Scrubbed logs are available on request. I reported this privately to security@kernel.org and the ocfs2 maintainers on 2026-06-20; with no response after the standard embargo period I am posting the fix publicly. I have no embargo requirement. This patch (of 2): A node receiving a DLM_MIGRATE_REQUEST message trusts the peer-supplied name length (migrate->namelen) without bounding it. dlm_init_mle() then copies that many bytes into the fixed DLM_LOCKID_NAME_MAX-byte mname[] array of an o2dlm_mle slab object, so a malformed message from a cluster peer overflows the slab object by up to ~215 bytes: a heap out-of-bounds write of attacker-controlled data, reachable by any node in the domain. Reject an oversized name, the way dlm_master_request_handler() and the other o2dlm receive handlers already do; the migration handler omits the check entirely. Conforming messages are unaffected. Link: https://lore.kernel.org/20260629-b4-disp-94fb6521-v1-0-6953bcc0421f@proton.me Link: https://lore.kernel.org/20260629-b4-disp-94fb6521-v1-1-6953bcc0421f@proton.me Fixes: 6714d8e86bf4 ("[PATCH] OCFS2: The Second Oracle Cluster Filesystem") Signed-off-by: Bryam Vargas Reviewed-by: Joseph Qi Cc: Changwei Ge Cc: Heming Zhao Cc: Joel Becker Cc: Jun Piao Cc: Junxiao Bi Cc: Mark Fasheh Cc: Signed-off-by: Andrew Morton --- fs/ocfs2/dlm/dlmmaster.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fs/ocfs2/dlm/dlmmaster.c b/fs/ocfs2/dlm/dlmmaster.c index aee3b4c56dcc..612969867ff9 100644 --- a/fs/ocfs2/dlm/dlmmaster.c +++ b/fs/ocfs2/dlm/dlmmaster.c @@ -3099,6 +3099,12 @@ int dlm_migrate_request_handler(struct o2net_msg *msg, u32 len, void *data, name = migrate->name; namelen = migrate->namelen; + if (namelen > DLM_LOCKID_NAME_MAX) { + mlog(ML_ERROR, "%s: invalid name length %u in migrate request\n", + dlm->name, namelen); + ret = -EINVAL; + goto leave; + } hash = dlm_lockid_hash(name, namelen); /* preallocate.. if this fails, abort */ From a2d03913973120f8609d3e276d1459f756765ec4 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Mon, 29 Jun 2026 00:01:44 -0500 Subject: [PATCH 378/562] ocfs2: validate lengths in dlm_mig_lockres_handler A node receiving a DLM_MIG_LOCKRES message trusts several fields of the peer-supplied dlm_migratable_lockres without validation. num_locks and lockname_len are bounded only on the sending side, and the message is never checked to actually carry num_locks migratable_lock entries. As a result dlm_process_recovery_data() walks mres->ml[0..num_locks) past the kmalloc(data_len) copy of the message (an out-of-bounds read that ends in a BUG_ON panic), and dlm_init_lockres() copies lockname_len bytes into the fixed 32-byte o2dlm_lockname slab object (a heap out-of-bounds write). Both are reachable by any node in the domain. Validate these fields right after dlm_grab(), before anything uses them -- including the not-joined error path, which already prints mres->lockname with the unbounded lockname_len as a %.*s precision. Reject the message unless lockname_len <= DLM_LOCKID_NAME_MAX, num_locks <= DLM_MAX_MIGRATABLE_LOCKS (the bound the sender already asserts), and the payload is large enough to hold the claimed locks. Conforming recovery and migration messages are unaffected. Link: https://lore.kernel.org/20260629-b4-disp-94fb6521-v1-2-6953bcc0421f@proton.me Fixes: 6714d8e86bf4 ("[PATCH] OCFS2: The Second Oracle Cluster Filesystem") Signed-off-by: Bryam Vargas Reviewed-by: Joseph Qi Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao Cc: Signed-off-by: Andrew Morton --- fs/ocfs2/dlm/dlmrecovery.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/fs/ocfs2/dlm/dlmrecovery.c b/fs/ocfs2/dlm/dlmrecovery.c index 9b97bf73df22..9d4a2695b959 100644 --- a/fs/ocfs2/dlm/dlmrecovery.c +++ b/fs/ocfs2/dlm/dlmrecovery.c @@ -1357,6 +1357,15 @@ int dlm_mig_lockres_handler(struct o2net_msg *msg, u32 len, void *data, if (!dlm_grab(dlm)) return -EINVAL; + if (mres->lockname_len > DLM_LOCKID_NAME_MAX || + mres->num_locks > DLM_MAX_MIGRATABLE_LOCKS || + be16_to_cpu(msg->data_len) < struct_size(mres, ml, mres->num_locks)) { + mlog(ML_ERROR, "%s: invalid lockres migration message from %u\n", + dlm->name, mres->master); + dlm_put(dlm); + return -EINVAL; + } + if (!dlm_joined(dlm)) { mlog(ML_ERROR, "Domain %s not joined! " "lockres %.*s, master %u\n", From 59d6e81c11a8f714a89a21c13f691ac7ef5db606 Mon Sep 17 00:00:00 2001 From: Wang Yaxin Date: Thu, 2 Jul 2026 20:58:15 +0800 Subject: [PATCH 379/562] delaytop: add delay max for delaytop Patch series "delaytop: add delay max, timestamp and sorting for top latency analysis". Previously delaytop only showed average delays. This patch adds: 1. delay_max fields to track the maximum delay value for each delay type (cpu, blkio, irq, swapin, freepages, thrashing, compact, wpcopy) per task. 2. The -t/--type option displays only the specified delay type with avg/max values side by side, allowing focused analysis: delaytop -t cpu # Show only CPU delay with avg/max delaytop -t wpcopy # Show Copy-on-Write delay with avg/max 3. Wall-clock timestamp when each maximum delay occurred, displayed in the MAX_TIMESTAMP column when using -t/--type option. This enables: - Identifying the time when a process experienced an abnormal delay max - Correlating delay max across multiple processes at the same timestamp - Cross-referencing with logs, traces, or other metrics at that time 4. When using -t/--type option, tasks are sorted by maximum delay value in descending order (largest delay first), enabling quick identification of top N processes with highest delay spikes. This patch (of 3): Previously delaytop only showed average delays. Add delay_max fields to track the maximum delay value for each delay type (cpu, blkio, irq, swapin, freepages, thrashing, compact, wpcopy) per task. This provides a global view of all tasks' delay spikes, which is essential for identifying processes that experienced brief but significant latency events that would be hidden by average-only metrics. The -t/--type option displays only the specified delay type with avg/max values side by side, allowing focused analysis: delaytop -t cpu # Show only CPU delay with avg/max delaytop -t wpcopy # Show Copy-on-Write delay with avg/max Link: https://lore.kernel.org/20260702205704180NZ3cu_QF04KfBIL6vjTHL@zte.com.cn Link: https://lore.kernel.org/202607022058152607Y25X-YgssuvncpVNHljz@zte.com.cn Signed-off-by: Wang Yaxin Cc: Fan Yu Cc: Jonathan Corbet Cc: xu xin Cc: Yang Yang Signed-off-by: Andrew Morton --- tools/accounting/delaytop.c | 165 ++++++++++++++++++++++++++++-------- 1 file changed, 131 insertions(+), 34 deletions(-) diff --git a/tools/accounting/delaytop.c b/tools/accounting/delaytop.c index 0a863dfeae8a..29b0fff1d4a0 100644 --- a/tools/accounting/delaytop.c +++ b/tools/accounting/delaytop.c @@ -75,13 +75,21 @@ {#name, #cmd, \ offsetof(struct task_info, name##_delay_total), \ offsetof(struct task_info, name##_count), \ + offsetof(struct task_info, name##_delay_max), \ modes} -#define END_FIELD {NULL, 0, 0} +#define SORT_FIELD_NO_MAX(name, cmd, modes) \ + {#name, #cmd, \ + offsetof(struct task_info, name##_delay_total), \ + offsetof(struct task_info, name##_count), \ + 0, \ + modes} +#define END_FIELD {NULL, 0, 0, 0, 0, 0} /* Display mode types */ #define MODE_TYPE_ALL (0xFFFFFFFF) #define MODE_DEFAULT (1 << 0) #define MODE_MEMVERBOSE (1 << 1) +#define MODE_TYPE (1 << 2) /* Display specific type with avg/max */ /* PSI statistics structure */ struct psi_stats { @@ -108,20 +116,28 @@ struct task_info { char command[TASK_COMM_LEN]; unsigned long long cpu_count; unsigned long long cpu_delay_total; + unsigned long long cpu_delay_max; unsigned long long blkio_count; unsigned long long blkio_delay_total; + unsigned long long blkio_delay_max; unsigned long long swapin_count; unsigned long long swapin_delay_total; + unsigned long long swapin_delay_max; unsigned long long freepages_count; unsigned long long freepages_delay_total; + unsigned long long freepages_delay_max; unsigned long long thrashing_count; unsigned long long thrashing_delay_total; + unsigned long long thrashing_delay_max; unsigned long long compact_count; unsigned long long compact_delay_total; + unsigned long long compact_delay_max; unsigned long long wpcopy_count; unsigned long long wpcopy_delay_total; + unsigned long long wpcopy_delay_max; unsigned long long irq_count; unsigned long long irq_delay_total; + unsigned long long irq_delay_max; unsigned long long mem_count; unsigned long long mem_delay_total; }; @@ -141,6 +157,7 @@ struct field_desc { const char *cmd_char; /* Interactive command */ unsigned long total_offset; /* Offset of total delay in task_info */ unsigned long count_offset; /* Offset of count in task_info */ + unsigned long max_offset; /* Offset of max delay in task_info */ size_t supported_modes; /* Supported display modes */ }; @@ -153,6 +170,7 @@ struct config { int monitor_pid; /* Monitor specific PID */ char *container_path; /* Path to container cgroup */ const struct field_desc *sort_field; /* Current sort field */ + const struct field_desc *type_field; /* Type field for -t option */ size_t display_mode; /* Current display mode */ }; @@ -164,15 +182,15 @@ static int task_count; static int running = 1; static struct container_stats container_stats; static const struct field_desc sort_fields[] = { - SORT_FIELD(cpu, c, MODE_DEFAULT), - SORT_FIELD(blkio, i, MODE_DEFAULT), - SORT_FIELD(irq, q, MODE_DEFAULT), - SORT_FIELD(mem, m, MODE_DEFAULT | MODE_MEMVERBOSE), - SORT_FIELD(swapin, s, MODE_MEMVERBOSE), - SORT_FIELD(freepages, r, MODE_MEMVERBOSE), - SORT_FIELD(thrashing, t, MODE_MEMVERBOSE), - SORT_FIELD(compact, p, MODE_MEMVERBOSE), - SORT_FIELD(wpcopy, w, MODE_MEMVERBOSE), + SORT_FIELD(cpu, c, MODE_DEFAULT | MODE_TYPE), + SORT_FIELD(blkio, i, MODE_DEFAULT | MODE_TYPE), + SORT_FIELD(irq, q, MODE_DEFAULT | MODE_TYPE), + SORT_FIELD_NO_MAX(mem, m, MODE_DEFAULT | MODE_MEMVERBOSE), + SORT_FIELD(swapin, s, MODE_MEMVERBOSE | MODE_TYPE), + SORT_FIELD(freepages, r, MODE_MEMVERBOSE | MODE_TYPE), + SORT_FIELD(thrashing, t, MODE_MEMVERBOSE | MODE_TYPE), + SORT_FIELD(compact, p, MODE_MEMVERBOSE | MODE_TYPE), + SORT_FIELD(wpcopy, w, MODE_MEMVERBOSE | MODE_TYPE), END_FIELD }; static int sort_selected; @@ -265,6 +283,7 @@ static void usage(void) " -p, --pid=PID Monitor only the specified PID\n" " -C, --container=PATH Monitor the container at specified cgroup path\n" " -s, --sort=FIELD Sort by delay field (default: cpu)\n" + " -t, --type=FIELD Display only specified delay type with avg/max\n" " -M, --memverbose Display memory detailed information\n"); exit(0); } @@ -283,6 +302,7 @@ static void parse_args(int argc, char **argv) {"processes", required_argument, 0, 'P'}, {"sort", required_argument, 0, 's'}, {"container", required_argument, 0, 'C'}, + {"type", required_argument, 0, 't'}, {"memverbose", no_argument, 0, 'M'}, {0, 0, 0, 0} }; @@ -292,6 +312,7 @@ static void parse_args(int argc, char **argv) cfg.iterations = 0; cfg.max_processes = 20; cfg.sort_field = &sort_fields[0]; /* Default sorted by CPU delay */ + cfg.type_field = NULL; /* No type field by default */ cfg.output_one_time = 0; cfg.monitor_pid = 0; /* 0 means monitor all PIDs */ cfg.container_path = NULL; @@ -300,7 +321,7 @@ static void parse_args(int argc, char **argv) while (1) { int option_index = 0; - c = getopt_long(argc, argv, "hd:n:p:oP:C:s:M", long_options, &option_index); + c = getopt_long(argc, argv, "hd:n:p:oP:C:s:t:M", long_options, &option_index); if (c == -1) break; @@ -363,9 +384,32 @@ static void parse_args(int argc, char **argv) cfg.sort_field = field; break; + case 't': + if (strlen(optarg) == 0) { + fprintf(stderr, "Error: empty type field\n"); + exit(1); + } + + field = get_field_by_name(optarg); + /* Show available fields if invalid option provided */ + if (!field || !(field->supported_modes & MODE_TYPE)) { + fprintf(stderr, "Error: invalid type field '%s'\n", optarg); + display_available_fields(MODE_TYPE); + exit(1); + } + + cfg.type_field = field; + cfg.display_mode = MODE_TYPE; + break; case 'M': cfg.display_mode = MODE_MEMVERBOSE; - cfg.sort_field = get_field_by_name("mem"); + /* Find first field supporting MODE_MEMVERBOSE for sorting */ + for (field = sort_fields; field->name != NULL; field++) { + if (field->supported_modes & MODE_MEMVERBOSE) { + cfg.sort_field = field; + break; + } + } break; default: fprintf(stderr, "Try 'delaytop --help' for more information.\n"); @@ -690,7 +734,12 @@ static void fetch_and_fill_task_info(int pid, const char *comm) nested_len = NLA_PAYLOAD(na->nla_len); while (nested_len > 0) { if (nested->nla_type == TASKSTATS_TYPE_STATS) { - memcpy(&stats, NLA_DATA(nested), sizeof(stats)); + size_t payload_len = NLA_PAYLOAD(nested->nla_len); + + memset(&stats, 0, sizeof(stats)); + if (payload_len > sizeof(stats)) + payload_len = sizeof(stats); + memcpy(&stats, NLA_DATA(nested), payload_len); if (task_count < MAX_TASKS) { tasks[task_count].pid = pid; tasks[task_count].tgid = pid; @@ -699,20 +748,28 @@ static void fetch_and_fill_task_info(int pid, const char *comm) tasks[task_count].command[TASK_COMM_LEN - 1] = '\0'; SET_TASK_STAT(task_count, cpu_count); SET_TASK_STAT(task_count, cpu_delay_total); + SET_TASK_STAT(task_count, cpu_delay_max); SET_TASK_STAT(task_count, blkio_count); SET_TASK_STAT(task_count, blkio_delay_total); + SET_TASK_STAT(task_count, blkio_delay_max); SET_TASK_STAT(task_count, swapin_count); SET_TASK_STAT(task_count, swapin_delay_total); + SET_TASK_STAT(task_count, swapin_delay_max); SET_TASK_STAT(task_count, freepages_count); SET_TASK_STAT(task_count, freepages_delay_total); + SET_TASK_STAT(task_count, freepages_delay_max); SET_TASK_STAT(task_count, thrashing_count); SET_TASK_STAT(task_count, thrashing_delay_total); + SET_TASK_STAT(task_count, thrashing_delay_max); SET_TASK_STAT(task_count, compact_count); SET_TASK_STAT(task_count, compact_delay_total); + SET_TASK_STAT(task_count, compact_delay_max); SET_TASK_STAT(task_count, wpcopy_count); SET_TASK_STAT(task_count, wpcopy_delay_total); + SET_TASK_STAT(task_count, wpcopy_delay_max); SET_TASK_STAT(task_count, irq_count); SET_TASK_STAT(task_count, irq_delay_total); + SET_TASK_STAT(task_count, irq_delay_max); set_mem_count(&tasks[task_count]); set_mem_delay_total(&tasks[task_count]); task_count++; @@ -777,14 +834,14 @@ static int compare_tasks(const void *a, const void *b) const struct task_info *t2 = (const struct task_info *)b; unsigned long long total1; unsigned long long total2; - unsigned long count1; - unsigned long count2; + unsigned long long count1; + unsigned long long count2; double avg1, avg2; total1 = *(unsigned long long *)((char *)t1 + cfg.sort_field->total_offset); total2 = *(unsigned long long *)((char *)t2 + cfg.sort_field->total_offset); - count1 = *(unsigned long *)((char *)t1 + cfg.sort_field->count_offset); - count2 = *(unsigned long *)((char *)t2 + cfg.sort_field->count_offset); + count1 = *(unsigned long long *)((char *)t1 + cfg.sort_field->count_offset); + count2 = *(unsigned long long *)((char *)t2 + cfg.sort_field->count_offset); avg1 = average_ms(total1, count1); avg2 = average_ms(total2, count2); @@ -794,6 +851,26 @@ static int compare_tasks(const void *a, const void *b) return 0; } +/* Get delay values for a specific field */ +static void get_field_delay_values(const struct task_info *task, const struct field_desc *field, + double *avg_ms, double *max_ms) +{ + unsigned long long total, count, max; + + if (!field || !field->max_offset) { + *avg_ms = 0; + *max_ms = 0; + return; + } + + total = *(unsigned long long *)((char *)task + field->total_offset); + count = *(unsigned long long *)((char *)task + field->count_offset); + *avg_ms = average_ms(total, count); + + max = *(unsigned long long *)((char *)task + field->max_offset); + *max_ms = (double)max / 1000000.0; /* Convert nanoseconds to milliseconds */ +} + /* Sort tasks by selected field */ static void sort_tasks(void) { @@ -847,7 +924,12 @@ static void get_container_stats(void) while (nl_len > 0) { if (na->nla_type == CGROUPSTATS_TYPE_CGROUP_STATS) { /* Get the cgroupstats structure */ - memcpy(&stats, NLA_DATA(na), sizeof(stats)); + size_t payload_len = NLA_PAYLOAD(na->nla_len); + + memset(&stats, 0, sizeof(stats)); + if (payload_len > sizeof(stats)) + payload_len = sizeof(stats); + memcpy(&stats, NLA_DATA(na), payload_len); /* Fill container stats */ container_stats.nr_sleeping = stats.nr_sleeping; @@ -950,29 +1032,44 @@ static void display_results(int psi_ret) suc &= BOOL_FPRINT(out, "Top %d processes (sorted by %s delay):\n", cfg.max_processes, get_name_by_field(cfg.sort_field)); - suc &= BOOL_FPRINT(out, "%8s %8s %-17s", "PID", "TGID", "COMMAND"); - if (cfg.display_mode == MODE_MEMVERBOSE) { - suc &= BOOL_FPRINT(out, "%8s %8s %8s %8s %8s %8s\n", - "MEM(ms)", "SWAP(ms)", "RCL(ms)", - "THR(ms)", "CMP(ms)", "WP(ms)"); - suc &= BOOL_FPRINT(out, "-----------------------"); - suc &= BOOL_FPRINT(out, "-----------------------"); - suc &= BOOL_FPRINT(out, "-----------------------"); - suc &= BOOL_FPRINT(out, "---------------------\n"); + if (cfg.display_mode == MODE_TYPE && cfg.type_field) { + /* Display mode for -t option: show only specified type with avg/max */ + suc &= BOOL_FPRINT(out, "%8s %8s %-17s %12s %12s\n", + "PID", "TGID", "COMMAND", + "AVG(ms)", "MAX(ms)"); + suc &= BOOL_FPRINT(out, "----------------------------------------------------\n"); } else { - suc &= BOOL_FPRINT(out, "%8s %8s %8s %8s\n", - "CPU(ms)", "IO(ms)", "IRQ(ms)", "MEM(ms)"); - suc &= BOOL_FPRINT(out, "-----------------------"); - suc &= BOOL_FPRINT(out, "-----------------------"); - suc &= BOOL_FPRINT(out, "--------------------------\n"); + suc &= BOOL_FPRINT(out, "%8s %8s %-17s", "PID", "TGID", "COMMAND"); + if (cfg.display_mode == MODE_MEMVERBOSE) { + suc &= BOOL_FPRINT(out, "%8s %8s %8s %8s %8s %8s\n", + "MEM(ms)", "SWAP(ms)", "RCL(ms)", + "THR(ms)", "CMP(ms)", "WP(ms)"); + suc &= BOOL_FPRINT(out, "-----------------------"); + suc &= BOOL_FPRINT(out, "-----------------------"); + suc &= BOOL_FPRINT(out, "-----------------------"); + suc &= BOOL_FPRINT(out, "---------------------\n"); + } else { + suc &= BOOL_FPRINT(out, "%8s %8s %8s %8s\n", + "CPU(ms)", "IO(ms)", "IRQ(ms)", "MEM(ms)"); + suc &= BOOL_FPRINT(out, "-----------------------"); + suc &= BOOL_FPRINT(out, "-----------------------"); + suc &= BOOL_FPRINT(out, "--------------------------\n"); + } } count = task_count < cfg.max_processes ? task_count : cfg.max_processes; for (i = 0; i < count; i++) { - suc &= BOOL_FPRINT(out, "%8d %8d %-15s", + suc &= BOOL_FPRINT(out, "%8d %8d %-17s", tasks[i].pid, tasks[i].tgid, tasks[i].command); - if (cfg.display_mode == MODE_MEMVERBOSE) { + if (cfg.display_mode == MODE_TYPE && cfg.type_field) { + double avg_ms, max_ms; + + get_field_delay_values(&tasks[i], cfg.type_field, &avg_ms, &max_ms); + + suc &= BOOL_FPRINT(out, "%12.2f %12.2f\n", + avg_ms, max_ms); + } else if (cfg.display_mode == MODE_MEMVERBOSE) { suc &= BOOL_FPRINT(out, DELAY_FMT_MEMVERBOSE, TASK_AVG(tasks[i], mem), TASK_AVG(tasks[i], swapin), From a5d2e89dbf5477f51614ea3103c33062c5272e37 Mon Sep 17 00:00:00 2001 From: Wang Yaxin Date: Thu, 2 Jul 2026 20:58:54 +0800 Subject: [PATCH 380/562] delaytop: add timestamp of delay max Record the wall-clock timestamp when each maximum delay occurred for all delay types. The timestamp is displayed in the MAX_TIMESTAMP column when using -t/--type option. This enables: - Identifying the time when a process experienced an abnormal delay spike - Correlating delay peaks across multiple processes at the same timestamp - Cross-referencing with system logs, traces, or other metrics at that time - Pinpointing the root cause of latency issues by finding concurrent events Link: https://lore.kernel.org/20260702205854461V25Py2xQvLesD8HF_2Rh8@zte.com.cn Signed-off-by: Wang Yaxin Cc: Fan Yu Cc: Jonathan Corbet Cc: xu xin Cc: Yang Yang Signed-off-by: Andrew Morton --- tools/accounting/delaytop.c | 84 ++++++++++++++++++++++++++++++++----- 1 file changed, 74 insertions(+), 10 deletions(-) diff --git a/tools/accounting/delaytop.c b/tools/accounting/delaytop.c index 29b0fff1d4a0..7e80f3875a91 100644 --- a/tools/accounting/delaytop.c +++ b/tools/accounting/delaytop.c @@ -76,14 +76,16 @@ offsetof(struct task_info, name##_delay_total), \ offsetof(struct task_info, name##_count), \ offsetof(struct task_info, name##_delay_max), \ + offsetof(struct task_info, name##_delay_max_ts), \ modes} #define SORT_FIELD_NO_MAX(name, cmd, modes) \ {#name, #cmd, \ offsetof(struct task_info, name##_delay_total), \ offsetof(struct task_info, name##_count), \ 0, \ + 0, \ modes} -#define END_FIELD {NULL, 0, 0, 0, 0, 0} +#define END_FIELD {NULL, 0, 0, 0, 0, 0, 0} /* Display mode types */ #define MODE_TYPE_ALL (0xFFFFFFFF) @@ -117,27 +119,35 @@ struct task_info { unsigned long long cpu_count; unsigned long long cpu_delay_total; unsigned long long cpu_delay_max; + struct __kernel_timespec cpu_delay_max_ts; unsigned long long blkio_count; unsigned long long blkio_delay_total; unsigned long long blkio_delay_max; + struct __kernel_timespec blkio_delay_max_ts; unsigned long long swapin_count; unsigned long long swapin_delay_total; unsigned long long swapin_delay_max; + struct __kernel_timespec swapin_delay_max_ts; unsigned long long freepages_count; unsigned long long freepages_delay_total; unsigned long long freepages_delay_max; + struct __kernel_timespec freepages_delay_max_ts; unsigned long long thrashing_count; unsigned long long thrashing_delay_total; unsigned long long thrashing_delay_max; + struct __kernel_timespec thrashing_delay_max_ts; unsigned long long compact_count; unsigned long long compact_delay_total; unsigned long long compact_delay_max; + struct __kernel_timespec compact_delay_max_ts; unsigned long long wpcopy_count; unsigned long long wpcopy_delay_total; unsigned long long wpcopy_delay_max; + struct __kernel_timespec wpcopy_delay_max_ts; unsigned long long irq_count; unsigned long long irq_delay_total; unsigned long long irq_delay_max; + struct __kernel_timespec irq_delay_max_ts; unsigned long long mem_count; unsigned long long mem_delay_total; }; @@ -158,6 +168,7 @@ struct field_desc { unsigned long total_offset; /* Offset of total delay in task_info */ unsigned long count_offset; /* Offset of count in task_info */ unsigned long max_offset; /* Offset of max delay in task_info */ + unsigned long max_ts_offset; /* Offset of max delay timestamp in task_info */ size_t supported_modes; /* Supported display modes */ }; @@ -283,7 +294,7 @@ static void usage(void) " -p, --pid=PID Monitor only the specified PID\n" " -C, --container=PATH Monitor the container at specified cgroup path\n" " -s, --sort=FIELD Sort by delay field (default: cpu)\n" - " -t, --type=FIELD Display only specified delay type with avg/max\n" + " -t, --type=FIELD Display only specified delay type with avg/max/timestamp\n" " -M, --memverbose Display memory detailed information\n"); exit(0); } @@ -749,27 +760,35 @@ static void fetch_and_fill_task_info(int pid, const char *comm) SET_TASK_STAT(task_count, cpu_count); SET_TASK_STAT(task_count, cpu_delay_total); SET_TASK_STAT(task_count, cpu_delay_max); + SET_TASK_STAT(task_count, cpu_delay_max_ts); SET_TASK_STAT(task_count, blkio_count); SET_TASK_STAT(task_count, blkio_delay_total); SET_TASK_STAT(task_count, blkio_delay_max); + SET_TASK_STAT(task_count, blkio_delay_max_ts); SET_TASK_STAT(task_count, swapin_count); SET_TASK_STAT(task_count, swapin_delay_total); SET_TASK_STAT(task_count, swapin_delay_max); + SET_TASK_STAT(task_count, swapin_delay_max_ts); SET_TASK_STAT(task_count, freepages_count); SET_TASK_STAT(task_count, freepages_delay_total); SET_TASK_STAT(task_count, freepages_delay_max); + SET_TASK_STAT(task_count, freepages_delay_max_ts); SET_TASK_STAT(task_count, thrashing_count); SET_TASK_STAT(task_count, thrashing_delay_total); SET_TASK_STAT(task_count, thrashing_delay_max); + SET_TASK_STAT(task_count, thrashing_delay_max_ts); SET_TASK_STAT(task_count, compact_count); SET_TASK_STAT(task_count, compact_delay_total); SET_TASK_STAT(task_count, compact_delay_max); + SET_TASK_STAT(task_count, compact_delay_max_ts); SET_TASK_STAT(task_count, wpcopy_count); SET_TASK_STAT(task_count, wpcopy_delay_total); SET_TASK_STAT(task_count, wpcopy_delay_max); + SET_TASK_STAT(task_count, wpcopy_delay_max_ts); SET_TASK_STAT(task_count, irq_count); SET_TASK_STAT(task_count, irq_delay_total); SET_TASK_STAT(task_count, irq_delay_max); + SET_TASK_STAT(task_count, irq_delay_max_ts); set_mem_count(&tasks[task_count]); set_mem_delay_total(&tasks[task_count]); task_count++; @@ -827,6 +846,41 @@ static double average_ms(unsigned long long total, unsigned long long count) return (double)total / 1000000.0 / count; } +/* + * Format __kernel_timespec to human readable string (YYYY-MM-DDTHH:MM:SS) + * Returns formatted string or "N/A" if timestamp is zero + */ +static const char *format_timespec64(struct __kernel_timespec *ts) +{ + static char buffer[32]; + time_t time_sec; + struct tm tm_info; + + /* Check if timestamp is zero (not set) */ + if (ts->tv_sec == 0 && ts->tv_nsec == 0) + return "N/A"; + + /* Avoid Y2038 truncation: check if timestamp fits in time_t on 32-bit platforms */ + if (sizeof(time_t) < sizeof(ts->tv_sec) && + ts->tv_sec > (__u64)((1ULL << (sizeof(time_t) * 8 - 1)) - 1)) + return "N/A"; + + time_sec = (time_t)ts->tv_sec; + + if (localtime_r(&time_sec, &tm_info) == NULL) + return "N/A"; + + snprintf(buffer, sizeof(buffer), "%04d-%02d-%02dT%02d:%02d:%02d", + tm_info.tm_year + 1900, + tm_info.tm_mon + 1, + tm_info.tm_mday, + tm_info.tm_hour, + tm_info.tm_min, + tm_info.tm_sec); + + return buffer; +} + /* Comparison function for sorting tasks */ static int compare_tasks(const void *a, const void *b) { @@ -853,13 +907,15 @@ static int compare_tasks(const void *a, const void *b) /* Get delay values for a specific field */ static void get_field_delay_values(const struct task_info *task, const struct field_desc *field, - double *avg_ms, double *max_ms) + double *avg_ms, double *max_ms, + struct __kernel_timespec *max_ts) { unsigned long long total, count, max; if (!field || !field->max_offset) { *avg_ms = 0; *max_ms = 0; + memset(max_ts, 0, sizeof(*max_ts)); return; } @@ -869,6 +925,11 @@ static void get_field_delay_values(const struct task_info *task, const struct fi max = *(unsigned long long *)((char *)task + field->max_offset); *max_ms = (double)max / 1000000.0; /* Convert nanoseconds to milliseconds */ + + if (field->max_ts_offset) + *max_ts = *(struct __kernel_timespec *)((char *)task + field->max_ts_offset); + else + memset(max_ts, 0, sizeof(*max_ts)); } /* Sort tasks by selected field */ @@ -1033,11 +1094,12 @@ static void display_results(int psi_ret) cfg.max_processes, get_name_by_field(cfg.sort_field)); if (cfg.display_mode == MODE_TYPE && cfg.type_field) { - /* Display mode for -t option: show only specified type with avg/max */ - suc &= BOOL_FPRINT(out, "%8s %8s %-17s %12s %12s\n", + /* Display mode for -t option: show only specified type with avg/max/timestamp */ + suc &= BOOL_FPRINT(out, "%8s %8s %-17s %12s %12s %20s\n", "PID", "TGID", "COMMAND", - "AVG(ms)", "MAX(ms)"); - suc &= BOOL_FPRINT(out, "----------------------------------------------------\n"); + "AVG(ms)", "MAX(ms)", "MAX_TIMESTAMP"); + suc &= BOOL_FPRINT(out, "--------------------------------------------------------"); + suc &= BOOL_FPRINT(out, "----------------------------------------\n"); } else { suc &= BOOL_FPRINT(out, "%8s %8s %-17s", "PID", "TGID", "COMMAND"); if (cfg.display_mode == MODE_MEMVERBOSE) { @@ -1064,11 +1126,13 @@ static void display_results(int psi_ret) tasks[i].pid, tasks[i].tgid, tasks[i].command); if (cfg.display_mode == MODE_TYPE && cfg.type_field) { double avg_ms, max_ms; + struct __kernel_timespec max_ts; - get_field_delay_values(&tasks[i], cfg.type_field, &avg_ms, &max_ms); + get_field_delay_values(&tasks[i], cfg.type_field, &avg_ms, + &max_ms, &max_ts); - suc &= BOOL_FPRINT(out, "%12.2f %12.2f\n", - avg_ms, max_ms); + suc &= BOOL_FPRINT(out, "%12.2f %12.2f %20s\n", + avg_ms, max_ms, format_timespec64(&max_ts)); } else if (cfg.display_mode == MODE_MEMVERBOSE) { suc &= BOOL_FPRINT(out, DELAY_FMT_MEMVERBOSE, TASK_AVG(tasks[i], mem), From 717fb332b1889157be2fa49c21896a26a77502a0 Mon Sep 17 00:00:00 2001 From: Wang Yaxin Date: Thu, 2 Jul 2026 21:00:00 +0800 Subject: [PATCH 381/562] delaytop: sort by max delay to highlight top latency processes When using -t/--type option, sort tasks by the maximum delay value of the selected type in descending order (largest delay first). This enables quickly identifying the top N processes with the highest delay spikes, which is essential for diagnosing latency problems by pinpointing which processes contributed most to system delays. Link: https://lore.kernel.org/20260702210000676TkC9mShguDS_34k8M6AtP@zte.com.cn Signed-off-by: Wang Yaxin Cc: Fan Yu Cc: Jonathan Corbet Cc: xu xin Cc: Yang Yang Signed-off-by: Andrew Morton --- Documentation/accounting/delay-accounting.rst | 43 +++++++++++ tools/accounting/delaytop.c | 71 ++++++++++++++++--- tools/accounting/getdelays.c | 5 ++ 3 files changed, 108 insertions(+), 11 deletions(-) diff --git a/Documentation/accounting/delay-accounting.rst b/Documentation/accounting/delay-accounting.rst index e209c46241b0..4bb2e72a6043 100644 --- a/Documentation/accounting/delay-accounting.rst +++ b/Documentation/accounting/delay-accounting.rst @@ -212,3 +212,46 @@ Advanced usage examples:: # ./delaytop -d secs Specify refresh interval as secs + + # ./delaytop -t type + Display only specified delay type with avg/max/timestamp + (rows sorted by MAX for that type, largest first) + + + +delaytop add delay_max fields to track the maximum delay value for each delay type +(cpu, blkio, irq, swapin, freepages, thrashing, compact, wpcopy) per task. + + bash# ./delaytop -t cpu + System Pressure Information: (avg10/avg60/avg300/total) + CPU some: 0.4%/ 0.2%/ 0.1%/ 220(ms) + CPU full: 0.0%/ 0.0%/ 0.0%/ 0(ms) + Memory full: 0.0%/ 0.0%/ 0.0%/ 0(ms) + Memory some: 0.0%/ 0.0%/ 0.0%/ 0(ms) + IO full: 0.0%/ 0.0%/ 0.0%/ 12(ms) + IO some: 0.0%/ 0.0%/ 0.0%/ 13(ms) + IRQ full: 0.0%/ 0.0%/ 0.0%/ 0(ms) + [q]quit + Top 20 processes (sorted by cpu MAX delay, largest first): + PID TGID COMMAND AVG(ms) MAX(ms) MAX_TIMESTAMP + ------------------------------------------------------------------------ + 9 9 kworker/0:0-eve 0.59 16.87 2026-05-27T13:32:39 + 30 30 kworker/2:0H-kb 2.87 11.36 2026-05-27T13:32:36 + 27 27 migration/2 1.05 9.51 2026-05-27T13:32:37 + 50 50 kworker/2:1-eve 0.50 9.13 2026-05-27T13:32:37 + 15 15 rcu_preempt 0.11 8.98 2026-05-27T13:32:37 + 1 1 init 0.17 7.12 2026-05-27T13:32:38 + 67 67 scsi_eh_0 1.20 4.23 2026-05-27T13:32:37 + 23 23 ksoftirqd/1 1.12 3.77 2026-05-27T13:32:36 + 3 3 pool_workqueue_ 0.72 3.55 2026-05-27T13:32:38 + 62 62 kworker/u20:2-a 0.49 3.03 2026-05-27T13:32:37 + 2 2 kthreadd 0.18 2.82 2026-05-27T13:32:37 + 11 11 kworker/0:1 1.42 2.76 2026-05-27T13:32:36 + 39 39 kworker/u20:0-a 0.10 2.71 2026-05-27T13:32:38 + 17 17 rcu_exp_gp_kthr 0.25 2.65 2026-05-27T13:32:37 + 66 66 kworker/u20:3-e 0.38 2.55 2026-05-27T13:32:37 + 20 20 cpuhp/0 0.53 2.51 2026-05-27T13:32:37 + 28 28 ksoftirqd/2 0.59 2.48 2026-05-27T13:32:37 + 55 55 kworker/u19:1 0.88 2.42 2026-05-27T13:32:37 + 13 13 kworker/R-mm_pe 1.18 2.35 2026-05-27T13:32:36 + 54 54 kworker/3:1-eve 0.14 2.20 2026-05-27T13:32:38 diff --git a/tools/accounting/delaytop.c b/tools/accounting/delaytop.c index 7e80f3875a91..af8a1e42a055 100644 --- a/tools/accounting/delaytop.c +++ b/tools/accounting/delaytop.c @@ -295,6 +295,7 @@ static void usage(void) " -C, --container=PATH Monitor the container at specified cgroup path\n" " -s, --sort=FIELD Sort by delay field (default: cpu)\n" " -t, --type=FIELD Display only specified delay type with avg/max/timestamp\n" + " (rows sorted by MAX for that type, largest first)\n" " -M, --memverbose Display memory detailed information\n"); exit(0); } @@ -838,6 +839,15 @@ static void get_task_delays(void) closedir(dir); } +static void field_delay_max_and_ts(const struct task_info *task, + const struct field_desc *field, + unsigned long long *max_ns, + struct __kernel_timespec *max_ts); +static void get_field_delay_values(const struct task_info *task, + const struct field_desc *field, + double *avg_ms, double *max_ms, + struct __kernel_timespec *max_ts); + /* Calculate average delay in milliseconds */ static double average_ms(unsigned long long total, unsigned long long count) { @@ -850,7 +860,7 @@ static double average_ms(unsigned long long total, unsigned long long count) * Format __kernel_timespec to human readable string (YYYY-MM-DDTHH:MM:SS) * Returns formatted string or "N/A" if timestamp is zero */ -static const char *format_timespec64(struct __kernel_timespec *ts) +static const char *format_kernel_timespec(struct __kernel_timespec *ts) { static char buffer[32]; time_t time_sec; @@ -891,6 +901,16 @@ static int compare_tasks(const void *a, const void *b) unsigned long long count1; unsigned long long count2; double avg1, avg2; + unsigned long long max1, max2; + + /* -t/--type: default sort by MAX column for the selected type (descending) */ + if (cfg.display_mode == MODE_TYPE && cfg.type_field) { + field_delay_max_and_ts(t1, cfg.type_field, &max1, NULL); + field_delay_max_and_ts(t2, cfg.type_field, &max2, NULL); + if (max1 != max2) + return max2 > max1 ? 1 : -1; + return 0; + } total1 = *(unsigned long long *)((char *)t1 + cfg.sort_field->total_offset); total2 = *(unsigned long long *)((char *)t2 + cfg.sort_field->total_offset); @@ -905,6 +925,28 @@ static int compare_tasks(const void *a, const void *b) return 0; } +/* Max delay (ns) and timestamp for field (shared by display and sort) */ +static void field_delay_max_and_ts(const struct task_info *task, const struct field_desc *field, + unsigned long long *max_ns, struct __kernel_timespec *max_ts) +{ + if (!field || !field->max_offset) { + *max_ns = 0; + if (max_ts) + memset(max_ts, 0, sizeof(*max_ts)); + return; + } + + *max_ns = *(unsigned long long *)((char *)task + field->max_offset); + + if (max_ts) { + if (field->max_ts_offset) + *max_ts = *(struct __kernel_timespec *)((char *)task + + field->max_ts_offset); + else + memset(max_ts, 0, sizeof(*max_ts)); + } +} + /* Get delay values for a specific field */ static void get_field_delay_values(const struct task_info *task, const struct field_desc *field, double *avg_ms, double *max_ms, @@ -923,13 +965,8 @@ static void get_field_delay_values(const struct task_info *task, const struct fi count = *(unsigned long long *)((char *)task + field->count_offset); *avg_ms = average_ms(total, count); - max = *(unsigned long long *)((char *)task + field->max_offset); + field_delay_max_and_ts(task, field, &max, max_ts); *max_ms = (double)max / 1000000.0; /* Convert nanoseconds to milliseconds */ - - if (field->max_ts_offset) - *max_ts = *(struct __kernel_timespec *)((char *)task + field->max_ts_offset); - else - memset(max_ts, 0, sizeof(*max_ts)); } /* Sort tasks by selected field */ @@ -1079,7 +1116,10 @@ static void display_results(int psi_ret) } /* Interacive command */ - suc &= BOOL_FPRINT(out, "[o]sort [M]memverbose [q]quit\n"); + if (cfg.display_mode == MODE_TYPE && cfg.type_field) + suc &= BOOL_FPRINT(out, "[q]quit\n"); + else + suc &= BOOL_FPRINT(out, "[o]sort [M]memverbose [q]quit\n"); if (sort_selected) { if (cfg.display_mode == MODE_MEMVERBOSE) suc &= BOOL_FPRINT(out, @@ -1090,8 +1130,13 @@ static void display_results(int psi_ret) } /* Task delay output */ - suc &= BOOL_FPRINT(out, "Top %d processes (sorted by %s delay):\n", - cfg.max_processes, get_name_by_field(cfg.sort_field)); + if (cfg.display_mode == MODE_TYPE && cfg.type_field) + suc &= BOOL_FPRINT(out, + "Top %d processes (sorted by %s MAX delay, largest first):\n", + cfg.max_processes, get_name_by_field(cfg.type_field)); + else + suc &= BOOL_FPRINT(out, "Top %d processes (sorted by %s delay):\n", + cfg.max_processes, get_name_by_field(cfg.sort_field)); if (cfg.display_mode == MODE_TYPE && cfg.type_field) { /* Display mode for -t option: show only specified type with avg/max/timestamp */ @@ -1132,7 +1177,7 @@ static void display_results(int psi_ret) &max_ms, &max_ts); suc &= BOOL_FPRINT(out, "%12.2f %12.2f %20s\n", - avg_ms, max_ms, format_timespec64(&max_ts)); + avg_ms, max_ms, format_kernel_timespec(&max_ts)); } else if (cfg.display_mode == MODE_MEMVERBOSE) { suc &= BOOL_FPRINT(out, DELAY_FMT_MEMVERBOSE, TASK_AVG(tasks[i], mem), @@ -1201,9 +1246,13 @@ static void handle_keypress(char ch, int *running) } else { switch (ch) { case 'o': + if (cfg.display_mode == MODE_TYPE) + break; sort_selected = 1; break; case 'M': + if (cfg.display_mode == MODE_TYPE) + break; toggle_display_mode(); for (field = sort_fields; field->name != NULL; field++) { if (field->supported_modes & cfg.display_mode) { diff --git a/tools/accounting/getdelays.c b/tools/accounting/getdelays.c index caa5fe9dd573..6ac30d4f96f7 100644 --- a/tools/accounting/getdelays.c +++ b/tools/accounting/getdelays.c @@ -235,6 +235,11 @@ static const char *format_timespec(struct __kernel_timespec *ts) if (ts->tv_sec == 0 && ts->tv_nsec == 0) return "N/A"; + /* Avoid Y2038 truncation on 32-bit platforms */ + if (sizeof(time_sec) < sizeof(ts->tv_sec) && + ts->tv_sec > (__u64)((1ULL << (sizeof(time_sec) * 8 - 1)) - 1)) + return "N/A"; + time_sec = ts->tv_sec; /* Use thread-safe localtime_r */ From b29d6c0005a58c5165146052f6286ccda003730c Mon Sep 17 00:00:00 2001 From: Jiaming Zhang Date: Thu, 2 Jul 2026 17:05:07 +0800 Subject: [PATCH 382/562] ocfs2: fix hung task in orphan recovery A crafted OCFS2 image with corrupted orphan-directory extent metadata can make umount hang. During unmount, ocfs2_recovery_disable() waits for the ocfs2_complete_recovery work item to finish. The worker scans the orphan directory through ocfs2_queue_orphans() and ocfs2_dir_foreach(). If ocfs2_read_dir_block() fails on a corrupted directory block, ocfs2_dir_foreach_blk_el() skips the block and continues walking. On a badly corrupted directory this can keep orphan recovery busy for a long time, leaving umount blocked while flushing osb->ocfs2_wq. Return the read error immediately for full directory scans and propagate the error from ocfs2_dir_foreach(). When ocfs2_empty_dir() receives such an error, report the directory as non-empty so unlink/rmdir does not proceed on an unreadable directory. Link: https://lore.kernel.org/20260702090507.446517-1-r772577952@gmail.com Closes: https://lore.kernel.org/lkml/CANypQFbWH76Y6LWHEwAvTP7aQL04uMJ=dDyL6YDmxa3fv3Tyjg@mail.gmail.com/ Assisted-by: Codex:gpt-5.5-xhigh Signed-off-by: Jiaming Zhang Reviewed-by: Joseph Qi Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao Signed-off-by: Andrew Morton --- fs/ocfs2/dir.c | 20 ++++++++++++++------ fs/ocfs2/namei.c | 11 ++++++++--- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/fs/ocfs2/dir.c b/fs/ocfs2/dir.c index 8e6b03238327..baf3eca7b4e4 100644 --- a/fs/ocfs2/dir.c +++ b/fs/ocfs2/dir.c @@ -1866,6 +1866,7 @@ static int ocfs2_dir_foreach_blk_el(struct inode *inode, struct super_block * sb = inode->i_sb; unsigned int ra_sectors = 16; int stored = 0; + int ret; bh = NULL; @@ -1873,9 +1874,13 @@ static int ocfs2_dir_foreach_blk_el(struct inode *inode, while (ctx->pos < i_size_read(inode)) { blk = ctx->pos >> sb->s_blocksize_bits; - if (ocfs2_read_dir_block(inode, blk, &bh, 0)) { + ret = ocfs2_read_dir_block(inode, blk, &bh, 0); + if (ret) { + if (persist) + return ret; /* Skip the corrupt dirblock and keep trying */ ctx->pos += sb->s_blocksize - offset; + offset = 0; continue; } @@ -1969,8 +1974,7 @@ static int ocfs2_dir_foreach_blk(struct inode *inode, u64 *f_version, int ocfs2_dir_foreach(struct inode *inode, struct dir_context *ctx) { u64 version = inode_query_iversion(inode); - ocfs2_dir_foreach_blk(inode, &version, ctx, true); - return 0; + return ocfs2_dir_foreach_blk(inode, &version, ctx, true); } /* @@ -2167,7 +2171,7 @@ static int ocfs2_empty_dir_dx(struct inode *inode, /* * routine to check that the specified directory is empty (for rmdir) * - * Returns 1 if dir is empty, zero otherwise. + * Returns 1 if dir is empty, zero if not, and a negative errno on error. * * XXX: This is a performance problem for unindexed directories. */ @@ -2180,8 +2184,10 @@ int ocfs2_empty_dir(struct inode *inode) if (ocfs2_dir_indexed(inode)) { ret = ocfs2_empty_dir_dx(inode, &priv); - if (ret) + if (ret) { mlog_errno(ret); + return ret; + } /* * We still run ocfs2_dir_foreach to get the checks * for "." and "..". @@ -2189,8 +2195,10 @@ int ocfs2_empty_dir(struct inode *inode) } ret = ocfs2_dir_foreach(inode, &priv.ctx); - if (ret) + if (ret) { mlog_errno(ret); + return ret; + } if (!priv.seen_dot || !priv.seen_dot_dot) { mlog(ML_ERROR, "bad directory (dir #%llu) - no `.' or `..'\n", diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c index 4cfd7b3d3e1a..8368a7f3d4a2 100644 --- a/fs/ocfs2/namei.c +++ b/fs/ocfs2/namei.c @@ -945,7 +945,10 @@ static int ocfs2_unlink(struct inode *dir, child_locked = 1; if (S_ISDIR(inode->i_mode)) { - if (inode->i_nlink != 2 || !ocfs2_empty_dir(inode)) { + status = ocfs2_empty_dir(inode); + if (status < 0) + goto leave; + if (inode->i_nlink != 2 || !status) { status = -ENOTEMPTY; goto leave; } @@ -1499,8 +1502,10 @@ static int ocfs2_rename(struct mnt_idmap *idmap, if (target_exists) { if (S_ISDIR(new_inode->i_mode)) { - if (new_inode->i_nlink != 2 || - !ocfs2_empty_dir(new_inode)) { + status = ocfs2_empty_dir(new_inode); + if (status < 0) + goto bail; + if (new_inode->i_nlink != 2 || !status) { status = -ENOTEMPTY; goto bail; } From 6f452ae1eb1c9ce900cb12108a45a2266b8441f3 Mon Sep 17 00:00:00 2001 From: Michael Byczkowski Date: Mon, 1 Jun 2026 17:44:09 -0700 Subject: [PATCH 383/562] pps: pps-gpio: split IRQ handler into hardirq timestamper + threaded handler Split the pps-gpio interrupt handler into a primary (hardirq) handler that captures the PPS timestamp at interrupt entry, and a threaded handler that processes the event. This produces the same two-part handler structure on both PREEMPT_RT and non-RT kernels. On non-RT kernels the threaded portion runs immediately after the primary, with no behavioral change compared to the previous single-handler implementation. On PREEMPT_RT, where interrupt handlers are force-threaded by default, the previous single-handler implementation captured the timestamp inside the threaded portion, after IRQ-thread scheduling delay. With the split, the timestamp is captured in true hardirq context as it is on non-RT kernels, eliminating a significant source of PPS jitter on RT systems. Link: https://lore.kernel.org/2e32729029fbf6977ecf04665eb00f2efd3e2c17.1780359378.git.calvin@wbinvd.org Signed-off-by: Michael Byczkowski Signed-off-by: Calvin Owens Reviewed-by: Sebastian Andrzej Siewior Tested-by: Michael Byczkowski Tested-by: Calvin Owens Acked-by: Rodolfo Giometti Signed-off-by: Andrew Morton --- drivers/pps/clients/pps-gpio.c | 37 +++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/drivers/pps/clients/pps-gpio.c b/drivers/pps/clients/pps-gpio.c index 935da68610c7..ed111621ee5f 100644 --- a/drivers/pps/clients/pps-gpio.c +++ b/drivers/pps/clients/pps-gpio.c @@ -35,33 +35,44 @@ struct pps_gpio_device_data { bool capture_clear; unsigned int echo_active_ms; /* PPS echo active duration */ unsigned long echo_timeout; /* timer timeout value in jiffies */ + struct pps_event_time ts; /* timestamp captured in hardirq */ }; /* * Report the PPS event */ -static irqreturn_t pps_gpio_irq_handler(int irq, void *data) +/* + * Primary hardirq handler -- runs in hardirq context even on PREEMPT_RT. + * Only captures the timestamp; all other work is deferred to the thread. + */ +static irqreturn_t pps_gpio_irq_hardirq(int irq, void *data) { - const struct pps_gpio_device_data *info; - struct pps_event_time ts; - int rising_edge; + struct pps_gpio_device_data *info = data; + + pps_get_ts(&info->ts); - /* Get the time stamp first */ - pps_get_ts(&ts); + return IRQ_WAKE_THREAD; +} - info = data; +/* + * Threaded handler -- processes the PPS event using the timestamp + * captured in hardirq context above. + */ +static irqreturn_t pps_gpio_irq_thread(int irq, void *data) +{ + struct pps_gpio_device_data *info = data; + int rising_edge; - /* Small trick to bypass the check on edge's direction when capture_clear is unset */ rising_edge = info->capture_clear ? gpiod_get_value(info->gpio_pin) : !info->assert_falling_edge; if ((rising_edge && !info->assert_falling_edge) || (!rising_edge && info->assert_falling_edge)) - pps_event(info->pps, &ts, PPS_CAPTUREASSERT, data); + pps_event(info->pps, &info->ts, PPS_CAPTUREASSERT, data); else if (info->capture_clear && ((rising_edge && info->assert_falling_edge) || (!rising_edge && !info->assert_falling_edge))) - pps_event(info->pps, &ts, PPS_CAPTURECLEAR, data); + pps_event(info->pps, &info->ts, PPS_CAPTURECLEAR, data); else dev_warn_ratelimited(&info->pps->dev, "IRQ did not trigger any PPS event\n"); @@ -210,8 +221,10 @@ static int pps_gpio_probe(struct platform_device *pdev) } /* register IRQ interrupt handler */ - ret = request_irq(data->irq, pps_gpio_irq_handler, - get_irqf_trigger_flags(data), data->info.name, data); + ret = request_threaded_irq(data->irq, + pps_gpio_irq_hardirq, pps_gpio_irq_thread, + get_irqf_trigger_flags(data) | IRQF_ONESHOT, + data->info.name, data); if (ret) { pps_unregister_source(data->pps); dev_err(dev, "failed to acquire IRQ %d\n", data->irq); From 8f5d089e0853853c2e542f71eb537c7a57b77260 Mon Sep 17 00:00:00 2001 From: Kir Chou Date: Fri, 3 Jul 2026 14:00:59 +0900 Subject: [PATCH 384/562] lib/random32: convert selftest to KUnit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert the existing prandom selftest (lib/random32.c) to use the KUnit framework (lib/tests/random32_kunit.c). Unlike typical KUnit tests, this file is directly #included into lib/random32.c. The new test: - Removes the legacy CONFIG_RANDOM32_SELFTEST from lib/random32.c. - Adds CONFIG_PRANDOM_KUNIT_TEST (defaulting to KUNIT_ALL_TESTS). - Moves the test logic to lib/tests/random32_kunit.c. This commit is verified by `./tools/testing/kunit/kunit.py run` with the .kunit/.kunitconfig: CONFIG_KUNIT=y CONFIG_PRANDOM_KUNIT_TEST=y Link: https://lore.kernel.org/20260703050100.23944-1-note351@hotmail.com Signed-off-by: Kir Chou Reviewed-by: David Gow Cc: Brendan Higgins Cc: David S. Miller Cc: Eric Dumazet Cc: Geert Uytterhoeven Cc: Jakub Kacinski Cc: Kuan-Wei Chiu Cc: Paolo Abeni Cc: Simon Horman Cc: Thomas Weißschuh Signed-off-by: Andrew Morton --- lib/Kconfig | 6 -- lib/Kconfig.debug | 9 ++ lib/random32.c | 184 ++----------------------------------- lib/tests/Makefile | 1 + lib/tests/random32_kunit.c | 182 ++++++++++++++++++++++++++++++++++++ 5 files changed, 202 insertions(+), 180 deletions(-) create mode 100644 lib/tests/random32_kunit.c diff --git a/lib/Kconfig b/lib/Kconfig index 55748b68714e..4e6b34c3346d 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -159,12 +159,6 @@ config AUDIT_COMPAT_GENERIC depends on AUDIT_GENERIC && AUDIT_ARCH_COMPAT_GENERIC && COMPAT default y -config RANDOM32_SELFTEST - bool "PRNG perform self test on init" - help - This option enables the 32 bit PRNG library functions to perform a - self test on initialization. - # # compression support is select'ed if needed # diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 311b99790416..009968c44de1 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -3548,6 +3548,15 @@ config GLOB_KUNIT_TEST If unsure, say N +config PRANDOM_KUNIT_TEST + tristate "KUnit test for prandom" if !KUNIT_ALL_TESTS + depends on KUNIT + default KUNIT_ALL_TESTS + help + Enable this option to test the prandom functions at runtime. + + If unsure, say N + endif # RUNTIME_TESTING_MENU config ARCH_USE_MEMTEST diff --git a/lib/random32.c b/lib/random32.c index 24e7acd9343f..dad90219c351 100644 --- a/lib/random32.c +++ b/lib/random32.c @@ -41,6 +41,7 @@ #include #include #include +#include /** * prandom_u32_state - seeded pseudo-random number generator. @@ -92,7 +93,14 @@ void prandom_bytes_state(struct rnd_state *state, void *buf, size_t bytes) } EXPORT_SYMBOL(prandom_bytes_state); -static void prandom_warmup(struct rnd_state *state) +/* + * Only declared here so that it has a prototype when made + * non-static for KUnit testing (avoids -Wmissing-prototypes). + */ +#if IS_ENABLED(CONFIG_KUNIT) +void prandom_warmup(struct rnd_state *state); +#endif +VISIBLE_IF_KUNIT void prandom_warmup(struct rnd_state *state) { /* Calling RNG ten times to satisfy recurrence condition */ prandom_u32_state(state); @@ -106,6 +114,7 @@ static void prandom_warmup(struct rnd_state *state) prandom_u32_state(state); prandom_u32_state(state); } +EXPORT_SYMBOL_IF_KUNIT(prandom_warmup); void prandom_seed_full_state(struct rnd_state __percpu *pcpu_state) { @@ -125,176 +134,3 @@ void prandom_seed_full_state(struct rnd_state __percpu *pcpu_state) } } EXPORT_SYMBOL(prandom_seed_full_state); - -#ifdef CONFIG_RANDOM32_SELFTEST -static struct prandom_test1 { - u32 seed; - u32 result; -} test1[] = { - { 1U, 3484351685U }, - { 2U, 2623130059U }, - { 3U, 3125133893U }, - { 4U, 984847254U }, -}; - -static struct prandom_test2 { - u32 seed; - u32 iteration; - u32 result; -} test2[] = { - /* Test cases against taus113 from GSL library. */ - { 931557656U, 959U, 2975593782U }, - { 1339693295U, 876U, 3887776532U }, - { 1545556285U, 961U, 1615538833U }, - { 601730776U, 723U, 1776162651U }, - { 1027516047U, 687U, 511983079U }, - { 416526298U, 700U, 916156552U }, - { 1395522032U, 652U, 2222063676U }, - { 366221443U, 617U, 2992857763U }, - { 1539836965U, 714U, 3783265725U }, - { 556206671U, 994U, 799626459U }, - { 684907218U, 799U, 367789491U }, - { 2121230701U, 931U, 2115467001U }, - { 1668516451U, 644U, 3620590685U }, - { 768046066U, 883U, 2034077390U }, - { 1989159136U, 833U, 1195767305U }, - { 536585145U, 996U, 3577259204U }, - { 1008129373U, 642U, 1478080776U }, - { 1740775604U, 939U, 1264980372U }, - { 1967883163U, 508U, 10734624U }, - { 1923019697U, 730U, 3821419629U }, - { 442079932U, 560U, 3440032343U }, - { 1961302714U, 845U, 841962572U }, - { 2030205964U, 962U, 1325144227U }, - { 1160407529U, 507U, 240940858U }, - { 635482502U, 779U, 4200489746U }, - { 1252788931U, 699U, 867195434U }, - { 1961817131U, 719U, 668237657U }, - { 1071468216U, 983U, 917876630U }, - { 1281848367U, 932U, 1003100039U }, - { 582537119U, 780U, 1127273778U }, - { 1973672777U, 853U, 1071368872U }, - { 1896756996U, 762U, 1127851055U }, - { 847917054U, 500U, 1717499075U }, - { 1240520510U, 951U, 2849576657U }, - { 1685071682U, 567U, 1961810396U }, - { 1516232129U, 557U, 3173877U }, - { 1208118903U, 612U, 1613145022U }, - { 1817269927U, 693U, 4279122573U }, - { 1510091701U, 717U, 638191229U }, - { 365916850U, 807U, 600424314U }, - { 399324359U, 702U, 1803598116U }, - { 1318480274U, 779U, 2074237022U }, - { 697758115U, 840U, 1483639402U }, - { 1696507773U, 840U, 577415447U }, - { 2081979121U, 981U, 3041486449U }, - { 955646687U, 742U, 3846494357U }, - { 1250683506U, 749U, 836419859U }, - { 595003102U, 534U, 366794109U }, - { 47485338U, 558U, 3521120834U }, - { 619433479U, 610U, 3991783875U }, - { 704096520U, 518U, 4139493852U }, - { 1712224984U, 606U, 2393312003U }, - { 1318233152U, 922U, 3880361134U }, - { 855572992U, 761U, 1472974787U }, - { 64721421U, 703U, 683860550U }, - { 678931758U, 840U, 380616043U }, - { 692711973U, 778U, 1382361947U }, - { 677703619U, 530U, 2826914161U }, - { 92393223U, 586U, 1522128471U }, - { 1222592920U, 743U, 3466726667U }, - { 358288986U, 695U, 1091956998U }, - { 1935056945U, 958U, 514864477U }, - { 735675993U, 990U, 1294239989U }, - { 1560089402U, 897U, 2238551287U }, - { 70616361U, 829U, 22483098U }, - { 368234700U, 731U, 2913875084U }, - { 20221190U, 879U, 1564152970U }, - { 539444654U, 682U, 1835141259U }, - { 1314987297U, 840U, 1801114136U }, - { 2019295544U, 645U, 3286438930U }, - { 469023838U, 716U, 1637918202U }, - { 1843754496U, 653U, 2562092152U }, - { 400672036U, 809U, 4264212785U }, - { 404722249U, 965U, 2704116999U }, - { 600702209U, 758U, 584979986U }, - { 519953954U, 667U, 2574436237U }, - { 1658071126U, 694U, 2214569490U }, - { 420480037U, 749U, 3430010866U }, - { 690103647U, 969U, 3700758083U }, - { 1029424799U, 937U, 3787746841U }, - { 2012608669U, 506U, 3362628973U }, - { 1535432887U, 998U, 42610943U }, - { 1330635533U, 857U, 3040806504U }, - { 1223800550U, 539U, 3954229517U }, - { 1322411537U, 680U, 3223250324U }, - { 1877847898U, 945U, 2915147143U }, - { 1646356099U, 874U, 965988280U }, - { 805687536U, 744U, 4032277920U }, - { 1948093210U, 633U, 1346597684U }, - { 392609744U, 783U, 1636083295U }, - { 690241304U, 770U, 1201031298U }, - { 1360302965U, 696U, 1665394461U }, - { 1220090946U, 780U, 1316922812U }, - { 447092251U, 500U, 3438743375U }, - { 1613868791U, 592U, 828546883U }, - { 523430951U, 548U, 2552392304U }, - { 726692899U, 810U, 1656872867U }, - { 1364340021U, 836U, 3710513486U }, - { 1986257729U, 931U, 935013962U }, - { 407983964U, 921U, 728767059U }, -}; - -static void prandom_state_selftest_seed(struct rnd_state *state, u32 seed) -{ -#define LCG(x) ((x) * 69069U) /* super-duper LCG */ - state->s1 = __seed(LCG(seed), 2U); - state->s2 = __seed(LCG(state->s1), 8U); - state->s3 = __seed(LCG(state->s2), 16U); - state->s4 = __seed(LCG(state->s3), 128U); -} - -static int __init prandom_state_selftest(void) -{ - int i, j, errors = 0, runs = 0; - bool error = false; - - for (i = 0; i < ARRAY_SIZE(test1); i++) { - struct rnd_state state; - - prandom_state_selftest_seed(&state, test1[i].seed); - prandom_warmup(&state); - - if (test1[i].result != prandom_u32_state(&state)) - error = true; - } - - if (error) - pr_warn("prandom: seed boundary self test failed\n"); - else - pr_info("prandom: seed boundary self test passed\n"); - - for (i = 0; i < ARRAY_SIZE(test2); i++) { - struct rnd_state state; - - prandom_state_selftest_seed(&state, test2[i].seed); - prandom_warmup(&state); - - for (j = 0; j < test2[i].iteration - 1; j++) - prandom_u32_state(&state); - - if (test2[i].result != prandom_u32_state(&state)) - errors++; - - runs++; - cond_resched(); - } - - if (errors) - pr_warn("prandom: %d/%d self tests failed\n", errors, runs); - else - pr_info("prandom: %d self tests passed\n", runs); - return 0; -} -core_initcall(prandom_state_selftest); -#endif diff --git a/lib/tests/Makefile b/lib/tests/Makefile index 4ead57602eac..c045b82169bc 100644 --- a/lib/tests/Makefile +++ b/lib/tests/Makefile @@ -43,6 +43,7 @@ CFLAGS_overflow_kunit.o = $(call cc-disable-warning, tautological-constant-out-o obj-$(CONFIG_OVERFLOW_KUNIT_TEST) += overflow_kunit.o # GCC < 12.1 can miscompile errptr() test when branch profiling is enabled. CFLAGS_printf_kunit.o += -DDISABLE_BRANCH_PROFILING +obj-$(CONFIG_PRANDOM_KUNIT_TEST) += random32_kunit.o obj-$(CONFIG_PRINTF_KUNIT_TEST) += printf_kunit.o obj-$(CONFIG_RANDSTRUCT_KUNIT_TEST) += randstruct_kunit.o obj-$(CONFIG_SCANF_KUNIT_TEST) += scanf_kunit.o diff --git a/lib/tests/random32_kunit.c b/lib/tests/random32_kunit.c new file mode 100644 index 000000000000..0b4af2b09c01 --- /dev/null +++ b/lib/tests/random32_kunit.c @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Test cases for random32 functions. + */ + +#include +#include + +/* prandom_warmup() is static in lib/random32.c; exposed for testing only. */ +void prandom_warmup(struct rnd_state *state); + +static const struct prandom_test1 { + u32 seed; + u32 result; +} test1[] = { + { 1U, 3484351685U }, + { 2U, 2623130059U }, + { 3U, 3125133893U }, + { 4U, 984847254U }, +}; + +static const struct prandom_test2 { + u32 seed; + u32 iteration; + u32 result; +} test2[] = { + /* Test cases against taus113 from GSL library. */ + { 931557656U, 959U, 2975593782U }, + { 1339693295U, 876U, 3887776532U }, + { 1545556285U, 961U, 1615538833U }, + { 601730776U, 723U, 1776162651U }, + { 1027516047U, 687U, 511983079U }, + { 416526298U, 700U, 916156552U }, + { 1395522032U, 652U, 2222063676U }, + { 366221443U, 617U, 2992857763U }, + { 1539836965U, 714U, 3783265725U }, + { 556206671U, 994U, 799626459U }, + { 684907218U, 799U, 367789491U }, + { 2121230701U, 931U, 2115467001U }, + { 1668516451U, 644U, 3620590685U }, + { 768046066U, 883U, 2034077390U }, + { 1989159136U, 833U, 1195767305U }, + { 536585145U, 996U, 3577259204U }, + { 1008129373U, 642U, 1478080776U }, + { 1740775604U, 939U, 1264980372U }, + { 1967883163U, 508U, 10734624U }, + { 1923019697U, 730U, 3821419629U }, + { 442079932U, 560U, 3440032343U }, + { 1961302714U, 845U, 841962572U }, + { 2030205964U, 962U, 1325144227U }, + { 1160407529U, 507U, 240940858U }, + { 635482502U, 779U, 4200489746U }, + { 1252788931U, 699U, 867195434U }, + { 1961817131U, 719U, 668237657U }, + { 1071468216U, 983U, 917876630U }, + { 1281848367U, 932U, 1003100039U }, + { 582537119U, 780U, 1127273778U }, + { 1973672777U, 853U, 1071368872U }, + { 1896756996U, 762U, 1127851055U }, + { 847917054U, 500U, 1717499075U }, + { 1240520510U, 951U, 2849576657U }, + { 1685071682U, 567U, 1961810396U }, + { 1516232129U, 557U, 3173877U }, + { 1208118903U, 612U, 1613145022U }, + { 1817269927U, 693U, 4279122573U }, + { 1510091701U, 717U, 638191229U }, + { 365916850U, 807U, 600424314U }, + { 399324359U, 702U, 1803598116U }, + { 1318480274U, 779U, 2074237022U }, + { 697758115U, 840U, 1483639402U }, + { 1696507773U, 840U, 577415447U }, + { 2081979121U, 981U, 3041486449U }, + { 955646687U, 742U, 3846494357U }, + { 1250683506U, 749U, 836419859U }, + { 595003102U, 534U, 366794109U }, + { 47485338U, 558U, 3521120834U }, + { 619433479U, 610U, 3991783875U }, + { 704096520U, 518U, 4139493852U }, + { 1712224984U, 606U, 2393312003U }, + { 1318233152U, 922U, 3880361134U }, + { 855572992U, 761U, 1472974787U }, + { 64721421U, 703U, 683860550U }, + { 678931758U, 840U, 380616043U }, + { 692711973U, 778U, 1382361947U }, + { 677703619U, 530U, 2826914161U }, + { 92393223U, 586U, 1522128471U }, + { 1222592920U, 743U, 3466726667U }, + { 358288986U, 695U, 1091956998U }, + { 1935056945U, 958U, 514864477U }, + { 735675993U, 990U, 1294239989U }, + { 1560089402U, 897U, 2238551287U }, + { 70616361U, 829U, 22483098U }, + { 368234700U, 731U, 2913875084U }, + { 20221190U, 879U, 1564152970U }, + { 539444654U, 682U, 1835141259U }, + { 1314987297U, 840U, 1801114136U }, + { 2019295544U, 645U, 3286438930U }, + { 469023838U, 716U, 1637918202U }, + { 1843754496U, 653U, 2562092152U }, + { 400672036U, 809U, 4264212785U }, + { 404722249U, 965U, 2704116999U }, + { 600702209U, 758U, 584979986U }, + { 519953954U, 667U, 2574436237U }, + { 1658071126U, 694U, 2214569490U }, + { 420480037U, 749U, 3430010866U }, + { 690103647U, 969U, 3700758083U }, + { 1029424799U, 937U, 3787746841U }, + { 2012608669U, 506U, 3362628973U }, + { 1535432887U, 998U, 42610943U }, + { 1330635533U, 857U, 3040806504U }, + { 1223800550U, 539U, 3954229517U }, + { 1322411537U, 680U, 3223250324U }, + { 1877847898U, 945U, 2915147143U }, + { 1646356099U, 874U, 965988280U }, + { 805687536U, 744U, 4032277920U }, + { 1948093210U, 633U, 1346597684U }, + { 392609744U, 783U, 1636083295U }, + { 690241304U, 770U, 1201031298U }, + { 1360302965U, 696U, 1665394461U }, + { 1220090946U, 780U, 1316922812U }, + { 447092251U, 500U, 3438743375U }, + { 1613868791U, 592U, 828546883U }, + { 523430951U, 548U, 2552392304U }, + { 726692899U, 810U, 1656872867U }, + { 1364340021U, 836U, 3710513486U }, + { 1986257729U, 931U, 935013962U }, + { 407983964U, 921U, 728767059U }, +}; + +static void prandom_state_test_seed(struct rnd_state *state, u32 seed) +{ +#define LCG(x) ((x) * 69069U) /* super-duper LCG */ + state->s1 = __seed(LCG(seed), 2U); + state->s2 = __seed(LCG(state->s1), 8U); + state->s3 = __seed(LCG(state->s2), 16U); + state->s4 = __seed(LCG(state->s3), 128U); +} + +static void test_prandom_seed_boundary(struct kunit *test) +{ + int i; + struct rnd_state state; + + for (i = 0; i < ARRAY_SIZE(test1); i++) { + prandom_state_test_seed(&state, test1[i].seed); + prandom_warmup(&state); + KUNIT_EXPECT_EQ(test, test1[i].result, prandom_u32_state(&state)); + } +} + +static void test_prandom_taus113(struct kunit *test) +{ + int i, j; + struct rnd_state state; + + for (i = 0; i < ARRAY_SIZE(test2); i++) { + prandom_state_test_seed(&state, test2[i].seed); + prandom_warmup(&state); + + for (j = 0; j < test2[i].iteration - 1; j++) + prandom_u32_state(&state); + + KUNIT_EXPECT_EQ(test, test2[i].result, prandom_u32_state(&state)); + } +} + +static struct kunit_case prandom_test_cases[] = { + KUNIT_CASE(test_prandom_seed_boundary), + KUNIT_CASE(test_prandom_taus113), + {} +}; + +static struct kunit_suite prandom_test_suite = { + .name = "prandom", + .test_cases = prandom_test_cases, +}; + +kunit_test_suite(prandom_test_suite); + +MODULE_DESCRIPTION("KUnit test for prandom"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); From 1502b415cc00ef25096400ab2779d5f1068652da Mon Sep 17 00:00:00 2001 From: Bradley Morgan Date: Sun, 5 Jul 2026 16:41:23 +0000 Subject: [PATCH 385/562] panic: fix va_list reuse in panic_try_force_cpu() vsnprintf() consumes the caller's va_list. When the redirect fails, vpanic() reuses it for the panic message, which is undefined behavior. Use va_copy(). Link: https://lore.kernel.org/20260705164123.18746-1-include@grrlz.net Signed-off-by: Bradley Morgan Cc: Petr Mladek Cc: Wang Jinchao Signed-off-by: Andrew Morton --- kernel/panic.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/kernel/panic.c b/kernel/panic.c index 213725b612aa..90e21f15fd7e 100644 --- a/kernel/panic.c +++ b/kernel/panic.c @@ -412,7 +412,12 @@ static bool panic_try_force_cpu(const char *fmt, va_list args) * fall back to static message for early boot panics or allocation failure. */ if (panic_force_buf) { - vsnprintf(panic_force_buf, PANIC_MSG_BUFSZ, fmt, args); + va_list ap; + + /* Do not consume args, the caller reuses it if we fail */ + va_copy(ap, args); + vsnprintf(panic_force_buf, PANIC_MSG_BUFSZ, fmt, ap); + va_end(ap); msg = panic_force_buf; } else { msg = "Redirected panic (buffer unavailable)"; From 2349accea9cd547f8aa91f8ea1a81cb84f13bb41 Mon Sep 17 00:00:00 2001 From: Calvin Owens Date: Fri, 12 Jun 2026 11:52:09 -0700 Subject: [PATCH 386/562] pps: don't try to wait for negative timeouts in PPS_FETCH If userspace passes a negative timeout to PPS_FETCH, it triggers a kernel splat from schedule_timeout(): schedule_timeout: wrong timeout value fffffffffff0bfb4 CPU: 17 UID: 0 PID: 4720 Comm: a.out Not tainted 7.1.0-rc5-x86-kvm-00150-g331d97e36b37 #1 PREEMPT_RT Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-20240910_120124-localhost 04/01/2014 Call Trace: dump_stack_lvl+0x4b/0x70 schedule_timeout+0xb7/0xe0 pps_cdev_pps_fetch.isra.0+0x93/0x150 pps_cdev_ioctl+0x70/0x310 __x64_sys_ioctl+0x7b/0xc0 do_syscall_64+0xb6/0xfc0 entry_SYSCALL_64_after_hwframe+0x4b/0x53 Here is a trivial reproducer that works with the PPS_CLIENT_KTIMER test device enabled in the kernel: #include #include #include #include #include #include int main() { struct pps_fdata fdata; int fd; fd = open("/dev/pps0", O_RDWR); if (fd == -1) err(1, "Failed to open /dev/pps0"); fdata.timeout.sec = -1; fdata.timeout.nsec = 0; if (ioctl(fd, PPS_FETCH, &fdata)) err(2, "PPS_FETCH failed"); close(fd); return 0; } Sashiko imagines this to be some sort of security problem, which is obviously really silly. But I think it is still worth fixing, so buggy userspace code can't trigger the splat. Silence the splat by using timespec64_to_jiffies(), which hard limits the timeout to LONG_MAX jiffies. To be safe, explicitly preserve the -ETIMEDOUT return value userspace sees today if it passes a negative timeout. If you really squint, this is still a slight behavior change in that there are "denormalized" combinations of tv_sec and tv_nsec which used to work but will now return -ETIMEDOUT. I can't imagine anybody will care about that... Link: https://lore.kernel.org/c5c97c3b3c9d66010382094fd538e59a38f4aacf.1781289959.git.calvin@wbinvd.org Fixes: eae9d2ba0cfc ("LinuxPPS: core support") Signed-off-by: Calvin Owens Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/cover.1779733602.git.calvin%40wbinvd.org?part=3 Acked-by: Rodolfo Giometti Cc: Greg Kroah-Hartman Signed-off-by: Andrew Morton --- drivers/pps/pps.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/pps/pps.c b/drivers/pps/pps.c index de1122bb69ea..7ecdd774a44b 100644 --- a/drivers/pps/pps.c +++ b/drivers/pps/pps.c @@ -66,13 +66,19 @@ static int pps_cdev_pps_fetch(struct pps_device *pps, struct pps_fdata *fdata) err = wait_event_interruptible(pps->queue, ev != pps->last_ev); else { + struct timespec64 ts; unsigned long ticks; dev_dbg(&pps->dev, "timeout %lld.%09d\n", (long long) fdata->timeout.sec, fdata->timeout.nsec); - ticks = fdata->timeout.sec * HZ; - ticks += fdata->timeout.nsec / (NSEC_PER_SEC / HZ); + + if (fdata->timeout.sec < 0) + return -ETIMEDOUT; + + ts.tv_sec = fdata->timeout.sec; + ts.tv_nsec = fdata->timeout.nsec; + ticks = timespec64_to_jiffies(&ts); if (ticks != 0) { err = wait_event_interruptible_timeout( From a1e4a999fa76f1b904326dc1ba869625b6d48f32 Mon Sep 17 00:00:00 2001 From: Calvin Owens Date: Wed, 3 Jun 2026 10:25:44 -0700 Subject: [PATCH 387/562] pps: don't allow PPS_KC_BIND on removed devices If userspace holds its file descriptor open, it can call PPS_KC_BIND on a device which has been unplugged, leaving pps_kc_hardpps_dev as a dangling pointer after close(). After that sequence, PPS_KC_BIND is broken until the system is rebooted, because the pointer comparison in pps_kc_bind() can never be true. calling pps_ktimer_init+0x0/0x1000 [pps_ktimer] @ 1081 initcall pps_ktimer_init+0x0/0x1000 [pps_ktimer] returned 0 after 811 usecs pps pps0: bound kernel consumer: edge=0x1 pps pps0: unbound kernel consumer on device removal pps pps0: bound kernel consumer: edge=0x1 calling pps_ktimer_init+0x0/0x1000 [pps_ktimer] @ 1085 initcall pps_ktimer_init+0x0/0x1000 [pps_ktimer] returned 0 after 340 usecs pps pps0: another kernel consumer is already bound Here is a short reproducer, which uses rmmod of the pps-ktimer testcase to simulate a device being unplugged: #include #include #include #include #include #include #include #include int main(void) { while (1) { int fd; if (system("insmod ./pps-ktimer.ko")) err(1, "insmod failed"); fd = open("/dev/pps0", O_RDWR); if (fd == -1) err(1, "open failed"); struct pps_bind_args args = { .tsformat = PPS_TSFMT_TSPEC, .edge = PPS_CAPTUREASSERT, .consumer = PPS_KC_HARDPPS, }; if (ioctl(fd, PPS_KC_BIND, &args)) err(1, "first PPS_KC_BIND failed"); if (system("rmmod pps-ktimer")) err(1, "rmmod failed"); if (ioctl(fd, PPS_KC_BIND, &args)) { if (errno != ENODEV) err(1, "second PPS_KC_BIND failed"); else puts("Got ENODEV, kernel is patched"); } close(fd); } } Fix this by setting a flag when the device is unplugged, returning -ENODEV from PPS_KC_BIND if the flag is set. For userspace to encounter this new behavior, it must do something which breaks the interface today, so this fix shouldn't cause any observable behavior change for working programs. Link: https://lore.kernel.org/672778c177ac9b6fdcb445e35c97ac4ca7d1149f.1780506611.git.calvin@wbinvd.org Signed-off-by: Calvin Owens Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/cover.1779733602.git.calvin%40wbinvd.org?part=1 Acked-by: Rodolfo Giometti Cc: Greg Kroah-Hartman Signed-off-by: Andrew Morton --- drivers/pps/kc.c | 10 ++++++++++ include/linux/pps_kernel.h | 1 + 2 files changed, 11 insertions(+) diff --git a/drivers/pps/kc.c b/drivers/pps/kc.c index fbd23295afd7..4f8fffa7edd6 100644 --- a/drivers/pps/kc.c +++ b/drivers/pps/kc.c @@ -38,6 +38,14 @@ int pps_kc_bind(struct pps_device *pps, struct pps_bind_args *bind_args) /* Check if another consumer is already bound */ spin_lock_irq(&pps_kc_hardpps_lock); + /* + * Don't allow PPS_KC_BIND on a removed device. + */ + if (pps->kc_removed) { + spin_unlock_irq(&pps_kc_hardpps_lock); + return -ENODEV; + } + if (bind_args->edge == 0) if (pps_kc_hardpps_dev == pps) { pps_kc_hardpps_mode = 0; @@ -79,6 +87,8 @@ int pps_kc_bind(struct pps_device *pps, struct pps_bind_args *bind_args) void pps_kc_remove(struct pps_device *pps) { spin_lock_irq(&pps_kc_hardpps_lock); + + pps->kc_removed = true; if (pps == pps_kc_hardpps_dev) { pps_kc_hardpps_mode = 0; pps_kc_hardpps_dev = NULL; diff --git a/include/linux/pps_kernel.h b/include/linux/pps_kernel.h index 9f088c9023b1..00b840970d56 100644 --- a/include/linux/pps_kernel.h +++ b/include/linux/pps_kernel.h @@ -60,6 +60,7 @@ struct pps_device { struct device dev; struct fasync_struct *async_queue; /* fasync method */ spinlock_t lock; + bool kc_removed; }; /* From e293deea9f463f2dea1bb2aa8c87597fb1ee8b54 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Fri, 22 May 2026 16:37:23 -0400 Subject: [PATCH 388/562] nfsd: Reset write verifier when async COPY writeback fails Async COPY captures nn->writeverf at request time and reports it to the client via CB_OFFLOAD after the worker kthread completes. When the post-copy vfs_fsync_range() or filemap_check_wb_err() in _nfsd_copy_file_range() reports an error, the worker correctly leaves NFSD4_COPY_F_COMMITTED clear so that CB_OFFLOAD encodes wr_stable_how as NFS_UNSTABLE, but the server's write verifier is not rotated. A client that receives NFS_UNSTABLE in CB_OFFLOAD follows up with COMMIT to make the copied data durable. With the verifier unchanged, COMMIT returns the same value the client just received via CB_OFFLOAD, and the client concludes the copy is durable -- silently dropping the data whose writeback in fact failed. This violates the UNSTABLE+COMMIT durability contract (RFC 7862 section 15.1, RFC 8881 section 18.32) and matches the bug just fixed in nfsd_vfs_write() and nfsd_commit(). Rotate nn->writeverf at the writeback-failure site. The async COPY worker has no svc_rqst, so commit_reset_write_verifier() is not available here; calling nfsd_reset_write_verifier() directly mirrors the trace-less reset already used by nfsd_file_check_write_error() for the same purpose. Filter out -EAGAIN and -ESTALE, matching commit_reset_write_verifier(), since neither indicates a durable-storage failure. Fixes: eac0b17a77fb ("NFSD add vfs_fsync after async copy is done") Cc: stable@vger.kernel.org Assisted-by: kres:claude-opus-4-7 Reviewed-by: Jeff Layton Link: https://patch.msgid.link/20260522203723.446841-1-cel@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfs4proc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c index 8561540ab2db..93fcaf90d6ae 100644 --- a/fs/nfsd/nfs4proc.c +++ b/fs/nfsd/nfs4proc.c @@ -1972,6 +1972,8 @@ static ssize_t _nfsd_copy_file_range(struct nfsd4_copy *copy, status = filemap_check_wb_err(dst->f_mapping, since); if (!status) set_bit(NFSD4_COPY_F_COMMITTED, ©->cp_flags); + else if (status != -EAGAIN && status != -ESTALE) + nfsd_reset_write_verifier(copy->cp_nn); } return bytes_copied; } From 908c781a2ce9e1cef6bb3859289b3f114bedbedd Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Fri, 22 May 2026 17:45:58 -0400 Subject: [PATCH 389/562] nfsd: sample writeback error cursor before async COPY loop _nfsd_copy_file_range() samples dst->f_wb_err into "since" after the copy loop, then uses it to detect writeback errors via filemap_check_wb_err() once vfs_fsync_range() returns. Because the nfsd_file cache reuses a single struct file across requests targeting the same inode, a concurrent COMMIT or stable WRITE on dst advances dst->f_wb_err to the current mapping->wb_err via file_check_and_advance_wb_err() during its own vfs_fsync_range(). If that advancement lands between the writeback error appearing in mapping->wb_err and the COPY worker sampling "since", the worker captures the already-advanced cursor, errseq_check() sees cur == since and returns zero, and NFSD4_COPY_F_COMMITTED is set even though writeback failed. CB_OFFLOAD then encodes wr_stable_how = FILE_SYNC4, the client treats the copied data as durable, and the failure becomes silent data loss. Sample since once at the start of the function. The cursor then reflects state in effect before this COPY issues any writes, and filemap_check_wb_err() detects any error that occurs during the copy regardless of which thread first observes it. This matches the pattern used by nfsd_vfs_write() and nfsd4_clone_file_range(). Closes: https://sashiko.dev/#/patchset/20260522194441.436065-1-cel@kernel.org?part=1 Fixes: 555dbf1a9aac ("nfsd: Replace use of rwsem with errseq_t") Cc: stable@vger.kernel.org Reviewed-by: Jeff Layton Link: https://patch.msgid.link/20260522214558.460859-1-cel@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfs4proc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c index 93fcaf90d6ae..3024d51d6fb7 100644 --- a/fs/nfsd/nfs4proc.c +++ b/fs/nfsd/nfs4proc.c @@ -1950,6 +1950,7 @@ static ssize_t _nfsd_copy_file_range(struct nfsd4_copy *copy, /* See RFC 7862 p.67: */ if (bytes_total == 0) bytes_total = ULLONG_MAX; + since = READ_ONCE(dst->f_wb_err); do { /* Only async copies can be stopped here */ if (kthread_should_stop()) @@ -1965,7 +1966,6 @@ static ssize_t _nfsd_copy_file_range(struct nfsd4_copy *copy, } while (bytes_total > 0 && nfsd4_copy_is_async(copy)); /* for a non-zero asynchronous copy do a commit of data */ if (nfsd4_copy_is_async(copy) && copy->cp_res.wr_bytes_written > 0) { - since = READ_ONCE(dst->f_wb_err); end = copy->cp_dst_pos + copy->cp_res.wr_bytes_written - 1; status = vfs_fsync_range(dst, copy->cp_dst_pos, end, 0); if (!status) From e349dc6a0ad1afddbecaf4bb43552e26787e2c57 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Sat, 23 May 2026 12:52:37 -0400 Subject: [PATCH 390/562] SUNRPC: Reject short RFC 4121 MIC tokens in gss_krb5_verify_mic_v2 gss_krb5_verify_mic_v2() reads the token ID at ptr[0..1], the flags byte at ptr[2], and padding at ptr[3..7], then passes ptr + GSS_KRB5_TOK_HDR_LEN and cksum_len to gss_krb5_mic_build_sg(). None of these accesses check read_token->len first. The minimum safe token size is GSS_KRB5_TOK_HDR_LEN (16) plus ctx->krb5e->cksum_len (12-24, depending on the enctype). All callers accept shorter tokens from the wire: - gss_unwrap_resp_integ() enforces only an upper bound (offset + len <= rcv_buf->len) before allocating mic.data = kmalloc(len) and passing it to gss_verify_mic(). A malicious NFS server can therefore supply a short checksum opaque, producing a small slab allocation that the Kerberos MIC verifier reads past. - gss_validate() enforces only len <= RPC_MAX_AUTH_SIZE (400) before passing the wire-supplied length to gss_validate_seqno_mic(), which constructs a mic xdr_netobj and calls gss_verify_mic(). - svcauth_gss_verify_header() enforces only checksum.len >= XDR_UNIT (4 bytes) before dispatching to gss_verify_mic(). - svcauth_gss_unwrap_integ() checks only that the checksum fits in gsd->gsd_scratch. Add a length guard at the top of gss_krb5_verify_mic_v2(), before any ptr[] access or scatterlist construction. Well-formed MIC tokens from gss_krb5_get_mic_v2() already have exactly GSS_KRB5_TOK_HDR_LEN + cksum_len bytes, so valid traffic is unaffected. Reported-by: Chris Mason Fixes: de9c17eb4a91 ("gss_krb5: add support for new token formats in rfc4121") Cc: stable@vger.kernel.org Reviewed-by: Jeff Layton Link: https://patch.msgid.link/20260523165237.510204-1-cel@kernel.org Signed-off-by: Chuck Lever --- net/sunrpc/auth_gss/gss_krb5_unseal.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/sunrpc/auth_gss/gss_krb5_unseal.c b/net/sunrpc/auth_gss/gss_krb5_unseal.c index b5fb70419faa..4d12d49434c2 100644 --- a/net/sunrpc/auth_gss/gss_krb5_unseal.c +++ b/net/sunrpc/auth_gss/gss_krb5_unseal.c @@ -89,6 +89,9 @@ gss_krb5_verify_mic_v2(struct krb5_ctx *ctx, struct xdr_buf *message_buffer, dprintk("RPC: %s\n", __func__); + if (read_token->len < GSS_KRB5_TOK_HDR_LEN + cksum_len) + return GSS_S_DEFECTIVE_TOKEN; + memcpy(&be16_ptr, (char *) ptr, 2); if (be16_to_cpu(be16_ptr) != KG2_TOK_MIC) return GSS_S_DEFECTIVE_TOKEN; From 41d7f97c7fa5e515fa62c00176cfc9426e81e496 Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Sat, 23 May 2026 21:02:10 -0400 Subject: [PATCH 391/562] SUNRPC: svcauth_gss: enforce krb5 token minimum length svcauth_gss_unwrap_priv() validates only an upper bound on the wire-supplied opaque length before handing the buffer to gss_unwrap(): if (len > xdr_stream_remaining(xdr)) goto unwrap_failed; offset = xdr_stream_pos(xdr); ... maj_stat = gss_unwrap(ctx, offset, offset + len, buf); The wire value `len` flows unchanged as the upper bound into the krb5 unwrap path, so a len in [0, 16] passes this check and is handed to gss_unwrap(). For a krb5 v2 context that lands in gss_krb5_unwrap_v2(), which reads the 16-byte RFC 4121 token header fields at ptr+4 and ptr+6 and then calls rotate_left() before any integrity check. With a sub-header length the header reads run past the token, and _rotate_left()'s `shift %= buf->len` path can divide by zero when buf->len has been driven to zero by the truncated token. A header-only token (len == 16) is equally invalid: with a non-zero RRC field and the opaque blob ending at the XDR buffer boundary, rotate_left() builds a zero-length subbuffer, reaching the same division. Reject the token at the server entry point before it reaches the krb5 unwrap core. A valid sealed RFC 4121 token must contain the 16-byte header plus at least some encrypted payload. Fix by adding a minimum-length check immediately after the existing upper-bound check: if (len <= GSS_KRB5_TOK_HDR_LEN) goto unwrap_failed; Fixes: 7c9fdcfb1b64 ("[PATCH] knfsd: svcrpc: gss: server-side implementation of rpcsec_gss privacy") Cc: stable@vger.kernel.org Assisted-by: kres (claude-opus-4-7) Signed-off-by: Chris Mason Reviewed-by: Jeff Layton Link: https://patch.msgid.link/20260524010213.557424-2-cel@kernel.org Signed-off-by: Chuck Lever --- net/sunrpc/auth_gss/svcauth_gss.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/sunrpc/auth_gss/svcauth_gss.c b/net/sunrpc/auth_gss/svcauth_gss.c index d14209031e18..8e8aceb31270 100644 --- a/net/sunrpc/auth_gss/svcauth_gss.c +++ b/net/sunrpc/auth_gss/svcauth_gss.c @@ -949,6 +949,8 @@ svcauth_gss_unwrap_priv(struct svc_rqst *rqstp, u32 seq, struct gss_ctx *ctx) } if (len > xdr_stream_remaining(xdr)) goto unwrap_failed; + if (len <= GSS_KRB5_TOK_HDR_LEN) + goto unwrap_failed; offset = xdr_stream_pos(xdr); saved_len = buf->len; From 4054ee478d776b386c61dceab20ee2447672cb7c Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Sat, 23 May 2026 21:02:11 -0400 Subject: [PATCH 392/562] SUNRPC: harden gss_unwrap_resp_priv length checks gss_unwrap_resp_priv() validates the RPCSEC_GSS opaque length with offset = (u8 *)(p) - (u8 *)head->iov_base; if (offset + opaque_len > rcv_buf->len) goto unwrap_failed; maj_stat = gss_unwrap(ctx->gc_gss_ctx, offset, offset + opaque_len, rcv_buf); Both operands are u32 and the sum is computed in u32. A reply with opaque_len near 0xffffffff makes offset + opaque_len wrap to a small value that is below rcv_buf->len, so the bound check passes and gss_unwrap() is called with end < begin. The check also lacks a lower bound, so any opaque_len in [0, GSS_KRB5_TOK_HDR_LEN) is accepted and forwarded to gss_krb5_unwrap_v2(), whose pre-decrypt header reads at ptr+4 and ptr+6 then run past the token. A krb5p NFS server returning a crafted RPCSEC_GSS reply can drive the client into out-of-bounds reads in gss_krb5_unwrap_v2() and the rotate_left() loop that follows. Fix by replacing the single combined check with three guards that are safe in u32 arithmetic and that enforce the RFC 4121 minimum outer token length: if (offset > rcv_buf->len) goto unwrap_failed; if (opaque_len > rcv_buf->len - offset) goto unwrap_failed; if (opaque_len < GSS_KRB5_TOK_HDR_LEN) goto unwrap_failed; The first guard makes the subtraction in the second guard unconditionally safe; offset is derived from a successful xdr_inline_decode() in the head kvec, so in practice it already satisfies the bound. The floor mirrors the server-side check added in commit 5b757c2e57a5 ("SUNRPC: svcauth_gss: enforce krb5 token minimum length"). Fixes: 2d2da60c63b6 ("RPCSEC_GSS: client-side privacy support") Cc: stable@vger.kernel.org Assisted-by: kres (claude-opus-4-7) Signed-off-by: Chris Mason Reviewed-by: Jeff Layton Link: https://patch.msgid.link/20260524010213.557424-3-cel@kernel.org Signed-off-by: Chuck Lever --- net/sunrpc/auth_gss/auth_gss.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net/sunrpc/auth_gss/auth_gss.c b/net/sunrpc/auth_gss/auth_gss.c index 9d3fb6848f40..8ddc65e894da 100644 --- a/net/sunrpc/auth_gss/auth_gss.c +++ b/net/sunrpc/auth_gss/auth_gss.c @@ -2072,7 +2072,11 @@ gss_unwrap_resp_priv(struct rpc_task *task, struct rpc_cred *cred, goto unwrap_failed; opaque_len = be32_to_cpup(p++); offset = (u8 *)(p) - (u8 *)head->iov_base; - if (offset + opaque_len > rcv_buf->len) + if (offset > rcv_buf->len) + goto unwrap_failed; + if (opaque_len > rcv_buf->len - offset) + goto unwrap_failed; + if (opaque_len <= GSS_KRB5_TOK_HDR_LEN) goto unwrap_failed; maj_stat = gss_unwrap(ctx->gc_gss_ctx, offset, From 0549cb2fc8aba024af1a36a0996457a1759dd6fa Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Sat, 23 May 2026 21:02:12 -0400 Subject: [PATCH 393/562] SUNRPC: xdr_buf_trim: clamp buf->len to avoid underflow xdr_buf_trim() trims `len` bytes from the tail of an xdr_buf by walking the tail, pages, and head iovecs. Each per-section step uses min_t() so it never removes more bytes than that section holds, but the final accounting at the fix_len label subtracts the total bytes actually consumed from buf->len without any clamp: fix_len: buf->len -= (len - trim); When the caller has set buf->len to a value smaller than the sum of the iov_lens, (len - trim) can exceed buf->len and the unsigned subtraction wraps to near UINT_MAX. gss_krb5_unwrap_v2() reaches xdr_buf_trim() in exactly that state: buf->head[0].iov_len -= GSS_KRB5_TOK_HDR_LEN + headskip; buf->len = len - (GSS_KRB5_TOK_HDR_LEN + headskip); xdr_buf_trim(buf, ec + GSS_KRB5_TOK_HDR_LEN + tailskip); buf->len is a small wire-derived value while the iov_lens are at page scale, so the per-section loops legitimately consume far more bytes than buf->len records. The wrapped buf->len then propagates as the authoritative stream bound into every downstream XDR decoder. Fix by clamping the decrement so buf->len bottoms out at zero: buf->len -= min_t(unsigned int, buf->len, len - trim); On the normal path where the iov_lens sum to buf->len, (len - trim) is always <= buf->len and the result is identical to before. No callers change behavior outside the underflow case. Fixes: 4c190e2f913f ("sunrpc: trim off trailing checksum before returning decrypted or integrity authenticated buffer") Cc: stable@vger.kernel.org Assisted-by: kres (claude-opus-4-7) Signed-off-by: Chris Mason Reviewed-by: Jeff Layton Link: https://patch.msgid.link/20260524010213.557424-4-cel@kernel.org Signed-off-by: Chuck Lever --- net/sunrpc/xdr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/sunrpc/xdr.c b/net/sunrpc/xdr.c index fa6a30b5f046..cb2ef428651f 100644 --- a/net/sunrpc/xdr.c +++ b/net/sunrpc/xdr.c @@ -2049,7 +2049,7 @@ void xdr_buf_trim(struct xdr_buf *buf, unsigned int len) trim -= cur; } fix_len: - buf->len -= (len - trim); + buf->len -= min_t(unsigned int, buf->len, len - trim); } EXPORT_SYMBOL_GPL(xdr_buf_trim); From 74cb3b84edb8c06373b6fdd867d49969a3401426 Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Sat, 23 May 2026 21:02:13 -0400 Subject: [PATCH 394/562] SUNRPC: harden gss_krb5_unwrap_v2 against short tokens gss_krb5_unwrap_v2() reads the EC and RRC header fields at ptr+4 and ptr+6 before validating that the token is at least GSS_KRB5_TOK_HDR_LEN (16) bytes long, and its rotate_left() helper passes buf->len - base to xdr_buf_subsegment() without verifying that base <= buf->len. When a caller hands in a sub-16-byte token, or a token whose declared len leaves base past the end of the buffer, three distinct failures follow: gss_krb5_unwrap_v2(offset, len, buf) ptr = buf->head[0].iov_base + offset ec = *(ptr + 4) /* OOB read on short head */ rrc = *(ptr + 6) /* OOB read on short head */ rotate_left(offset + 16, buf, rrc) xdr_buf_subsegment(buf, &subbuf, base, buf->len - base) /* u32 wrap when base > len */ _rotate_left(&subbuf, shift) shift %= buf->len /* divide-by-zero when base == len */ After decryption, the cleanup arithmetic has the same shape: movelen = min_t(unsigned int, buf->head[0].iov_len, len); movelen -= offset + GSS_KRB5_TOK_HDR_LEN + headskip; BUG_ON(offset + GSS_KRB5_TOK_HDR_LEN + headskip + movelen > buf->head[0].iov_len); The BUG_ON re-adds the value just subtracted, so it reduces to min(A, B) > A and is permanently false; it cannot catch the unsigned underflow of movelen, which then drives a ~UINT_MAX-byte memmove(). Add four defense-in-depth guards inside the unwrap core so it is safe regardless of what its callers validate: - reject tokens with len - offset < GSS_KRB5_TOK_HDR_LEN before touching ptr+4/ptr+6; - bail from rotate_left() when buf->len <= base, covering both the underflow and zero-length cases; - return early from _rotate_left() when buf->len is zero, so the shift %= buf->len modulo cannot fault; - replace the dead BUG_ON with a live check that returns GSS_S_DEFECTIVE_TOKEN before the movelen subtraction. Fixes: de9c17eb4a91 ("gss_krb5: add support for new token formats in rfc4121") Cc: stable@vger.kernel.org Assisted-by: kres (claude-opus-4-7) Signed-off-by: Chris Mason Reviewed-by: Jeff Layton Link: https://patch.msgid.link/20260524010213.557424-5-cel@kernel.org Signed-off-by: Chuck Lever --- net/sunrpc/auth_gss/gss_krb5_wrap.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/net/sunrpc/auth_gss/gss_krb5_wrap.c b/net/sunrpc/auth_gss/gss_krb5_wrap.c index ac4b32df42b9..d84c35f779f5 100644 --- a/net/sunrpc/auth_gss/gss_krb5_wrap.c +++ b/net/sunrpc/auth_gss/gss_krb5_wrap.c @@ -73,6 +73,8 @@ static void _rotate_left(struct xdr_buf *buf, unsigned int shift) int shifted = 0; int this_shift; + if (!buf->len) + return; shift %= buf->len; while (shifted < shift) { this_shift = min(shift - shifted, LOCAL_BUF_LEN); @@ -85,6 +87,8 @@ static void rotate_left(u32 base, struct xdr_buf *buf, unsigned int shift) { struct xdr_buf subbuf; + if (buf->len <= base) + return; xdr_buf_subsegment(buf, &subbuf, base, buf->len - base); _rotate_left(&subbuf, shift); } @@ -154,6 +158,9 @@ gss_krb5_unwrap_v2(struct krb5_ctx *kctx, int offset, int len, dprintk("RPC: %s\n", __func__); + if (len - offset <= GSS_KRB5_TOK_HDR_LEN) + return GSS_S_DEFECTIVE_TOKEN; + ptr = buf->head[0].iov_base + offset; if (be16_to_cpu(*((__be16 *)ptr)) != KG2_TOK_WRAP) @@ -220,9 +227,9 @@ gss_krb5_unwrap_v2(struct krb5_ctx *kctx, int offset, int len, * head buffer space rather than that actually occupied. */ movelen = min_t(unsigned int, buf->head[0].iov_len, len); + if (movelen < offset + GSS_KRB5_TOK_HDR_LEN + headskip) + return GSS_S_DEFECTIVE_TOKEN; movelen -= offset + GSS_KRB5_TOK_HDR_LEN + headskip; - BUG_ON(offset + GSS_KRB5_TOK_HDR_LEN + headskip + movelen > - buf->head[0].iov_len); memmove(ptr, ptr + GSS_KRB5_TOK_HDR_LEN + headskip, movelen); buf->head[0].iov_len -= GSS_KRB5_TOK_HDR_LEN + headskip; buf->len = len - (GSS_KRB5_TOK_HDR_LEN + headskip); From 72c8f61a415fcffed5f36bbf85772bb0fa39158c Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Sun, 24 May 2026 07:55:27 -0400 Subject: [PATCH 395/562] lockd: pin next file across nlm_inspect_file lock-drop nlm_traverse_files() pins the current file with f_count++ across a mutex_unlock for nlm_inspect_file(), but nothing pins the saved next pointer. A concurrent nlm_release_file() can kfree the next file during the unlock window, and the iterator dereferences freed memory on the next loop step. Pin both current and next before the lock-drop. Advance by swapping the pinned cursors at the end of each iteration so next is always held alive across the unlock. Always call nlm_file_release() after dropping the iteration pin, regardless of whether the file matched the predicate. Use nlm_file_inuse(), which does a live walk of the inode lock list, rather than the cached f_locks field, so skipped files that never ran nlm_inspect_file() are evaluated correctly. Because every file in a hash bucket is now pinned and released, files skipped by the is_failover_file predicate that have no locks, blocks, shares, or external references are deleted during traversal. The old code never evaluated skipped files for cleanup. The new behavior is intentional: such files are stale and should not persist in the table. Fixes: 01df9c5e918a ("LOCKD: Fix a deadlock in nlm_traverse_files()") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Michael Bommarito Link: https://patch.msgid.link/20260524115527.1734251-1-michael.bommarito@gmail.com Signed-off-by: Chuck Lever --- fs/lockd/svcsubs.c | 55 ++++++++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/fs/lockd/svcsubs.c b/fs/lockd/svcsubs.c index a0d1a6fbf61e..d7ada90dc048 100644 --- a/fs/lockd/svcsubs.c +++ b/fs/lockd/svcsubs.c @@ -312,12 +312,10 @@ nlm_file_inuse(struct nlm_file *file) return 0; } -static void nlm_close_files(struct nlm_file *file) +static void nlm_file_release(struct nlm_file *file) { - if (file->f_file[O_RDONLY]) - nlmsvc_ops->fclose(file->f_file[O_RDONLY]); - if (file->f_file[O_WRONLY]) - nlmsvc_ops->fclose(file->f_file[O_WRONLY]); + if (!nlm_file_inuse(file)) + nlm_delete_file(file); } /* @@ -327,32 +325,41 @@ static int nlm_traverse_files(void *data, nlm_host_match_fn_t match, int (*is_failover_file)(void *data, struct nlm_file *file)) { - struct hlist_node *next; - struct nlm_file *file; + struct nlm_file *file, *next; int i, ret = 0; mutex_lock(&nlm_file_mutex); for (i = 0; i < FILE_NRHASH; i++) { - hlist_for_each_entry_safe(file, next, &nlm_files[i], f_list) { - if (is_failover_file && !is_failover_file(data, file)) - continue; + file = hlist_entry_safe(nlm_files[i].first, + struct nlm_file, f_list); + if (file) file->f_count++; - mutex_unlock(&nlm_file_mutex); - - /* Traverse locks, blocks and shares of this file - * and update file->f_locks count */ - if (nlm_inspect_file(data, file, match)) - ret = 1; + while (file) { + /* + * Pin the next neighbour before we drop the mutex + * for nlm_inspect_file(); a concurrent + * nlm_release_file() under the same mutex would + * otherwise be free to unlink and kfree it during + * the unlock window, leaving us to dereference a + * freed slab when we walked to next afterwards. + */ + next = hlist_entry_safe(file->f_list.next, + struct nlm_file, f_list); + if (next) + next->f_count++; + + if (!is_failover_file || is_failover_file(data, file)) { + mutex_unlock(&nlm_file_mutex); + + if (nlm_inspect_file(data, file, match)) + ret = 1; + + mutex_lock(&nlm_file_mutex); + } - mutex_lock(&nlm_file_mutex); file->f_count--; - /* No more references to this file. Let go of it. */ - if (list_empty(&file->f_blocks) && !file->f_locks - && !file->f_shares && !file->f_count) { - hlist_del(&file->f_list); - nlm_close_files(file); - kfree(file); - } + nlm_file_release(file); + file = next; } } mutex_unlock(&nlm_file_mutex); From c5641b0fb66cc4a70d6fb2dc2ae354d84df0dc5e Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Sun, 24 May 2026 09:06:54 -0400 Subject: [PATCH 396/562] NFSD: restart ssc_expire_umount walk after dropping nfsd_ssc_lock nfsd4_ssc_expire_umount() walks nn->nfsd_ssc_mount_list with list_for_each_entry_safe(ni, tmp, ...). For each expired entry it sets nsui_busy = true, drops nfsd_ssc_lock to run mntput() on the source vfsmount, then reacquires the lock to list_del + kfree the entry and continue iterating via the macro's saved tmp pointer. The nsui_busy flag protects the current ni from concurrent nfsd4_ssc_setup_dul() finders during the lock-drop window, but it does not pin tmp. Another nfsd RPC thread that fails its source- server mount and reaches nfsd4_ssc_cancel_dul() will, during that same window, take nfsd_ssc_lock, list_del + kfree its own ssc_umount item, and release the lock. If that item is the saved tmp of the expire walk, the next iteration dereferences a freed nfsd4_ssc_umount_item. Restart the walk from the head after the mntput() unlock window so no saved next pointer survives the lock-drop. The list is bounded by the number of active inter-server source mounts (typically small) and the expire delayed-work runs periodically rather than per-IO, so the restart is cheap. Fixes: f4e44b393389 ("NFSD: delay unmount source's export after inter-server copy completed.") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Michael Bommarito Link: https://patch.msgid.link/20260524130654.1924556-1-michael.bommarito@gmail.com Signed-off-by: Chuck Lever --- fs/nfsd/nfs4state.c | 44 +++++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index a42f34842d77..a4e3ef1b6763 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -6859,30 +6859,36 @@ static void nfsd4_ssc_shutdown_umount(struct nfsd_net *nn) static void nfsd4_ssc_expire_umount(struct nfsd_net *nn) { bool do_wakeup = false; - struct nfsd4_ssc_umount_item *ni = NULL; - struct nfsd4_ssc_umount_item *tmp; + struct nfsd4_ssc_umount_item *ni; +restart: spin_lock(&nn->nfsd_ssc_lock); - list_for_each_entry_safe(ni, tmp, &nn->nfsd_ssc_mount_list, nsui_list) { - if (time_after(jiffies, ni->nsui_expire)) { - if (refcount_read(&ni->nsui_refcnt) > 1) - continue; + list_for_each_entry(ni, &nn->nfsd_ssc_mount_list, nsui_list) { + if (!time_after(jiffies, ni->nsui_expire)) + break; + if (refcount_read(&ni->nsui_refcnt) > 1) + continue; - /* mark being unmount */ - ni->nsui_busy = true; - spin_unlock(&nn->nfsd_ssc_lock); - mntput(ni->nsui_vfsmount); - spin_lock(&nn->nfsd_ssc_lock); + /* Prevent concurrent setup during unmount */ + ni->nsui_busy = true; + spin_unlock(&nn->nfsd_ssc_lock); + mntput(ni->nsui_vfsmount); + spin_lock(&nn->nfsd_ssc_lock); - /* waiters need to start from begin of list */ - list_del(&ni->nsui_list); - kfree(ni); + /* Force concurrent scanners to restart */ + list_del(&ni->nsui_list); + kfree(ni); - /* wakeup ssc_connect waiters */ - do_wakeup = true; - continue; - } - break; + /* wakeup ssc_connect waiters */ + do_wakeup = true; + /* + * Concurrent nfsd4_ssc_cancel_dul() can free any item + * on the list under nfsd_ssc_lock while mntput() runs + * above. Restart from the head; the list is short and + * the expire worker is periodic, so this is cheap. + */ + spin_unlock(&nn->nfsd_ssc_lock); + goto restart; } if (do_wakeup) wake_up_all(&nn->nfsd_ssc_waitq); From 679d02703aad97498c7db6b13785d2729accfdc3 Mon Sep 17 00:00:00 2001 From: NeilBrown Date: Tue, 26 May 2026 15:27:58 +1000 Subject: [PATCH 397/562] nfsd: fix possible fh_compose of wrong dentry in nfsd4_create_file() dentry_create() can hypothetically provide a different dentry than the one passed in. This could happen, for example, if the exported filesystem is NFS, and the server returned to OPEN a filehandle which matched a directory that was already in the dcache. Clearly this would not be expected! If this were to happen the dentry (child) that was already stored in resfhp could be freed and later dereferenced. We shouldn't call fh_compose() until we are certain that we have the final dentry, so this patch moved the fh_compose() call to two places: one for the case where the target already exists, and one after dentry_create() where it was created. Fixes: 64a989dbd144 ("VFS/knfsd: Teach dentry_create() to use atomic_open()") Cc: stable@vger.kernel.org Signed-off-by: NeilBrown Link: https://patch.msgid.link/20260526053004.4014491-2-neilb@ownmail.net Signed-off-by: Chuck Lever Reviewed-by: Jeff Layton Reviewed-by: Benjamin Coddington --- fs/nfsd/nfs4proc.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c index 3024d51d6fb7..c16ccb403a8d 100644 --- a/fs/nfsd/nfs4proc.c +++ b/fs/nfsd/nfs4proc.c @@ -306,10 +306,6 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp, goto out; } - status = fh_compose(resfhp, fhp->fh_export, child, fhp); - if (status != nfs_ok) - goto out; - v_mtime = 0; v_atime = 0; if (nfsd4_create_is_exclusive(open->op_createmode)) { @@ -335,6 +331,10 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp, if (status != nfs_ok) goto out; + status = fh_compose(resfhp, fhp->fh_export, child, fhp); + if (status != nfs_ok) + goto out; + switch (open->op_createmode) { case NFS4_CREATE_UNCHECKED: if (!d_is_reg(child)) @@ -385,6 +385,10 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp, open->op_created = true; fh_fill_post_attrs(fhp); + status = fh_compose(resfhp, fhp->fh_export, child, fhp); + if (status != nfs_ok) + goto out; + /* A newly created file already has a file size of zero. */ if ((iap->ia_valid & ATTR_SIZE) && (iap->ia_size == 0)) iap->ia_valid &= ~ATTR_SIZE; From e433999d4f3b8b49d6c60569e246eb8b6d720088 Mon Sep 17 00:00:00 2001 From: NeilBrown Date: Tue, 26 May 2026 15:27:59 +1000 Subject: [PATCH 398/562] nfsd: ensure nfsd_file_do_acquire() does not use a non-opened file ->atomic_open is permitted to return success without actually opening the file. It indicates this by calling finish_no_open(). This means dentry_create() can return a file which hasn't been opened. This is extremely unlikely as ->atomic_open handlers typically use finish_no_open() only for already existing files, and dentry_create() isn't called in that case, and the parent being locked should prevent races. However out of an abundance of caution it seems wise to teach nfsd to only use the file returned by dentry_create() if FMODE_OPENED is set, indicating that it has in fact been opened. Fixes: 64a989dbd144 ("VFS/knfsd: Teach dentry_create() to use atomic_open()") Cc: stable@vger.kernel.org Signed-off-by: NeilBrown Link: https://patch.msgid.link/20260526053004.4014491-3-neilb@ownmail.net Signed-off-by: Chuck Lever Reviewed-by: Jeff Layton Reviewed-by: Benjamin Coddington --- fs/nfsd/filecache.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/nfsd/filecache.c b/fs/nfsd/filecache.c index 24511c3208db..3b5f18fb713f 100644 --- a/fs/nfsd/filecache.c +++ b/fs/nfsd/filecache.c @@ -1227,7 +1227,7 @@ nfsd_file_do_acquire(struct svc_rqst *rqstp, struct net *net, nf->nf_mark = nfsd_file_mark_find_or_create(inode); if (type != S_IFREG || nf->nf_mark) { - if (file) { + if (file && (file->f_mode & FMODE_OPENED)) { get_file(file); nf->nf_file = file; status = nfs_ok; @@ -1374,12 +1374,12 @@ nfsd_file_acquire_local(struct net *net, struct svc_cred *cred, * @rqstp: the RPC transaction being executed * @fhp: the NFS filehandle of the file just created * @may_flags: NFSD_MAY_ settings for the file - * @file: cached, already-open file (may be NULL) + * @file: cached, already-open file (may be NULL or not yet opened) * @pnf: OUT: new or found "struct nfsd_file" object * * Acquire a nfsd_file object that is not GC'ed. If one doesn't already exist, - * and @file is non-NULL, use it to instantiate a new nfsd_file instead of - * opening a new one. + * and @file has FMODE_OPENED set, use it to instantiate a new nfsd_file + * instead of opening a new one. * * Return values: * %nfs_ok - @pnf points to an nfsd_file with its reference From 0ce8670d395d05b6a586958456c99476b7351275 Mon Sep 17 00:00:00 2001 From: Zhenghang Xiao Date: Tue, 26 May 2026 18:45:54 +0800 Subject: [PATCH 399/562] nfsd: set SC_STATUS_FREED in nfsd4_drop_revoked_stid for delegations nfsd4_drop_revoked_stid() handles FREE_STATEID for admin-revoked delegations but does not set SC_STATUS_FREED before releasing cl_lock. revoke_delegation() uses this flag to detect whether FREE_STATEID has already processed the delegation -- without it, the freed delegation is added to cl_revoked via list_add(), producing a use-after-free when cl_revoked is later traversed in __destroy_client(). The SC_STATUS_REVOKED path in nfsd4_free_stateid() (line 7983) already sets SC_STATUS_FREED correctly. Apply the same pattern to the SC_STATUS_ADMIN_REVOKED path in nfsd4_drop_revoked_stid(). Fixes: 8dd91e8d31fe ("nfsd: fix race between laundromat and free_stateid") Cc: stable@vger.kernel.org Signed-off-by: Zhenghang Xiao Reviewed-by: Jeff Layton Link: https://patch.msgid.link/20260526104554.46262-1-kipreyyy@gmail.com Signed-off-by: Chuck Lever --- fs/nfsd/nfs4state.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index a4e3ef1b6763..8f90fba53357 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -5172,6 +5172,7 @@ static void nfsd4_drop_revoked_stid(struct nfs4_stid *s) case SC_TYPE_DELEG: dp = delegstateid(s); list_del_init(&dp->dl_recall_lru); + s->sc_status |= SC_STATUS_FREED; spin_unlock(&cl->cl_lock); nfs4_put_stid(s); break; From af524b16e0d942c0802cb48cf8acbab55a073cca Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 26 May 2026 09:35:55 -0400 Subject: [PATCH 400/562] svcrdma: Validate Read chunk positions before reconstruction The RPC/RDMA Read chunk position field is supplied by the remote client and stored verbatim in the parsed chunk list. xdr_count_read_segments() checks only 4-byte alignment; it never compares the position against the received inline body length. In the single-chunk path, svc_rdma_read_complete_one() splits the head and tail kvecs at ch_position. A position past the inline body underflows the tail length, exposing adjacent slab memory to the upper XDR decoder. In the multi-chunk path, svc_rdma_read_multiple_chunks() computes gap lengths between chunks as unsigned subtractions from ch_position. Overlapping Read chunks cause these subtractions to underflow. A final position past the inline body likewise underflows the trailing gap length. svc_rdma_copy_inline_range() then copies past the receive buffer into request pages that are returned to the client through the Reply channel. Bound inline-range copies in svc_rdma_copy_inline_range() against the decoded inline RPC body saved in rc_saved_arg. Reject a single Read chunk positioned beyond that body, and reject multi-chunk lists where accumulated read bytes exceed the next chunk's position. Apply the same position and overlap checks in the call-chunk interleaving path. Fixes: d96962e6d0e2 ("svcrdma: Use the new parsed chunk list when pulling Read chunks") Cc: stable@vger.kernel.org Acked-by: Jeff Layton Link: https://patch.msgid.link/20260526-rpc-kernel-bugs-v1-1-e251306ccca9@oracle.com Signed-off-by: Chuck Lever --- net/sunrpc/xprtrdma/svc_rdma_rw.c | 38 ++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/net/sunrpc/xprtrdma/svc_rdma_rw.c b/net/sunrpc/xprtrdma/svc_rdma_rw.c index f7fd22cc4a59..8dae418d15b4 100644 --- a/net/sunrpc/xprtrdma/svc_rdma_rw.c +++ b/net/sunrpc/xprtrdma/svc_rdma_rw.c @@ -847,7 +847,7 @@ static int svc_rdma_build_read_chunk(struct svc_rqst *rqstp, * svc_rdma_copy_inline_range - Copy part of the inline content into pages * @rqstp: RPC transaction context * @head: context for ongoing I/O - * @offset: offset into the Receive buffer of region to copy + * @offset: offset into the inline content of region to copy * @remaining: length of region to copy * * Take a page at a time from rqstp->rq_pages and copy the inline @@ -864,9 +864,13 @@ static int svc_rdma_copy_inline_range(struct svc_rqst *rqstp, unsigned int offset, unsigned int remaining) { - unsigned char *dst, *src = head->rc_recv_buf; + unsigned char *dst, *src = head->rc_saved_arg.head[0].iov_base; + unsigned int inline_len = head->rc_saved_arg.head[0].iov_len; unsigned int page_no, numpages; + if (offset > inline_len || remaining > inline_len - offset) + return -EINVAL; + numpages = PAGE_ALIGN(head->rc_pageoff + remaining) >> PAGE_SHIFT; for (page_no = 0; page_no < numpages; page_no++) { unsigned int page_len; @@ -917,9 +921,10 @@ svc_rdma_read_multiple_chunks(struct svc_rqst *rqstp, { const struct svc_rdma_pcl *pcl = &head->rc_read_pcl; struct svc_rdma_chunk *chunk, *next; - unsigned int start, length; + unsigned int inline_len, start, length; int ret; + inline_len = head->rc_saved_arg.head[0].iov_len; start = 0; chunk = pcl_first_chunk(pcl); length = chunk->ch_position; @@ -937,6 +942,8 @@ svc_rdma_read_multiple_chunks(struct svc_rqst *rqstp, break; start += length; + if (head->rc_readbytes > next->ch_position) + return -EINVAL; length = next->ch_position - head->rc_readbytes; ret = svc_rdma_copy_inline_range(rqstp, head, start, length); if (ret < 0) @@ -944,7 +951,9 @@ svc_rdma_read_multiple_chunks(struct svc_rqst *rqstp, } start += length; - length = head->rc_byte_len - start; + if (start > inline_len) + return -EINVAL; + length = inline_len - start; return svc_rdma_copy_inline_range(rqstp, head, start, length); } @@ -969,8 +978,12 @@ svc_rdma_read_multiple_chunks(struct svc_rqst *rqstp, static int svc_rdma_read_data_item(struct svc_rqst *rqstp, struct svc_rdma_recv_ctxt *head) { - return svc_rdma_build_read_chunk(rqstp, head, - pcl_first_chunk(&head->rc_read_pcl)); + struct svc_rdma_chunk *chunk = pcl_first_chunk(&head->rc_read_pcl); + + if (chunk->ch_position > head->rc_saved_arg.head[0].iov_len) + return -EINVAL; + + return svc_rdma_build_read_chunk(rqstp, head, chunk); } /** @@ -1039,14 +1052,17 @@ static int svc_rdma_read_call_chunk(struct svc_rqst *rqstp, pcl_first_chunk(&head->rc_call_pcl); const struct svc_rdma_pcl *pcl = &head->rc_read_pcl; struct svc_rdma_chunk *chunk, *next; - unsigned int start, length; + unsigned int call_len, start, length; int ret; if (pcl_is_empty(pcl)) return svc_rdma_build_read_chunk(rqstp, head, call_chunk); + call_len = call_chunk->ch_length; start = 0; chunk = pcl_first_chunk(pcl); + if (chunk->ch_position > call_len) + return -EINVAL; length = chunk->ch_position; ret = svc_rdma_read_chunk_range(rqstp, head, call_chunk, start, length); @@ -1063,6 +1079,10 @@ static int svc_rdma_read_call_chunk(struct svc_rqst *rqstp, break; start += length; + if (next->ch_position > call_len) + return -EINVAL; + if (head->rc_readbytes > next->ch_position) + return -EINVAL; length = next->ch_position - head->rc_readbytes; ret = svc_rdma_read_chunk_range(rqstp, head, call_chunk, start, length); @@ -1071,7 +1091,9 @@ static int svc_rdma_read_call_chunk(struct svc_rqst *rqstp, } start += length; - length = call_chunk->ch_length - start; + if (start > call_len) + return -EINVAL; + length = call_len - start; return svc_rdma_read_chunk_range(rqstp, head, call_chunk, start, length); } From 8bbde2980700acc8bf2f3c4538cf7be04cc3ba31 Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Tue, 26 May 2026 09:35:56 -0400 Subject: [PATCH 401/562] svcrdma: Fix offset arithmetic in read_chunk_range svc_rdma_read_chunk_range() walks a Read chunk's segment list to build a sub-range starting at byte offset and spanning length bytes for a Position-Zero or Call chunk. Two arithmetic defects in the per-segment loop produce wrong DMA lengths and a u32 underflow: pcl_for_each_segment(segment, chunk) { if (offset > segment->rs_length) { offset -= segment->rs_length; continue; } dummy.rs_handle = segment->rs_handle; dummy.rs_length = min_t(u32, length, segment->rs_length) - offset; dummy.rs_offset = segment->rs_offset + offset; First, the skip predicate uses '>' instead of '>='. When offset equals the segment's full rs_length, the segment is fully consumed and should be skipped, but the loop falls through into the body. The resulting dummy.rs_length is min_t(u32, length, rs_length) - rs_length, which underflows to a near-UINT_MAX u32 when length is smaller than rs_length, or is zero otherwise. Second, the length formula subtracts offset from the min_t() result rather than from segment->rs_length before the cap. For offset > 0 the segment's residual is rs_length - offset, not rs_length, so the cap must be applied to the residual. With the current bracketing, whenever length is smaller than rs_length - offset the per-segment length becomes length - offset instead of length, silently dropping offset bytes from the rebuilt chunk. Combined with the boundary case above it also enables the u32 underflow path, which propagates a huge nr_bvec into svc_rdma_build_read_segment() and a multi-MiB kmalloc_array_node() in svc_rdma_get_rw_ctxt(). Additionally, svc_rdma_read_call_chunk() can invoke this function with length == 0 when the last Read chunk ends exactly at the end of the Call chunk. With the corrected >= predicate, every segment is skipped and the function returns the initial -EINVAL, rejecting a valid request. Return success immediately when length is zero. Also break out of the loop once length is fully consumed to avoid passing zero-length segments to svc_rdma_build_read_segment(). Fix by using '>=' so a fully-consumed segment is skipped, by moving '- offset' inside min_t() so the cap is applied to the segment's residual length, by returning success for zero-length requests, and by stopping iteration when the requested range has been consumed. Fixes: d7cc73972661 ("svcrdma: support multiple Read chunks per RPC") Cc: stable@vger.kernel.org Assisted-by: kres (claude-opus-4-7) Signed-off-by: Chris Mason Acked-by: Jeff Layton Link: https://patch.msgid.link/20260526-rpc-kernel-bugs-v1-2-e251306ccca9@oracle.com Signed-off-by: Chuck Lever --- net/sunrpc/xprtrdma/svc_rdma_rw.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/net/sunrpc/xprtrdma/svc_rdma_rw.c b/net/sunrpc/xprtrdma/svc_rdma_rw.c index 8dae418d15b4..b4cb4f991235 100644 --- a/net/sunrpc/xprtrdma/svc_rdma_rw.c +++ b/net/sunrpc/xprtrdma/svc_rdma_rw.c @@ -1009,17 +1009,20 @@ static int svc_rdma_read_chunk_range(struct svc_rqst *rqstp, const struct svc_rdma_segment *segment; int ret; + if (!length) + return 0; + ret = -EINVAL; pcl_for_each_segment(segment, chunk) { struct svc_rdma_segment dummy; - if (offset > segment->rs_length) { + if (offset >= segment->rs_length) { offset -= segment->rs_length; continue; } dummy.rs_handle = segment->rs_handle; - dummy.rs_length = min_t(u32, length, segment->rs_length) - offset; + dummy.rs_length = min_t(u32, length, segment->rs_length - offset); dummy.rs_offset = segment->rs_offset + offset; ret = svc_rdma_build_read_segment(rqstp, head, &dummy); @@ -1028,6 +1031,8 @@ static int svc_rdma_read_chunk_range(struct svc_rqst *rqstp, head->rc_readbytes += dummy.rs_length; length -= dummy.rs_length; + if (!length) + break; offset = 0; } return ret; From c057662569e44cd4859daa890c007a2badf4b10e Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 26 May 2026 09:35:57 -0400 Subject: [PATCH 402/562] svcrdma: Reject oversized Read segments at decode time The RPC/RDMA Read list decoder stores wire-supplied segment lengths without validation. xdr_count_read_segments() checks 4-byte alignment for non-zero position values but does not cap the segment length. An oversized rs_length reaches svc_rdma_build_read_segment(), which derives nr_bvec from it and can drive a large dynamic bvec allocation before verifying that enough rq_pages remain. If the post-allocation page-overrun guard fires, the freshly acquired rw context is not returned, leaking the resource. Reject any segment whose length exceeds the receive context's page budget during Read list decoding, consistent with how xdr_check_write_chunk() bounds Write segment counts against rc_maxpages. Also return the rw context on the existing post-allocation overrun path in svc_rdma_build_read_segment(), keeping that defensive guard balanced. Fixes: 5ee62b4a9113 ("svcrdma: use bvec-based RDMA read/write API") Cc: stable@vger.kernel.org Acked-by: Jeff Layton Link: https://patch.msgid.link/20260526-rpc-kernel-bugs-v1-3-e251306ccca9@oracle.com Signed-off-by: Chuck Lever --- net/sunrpc/xprtrdma/svc_rdma_recvfrom.c | 2 ++ net/sunrpc/xprtrdma/svc_rdma_rw.c | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/net/sunrpc/xprtrdma/svc_rdma_recvfrom.c b/net/sunrpc/xprtrdma/svc_rdma_recvfrom.c index fe9bf0371b6e..15c1d8ae5301 100644 --- a/net/sunrpc/xprtrdma/svc_rdma_recvfrom.c +++ b/net/sunrpc/xprtrdma/svc_rdma_recvfrom.c @@ -458,6 +458,8 @@ static bool xdr_count_read_segments(struct svc_rdma_recv_ctxt *rctxt, __be32 *p) xdr_decode_read_segment(p, &position, &handle, &length, &offset); + if (length > rctxt->rc_maxpages << PAGE_SHIFT) + return false; if (position) { if (position & 3) return false; diff --git a/net/sunrpc/xprtrdma/svc_rdma_rw.c b/net/sunrpc/xprtrdma/svc_rdma_rw.c index b4cb4f991235..9aaaade99e6e 100644 --- a/net/sunrpc/xprtrdma/svc_rdma_rw.c +++ b/net/sunrpc/xprtrdma/svc_rdma_rw.c @@ -795,7 +795,7 @@ static int svc_rdma_build_read_segment(struct svc_rqst *rqstp, len -= seg_len; if (len && ((head->rc_curpage + 1) > rqstp->rq_maxpages)) - goto out_overrun; + goto out_put; } ret = svc_rdma_rw_ctx_init(rdma, ctxt, segment->rs_offset, @@ -809,7 +809,8 @@ static int svc_rdma_build_read_segment(struct svc_rqst *rqstp, cc->cc_sqecount += ret; return 0; -out_overrun: +out_put: + svc_rdma_put_rw_ctxt(rdma, ctxt); trace_svcrdma_page_overrun_err(&cc->cc_cid, head->rc_curpage); return -EINVAL; } From 65a20be3cb37f02f9b9f89a56ede3b4c0e3afc64 Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Tue, 26 May 2026 09:35:58 -0400 Subject: [PATCH 403/562] svcrdma: Fix pcl_for_each_segment for empty chunks When a parsed chunk list contains a chunk whose ch_segcount is zero, pcl_for_each_segment computes its inclusive upper bound as &chunk->ch_segments[ch_segcount - 1]. ch_segcount is u32, so the subtraction wraps to 0xFFFFFFFF and the bound lands far past the ch_segments flex array. The loop body then walks unrelated memory at sizeof(struct svc_rdma_segment) stride until it faults. A zero-segcount chunk is reachable from the wire: xdr_check_write_chunk() only rejects segcount values greater than rc_maxpages, and pcl_alloc_write() links a freshly allocated chunk onto rc_write_pcl/rc_reply_pcl before its segment-fill loop runs, so a Write or Reply chunk advertising zero segments leaves ch_segcount == 0 on the list. When the transport has negotiated Send-With-Invalidate, svc_rdma_get_inv_rkey() iterates all four PCLs with pcl_for_each_segment and dereferences segment->rs_handle on each iteration, turning the underflow into an out-of-bounds read and a general protection fault. xdr_check_write_list / xdr_check_reply_chunk pcl_alloc_write() chunk = pcl_alloc_chunk(...) /* ch_segcount = 0 */ list_add_tail(&chunk->ch_list, &pcl->cl_chunks) /* fill loop iterates zero times for wire segcount 0 */ svc_rdma_get_inv_rkey() pcl_for_each_chunk(rc_write_pcl) pcl_for_each_segment(segment, chunk) pos <= &ch_segments[0u - 1u] /* 0xFFFFFFFF */ segment->rs_handle /* OOB read -> GPF */ Fix by switching the macro to a half-open upper bound that uses ch_segcount directly. For ch_segcount == 0 the loop start equals the loop end and the body is skipped; for ch_segcount > 0 the iteration range is unchanged. All six existing call sites in net/sunrpc/xprtrdma/svc_rdma_recvfrom.c and net/sunrpc/xprtrdma/svc_rdma_rw.c remain correct under the new bound, so no caller changes are needed. Fixes: 78147ca8b4a9 ("svcrdma: Add a "parsed chunk list" data structure") Cc: stable@vger.kernel.org Assisted-by: kres (claude-opus-4-7) Signed-off-by: Chris Mason Acked-by: Jeff Layton Link: https://patch.msgid.link/20260526-rpc-kernel-bugs-v1-4-e251306ccca9@oracle.com Signed-off-by: Chuck Lever --- include/linux/sunrpc/svc_rdma_pcl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/sunrpc/svc_rdma_pcl.h b/include/linux/sunrpc/svc_rdma_pcl.h index 7516ad0fae80..655681cf8fed 100644 --- a/include/linux/sunrpc/svc_rdma_pcl.h +++ b/include/linux/sunrpc/svc_rdma_pcl.h @@ -97,7 +97,7 @@ pcl_next_chunk(const struct svc_rdma_pcl *pcl, struct svc_rdma_chunk *chunk) */ #define pcl_for_each_segment(pos, chunk) \ for (pos = &(chunk)->ch_segments[0]; \ - pos <= &(chunk)->ch_segments[(chunk)->ch_segcount - 1]; \ + pos < &(chunk)->ch_segments[(chunk)->ch_segcount]; \ pos++) /** From 48544835c9f3c55ba074a3ced4c8a1c009767964 Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Tue, 26 May 2026 09:35:59 -0400 Subject: [PATCH 404/562] svcrdma: Reject Write/Reply chunks with segcount 0 A peer can send a Write or Reply chunk whose segcount field is zero. xdr_check_write_chunk() only rejects segcount > rc_maxpages, so zero passes the range check, and xdr_inline_decode(stream, 0) returns the current (non-NULL) cursor without advancing. The function returns true and pcl_alloc_write() then links a struct svc_rdma_chunk with ch_segcount == 0 onto rc_write_pcl or rc_reply_pcl. An earlier patch in this series made pcl_for_each_segment() safe for ch_segcount == 0, so this no longer drives the memory walk it used to. Rejecting the malformed frame at the decode boundary is still worthwhile as defense in depth: it keeps degenerate zero-segment chunks off the parsed chunk lists entirely, so any future consumer that walks ch_segments directly cannot observe one, and it makes the zero-floor easy to backport to trees where the macro change is more intrusive. RFC 8166 has no meaning for a Write/Reply chunk that describes no remote buffer, so no legitimate client is affected. xdr_check_reply_chunk() funnels Reply chunks through xdr_check_write_chunk() and inherits the same rejection. pcl_alloc_write() also links each chunk onto the parsed chunk list before filling its segment array. If a future change weakens the segcount-0 rejection, an incomplete chunk is visible to consumers during the fill loop. Reorder so that list_add_tail() follows the segment fill loop, ensuring only fully-populated chunks appear on the list. Fixes: 78147ca8b4a9 ("svcrdma: Add a "parsed chunk list" data structure") Cc: stable@vger.kernel.org Assisted-by: kres (claude-opus-4-7) Signed-off-by: Chris Mason Acked-by: Jeff Layton Link: https://patch.msgid.link/20260526-rpc-kernel-bugs-v1-5-e251306ccca9@oracle.com Signed-off-by: Chuck Lever --- net/sunrpc/xprtrdma/svc_rdma_pcl.c | 2 +- net/sunrpc/xprtrdma/svc_rdma_recvfrom.c | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/net/sunrpc/xprtrdma/svc_rdma_pcl.c b/net/sunrpc/xprtrdma/svc_rdma_pcl.c index 1f8f7dad8b6f..18d1045799ce 100644 --- a/net/sunrpc/xprtrdma/svc_rdma_pcl.c +++ b/net/sunrpc/xprtrdma/svc_rdma_pcl.c @@ -213,7 +213,6 @@ bool pcl_alloc_write(struct svc_rdma_recv_ctxt *rctxt, chunk = pcl_alloc_chunk(segcount, 0); if (!chunk) return false; - list_add_tail(&chunk->ch_list, &pcl->cl_chunks); for (j = 0; j < segcount; j++) { segment = &chunk->ch_segments[j]; @@ -225,6 +224,7 @@ bool pcl_alloc_write(struct svc_rdma_recv_ctxt *rctxt, chunk->ch_length += segment->rs_length; chunk->ch_segcount++; } + list_add_tail(&chunk->ch_list, &pcl->cl_chunks); } return true; } diff --git a/net/sunrpc/xprtrdma/svc_rdma_recvfrom.c b/net/sunrpc/xprtrdma/svc_rdma_recvfrom.c index 15c1d8ae5301..f6a7533a7555 100644 --- a/net/sunrpc/xprtrdma/svc_rdma_recvfrom.c +++ b/net/sunrpc/xprtrdma/svc_rdma_recvfrom.c @@ -510,10 +510,13 @@ static bool xdr_check_write_chunk(struct svc_rdma_recv_ctxt *rctxt) return false; /* Before trusting the segcount value enough to use it in - * a computation, perform a simple range check. This is an - * arbitrary but sensible limit (ie, not architectural). + * a computation, perform a simple range check. A zero + * segcount describes no remote buffer and is rejected so + * downstream consumers never see a degenerate ch_segcount==0 + * chunk. The upper bound is an arbitrary but sensible limit + * (ie, not architectural). */ - if (unlikely(segcount > rctxt->rc_maxpages)) + if (segcount == 0 || unlikely(segcount > rctxt->rc_maxpages)) return false; p = xdr_inline_decode(&rctxt->rc_stream, From 3564d142507a5799845e9912e36a8d0380d1a625 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 26 May 2026 09:36:00 -0400 Subject: [PATCH 405/562] svcrdma: Validate Read chunk positions at decode time Read chunk position and length validation is currently scattered across three consumer functions: svc_rdma_read_data_item(), svc_rdma_read_multiple_chunks(), and svc_rdma_read_call_chunk(). Each independently guards against the same class of unsigned arithmetic underflow from untrusted wire values. Any new consumer of the parsed Read chunk list must replicate these checks or risk re-introducing the defects fixed by earlier patches in this series. Add pcl_check_read_chunk_positions() to consolidate position and length validation into a single post-decode pass, called from svc_rdma_xdr_decode_req() after all three chunk lists have been parsed and the inline body length is known. The pass verifies three properties: - Each Read chunk's inline-body offset (its unreduced-stream position minus the cumulative length of preceding Read chunks) falls within the inline body length, or within the Call chunk length for interleaved reads. - Adjacent Read chunk positions do not overlap: cumulative read bytes at each transition do not exceed the next position. - Each chunk length does not exceed the receive context's page budget. Malformed frames are rejected before reaching any consumer. The existing consumer-side guards remain as defense in depth. Acked-by: Jeff Layton Link: https://patch.msgid.link/20260526-rpc-kernel-bugs-v1-6-e251306ccca9@oracle.com Signed-off-by: Chuck Lever --- include/linux/sunrpc/svc_rdma_pcl.h | 2 + net/sunrpc/xprtrdma/svc_rdma_pcl.c | 61 +++++++++++++++++++++++-- net/sunrpc/xprtrdma/svc_rdma_recvfrom.c | 3 ++ 3 files changed, 63 insertions(+), 3 deletions(-) diff --git a/include/linux/sunrpc/svc_rdma_pcl.h b/include/linux/sunrpc/svc_rdma_pcl.h index 655681cf8fed..6346d8cf2587 100644 --- a/include/linux/sunrpc/svc_rdma_pcl.h +++ b/include/linux/sunrpc/svc_rdma_pcl.h @@ -119,6 +119,8 @@ extern bool pcl_alloc_call(struct svc_rdma_recv_ctxt *rctxt, __be32 *p); extern bool pcl_alloc_read(struct svc_rdma_recv_ctxt *rctxt, __be32 *p); extern bool pcl_alloc_write(struct svc_rdma_recv_ctxt *rctxt, struct svc_rdma_pcl *pcl, __be32 *p); +extern bool pcl_check_read_chunk_positions(struct svc_rdma_recv_ctxt *rctxt, + unsigned int inline_len); extern int pcl_process_nonpayloads(const struct svc_rdma_pcl *pcl, const struct xdr_buf *xdr, int (*actor)(const struct xdr_buf *, diff --git a/net/sunrpc/xprtrdma/svc_rdma_pcl.c b/net/sunrpc/xprtrdma/svc_rdma_pcl.c index 18d1045799ce..8623722790f2 100644 --- a/net/sunrpc/xprtrdma/svc_rdma_pcl.c +++ b/net/sunrpc/xprtrdma/svc_rdma_pcl.c @@ -149,9 +149,6 @@ bool pcl_alloc_call(struct svc_rdma_recv_ctxt *rctxt, __be32 *p) * cl_count is updated to be the number of chunks (ie. * unique position values) in the Read list. * %false: Memory allocation failed. - * - * TODO: - * - Check for chunk range overlaps */ bool pcl_alloc_read(struct svc_rdma_recv_ctxt *rctxt, __be32 *p) { @@ -229,6 +226,64 @@ bool pcl_alloc_write(struct svc_rdma_recv_ctxt *rctxt, return true; } +/** + * pcl_check_read_chunk_positions - Validate Read chunk positions + * @rctxt: Ingress receive context with populated chunk lists + * @inline_len: Length of the inline RPC body after the transport header + * + * Read chunk positions are offsets in the unreduced XDR stream + * (RFC 8166 Section 3.4.4), so each position includes the + * cumulative length of preceding Read chunks. This function + * subtracts those lengths to recover the inline-body offset + * before comparing against @inline_len or the Call chunk length. + * + * Rejects frames where a Read chunk's inline-body offset exceeds + * the bound, where adjacent Read chunks overlap, or where any + * single chunk length exceeds the page budget. + * + * Return values: + * %true: Read chunk positions and lengths are valid + * %false: Malformed chunk list detected + */ +bool pcl_check_read_chunk_positions(struct svc_rdma_recv_ctxt *rctxt, + unsigned int inline_len) +{ + unsigned int max_len, bound, total_read; + struct svc_rdma_chunk *chunk, *next; + + max_len = rctxt->rc_maxpages << PAGE_SHIFT; + + if (!pcl_is_empty(&rctxt->rc_call_pcl)) { + chunk = pcl_first_chunk(&rctxt->rc_call_pcl); + if (chunk->ch_length > max_len) + return false; + bound = chunk->ch_length; + } else { + bound = inline_len; + } + + if (pcl_is_empty(&rctxt->rc_read_pcl)) + return true; + + total_read = 0; + pcl_for_each_chunk(chunk, &rctxt->rc_read_pcl) { + if (chunk->ch_position - total_read > bound) + return false; + if (chunk->ch_length > max_len) + return false; + + next = pcl_next_chunk(&rctxt->rc_read_pcl, chunk); + if (!next) + break; + + if (chunk->ch_position + chunk->ch_length > next->ch_position) + return false; + total_read += chunk->ch_length; + } + + return true; +} + static int pcl_process_region(const struct xdr_buf *xdr, unsigned int offset, unsigned int length, int (*actor)(const struct xdr_buf *, void *), diff --git a/net/sunrpc/xprtrdma/svc_rdma_recvfrom.c b/net/sunrpc/xprtrdma/svc_rdma_recvfrom.c index f6a7533a7555..d64b5f78ce8a 100644 --- a/net/sunrpc/xprtrdma/svc_rdma_recvfrom.c +++ b/net/sunrpc/xprtrdma/svc_rdma_recvfrom.c @@ -724,6 +724,9 @@ static int svc_rdma_xdr_decode_req(struct xdr_buf *rq_arg, rq_arg->head[0].iov_base = rctxt->rc_stream.p; hdr_len = xdr_stream_pos(&rctxt->rc_stream); + if (!pcl_check_read_chunk_positions(rctxt, + rq_arg->head[0].iov_len - hdr_len)) + goto out_inval; rq_arg->head[0].iov_len -= hdr_len; rq_arg->len -= hdr_len; trace_svcrdma_decode_rqst(rctxt, rdma_argp, hdr_len); From 55e51bfe629482741471214fb5d2c2788a9812e4 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Tue, 26 May 2026 12:24:48 -0400 Subject: [PATCH 406/562] nfsd: don't free session slots that are still in use nfsd4_sequence() can free the very slot it is currently processing. When the session shrinker has reduced se_target_maxslots below se_fchannel.maxreqs, the shrink path checks three conditions before calling free_session_slots(): 1. se_target_maxslots < maxreqs (shrink was advertised) 2. slot->sl_generation == se_slot_gen (slot is up-to-date) 3. seq->maxslots <= se_target_maxslots (client acknowledges) However, seq->slotid is never checked against se_target_maxslots. A client using a slot in the range [se_target_maxslots, maxreqs) can satisfy all three conditions: its slot has the current generation (set by a prior SEQUENCE), and it sends sa_highest_slotid <= se_target_maxslots to acknowledge the reduction. free_session_slots() then kfrees every slot at index >= se_target_maxslots, including the caller's own slot. The function continues to write sl_seqid, sl_flags, sl_generation, and stores the dangling pointer in cstate->slot. Later, nfsd4_store_cache_entry() copies up to maxresp_cached bytes of the compound reply into the freed sl_data[] array, corrupting whatever slab object now occupies that address. Additionally, a concurrent thread processing SEQUENCE on a different high-numbered slot can have its slot freed out from under it. NFSD4_SLOT_INUSE is set under nn->client_lock before the lock is released, so any concurrent thread past SEQUENCE will have its slot marked. However, free_session_slots() does not check NFSD4_SLOT_INUSE before freeing. Fix both problems by: 1. Checking that the current request's slotid is below the shrink boundary. 2. Scanning slots in the to-be-freed range for NFSD4_SLOT_INUSE and deferring the shrink if any are active. Fixes: fc8738c68d0b ("nfsd: add support for freeing unused session-DRC slots") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260526-nfsd4_sequence_shrink_uaf_on_loaded_slot-v2-1-74a89db0639e@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfs4state.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index 8f90fba53357..0d6a7440a4e2 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -4496,6 +4496,19 @@ static void nfsd4_construct_sequence_response(struct nfsd4_session *session, seq->status_flags |= SEQ4_STATUS_ADMIN_STATE_REVOKED; } +static bool nfsd4_slots_inuse(struct nfsd4_session *ses, int from) +{ + int i; + + for (i = from; i < ses->se_fchannel.maxreqs; i++) { + struct nfsd4_slot *slot = xa_load(&ses->se_slots, i); + + if (slot->sl_flags & NFSD4_SLOT_INUSE) + return true; + } + return false; +} + __be32 nfsd4_sequence(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, union nfsd4_op_u *u) @@ -4575,7 +4588,9 @@ nfsd4_sequence(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, if (session->se_target_maxslots < session->se_fchannel.maxreqs && slot->sl_generation == session->se_slot_gen && - seq->maxslots <= session->se_target_maxslots) + seq->maxslots <= session->se_target_maxslots && + seq->slotid < session->se_target_maxslots && + !nfsd4_slots_inuse(session, session->se_target_maxslots)) /* Client acknowledged our reduce maxreqs */ free_session_slots(session, session->se_target_maxslots); From 356ccf8251a29df88b8a0c94c6490ea38c878fe1 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Tue, 26 May 2026 12:38:45 -0400 Subject: [PATCH 407/562] nfsd: defer setting NFSD4_CALLBACK_RUNNING in deleg_reaper deleg_reaper() sets NFSD4_CALLBACK_RUNNING before checking the 5-second rate limit and cl_cb_state gates. When either gate fires the loop continues without queuing callback work, so the bit's only clear site in nfsd41_destroy_cb() is never reached and RECALL_ANY dispatch is permanently disabled for the affected client. Move the test_and_set_bit() below both non-queueing gates so the bit is taken only when nfsd4_run_cb() will be called. Fixes: 424dd3df1f99 ("nfsd: eliminate cl_ra_cblist and NFSD4_CLIENT_CB_RECALL_ANY") Cc: stable@vger.kernel.org Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260526-cb_recall_any_callback_running_stuck-v1-1-310011a028f3@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfs4state.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index 0d6a7440a4e2..b8c2a041ec4b 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -7217,12 +7217,12 @@ deleg_reaper(struct nfsd_net *nn) continue; if (atomic_read(&clp->cl_delegs_in_recall)) continue; - if (test_and_set_bit(NFSD4_CALLBACK_RUNNING, &clp->cl_ra->ra_cb.cb_flags)) - continue; if (ktime_get_boottime_seconds() - clp->cl_ra_time < 5) continue; if (clp->cl_cb_state != NFSD4_CB_UP) continue; + if (test_and_set_bit(NFSD4_CALLBACK_RUNNING, &clp->cl_ra->ra_cb.cb_flags)) + continue; /* release in nfsd4_cb_recall_any_release */ kref_get(&clp->cl_nfsdfs.cl_ref); From 163ba2d3180b4b7470bd159b2b4fb7680af73852 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Tue, 26 May 2026 12:38:46 -0400 Subject: [PATCH 408/562] nfsd: clear CALLBACK_RUNNING on failed delegation recall queue nfsd_break_one_deleg() sets NFSD4_CALLBACK_RUNNING via test_and_set_bit at entry to serialize recall work, then calls nfsd4_run_cb() to queue the recall. When the queue attempt fails the refcount bump is undone, but the RUNNING bit is left set. The only site that clears the bit is nfsd41_destroy_cb() (fs/nfsd/nfs4callback.c), which runs from the workqueue and is therefore unreachable when nothing was queued. The bit becomes a permanent latch on dp->dl_recall.cb_flags: every subsequent break_lease() on the same delegation hits the early-return guard in nfsd_break_one_deleg() and silently skips the recall, so the delegation is never broken and the conflicting open or lock stalls. Fix by clearing NFSD4_CALLBACK_RUNNING on the !queued branch alongside the refcount_dec. Fixes: 1054e8ffc5c4 ("nfsd: prevent callback tasks running concurrently") Cc: stable@vger.kernel.org Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260526-cb_recall_any_callback_running_stuck-v1-2-310011a028f3@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfs4state.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index b8c2a041ec4b..c7eb558cb86b 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -5631,8 +5631,10 @@ static void nfsd_break_one_deleg(struct nfs4_delegation *dp) refcount_inc(&dp->dl_stid.sc_count); queued = nfsd4_run_cb(&dp->dl_recall); WARN_ON_ONCE(!queued); - if (!queued) + if (!queued) { refcount_dec(&dp->dl_stid.sc_count); + clear_bit(NFSD4_CALLBACK_RUNNING, &dp->dl_recall.cb_flags); + } } /* Called from break_lease() with flc_lock held. */ From 06c7b674f0a28ee2128b77fe4ee7d019cd917bd4 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 26 May 2026 13:34:54 -0400 Subject: [PATCH 409/562] svcrdma: Reject Read lists that exceed the page budget Individual Read segment lengths are validated at decode time, but nothing prevents a requester from sending multiple segments whose cumulative length exceeds the rq_pages array budget. When one segment fills the page array exactly, the runtime guard in svc_rdma_build_read_segment() is bypassed because len reaches zero. A subsequent segment then accesses the NULL sentinel slot at rq_pages[rq_maxpages], resulting in a NULL pointer dereference during DMA mapping. Accumulate pages across all Read segments and reject the message at decode time when the total would overflow the page budget. Fixes: 026d958b38c6 ("svcrdma: Add recvfrom helpers to svc_rdma_rw.c") Cc: stable@vger.kernel.org Signed-off-by: Chuck Lever --- net/sunrpc/xprtrdma/svc_rdma_recvfrom.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/net/sunrpc/xprtrdma/svc_rdma_recvfrom.c b/net/sunrpc/xprtrdma/svc_rdma_recvfrom.c index d64b5f78ce8a..fdfed1be97da 100644 --- a/net/sunrpc/xprtrdma/svc_rdma_recvfrom.c +++ b/net/sunrpc/xprtrdma/svc_rdma_recvfrom.c @@ -440,11 +440,14 @@ static void svc_rdma_build_arg_xdr(struct svc_rqst *rqstp, * to the first byte past the Read list. rc_read_pcl and * rc_call_pcl cl_count fields are set to the number of * Read segments in the list. - * %false: Read list is corrupt. @rctxt's xdr_stream is left in an - * unknown state. + * %false: Read list is corrupt or exceeds the page budget. @rctxt's + * xdr_stream is left in an unknown state. */ static bool xdr_count_read_segments(struct svc_rdma_recv_ctxt *rctxt, __be32 *p) { + unsigned int maxlen = rctxt->rc_maxpages << PAGE_SHIFT; + unsigned int total_len = 0; + rctxt->rc_call_pcl.cl_count = 0; rctxt->rc_read_pcl.cl_count = 0; while (xdr_item_is_present(p)) { @@ -458,7 +461,10 @@ static bool xdr_count_read_segments(struct svc_rdma_recv_ctxt *rctxt, __be32 *p) xdr_decode_read_segment(p, &position, &handle, &length, &offset); - if (length > rctxt->rc_maxpages << PAGE_SHIFT) + if (length > maxlen) + return false; + total_len += length; + if (PAGE_ALIGN(total_len) > maxlen) return false; if (position) { if (position & 3) From 4daab24d680b64b4c7fc548b62c717ddeab28ee0 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Tue, 26 May 2026 15:35:06 -0400 Subject: [PATCH 410/562] SUNRPC: always drain cache_cleaner before destroying a cache_detail sunrpc_destroy_cache_detail() only cancels the global cache_cleaner delayed_work when cache_list is empty. During per-netns teardown cache_list is never empty because init_net's caches remain registered, so the cancel never fires. After unlink, the caller proceeds to cache_destroy_net() which kfrees the cache_detail while cache_clean() may still hold a dangling pointer to it. The result is a use-after-free: cache_dequeue() takes cd->queue_lock on freed memory, and cache_put() dereferences cd->cache_put as a function pointer from freed slab. Drop the list_empty guard so that cancel_delayed_work_sync() always runs, ensuring any in-flight cache_clean() completes before the cache_detail is freed. Re-arm the cleaner afterwards if other caches are still registered. Fixes: 820f9442e711 ("SUNRPC: split cache creation and PipeFS registration") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260526-cache_cleaner_vs_destroy_no_sync-v1-1-a707a6fcfd32@kernel.org Signed-off-by: Chuck Lever --- net/sunrpc/cache.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/net/sunrpc/cache.c b/net/sunrpc/cache.c index 391037f15292..1bc04109d213 100644 --- a/net/sunrpc/cache.c +++ b/net/sunrpc/cache.c @@ -430,10 +430,9 @@ void sunrpc_destroy_cache_detail(struct cache_detail *cd) list_del_init(&cd->others); spin_unlock(&cd->hash_lock); spin_unlock(&cache_list_lock); - if (list_empty(&cache_list)) { - /* module must be being unloaded so its safe to kill the worker */ - cancel_delayed_work_sync(&cache_cleaner); - } + cancel_delayed_work_sync(&cache_cleaner); + if (!list_empty(&cache_list)) + queue_delayed_work(system_power_efficient_wq, &cache_cleaner, 0); } EXPORT_SYMBOL_GPL(sunrpc_destroy_cache_detail); From 448fb59235ef0f5b41b3cdfd237c8c7178492aa4 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Wed, 27 May 2026 10:53:37 -0400 Subject: [PATCH 411/562] nfsd: block non-SAVEFH ops after FOREIGN PUTFH to prevent NULL deref When CONFIG_NFSD_V4_2_INTER_SSC is enabled, nfsd4_putfh() can return success with fh_dentry and fh_export both NULL if fh_verify() returns nfserr_stale and putfh->no_verify is true. The NFSD4_FH_FOREIGN flag is set, but the compound dispatch loop only uses this flag to bypass the nfserr_nofilehandle check -- it does not prevent subsequent ops from running with a NULL fh_dentry. A remote client can exploit this by crafting a COMPOUND that includes an inter-SSC COPY (which causes check_if_stalefh_allowed() to set no_verify=true on the saved PUTFH) with an additional op inserted between the source PUTFH and SAVEFH. For example, SETATTR calls fh_want_write() which dereferences fh_export->ex_path.mnt without calling fh_verify() first, causing a NULL pointer dereference in the nfsd kthread. Fix this by gating the dispatch loop: when NFSD4_FH_FOREIGN is set and fh_dentry is NULL, only OP_SAVEFH (needed for the inter-SSC flow) and ops with ALLOWED_WITHOUT_FH (which don't need a resolved filehandle) may proceed. All other ops receive nfserr_stale, per RFC 7862 Section 15.2.3 which specifies that foreign filehandle validation is deferred to the consuming operation and NFS4ERR_STALE returned at that point. Fixes: b9e8638e3d9e ("NFSD: allow inter server COPY to have a STALE source server fh") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260527-putfh_foreign_fh_null_deref_consumers-v1-1-1b8a5aa28c59@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfs4proc.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c index c16ccb403a8d..f4884827b7a0 100644 --- a/fs/nfsd/nfs4proc.c +++ b/fs/nfsd/nfs4proc.c @@ -3125,9 +3125,22 @@ nfsd4_proc_compound(struct svc_rqst *rqstp) op->status = nfsd4_open_omfg(rqstp, cstate, op); goto encode_op; } - if (!current_fh->fh_dentry && - !HAS_FH_FLAG(current_fh, NFSD4_FH_FOREIGN)) { - if (!(op->opdesc->op_flags & ALLOWED_WITHOUT_FH)) { + if (!current_fh->fh_dentry) { + if (HAS_FH_FLAG(current_fh, NFSD4_FH_FOREIGN)) { + /* + * FOREIGN fh from inter-SSC PUTFH: only + * SAVEFH may proceed with a NULL fh_dentry. + * Per RFC 7862 S15.2.3, validation of a + * foreign fh is deferred to the operation + * that consumes it, and NFS4ERR_STALE is + * returned at that point. + */ + if (op->opnum != OP_SAVEFH && + !(op->opdesc->op_flags & ALLOWED_WITHOUT_FH)) { + op->status = nfserr_stale; + goto encode_op; + } + } else if (!(op->opdesc->op_flags & ALLOWED_WITHOUT_FH)) { op->status = nfserr_nofilehandle; goto encode_op; } From e0f17197579db8659530b1367e2ee9fc7cc33ed1 Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Wed, 27 May 2026 11:00:11 -0400 Subject: [PATCH 412/562] svcrdma: Fix unmatched rn_unregister on failed accept When svc_rdma_accept() takes the errout path before rpcrdma_rn_register() has succeeded, the existing cleanup block calls rpcrdma_rn_unregister(dev, &newxprt->sc_rn) unconditionally. svcxprt_rdma is kzalloc'd, so on that path sc_rn.rn_index is 0 and sc_rn.rn_done is NULL; the unregister therefore xa_erase()s another caller's slot 0 and performs an unmatched kref_put() on the rpcrdma_device's rd_kref. The same errout also brackets the cleanup with svc_xprt_get()/ svc_xprt_put() around the kref_init() birth reference. The kref goes 1 -> 2 -> 1 and never reaches 0, so the svcxprt_rdma (and the net/ns_tracker it pinned) is leaked on every failed accept. rpcrdma_rn_register() writes rn->rn_done last, only after xa_alloc() and kref_get() have both succeeded, so rn_done == NULL is a natural "never registered" sentinel. Guard rpcrdma_rn_unregister() with an early return when rn_done is NULL, and clear rn_done before the matching xa_erase() so a repeated unregister is also a no-op. With that guard in place, the accept errout drops the kref_init() birth reference via svc_xprt_put(), which dispatches svc_rdma_free(). Teardown of sc_qp, sc_sq_cq, sc_rq_cq, and sc_pd runs under existing IS_ERR/NULL guards in svc_rdma_free(); sc_rn is covered by the new rn_done sentinel; sc_cm_id is non-NULL on every errout path because svc_rdma_accept() dereferences it above the first goto errout. svc_xprt_free() drops the module reference associated with the freed transport, and svc_handle_xprt() drops its pre-acquired reference when ->xpo_accept() returns NULL. Take a replacement module reference before svc_xprt_put() so the two module_put()s remain balanced. The rn_done guard also covers svc_rdma_free()'s non-listener call to rpcrdma_rn_unregister() for transports whose register attempt failed or never ran. Fixes: 8ac6fcae5dc0 ("svcrdma: Unregister the device if svc_rdma_accept() fails") Assisted-by: kres (claude-opus-4-7) Signed-off-by: Chris Mason Acked-by: Jeff Layton Link: https://patch.msgid.link/20260527-rdma-follow-on-v1-1-1b09bd87b6cd@oracle.com Signed-off-by: Chuck Lever --- net/sunrpc/xprtrdma/ib_client.c | 24 +++++++++++++++++++- net/sunrpc/xprtrdma/svc_rdma_transport.c | 28 ++++++++++++++++++------ 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/net/sunrpc/xprtrdma/ib_client.c b/net/sunrpc/xprtrdma/ib_client.c index de49ad02053d..69166d5d9987 100644 --- a/net/sunrpc/xprtrdma/ib_client.c +++ b/net/sunrpc/xprtrdma/ib_client.c @@ -51,7 +51,11 @@ static struct rpcrdma_device *rpcrdma_get_client_data(struct ib_device *device) * to be invoked when the device is removed, unless this notification * is unregistered first. * - * On failure, a negative errno is returned. + * On failure, a negative errno is returned. rn->rn_done is left + * NULL on every failure path (it is assigned only after xa_alloc + * and kref_get have both succeeded), so the @rn may safely be + * passed to rpcrdma_rn_unregister() without a separate + * registered/unregistered flag in the caller. */ int rpcrdma_rn_register(struct ib_device *device, struct rpcrdma_notification *rn, @@ -83,6 +87,10 @@ static void rpcrdma_rn_release(struct kref *kref) * rpcrdma_rn_unregister - stop device removal notifications * @device: monitored device * @rn: notification object that no longer wishes to be notified + * + * It is safe to call this on an @rn whose registration never + * completed or failed; rn_done == NULL is treated as + * never-registered and the call is a no-op. */ void rpcrdma_rn_unregister(struct ib_device *device, struct rpcrdma_notification *rn) @@ -92,6 +100,20 @@ void rpcrdma_rn_unregister(struct ib_device *device, if (!rd) return; + /* + * rn_done is the registration sentinel: rpcrdma_rn_register + * assigns it last, after xa_alloc and kref_get have both + * succeeded. A NULL rn_done means this notification was + * never registered (or its registration failed) or has + * already been unregistered, and the call is a no-op. + * Without this guard, rn_index == 0 from a kzalloc'd + * parent would erase another caller's slot 0 and underflow + * rd_kref. + */ + if (!rn->rn_done) + return; + rn->rn_done = NULL; + trace_rpcrdma_client_unregister(device, rn); xa_erase(&rd->rd_xa, rn->rn_index); kref_put(&rd->rd_kref, rpcrdma_rn_release); diff --git a/net/sunrpc/xprtrdma/svc_rdma_transport.c b/net/sunrpc/xprtrdma/svc_rdma_transport.c index 7ca71741106b..9268b6105a74 100644 --- a/net/sunrpc/xprtrdma/svc_rdma_transport.c +++ b/net/sunrpc/xprtrdma/svc_rdma_transport.c @@ -43,6 +43,7 @@ */ #include +#include #include #include #include @@ -598,13 +599,26 @@ static struct svc_xprt *svc_rdma_accept(struct svc_xprt *xprt) return &newxprt->sc_xprt; errout: - /* Take a reference in case the DTO handler runs */ - svc_xprt_get(&newxprt->sc_xprt); - if (newxprt->sc_qp && !IS_ERR(newxprt->sc_qp)) - ib_destroy_qp(newxprt->sc_qp); - rdma_destroy_id(newxprt->sc_cm_id); - rpcrdma_rn_unregister(dev, &newxprt->sc_rn); - /* This call to put will destroy the transport */ + /* + * Drop the kref_init birth reference. svc_xprt_free will + * dispatch xpo_free = svc_rdma_free, which tears down sc_qp, + * sc_sq_cq, sc_rq_cq, and sc_pd under existing IS_ERR/NULL + * guards, and sc_rn under the rn_done sentinel guard inside + * rpcrdma_rn_unregister. + * + * sc_cm_id is destroyed unconditionally by svc_rdma_free; that + * is safe here because sc_cm_id is non-NULL by caller invariant + * on every path that reaches this errout: handle_connect_req + * installs newxprt->sc_cm_id before queueing the new xprt for + * accept, and svc_rdma_accept has already dereferenced it above + * the first goto errout. + * + * svc_handle_xprt() drops its pre-acquired module reference when + * ->xpo_accept() returns NULL. Take a replacement reference before + * freeing @newxprt, because svc_xprt_free() drops the module + * reference associated with @newxprt. + */ + __module_get(newxprt->sc_xprt.xpt_class->xcl_owner); svc_xprt_put(&newxprt->sc_xprt); return NULL; } From 452f3092013e533b70725058cf7c365bce9f5ea1 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Wed, 27 May 2026 11:00:12 -0400 Subject: [PATCH 413/562] svcrdma: Reorder rpcrdma_rn_unregister before rdma_destroy_id svc_rdma_free() caches rdma->sc_cm_id->device before teardown, then calls rdma_destroy_id(sc_cm_id) which frees the cm_id. rpcrdma_rn_unregister() follows, but between those two calls the transport's sc_rn entry is still installed in the device's rd_xa. A concurrent ib_unregister_device walk can dispatch svc_rdma_xprt_done() against the now-freed sc_cm_id. Move rpcrdma_rn_unregister() before rdma_destroy_id() so the transport's notification entry is removed from the xarray before the cm_id it references is destroyed. Also guard the sc_cm_id dereference with a NULL check: the following patches introduce paths that reach svc_rdma_free() with sc_cm_id == NULL (listener create failure, ADDR_CHANGE replacement failure). Fixes: c4de97f7c454 ("svcrdma: Handle device removal outside of the CM event handler") Acked-by: Jeff Layton Link: https://patch.msgid.link/20260527-rdma-follow-on-v1-2-1b09bd87b6cd@oracle.com Signed-off-by: Chuck Lever --- net/sunrpc/xprtrdma/svc_rdma_transport.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/net/sunrpc/xprtrdma/svc_rdma_transport.c b/net/sunrpc/xprtrdma/svc_rdma_transport.c index 9268b6105a74..55e2ca036584 100644 --- a/net/sunrpc/xprtrdma/svc_rdma_transport.c +++ b/net/sunrpc/xprtrdma/svc_rdma_transport.c @@ -648,10 +648,15 @@ static void svc_rdma_free(struct svc_xprt *xprt) { struct svcxprt_rdma *rdma = container_of(xprt, struct svcxprt_rdma, sc_xprt); - struct ib_device *device = rdma->sc_cm_id->device; + struct ib_device *device; might_sleep(); + if (!rdma->sc_cm_id) + goto out_free; + + device = rdma->sc_cm_id->device; + /* This blocks until the Completion Queues are empty */ if (rdma->sc_qp && !IS_ERR(rdma->sc_qp)) ib_drain_qp(rdma->sc_qp); @@ -676,11 +681,13 @@ static void svc_rdma_free(struct svc_xprt *xprt) if (rdma->sc_pd && !IS_ERR(rdma->sc_pd)) ib_dealloc_pd(rdma->sc_pd); + if (!test_bit(XPT_LISTENER, &rdma->sc_xprt.xpt_flags)) + rpcrdma_rn_unregister(device, &rdma->sc_rn); + /* Destroy the CM ID */ rdma_destroy_id(rdma->sc_cm_id); - if (!test_bit(XPT_LISTENER, &rdma->sc_xprt.xpt_flags)) - rpcrdma_rn_unregister(device, &rdma->sc_rn); +out_free: kfree(rdma); } From 0cec74f759cc2a17e1e74673ec262f75299f6256 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Wed, 27 May 2026 11:00:13 -0400 Subject: [PATCH 414/562] svcrdma: Use svc_xprt_put to free listener on create failure svc_rdma_create() calls kfree(cma_xprt) when svc_rdma_create_listen_id() fails. svc_xprt_init() has already acquired a net namespace reference via get_net_track(); kfree bypasses svc_xprt_free() which releases it. Replace the kfree() with svc_xprt_put() so the kref_init birth reference drops to zero and svc_xprt_free() dispatches svc_rdma_free() to clean up properly. sc_cm_id is still NULL at that point; the preceding patch added the necessary NULL guard in svc_rdma_free(). svc_xprt_free() also drops the module reference via module_put(), but the caller _svc_xprt_create() does the same on xpo_create failure, double-putting the single try_module_get() it acquired. Take a compensating __module_get() before the svc_xprt_put() to keep the count balanced, matching the convention in svc_rdma_accept()'s error path. Fixes: 4fb8518bdac8 ("sunrpc: Tag svc_xprt with net") Acked-by: Jeff Layton Link: https://patch.msgid.link/20260527-rdma-follow-on-v1-3-1b09bd87b6cd@oracle.com Signed-off-by: Chuck Lever --- net/sunrpc/xprtrdma/svc_rdma_transport.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/net/sunrpc/xprtrdma/svc_rdma_transport.c b/net/sunrpc/xprtrdma/svc_rdma_transport.c index 55e2ca036584..63dbf16dbe7f 100644 --- a/net/sunrpc/xprtrdma/svc_rdma_transport.c +++ b/net/sunrpc/xprtrdma/svc_rdma_transport.c @@ -388,7 +388,13 @@ static struct svc_xprt *svc_rdma_create(struct svc_serv *serv, listen_id = svc_rdma_create_listen_id(net, sa, cma_xprt); if (IS_ERR(listen_id)) { - kfree(cma_xprt); + /* _svc_xprt_create() acquired one module reference and + * puts it on xpo_create failure. svc_xprt_free() puts + * a second one when the kref drops to zero. Take a + * compensating reference so both puts are balanced. + */ + __module_get(cma_xprt->sc_xprt.xpt_class->xcl_owner); + svc_xprt_put(&cma_xprt->sc_xprt); return ERR_CAST(listen_id); } cma_xprt->sc_cm_id = listen_id; From c255f6eceffce6f7fbfe3106501226fb92e37d57 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Wed, 27 May 2026 11:00:14 -0400 Subject: [PATCH 415/562] svcrdma: Reject connection when transport allocation fails handle_connect_req() returns without action when svc_rdma_create_xprt() fails to allocate the new transport. The CM core returns 0 for CONNECT_REQUEST events, so it does not destroy the new rdma_cm_id. Each allocation failure under memory pressure leaks one rdma_cm_id, and a remote peer driving connection attempts can amplify this. Reject the connection by returning a non-zero status from the CM event handler, which tells the CM core to destroy the orphaned cm_id. Fixes: 377f9b2f4529 ("rdma: SVCRDMA Core Transport Services") Acked-by: Jeff Layton Link: https://patch.msgid.link/20260527-rdma-follow-on-v1-4-1b09bd87b6cd@oracle.com Signed-off-by: Chuck Lever --- net/sunrpc/xprtrdma/svc_rdma_transport.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/net/sunrpc/xprtrdma/svc_rdma_transport.c b/net/sunrpc/xprtrdma/svc_rdma_transport.c index 63dbf16dbe7f..656b2bd258a9 100644 --- a/net/sunrpc/xprtrdma/svc_rdma_transport.c +++ b/net/sunrpc/xprtrdma/svc_rdma_transport.c @@ -246,12 +246,16 @@ svc_rdma_parse_connect_private(struct svcxprt_rdma *newxprt, * structure for the listening endpoint. * * This function creates a new xprt for the new connection and enqueues it on - * the accept queue for the listent xprt. When the listen thread is kicked, it + * the accept queue for the listen xprt. When the listen thread is kicked, it * will call the recvfrom method on the listen xprt which will accept the new * connection. + * + * Return values: + * %0: Do not destroy @new_cma_id + * %1: Destroy @new_cma_id (allocation failure) */ -static void handle_connect_req(struct rdma_cm_id *new_cma_id, - struct rdma_conn_param *param) +static int handle_connect_req(struct rdma_cm_id *new_cma_id, + struct rdma_conn_param *param) { struct svcxprt_rdma *listen_xprt = new_cma_id->context; struct svcxprt_rdma *newxprt; @@ -261,7 +265,7 @@ static void handle_connect_req(struct rdma_cm_id *new_cma_id, listen_xprt->sc_xprt.xpt_net, ibdev_to_node(new_cma_id->device)); if (!newxprt) - return; + return 1; newxprt->sc_cm_id = new_cma_id; new_cma_id->context = newxprt; svc_rdma_parse_connect_private(newxprt, param); @@ -295,6 +299,7 @@ static void handle_connect_req(struct rdma_cm_id *new_cma_id, set_bit(XPT_CONN, &listen_xprt->sc_xprt.xpt_flags); svc_xprt_enqueue(&listen_xprt->sc_xprt); + return 0; } /** @@ -318,8 +323,7 @@ static int svc_rdma_listen_handler(struct rdma_cm_id *cma_id, switch (event->event) { case RDMA_CM_EVENT_CONNECT_REQUEST: - handle_connect_req(cma_id, &event->param.conn); - break; + return handle_connect_req(cma_id, &event->param.conn); case RDMA_CM_EVENT_ADDR_CHANGE: listen_id = svc_rdma_create_listen_id(cma_rdma->xpt_net, sap, cma_xprt); From b24fcd5afd66a1a1f071f7004d3abc6520f3d862 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Wed, 27 May 2026 11:00:15 -0400 Subject: [PATCH 416/562] svcrdma: Clear sc_cm_id when ADDR_CHANGE replacement fails When svc_rdma_listen_handler() handles RDMA_CM_EVENT_ADDR_CHANGE, it creates a replacement listener cm_id and returns 1, telling the CM core to destroy the old one. If the replacement allocation fails, sc_cm_id still points at the old cm_id that the CM core is about to destroy. Any subsequent dereference of sc_cm_id -- such as svc_rdma_detach()'s rdma_disconnect() call -- is a use-after-free. NULL sc_cm_id on the failure path and guard svc_rdma_detach()'s rdma_disconnect() call against NULL so that the listener can be torn down safely when the server shuts down. Fixes: d1b586e75ec6 ("svcrdma: Handle ADDR_CHANGE CM event properly") Acked-by: Jeff Layton Link: https://patch.msgid.link/20260527-rdma-follow-on-v1-5-1b09bd87b6cd@oracle.com Signed-off-by: Chuck Lever --- net/sunrpc/xprtrdma/svc_rdma_transport.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/sunrpc/xprtrdma/svc_rdma_transport.c b/net/sunrpc/xprtrdma/svc_rdma_transport.c index 656b2bd258a9..093371f9d245 100644 --- a/net/sunrpc/xprtrdma/svc_rdma_transport.c +++ b/net/sunrpc/xprtrdma/svc_rdma_transport.c @@ -330,6 +330,7 @@ static int svc_rdma_listen_handler(struct rdma_cm_id *cma_id, if (IS_ERR(listen_id)) { pr_err("Listener dead, address change failed for device %s\n", cma_id->device->name); + cma_xprt->sc_cm_id = NULL; } else cma_xprt->sc_cm_id = listen_id; return 1; @@ -638,7 +639,8 @@ static void svc_rdma_detach(struct svc_xprt *xprt) struct svcxprt_rdma *rdma = container_of(xprt, struct svcxprt_rdma, sc_xprt); - rdma_disconnect(rdma->sc_cm_id); + if (rdma->sc_cm_id) + rdma_disconnect(rdma->sc_cm_id); /* * Most close paths go through svc_rdma_xprt_deferred_close(), From ad2a1bfafcb59d126e96e66ca8925076e9675b6b Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Wed, 27 May 2026 14:30:41 -0400 Subject: [PATCH 417/562] nfsd: fix XDR padding calculation in ff_encode_getdeviceinfo nfsd4_ff_encode_getdeviceinfo() computes the da_addr_body reservation as 16 + netid_len + addr_len, but the subsequent xdr_encode_opaque() calls emit 8 + round_up(netid_len, 4) + round_up(addr_len, 4) bytes. The mismatch means the declared da_addr_body length exceeds the actual encoded data by 2-8 bytes on every flexfile GETDEVICEINFO reply, leaking stale reply-page content to the client and mis-aligning the subsequent version list decode. Use xdr_align_size() for each string length to match what xdr_encode_opaque() actually writes. Fixes: efcae97fa425 ("NFSD: da_addr_body field missing in some GETDEVICEINFO replies") Cc: stable@vger.kernel.org Assisted-by: kres:claude-opus-4-6 Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260527-pnfs-fixes-v1-1-784f39dc1eca@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/flexfilelayoutxdr.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/nfsd/flexfilelayoutxdr.c b/fs/nfsd/flexfilelayoutxdr.c index f9f7e38cba13..7f357dbd1bb1 100644 --- a/fs/nfsd/flexfilelayoutxdr.c +++ b/fs/nfsd/flexfilelayoutxdr.c @@ -94,7 +94,8 @@ nfsd4_ff_encode_getdeviceinfo(struct xdr_stream *xdr, } /* len + padding for two strings */ - addr_len = 16 + da->netaddr.netid_len + da->netaddr.addr_len; + addr_len = 8 + xdr_align_size(da->netaddr.netid_len) + + xdr_align_size(da->netaddr.addr_len); ver_len = 20; len = 4 + ver_len + 4 + addr_len; From b1dfa86432990d6783a6eb9e67ec001ade80ea56 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 28 May 2026 10:38:15 -0400 Subject: [PATCH 418/562] nfsd: fix XDR length calculation in nfsd4_ff_encode_layoutget The XDR buffer size calculation in nfsd4_ff_encode_layoutget() has multiple errors that can result in either an out-of-bounds write or leaking uninitialized kernel memory to the client: - fh_len doesn't account for XDR padding on the file handle data - uid and gid lengths use "8 + len" but xdr_encode_opaque() actually writes "4 + xdr_align_size(len)" bytes - ds_len omits the flags and stats_collect_hint fields (8 bytes), while len's header constant overestimates by 8 bytes -- these partially cancel but leave a net mismatch The worst case occurs with short strings (e.g. uid=0, gid=0 with an odd-sized file handle), where the function writes up to 5 bytes past the reserved XDR buffer. Conversely, when string lengths happen to be 4-byte aligned, the reservation is too large and stale buffer content is sent to the client. Fix this by breaking out every encoded field explicitly in the ds_len calculation, using xdr_align_size() for all variable-length opaque fields, and correcting the header constants. Fixes: 9b9960a0ca47 ("nfsd: Add a super simple flex file server") Cc: stable@vger.kernel.org Assisted-by: kres:claude-opus-4-7 Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260528-pnfs-fixes-v1-1-8a1255ae2f16@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/flexfilelayoutxdr.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/fs/nfsd/flexfilelayoutxdr.c b/fs/nfsd/flexfilelayoutxdr.c index 7f357dbd1bb1..374e52d3064a 100644 --- a/fs/nfsd/flexfilelayoutxdr.c +++ b/fs/nfsd/flexfilelayoutxdr.c @@ -30,19 +30,24 @@ nfsd4_ff_encode_layoutget(struct xdr_stream *xdr, struct ff_idmap uid; struct ff_idmap gid; - fh_len = 4 + fl->fh.size; + fh_len = 4 + xdr_align_size(fl->fh.size); uid.len = sprintf(uid.buf, "%u", from_kuid(&init_user_ns, fl->uid)); gid.len = sprintf(gid.buf, "%u", from_kgid(&init_user_ns, fl->gid)); - /* 8 + len for recording the length, name, and padding */ - ds_len = 20 + sizeof(stateid_opaque_t) + 4 + fh_len + - 8 + uid.len + 8 + gid.len; + /* data server entry: deviceid + efficiency + stateid + fh list + + * user + group + flags + stats_collect_hint + */ + ds_len = 16 + 4 + 4 + sizeof(stateid_opaque_t) + 4 + fh_len + + 4 + xdr_align_size(uid.len) + + 4 + xdr_align_size(gid.len) + + 4 + 4; + /* mirror: ds_count + ds */ mirror_len = 4 + ds_len; - /* The layout segment */ - len = 20 + mirror_len; + /* stripe_unit + mirror_count + mirror */ + len = 12 + mirror_len; p = xdr_reserve_space(xdr, sizeof(__be32) + len); if (!p) From 9783397e6df4f9abd14b6a06958d53334afb34ae Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Thu, 28 May 2026 15:32:08 -0400 Subject: [PATCH 419/562] SUNRPC: Reject krb5 v2 wrap tokens with oversized ec field gss_krb5_unwrap_v2() sets buf->len to a logical length, which can be much smaller than head[0].iov_len (the allocated receive-page capacity). It then calls xdr_buf_trim() with a trim length derived from the 16-bit "extra count" (ec) field in the Kerberos v2 token header. The ec field is authenticated by the post-decrypt memcmp() against the encrypted header copy, so a randomly-mutated value is rejected. However, any peer holding a valid GSS context can legitimately encrypt a token whose ec exceeds the plaintext length. Per RFC 4121, such a token is structurally malformed. Although xdr_buf_trim() now clamps the buf->len subtraction to avoid unsigned underflow, the buffer is still left in a semantically invalid state (zero length, inconsistent iov lengths) when ec is oversized. Reject these tokens before calling xdr_buf_trim(), giving callers a well-defined GSS_S_DEFECTIVE_TOKEN error and keeping the xdr_buf internally consistent. The wrapped blob begins at a nonzero offset -- both callers pass len as offset + opaque_len -- so buf->len still counts the offset bytes that precede the blob. Compare the trim length against the remaining wrapped segment, buf->len - offset, rather than the whole buffer; comparing against buf->len alone leaves an offset-wide window in which an oversized ec passes the test and xdr_buf_trim() cuts into the bytes ahead of the blob. Fixes: cf4c024b9083 ("sunrpc: trim off EC bytes in GSSAPI v2 unwrap") Cc: stable@vger.kernel.org Reviewed-by: Jeff Layton Link: https://patch.msgid.link/20260528-tier2-v1-1-d026a1415e0b@oracle.com Signed-off-by: Chuck Lever --- net/sunrpc/auth_gss/gss_krb5_wrap.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/sunrpc/auth_gss/gss_krb5_wrap.c b/net/sunrpc/auth_gss/gss_krb5_wrap.c index d84c35f779f5..d3f61c4b5a13 100644 --- a/net/sunrpc/auth_gss/gss_krb5_wrap.c +++ b/net/sunrpc/auth_gss/gss_krb5_wrap.c @@ -235,6 +235,8 @@ gss_krb5_unwrap_v2(struct krb5_ctx *kctx, int offset, int len, buf->len = len - (GSS_KRB5_TOK_HDR_LEN + headskip); /* Trim off the trailing "extra count" and checksum blob */ + if (ec + GSS_KRB5_TOK_HDR_LEN + tailskip > buf->len - offset) + return GSS_S_DEFECTIVE_TOKEN; xdr_buf_trim(buf, ec + GSS_KRB5_TOK_HDR_LEN + tailskip); *align = XDR_QUADLEN(GSS_KRB5_TOK_HDR_LEN + headskip); From 7bb44f375554b9525de0318284c0b78d3e8addbc Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Thu, 28 May 2026 15:32:09 -0400 Subject: [PATCH 420/562] SUNRPC: fix gssx_dec_option_array error path bugs Four coupled defects in the gssx XDR option-array decoder make the error paths unsafe: a NULL deref in the caller, a refcount leak on the decoded group_info, and a latent use-after-free that the leak fix would otherwise expose. gssx_dec_option_array() sets oa->count = 1 before allocating oa->data. If that allocation fails, -ENOMEM is returned with oa->count == 1 and oa->data == NULL. All other error paths jump to free_oa: which frees oa->data and NULLs it but also leaves oa->count == 1. The caller trusts the count: gssp_accept_sec_context_upcall() gssx_dec_accept_sec_context() gssx_dec_option_array() /* fails, count=1 data=NULL */ data = res.options.data[0].value /* NULL deref */ Independently, free_creds: releases the partially decoded svc_cred with a bare kfree(creds). gssx_dec_linux_creds() installs a groups_alloc() result into creds->cr_group_info; that object is kvmalloc-backed and refcounted, and only put_group_info() reaches kvfree(). A plain kfree(creds) drops the wrapper and leaks the group_info allocation. The natural fix for the leak is to call free_svc_cred(creds) before kfree(creds), but free_svc_cred() invokes put_group_info() on creds->cr_group_info unconditionally when non-NULL. The existing out_free_groups: path in gssx_dec_linux_creds() already called groups_free() on that pointer without clearing it, so once free_svc_cred() is wired in, the subsequent put_group_info() would touch freed memory. Fix all four together: - Move the oa->count = 1 assignment below the oa->data allocation so it is never set when oa->data is NULL. - Reset oa->count to 0 at free_oa: so count and data stay coherent and the caller sees an empty option array. - Call free_svc_cred(creds) before kfree(creds) at free_creds: so the refcounted cr_group_info is released. free_svc_cred() either NULL-guards each field explicitly (cr_group_info has an if() check) or delegates to a helper that is NULL-safe itself (kfree for the string fields, gss_mech_put() which guards with if(gm) at gss_mech_switch.c:342), so it is safe to call on a partially decoded svc_cred where only cr_uid/cr_gid/cr_group_info have been written and everything else is zero from kzalloc. - In gssx_dec_linux_creds()'s out_free_groups: path, release cr_group_info with put_group_info() rather than groups_free() so the teardown matches free_svc_cred()'s refcount-aware path, and clear the pointer so a later free_svc_cred() on the same creds does not release it a second time. Fixes: 3cfcfc102a5e ("SUNRPC: fix some memleaks in gssx_dec_option_array") Cc: stable@vger.kernel.org Assisted-by: kres (claude-opus-4-7) Signed-off-by: Chris Mason Reviewed-by: Jeff Layton Link: https://patch.msgid.link/20260528-tier2-v1-2-d026a1415e0b@oracle.com Signed-off-by: Chuck Lever --- net/sunrpc/auth_gss/gss_rpc_xdr.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/net/sunrpc/auth_gss/gss_rpc_xdr.c b/net/sunrpc/auth_gss/gss_rpc_xdr.c index fceee648d545..f2b8f919adea 100644 --- a/net/sunrpc/auth_gss/gss_rpc_xdr.c +++ b/net/sunrpc/auth_gss/gss_rpc_xdr.c @@ -222,7 +222,8 @@ static int gssx_dec_linux_creds(struct xdr_stream *xdr, return 0; out_free_groups: - groups_free(creds->cr_group_info); + put_group_info(creds->cr_group_info); + creds->cr_group_info = NULL; return err; } @@ -242,12 +243,12 @@ static int gssx_dec_option_array(struct xdr_stream *xdr, return 0; /* we recognize only 1 currently: CREDS_VALUE */ - oa->count = 1; - oa->data = kmalloc_obj(struct gssx_option); if (!oa->data) return -ENOMEM; + oa->count = 1; + creds = kzalloc_obj(struct svc_cred); if (!creds) { err = -ENOMEM; @@ -294,8 +295,10 @@ static int gssx_dec_option_array(struct xdr_stream *xdr, return 0; free_creds: + free_svc_cred(creds); kfree(creds); free_oa: + oa->count = 0; kfree(oa->data); oa->data = NULL; return err; From 2c6d350b909355c2b4bdef496ed946bc119cd46e Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Thu, 28 May 2026 15:32:10 -0400 Subject: [PATCH 421/562] SUNRPC: reject duplicate CREDS_VALUE options gssx_dec_option_array() walks the wire-supplied option array and, for every entry whose name matches CREDS_VALUE, calls gssx_dec_linux_creds() on the same struct svc_cred. That helper unconditionally installs a fresh groups_alloc() result into creds->cr_group_info without releasing whatever pointer was already there: for (i = 0; i < count; i++) { ... decode name ... if (length == sizeof(CREDS_VALUE) && memcmp(p, CREDS_VALUE, sizeof(CREDS_VALUE)) == 0) { err = gssx_dec_linux_creds(xdr, creds); ... } } A reply that carries two CREDS_VALUE entries therefore overwrites cr_group_info on the second iteration and orphans the group_info allocated by the first call. The earlier free_creds path only releases the last cr_group_info via free_svc_cred(), so the first allocation's refcount stays at one and its kvmalloc-backed storage is leaked. No in-tree caller of gssp_accept_sec_context_upcall() expects more than one CREDS_VALUE per reply. Fix by tracking whether a CREDS_VALUE option has already been decoded and returning -EINVAL on any subsequent match, so the free_creds path releases the single group_info that was installed. Fixes: 1d658336b05f ("SUNRPC: Add RPC based upcall mechanism for RPCGSS auth") Cc: stable@vger.kernel.org Assisted-by: kres (claude-opus-4-7) Signed-off-by: Chris Mason Reviewed-by: Jeff Layton Link: https://patch.msgid.link/20260528-tier2-v1-3-d026a1415e0b@oracle.com Signed-off-by: Chuck Lever --- net/sunrpc/auth_gss/gss_rpc_xdr.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/sunrpc/auth_gss/gss_rpc_xdr.c b/net/sunrpc/auth_gss/gss_rpc_xdr.c index f2b8f919adea..0549edae1ebe 100644 --- a/net/sunrpc/auth_gss/gss_rpc_xdr.c +++ b/net/sunrpc/auth_gss/gss_rpc_xdr.c @@ -231,6 +231,7 @@ static int gssx_dec_option_array(struct xdr_stream *xdr, struct gssx_option_array *oa) { struct svc_cred *creds; + bool creds_decoded = false; u32 count, i; __be32 *p; int err; @@ -281,9 +282,14 @@ static int gssx_dec_option_array(struct xdr_stream *xdr, if (length == sizeof(CREDS_VALUE) && memcmp(p, CREDS_VALUE, sizeof(CREDS_VALUE)) == 0) { /* We have creds here. parse them */ + if (creds_decoded) { + err = -EINVAL; + goto free_creds; + } err = gssx_dec_linux_creds(xdr, creds); if (err) goto free_creds; + creds_decoded = true; oa->data[0].value.len = 1; /* presence */ } else { /* consume uninteresting buffer */ From c71c34d57e8c01f4d6ffb632f125206600c604c0 Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Thu, 28 May 2026 15:32:11 -0400 Subject: [PATCH 422/562] SUNRPC: Guard svcauth_gss_release() dispatch on rq_auth_stat svcauth_gss_release() reads gc_proc and switches on gc_svc before consulting rq_auth_stat. On the SVC_DENIED path after a failed svcauth_gss_accept(), those fields may hold stale values from a prior request or uninitialized slab residue: svcauth_gss_accept() allocates gss_svc_data with non-zeroing kmalloc and clears only gsd_databody_offset and rsci per request, not clcred. Because RPC_GSS_PROC_DATA is zero, a zeroed or stale-zero gc_proc passes the existing guard and falls through into the gc_svc switch, which can dispatch to svcauth_gss_wrap_integ() or svcauth_gss_wrap_priv(). Both wrap helpers call svcauth_gss_prepare_to_wrap() before any rsci->mechctx dereference, and that helper already returns early when rq_auth_stat is not rpc_auth_ok, so the downstream NULL dereference is blocked. The dispatch itself remains structurally wrong: it reads scalars that the caller has no contract to have initialized after a failed authentication. Mirror the existing rq_auth_stat gate in svcauth_gss_prepare_to_wrap() one frame up, so svcauth_gss_release() skips the clcred dispatch entirely when authentication has not succeeded. The cleanup tail that releases rq_client, rq_gssclient, cr_group_info, and rsci still runs. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Chris Mason Reviewed-by: Jeff Layton Link: https://patch.msgid.link/20260528-tier2-v1-4-d026a1415e0b@oracle.com Signed-off-by: Chuck Lever --- net/sunrpc/auth_gss/svcauth_gss.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/sunrpc/auth_gss/svcauth_gss.c b/net/sunrpc/auth_gss/svcauth_gss.c index 8e8aceb31270..4eb537410cb5 100644 --- a/net/sunrpc/auth_gss/svcauth_gss.c +++ b/net/sunrpc/auth_gss/svcauth_gss.c @@ -1944,6 +1944,8 @@ svcauth_gss_release(struct svc_rqst *rqstp) if (!gsd) goto out; + if (rqstp->rq_auth_stat != rpc_auth_ok) + goto out; gc = &gsd->clcred; if (gc->gc_proc != RPC_GSS_PROC_DATA) goto out; From f0e02e82ca258754b5f8acfe2ce3014c4397f367 Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Thu, 28 May 2026 15:32:12 -0400 Subject: [PATCH 423/562] SUNRPC: Zero rpc_gss_wire_cred at svcauth_gss_decode_credbody() entry svcauth_gss_decode_credbody() writes the caller's rpc_gss_wire_cred field by field and assigns gc_ctx.len only on the success tail. The caller storage is svcdata->clcred, which lives in the per-svc_rqst gss_svc_data and is reused across requests. Early decode failures leave partially decoded state mixed with residue from the prior request. The trailing body_len tightness check is the sharpest case: xdr_stream_decode_opaque_inline() has already written gc_ctx.data with a borrowed inline pointer into the current request's XDR pages, but gc_ctx.len retains its prior value. Once the request pages are released the pooled clcred carries a dangling pointer paired with a stale length. Zero the caller's rpc_gss_wire_cred at function entry so that every early-return path leaves a deterministic all-zero cred. On the trailing tightness-check path, gc_ctx.len is now zero instead of stale, which neuters length-driven consumers such as gss_svc_searchbyctx() that would otherwise walk the dangling data pointer. Fixes: b0bc53470d1a ("SUNRPC: Convert the svcauth_gss_accept() pre-amble to use xdr_stream") Cc: stable@vger.kernel.org Signed-off-by: Chris Mason Reviewed-by: Jeff Layton Link: https://patch.msgid.link/20260528-tier2-v1-5-d026a1415e0b@oracle.com Signed-off-by: Chuck Lever --- net/sunrpc/auth_gss/svcauth_gss.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/sunrpc/auth_gss/svcauth_gss.c b/net/sunrpc/auth_gss/svcauth_gss.c index 4eb537410cb5..764b4d82951d 100644 --- a/net/sunrpc/auth_gss/svcauth_gss.c +++ b/net/sunrpc/auth_gss/svcauth_gss.c @@ -1575,6 +1575,9 @@ svcauth_gss_decode_credbody(struct xdr_stream *xdr, u32 body_len; __be32 *p; + /* Early-return paths leave deterministic state, not stale residue. */ + memset(gc, 0, sizeof(*gc)); + p = xdr_inline_decode(xdr, XDR_UNIT); if (!p) return false; From 0814ee624c9e3efb33eb1c335cc4af718dafb79b Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Thu, 28 May 2026 15:32:13 -0400 Subject: [PATCH 424/562] SUNRPC: close backchannel before destroying callback service A backchannel receive can complete a request while the NFS callback service is being torn down. xprt_complete_bc_request() removes the request from bc_pa_list, drops bc_alloc_count, marks the request in use, and then asks xprt_enqueue_bc_request() to hand it to the callback service. If teardown has already cleared xprt->bc_serv, xprt_enqueue_bc_request() currently returns without enqueueing or freeing the committed request. The xprt_get() taken on entry is leaked as well. If the producer wins the race before bc_serv is cleared, it can also enqueue onto sv_cb_list after nfs_callback_down() has stopped the callback threads, leaving the request linked to a svc_serv that is about to be freed. Close the producer side before callback threads are stopped. Add xprt_svc_shutdown_bc() to clear xprt->bc_serv under bc_pa_lock, and call it on callback shutdown and callback-start failure before stopping the service threads. Requests that lose the NULL transition in xprt_enqueue_bc_request() are released through the normal backchannel free path after balancing bc_slot_count. Finally, drain any remaining sv_cb_list requests after the callback threads have stopped and before svc_destroy() frees the service. Fixes: 441244d4273a ("SUNRPC: cleanup common code in backchannel request") Fixes: 9e9fdd0ad0fb ("NFSv4.1: protect destroying and nullifying bc_serv structure") Cc: stable@vger.kernel.org Signed-off-by: Chris Mason Reviewed-by: Jeff Layton Link: https://patch.msgid.link/20260528-tier2-v1-6-d026a1415e0b@oracle.com Signed-off-by: Chuck Lever --- fs/nfs/callback.c | 4 +++- include/linux/sunrpc/bc_xprt.h | 5 +++++ net/sunrpc/backchannel_rqst.c | 38 +++++++++++++++++++++++++++------- 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/fs/nfs/callback.c b/fs/nfs/callback.c index ff4e9fd38e83..bc282b744f34 100644 --- a/fs/nfs/callback.c +++ b/fs/nfs/callback.c @@ -231,8 +231,9 @@ int nfs_callback_up(u32 minorversion, struct rpc_xprt *xprt) cb_info->users++; err_net: if (!cb_info->users) { + xprt_svc_shutdown_bc(xprt); svc_set_num_threads(cb_info->serv, 0, 0); - svc_destroy(&cb_info->serv); + xprt_svc_destroy_nullify_bc(xprt, &cb_info->serv); } err_create: mutex_unlock(&nfs_callback_mutex); @@ -254,6 +255,7 @@ void nfs_callback_down(int minorversion, struct net *net, struct rpc_xprt *xprt) mutex_lock(&nfs_callback_mutex); serv = cb_info->serv; + xprt_svc_shutdown_bc(xprt); nfs_callback_down_net(minorversion, serv, net); cb_info->users--; if (cb_info->users == 0) { diff --git a/include/linux/sunrpc/bc_xprt.h b/include/linux/sunrpc/bc_xprt.h index 98939cb664cf..59d0cc889beb 100644 --- a/include/linux/sunrpc/bc_xprt.h +++ b/include/linux/sunrpc/bc_xprt.h @@ -32,6 +32,7 @@ int xprt_setup_bc(struct rpc_xprt *xprt, unsigned int min_reqs); void xprt_destroy_bc(struct rpc_xprt *xprt, unsigned int max_reqs); void xprt_free_bc_rqst(struct rpc_rqst *req); unsigned int xprt_bc_max_slots(struct rpc_xprt *xprt); +void xprt_svc_shutdown_bc(struct rpc_xprt *xprt); void xprt_svc_destroy_nullify_bc(struct rpc_xprt *xprt, struct svc_serv **serv); /* @@ -71,6 +72,10 @@ static inline void xprt_free_bc_request(struct rpc_rqst *req) { } +static inline void xprt_svc_shutdown_bc(struct rpc_xprt *xprt) +{ +} + static inline void xprt_svc_destroy_nullify_bc(struct rpc_xprt *xprt, struct svc_serv **serv) { svc_destroy(serv); diff --git a/net/sunrpc/backchannel_rqst.c b/net/sunrpc/backchannel_rqst.c index 0ffa4d01a938..1482b06e0f38 100644 --- a/net/sunrpc/backchannel_rqst.c +++ b/net/sunrpc/backchannel_rqst.c @@ -25,20 +25,39 @@ unsigned int xprt_bc_max_slots(struct rpc_xprt *xprt) } /* - * Helper function to nullify backchannel server pointer in transport. - * We need to synchronize setting the pointer to NULL (done so after - * the backchannel server is shutdown) with the usage of that pointer - * by the backchannel request processing routines - * xprt_complete_bc_request() and rpcrdma_bc_receive_call(). + * Close the backchannel producer side, drain any requests still + * queued on sv_cb_list, then destroy the callback service. */ void xprt_svc_destroy_nullify_bc(struct rpc_xprt *xprt, struct svc_serv **serv) { - spin_lock(&xprt->bc_pa_lock); + struct svc_serv *bc_serv = *serv; + struct rpc_rqst *req; + + xprt_svc_shutdown_bc(xprt); + while ((req = lwq_dequeue(&bc_serv->sv_cb_list, struct rpc_rqst, + rq_bc_list)) != NULL) { + atomic_dec(&req->rq_xprt->bc_slot_count); + xprt_free_bc_request(req); + } svc_destroy(serv); +} +EXPORT_SYMBOL_GPL(xprt_svc_destroy_nullify_bc); + +/* + * Clear the backchannel server pointer in the transport. The NULL + * store is serialized under bc_pa_lock against readers of + * xprt->bc_serv in xprt_complete_bc_request() and + * rpcrdma_bc_receive_call(). Clearing it before the callback service + * is stopped prevents a producer from enqueueing onto a service that + * is being torn down. + */ +void xprt_svc_shutdown_bc(struct rpc_xprt *xprt) +{ + spin_lock(&xprt->bc_pa_lock); xprt->bc_serv = NULL; spin_unlock(&xprt->bc_pa_lock); } -EXPORT_SYMBOL_GPL(xprt_svc_destroy_nullify_bc); +EXPORT_SYMBOL_GPL(xprt_svc_shutdown_bc); /* * Helper routines that track the number of preallocation elements @@ -393,7 +412,12 @@ void xprt_enqueue_bc_request(struct rpc_rqst *req) if (bc_serv) { lwq_enqueue(&req->rq_bc_list, &bc_serv->sv_cb_list); svc_pool_wake_idle_thread(&bc_serv->sv_pools[0]); + spin_unlock(&xprt->bc_pa_lock); + return; } spin_unlock(&xprt->bc_pa_lock); + + atomic_dec(&xprt->bc_slot_count); + xprt_free_bc_request(req); } EXPORT_SYMBOL_GPL(xprt_enqueue_bc_request); From 0d383bc734941fa14ea9167a0a5fb4450154e624 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Sat, 30 May 2026 09:19:17 -0400 Subject: [PATCH 425/562] nfsd: fix BUG_ON in nfsd4_alloc_layout_stateid on racing delegation revoke nfsd4_alloc_layout_stateid reads fp->fi_deleg_file without holding fi_lock when the parent stateid is a delegation. A concurrent delegation revoke via the laundromat can clear fi_deleg_file under fi_lock, causing nfsd_file_get() to return NULL and triggering the BUG_ON. This race is client-reachable: two NFS clients can trigger it by having one hold a delegation while another opens the same file to force a recall. When the first client doesn't respond to the recall, the laundromat revokes it. A concurrent LAYOUTGET from any client using the delegation stateid hits the race window. Fix this by taking fi_lock around the fi_deleg_file read in the SC_TYPE_DELEG path, matching the locking discipline of the find_any_file() arm, and replacing the BUG_ON with a graceful error return that cleans up the partially-initialized layout stateid. Fixes: c5c707f96fc9 ("nfsd: implement pNFS layout recalls") Cc: stable@vger.kernel.org Assisted-by: kres:claude-opus-4-7 Reported-by: Chris Mason Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260530-nfsd-fixes-v2-1-f27e8eb4d974@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfs4layouts.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/fs/nfsd/nfs4layouts.c b/fs/nfsd/nfs4layouts.c index f34320e4c2f4..1de2f6cd1f09 100644 --- a/fs/nfsd/nfs4layouts.c +++ b/fs/nfsd/nfs4layouts.c @@ -247,11 +247,17 @@ nfsd4_alloc_layout_stateid(struct nfsd4_compound_state *cstate, nfsd4_init_cb(&ls->ls_recall, clp, &nfsd4_cb_layout_ops, NFSPROC4_CLNT_CB_LAYOUT); - if (parent->sc_type == SC_TYPE_DELEG) + if (parent->sc_type == SC_TYPE_DELEG) { + spin_lock(&fp->fi_lock); ls->ls_file = nfsd_file_get(fp->fi_deleg_file); - else + spin_unlock(&fp->fi_lock); + } else { ls->ls_file = find_any_file(fp); - BUG_ON(!ls->ls_file); + } + if (!ls->ls_file) { + nfs4_put_stid(stp); + return NULL; + } ls->ls_fenced = false; ls->ls_fence_delay = 0; From 69223ae1d5d5ce0214f444e83b3ccd7de0dfe912 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Sat, 30 May 2026 09:19:18 -0400 Subject: [PATCH 426/562] nfsd: RCU-protect cl_cb_session to fix use-after-free on session teardown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After a DESTROY_SESSION the per-session teardown path can free a session while rpciod still holds an inflight callback rpc_task that dereferences clp->cl_cb_session. nfsd4_probe_callback_sync() flushes cl_callback_wq, but once nfsd4_run_cb_work() has called rpc_call_async() the rpc_task lives on rpciod; flushing the workqueue does not wait for it. rpc_shutdown_client() does drain rpciod tasks, but uses a 1-second wait_event_timeout — tasks stuck in rpc_delay() (e.g. 2-second NFS4ERR_DELAY retries) can outlive the drain. destroy path rpciod ------------ ------ unhash_session(ses) nfsd4_probe_callback_sync(clp) flush_workqueue(cl_callback_wq) /* returns; rpc_task still live */ nfsd4_put_session_locked(ses) free_session(ses) -> kfree(ses) nfsd4_cb_sequence_done() reads cb_clp->cl_cb_session /* freed slab */ A second window exists in nfsd4_process_cb_update(). When __nfsd4_find_backchannel() returns NULL because unhash_session() has already removed the destroyed session from cl_sessions, setup_callback_client() takes the v4.1 early return so clp->cl_cb_session = ses never fires and the field retains a pointer to the about-to-be-freed session. Fix both by converting cl_cb_session to an RCU-protected pointer: - Move the cl_cb_session = ses assignment in setup_callback_client() to after rpc_create() succeeds, so it is only published when a working backchannel exists. Clear cl_cb_session on the error return in nfsd4_process_cb_update(). Both stores use rcu_assign_pointer(). - Annotate cl_cb_session with __rcu. All rpciod-side readers use rcu_read_lock()/rcu_dereference() and check for NULL, bailing to the appropriate error or requeue path: encode_cb_sequence4args(), decode_cb_sequence4resok(), nfsd41_cb_get_slot(), nfsd41_cb_release_slot(), nfsd4_cb_prepare(), and nfsd4_cb_sequence_done(). - Switch __free_session() from kfree() to kfree_rcu() so the session slab is not reclaimed until after an RCU grace period, guaranteeing that rpciod readers inside rcu_read_lock() never dereference freed memory. - Pass the session pointer to the nfsd_cb_seq_status and nfsd_cb_free_slot tracepoints instead of having them re-read cl_cb_session. - nfsd4_cb_prepare() calls rpc_exit() when the session is NULL, routing through the done/release path to requeue the callback. Fixes: dcbeaa68dbbd ("nfsd4: allow backchannel recovery") Cc: stable@vger.kernel.org Reported-by: Chris Mason Signed-off-by: Chris Mason Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260530-nfsd-fixes-v2-2-f27e8eb4d974@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfs4callback.c | 109 ++++++++++++++++++++++++++++++++++------- fs/nfsd/nfs4state.c | 4 +- fs/nfsd/state.h | 3 +- fs/nfsd/trace.h | 14 +++--- 4 files changed, 100 insertions(+), 30 deletions(-) diff --git a/fs/nfsd/nfs4callback.c b/fs/nfsd/nfs4callback.c index 50827405468d..1628bb9ef9dd 100644 --- a/fs/nfsd/nfs4callback.c +++ b/fs/nfsd/nfs4callback.c @@ -456,13 +456,20 @@ static void encode_cb_sequence4args(struct xdr_stream *xdr, const struct nfsd4_callback *cb, struct nfs4_cb_compound_hdr *hdr) { - struct nfsd4_session *session = cb->cb_clp->cl_cb_session; + struct nfsd4_session *session; struct nfsd4_referring_call_list *rcl; __be32 *p; if (hdr->minorversion == 0) return; + rcu_read_lock(); + session = rcu_dereference(cb->cb_clp->cl_cb_session); + if (!session) { + rcu_read_unlock(); + return; + } + encode_nfs_cb_opnum4(xdr, OP_CB_SEQUENCE); encode_sessionid4(xdr, session); @@ -478,6 +485,7 @@ static void encode_cb_sequence4args(struct xdr_stream *xdr, encode_referring_call_list4(xdr, rcl); hdr->nops++; + rcu_read_unlock(); } static void update_cb_slot_table(struct nfsd4_session *ses, u32 target) @@ -529,21 +537,32 @@ static void update_cb_slot_table(struct nfsd4_session *ses, u32 target) static int decode_cb_sequence4resok(struct xdr_stream *xdr, struct nfsd4_callback *cb) { - struct nfsd4_session *session = cb->cb_clp->cl_cb_session; + struct nfsd4_session *session; int status = -ESERVERFAULT; __be32 *p; u32 seqid, slotid, target; + rcu_read_lock(); + session = rcu_dereference(cb->cb_clp->cl_cb_session); + if (!session) { + rcu_read_unlock(); + cb->cb_seq_status = -NFS4ERR_BADSESSION; + return -NFS4ERR_BADSESSION; + } + /* * If the server returns different values for sessionID, slotID or * sequence number, the server is looney tunes. */ p = xdr_inline_decode(xdr, NFS4_MAX_SESSIONID_LEN + 4 + 4 + 4 + 4); - if (unlikely(p == NULL)) + if (unlikely(p == NULL)) { + rcu_read_unlock(); goto out_overflow; + } if (memcmp(p, session->se_sessionid.data, NFS4_MAX_SESSIONID_LEN)) { dprintk("NFS: %s Invalid session id\n", __func__); + rcu_read_unlock(); goto out; } p += XDR_QUADLEN(NFS4_MAX_SESSIONID_LEN); @@ -551,12 +570,14 @@ static int decode_cb_sequence4resok(struct xdr_stream *xdr, seqid = be32_to_cpup(p++); if (seqid != session->se_cb_seq_nr[cb->cb_held_slot]) { dprintk("NFS: %s Invalid sequence number\n", __func__); + rcu_read_unlock(); goto out; } slotid = be32_to_cpup(p++); if (slotid != cb->cb_held_slot) { dprintk("NFS: %s Invalid slotid\n", __func__); + rcu_read_unlock(); goto out; } @@ -564,6 +585,7 @@ static int decode_cb_sequence4resok(struct xdr_stream *xdr, target = be32_to_cpup(p++); update_cb_slot_table(session, target); + rcu_read_unlock(); status = 0; out: cb->cb_seq_status = status; @@ -1150,9 +1172,8 @@ static int setup_callback_client(struct nfs4_client *clp, struct nfs4_cb_conn *c } else { if (!conn->cb_xprt || !ses) return -EINVAL; - clp->cl_cb_session = ses; args.bc_xprt = conn->cb_xprt; - args.prognumber = clp->cl_cb_session->se_cb_prog; + args.prognumber = ses->se_cb_prog; args.protocol = conn->cb_xprt->xpt_class->xcl_ident | XPRT_TRANSPORT_BC; args.authflavor = ses->se_cb_sec.flavor; @@ -1170,8 +1191,10 @@ static int setup_callback_client(struct nfs4_client *clp, struct nfs4_cb_conn *c return -ENOMEM; } - if (clp->cl_minorversion != 0) + if (clp->cl_minorversion != 0) { clp->cl_cb_conn.cb_xprt = conn->cb_xprt; + rcu_assign_pointer(clp->cl_cb_session, ses); + } clp->cl_cb_client = client; clp->cl_cb_cred = cred; rcu_read_lock(); @@ -1278,18 +1301,33 @@ static int grab_slot(struct nfsd4_session *ses) static bool nfsd41_cb_get_slot(struct nfsd4_callback *cb, struct rpc_task *task) { struct nfs4_client *clp = cb->cb_clp; - struct nfsd4_session *ses = clp->cl_cb_session; + struct nfsd4_session *ses; if (cb->cb_held_slot >= 0) return true; + + rcu_read_lock(); + ses = rcu_dereference(clp->cl_cb_session); + if (!ses) { + rcu_read_unlock(); + rpc_sleep_on(&clp->cl_cb_waitq, task, NULL); + return false; + } cb->cb_held_slot = grab_slot(ses); if (cb->cb_held_slot < 0) { + rcu_read_unlock(); rpc_sleep_on(&clp->cl_cb_waitq, task, NULL); /* Race breaker */ - cb->cb_held_slot = grab_slot(ses); + rcu_read_lock(); + ses = rcu_dereference(clp->cl_cb_session); + if (ses) + cb->cb_held_slot = grab_slot(ses); + rcu_read_unlock(); if (cb->cb_held_slot < 0) return false; rpc_wake_up_queued_task(&clp->cl_cb_waitq, task); + } else { + rcu_read_unlock(); } return true; } @@ -1297,12 +1335,17 @@ static bool nfsd41_cb_get_slot(struct nfsd4_callback *cb, struct rpc_task *task) static void nfsd41_cb_release_slot(struct nfsd4_callback *cb) { struct nfs4_client *clp = cb->cb_clp; - struct nfsd4_session *ses = clp->cl_cb_session; + struct nfsd4_session *ses; if (cb->cb_held_slot >= 0) { - spin_lock(&ses->se_lock); - ses->se_cb_slot_avail |= BIT(cb->cb_held_slot); - spin_unlock(&ses->se_lock); + rcu_read_lock(); + ses = rcu_dereference(clp->cl_cb_session); + if (ses) { + spin_lock(&ses->se_lock); + ses->se_cb_slot_avail |= BIT(cb->cb_held_slot); + spin_unlock(&ses->se_lock); + } + rcu_read_unlock(); cb->cb_held_slot = -1; rpc_wake_up_next(&clp->cl_cb_waitq); } @@ -1434,22 +1477,35 @@ static void nfsd4_cb_prepare(struct rpc_task *task, void *calldata) trace_nfsd_cb_rpc_prepare(clp); cb->cb_seq_status = 1; cb->cb_status = 0; - if (minorversion && !nfsd41_cb_get_slot(cb, task)) - return; + if (minorversion) { + if (!rcu_access_pointer(clp->cl_cb_session)) { + rpc_exit(task, -EIO); + return; + } + if (!nfsd41_cb_get_slot(cb, task)) + return; + } rpc_call_start(task); } /* Returns true if CB_COMPOUND processing should continue */ static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callback *cb) { - struct nfsd4_session *session = cb->cb_clp->cl_cb_session; + struct nfsd4_session *session; bool ret = false; if (cb->cb_held_slot < 0) goto requeue; + rcu_read_lock(); + session = rcu_dereference(cb->cb_clp->cl_cb_session); + if (!session) { + rcu_read_unlock(); + goto requeue; + } + /* This is the operation status code for CB_SEQUENCE */ - trace_nfsd_cb_seq_status(task, cb); + trace_nfsd_cb_seq_status(task, cb, session); switch (cb->cb_seq_status) { case 0: /* @@ -1481,12 +1537,16 @@ static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callback fallthrough; case -NFS4ERR_BADSESSION: nfsd4_mark_cb_fault(cb->cb_clp); + rcu_read_unlock(); goto requeue; case -NFS4ERR_DELAY: cb->cb_seq_status = 1; - if (RPC_SIGNALLED(task) || !rpc_restart_call(task)) + if (RPC_SIGNALLED(task) || !rpc_restart_call(task)) { + rcu_read_unlock(); goto requeue; + } rpc_delay(task, 2 * HZ); + rcu_read_unlock(); return false; case -NFS4ERR_SEQ_MISORDERED: case -NFS4ERR_BADSLOT: @@ -1498,11 +1558,13 @@ static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callback */ nfsd4_mark_cb_fault(cb->cb_clp); cb->cb_held_slot = -1; + rcu_read_unlock(); goto retry_nowait; default: nfsd4_mark_cb_fault(cb->cb_clp); } - trace_nfsd_cb_free_slot(task, cb); + trace_nfsd_cb_free_slot(task, cb, session); + rcu_read_unlock(); nfsd41_cb_release_slot(cb); return ret; retry_nowait: @@ -1624,7 +1686,15 @@ static struct nfsd4_conn * __nfsd4_find_backchannel(struct nfs4_client *clp) * Note there isn't a lot of locking in this code; instead we depend on * the fact that it is run from clp->cl_callback_wq, which won't run two * work items at once. So, for example, clp->cl_callback_wq handles all - * access of cl_cb_client and all calls to rpc_create or rpc_shutdown_client. + * access of cl_cb_client, and all calls to rpc_create or + * rpc_shutdown_client. + * + * cl_cb_session is written only from cl_callback_wq (via + * rcu_assign_pointer) and read from rpciod under rcu_read_lock (via + * rcu_dereference) by encode_cb_sequence4args(), decode_cb_sequence4resok(), + * nfsd4_cb_sequence_done(), and the cb-slot helpers. Sessions are freed + * with kfree_rcu() so that rpciod readers in an RCU read-side critical + * section never dereference a freed session. */ static void nfsd4_process_cb_update(struct nfsd4_callback *cb) { @@ -1676,6 +1746,7 @@ static void nfsd4_process_cb_update(struct nfsd4_callback *cb) nfsd4_mark_cb_down(clp); if (c) svc_xprt_put(c->cn_xprt); + rcu_assign_pointer(clp->cl_cb_session, ses); return; } } diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index c7eb558cb86b..c99538d36470 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -2295,7 +2295,7 @@ static void __free_session(struct nfsd4_session *ses) { free_session_slots(ses, 0); xa_destroy(&ses->se_slots); - kfree(ses); + kfree_rcu(ses, rcu_head); } static void free_session(struct nfsd4_session *ses) @@ -3414,7 +3414,7 @@ static struct nfs4_client *create_client(struct xdr_netobj name, clp->cl_time = ktime_get_boottime_seconds(); copy_verf(clp, verf); memcpy(&clp->cl_addr, sa, sizeof(struct sockaddr_storage)); - clp->cl_cb_session = NULL; + RCU_INIT_POINTER(clp->cl_cb_session, NULL); clp->net = net; clp->cl_nfsd_dentry = nfsd_client_mkdir( nn, &clp->cl_nfsdfs, diff --git a/fs/nfsd/state.h b/fs/nfsd/state.h index dec83e92650d..ac6fd0d6d099 100644 --- a/fs/nfsd/state.h +++ b/fs/nfsd/state.h @@ -384,6 +384,7 @@ struct nfsd4_session { u16 se_slot_gen; bool se_dead; u32 se_target_maxslots; + struct rcu_head rcu_head; }; /* formatted contents of nfs4_sessionid */ @@ -496,7 +497,7 @@ struct nfs4_client { #define NFSD4_CB_FAULT 3 int cl_cb_state; struct nfsd4_callback cl_cb_null; - struct nfsd4_session *cl_cb_session; + struct nfsd4_session __rcu *cl_cb_session; /* for all client information that callback code might need: */ spinlock_t cl_lock; diff --git a/fs/nfsd/trace.h b/fs/nfsd/trace.h index 1c5a1e50f946..830d9ceb4fe3 100644 --- a/fs/nfsd/trace.h +++ b/fs/nfsd/trace.h @@ -1727,9 +1727,10 @@ DEFINE_NFSD_CB_LIFETIME_EVENT(bc_shutdown); TRACE_EVENT(nfsd_cb_seq_status, TP_PROTO( const struct rpc_task *task, - const struct nfsd4_callback *cb + const struct nfsd4_callback *cb, + const struct nfsd4_session *session ), - TP_ARGS(task, cb), + TP_ARGS(task, cb, session), TP_STRUCT__entry( __field(unsigned int, task_id) __field(unsigned int, client_id) @@ -1741,8 +1742,6 @@ TRACE_EVENT(nfsd_cb_seq_status, __field(int, seq_status) ), TP_fast_assign( - const struct nfs4_client *clp = cb->cb_clp; - const struct nfsd4_session *session = clp->cl_cb_session; const struct nfsd4_sessionid *sid = (struct nfsd4_sessionid *)&session->se_sessionid; @@ -1768,9 +1767,10 @@ TRACE_EVENT(nfsd_cb_seq_status, TRACE_EVENT(nfsd_cb_free_slot, TP_PROTO( const struct rpc_task *task, - const struct nfsd4_callback *cb + const struct nfsd4_callback *cb, + const struct nfsd4_session *session ), - TP_ARGS(task, cb), + TP_ARGS(task, cb, session), TP_STRUCT__entry( __field(unsigned int, task_id) __field(unsigned int, client_id) @@ -1781,8 +1781,6 @@ TRACE_EVENT(nfsd_cb_free_slot, __field(u32, slot_seqno) ), TP_fast_assign( - const struct nfs4_client *clp = cb->cb_clp; - const struct nfsd4_session *session = clp->cl_cb_session; const struct nfsd4_sessionid *sid = (struct nfsd4_sessionid *)&session->se_sessionid; From 0310998daaaf5ea9e2dacf9806d0ae71a31c4b8d Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Sat, 30 May 2026 09:19:19 -0400 Subject: [PATCH 427/562] nfsd: convert nfsd_net boolean flags to unsigned long flags word nfsd_net contains several boolean fields that are accessed from concurrent contexts without serialization. In particular, nfsd4_end_grace() guards its drain path with a plain bool: if (nn->grace_ended) return; nn->grace_ended = true; The read and the write are independent, and nothing in struct nfsd_net serializes them. At least two contexts can reach this code with no lock held: laundromat path laundry_wq kworker nfs4_laundromat() nfsd4_end_grace() RECLAIM_COMPLETE path nfsd compound kthread nfsd4_reclaim_complete() inc_reclaim_complete() nfsd4_end_grace() Both callers can observe grace_ended == false on different CPUs, both store true, and both proceed into nfsd4_record_grace_done(), which invokes the active client_tracking_ops->grace_done callback. For tracking ops that drain reclaim_str_hashtbl (legacy_tracking_ops via nfsd4_recdir_purge_old, and the cld v1+ ops via nfsd4_cld_grace_done), grace_done calls nfs4_release_reclaim(), which walks every bucket of reclaim_str_hashtbl with no lock and calls nfs4_remove_reclaim_record() (list_del + kfree) on each entry. Two concurrent walkers corrupt the list and double-free every nfs4_client_reclaim. A concurrent nfsd4_find_reclaim_client() iterating the same bucket reads through freed memory. A third call site exists in nfs4_state_start_net() on the skip_grace startup path, but it runs under nfsd_mutex before any client has connected and before the laundromat's first delayed work fires, so it cannot race with the two callers above. Replace the scattered boolean fields in nfsd_net with a single unsigned long flags word and an enum nfsd_net_flag for the bit positions. The grace_ended race is fixed by using test_and_set_bit(), which is atomic on all architectures. The remaining flags (grace_end_forced, in_grace, somebody_reclaimed, track_reclaim_completes, nfsd_net_up, lockd_up) are converted to use test_bit/set_bit/clear_bit for consistency. This avoids sub-word cmpxchg issues on architectures like Hexagon that only support word-sized atomic operations. Fixes: 362063a595be ("nfsd: keep a tally of RECLAIM_COMPLETE operations when using nfsdcld") Cc: stable@vger.kernel.org Assisted-by: kres:claude-opus-4-7 Reported-by: Chris Mason Signed-off-by: Chris Mason Link: https://patch.msgid.link/20260530-nfsd-fixes-v2-3-f27e8eb4d974@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/netns.h | 19 +++++++++++-------- fs/nfsd/nfs4proc.c | 2 +- fs/nfsd/nfs4recover.c | 12 ++++++------ fs/nfsd/nfs4state.c | 40 ++++++++++++++++++++++++---------------- fs/nfsd/nfsctl.c | 2 +- fs/nfsd/nfssvc.c | 22 +++++++++++----------- 6 files changed, 54 insertions(+), 43 deletions(-) diff --git a/fs/nfsd/netns.h b/fs/nfsd/netns.h index 27da1a3edacb..37dfecb9d49d 100644 --- a/fs/nfsd/netns.h +++ b/fs/nfsd/netns.h @@ -28,6 +28,16 @@ struct cld_net; struct nfsd_net_cb; struct nfsd4_client_tracking_ops; +enum nfsd_net_flag { + NFSD_NET_GRACE_ENDED, + NFSD_NET_GRACE_END_FORCED, + NFSD_NET_IN_GRACE, + NFSD_NET_SOMEBODY_RECLAIMED, + NFSD_NET_TRACK_RECLAIM_COMPLETES, + NFSD_NET_UP, + NFSD_NET_LOCKD_UP, +}; + enum { /* cache misses due only to checksum comparison failures */ NFSD_STATS_PAYLOAD_MISSES, @@ -66,8 +76,7 @@ struct nfsd_net { struct cache_detail *nametoid_cache; struct lock_manager nfsd4_manager; - bool grace_ended; - bool grace_end_forced; + unsigned long flags; time64_t boot_time; struct dentry *nfsd_client_dir; @@ -117,19 +126,13 @@ struct nfsd_net { spinlock_t blocked_locks_lock; struct file *rec_file; - bool in_grace; const struct nfsd4_client_tracking_ops *client_tracking_ops; time64_t nfsd4_lease; time64_t nfsd4_grace; - bool somebody_reclaimed; - bool track_reclaim_completes; atomic_t nr_reclaim_complete; - bool nfsd_net_up; - bool lockd_up; - seqlock_t writeverf_lock; unsigned char writeverf[8]; diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c index f4884827b7a0..505c96c14d9e 100644 --- a/fs/nfsd/nfs4proc.c +++ b/fs/nfsd/nfs4proc.c @@ -667,7 +667,7 @@ nfsd4_open(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, pr_warn("nfsd4_process_open2 failed to open newly-created file: status=%u\n", be32_to_cpu(status)); if (reclaim && !status) - nn->somebody_reclaimed = true; + set_bit(NFSD_NET_SOMEBODY_RECLAIMED, &nn->flags); out: if (open->op_filp) { fput(open->op_filp); diff --git a/fs/nfsd/nfs4recover.c b/fs/nfsd/nfs4recover.c index 6ea25a52d2f4..c841da585142 100644 --- a/fs/nfsd/nfs4recover.c +++ b/fs/nfsd/nfs4recover.c @@ -167,7 +167,7 @@ nfsd4_create_clid_dir(struct nfs4_client *clp) end_creating(dentry); out: if (status == 0) { - if (nn->in_grace) + if (test_bit(NFSD_NET_IN_GRACE, &nn->flags)) __nfsd4_create_reclaim_record_grace(clp, dname, nn); vfs_fsync(nn->rec_file, 0); } else { @@ -317,7 +317,7 @@ nfsd4_remove_clid_dir(struct nfs4_client *clp) nfs4_reset_creds(original_cred); if (status == 0) { vfs_fsync(nn->rec_file, 0); - if (nn->in_grace) + if (test_bit(NFSD_NET_IN_GRACE, &nn->flags)) __nfsd4_remove_reclaim_record_grace(dname, HEXDIR_LEN, nn); } @@ -373,7 +373,7 @@ nfsd4_recdir_purge_old(struct nfsd_net *nn) { int status; - nn->in_grace = false; + clear_bit(NFSD_NET_IN_GRACE, &nn->flags); if (!nn->rec_file) return; status = mnt_want_write_file(nn->rec_file); @@ -455,7 +455,7 @@ nfsd4_init_recdir(struct net *net) nfs4_reset_creds(original_cred); if (!status) - nn->in_grace = true; + set_bit(NFSD_NET_IN_GRACE, &nn->flags); return status; } @@ -1362,7 +1362,7 @@ nfs4_cld_state_init(struct net *net) for (i = 0; i < CLIENT_HASH_SIZE; i++) INIT_LIST_HEAD(&nn->reclaim_str_hashtbl[i]); nn->reclaim_str_hashtbl_size = 0; - nn->track_reclaim_completes = true; + set_bit(NFSD_NET_TRACK_RECLAIM_COMPLETES, &nn->flags); atomic_set(&nn->nr_reclaim_complete, 0); return 0; @@ -1373,7 +1373,7 @@ nfs4_cld_state_shutdown(struct net *net) { struct nfsd_net *nn = net_generic(net, nfsd_net_id); - nn->track_reclaim_completes = false; + clear_bit(NFSD_NET_TRACK_RECLAIM_COMPLETES, &nn->flags); kfree(nn->reclaim_str_hashtbl); } diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index c99538d36470..2e9773b3a60c 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -2675,7 +2675,7 @@ static void inc_reclaim_complete(struct nfs4_client *clp) { struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id); - if (!nn->track_reclaim_completes) + if (!test_bit(NFSD_NET_TRACK_RECLAIM_COMPLETES, &nn->flags)) return; if (!nfsd4_find_reclaim_client(clp->cl_name, nn)) return; @@ -5033,8 +5033,6 @@ nfsd4_init_leases_net(struct nfsd_net *nn) nn->nfsd4_lease = 90; /* default lease time */ nn->nfsd4_grace = 90; - nn->somebody_reclaimed = false; - nn->track_reclaim_completes = false; nn->clverifier_counter = get_random_u32(); nn->clientid_base = get_random_u32(); nn->clientid_counter = nn->clientid_base + 1; @@ -6746,12 +6744,21 @@ nfsd4_renew(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, static void nfsd4_end_grace(struct nfsd_net *nn) { - /* do nothing if grace period already ended */ - if (nn->grace_ended) + /* + * nfsd4_end_grace() can be entered concurrently from the + * laundromat workqueue and from an nfsd compound thread + * handling RECLAIM_COMPLETE. Without serialization, both + * callers can observe NFSD_NET_GRACE_ENDED clear and proceed + * into nfsd4_record_grace_done(). For tracking ops whose + * grace_done drains reclaim_str_hashtbl, that results in + * list corruption and a double free of every + * nfs4_client_reclaim entry. Use an atomic test-and-set so + * exactly one caller proceeds. + */ + if (test_and_set_bit(NFSD_NET_GRACE_ENDED, &nn->flags)) return; trace_nfsd_grace_complete(nn); - nn->grace_ended = true; /* * If the server goes down again right now, an NFSv4 * client will still be allowed to reclaim after it comes back up, @@ -6792,10 +6799,10 @@ bool nfsd4_force_end_grace(struct nfsd_net *nn) { if (!nn->client_tracking_ops) return false; - if (READ_ONCE(nn->grace_ended)) + if (test_bit(NFSD_NET_GRACE_ENDED, &nn->flags)) return false; /* laundromat_work must be initialised now, though it might be disabled */ - WRITE_ONCE(nn->grace_end_forced, true); + set_bit(NFSD_NET_GRACE_END_FORCED, &nn->flags); /* mod_delayed_work() doesn't queue work after * nfs4_state_shutdown_net() has called disable_delayed_work_sync() */ @@ -6812,15 +6819,15 @@ static bool clients_still_reclaiming(struct nfsd_net *nn) time64_t double_grace_period_end = nn->boot_time + 2 * nn->nfsd4_lease; - if (READ_ONCE(nn->grace_end_forced)) + if (test_bit(NFSD_NET_GRACE_END_FORCED, &nn->flags)) return false; - if (nn->track_reclaim_completes && + if (test_bit(NFSD_NET_TRACK_RECLAIM_COMPLETES, &nn->flags) && atomic_read(&nn->nr_reclaim_complete) == nn->reclaim_str_hashtbl_size) return false; - if (!nn->somebody_reclaimed) + if (!test_bit(NFSD_NET_SOMEBODY_RECLAIMED, &nn->flags)) return false; - nn->somebody_reclaimed = false; + clear_bit(NFSD_NET_SOMEBODY_RECLAIMED, &nn->flags); /* * If we've given them *two* lease times to reclaim, and they're * still not done, give up: @@ -8611,7 +8618,7 @@ nfsd4_lock(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, nfs4_inc_and_copy_stateid(&lock->lk_resp_stateid, &lock_stp->st_stid); status = 0; if (lock->lk_reclaim) - nn->somebody_reclaimed = true; + set_bit(NFSD_NET_SOMEBODY_RECLAIMED, &nn->flags); break; case FILE_LOCK_DEFERRED: kref_put(&nbl->nbl_kref, free_nbl); @@ -9137,8 +9144,8 @@ static int nfs4_state_create_net(struct net *net) nn->conf_name_tree = RB_ROOT; nn->unconf_name_tree = RB_ROOT; nn->boot_time = ktime_get_real_seconds(); - nn->grace_ended = false; - nn->grace_end_forced = false; + clear_bit(NFSD_NET_GRACE_ENDED, &nn->flags); + clear_bit(NFSD_NET_GRACE_END_FORCED, &nn->flags); nn->nfsd4_manager.block_opens = true; INIT_LIST_HEAD(&nn->nfsd4_manager.list); INIT_LIST_HEAD(&nn->client_lru); @@ -9224,7 +9231,8 @@ nfs4_state_start_net(struct net *net) nfsd4_client_tracking_init(net); /* safe for laundromat to run now */ enable_delayed_work(&nn->laundromat_work); - if (nn->track_reclaim_completes && nn->reclaim_str_hashtbl_size == 0) + if (test_bit(NFSD_NET_TRACK_RECLAIM_COMPLETES, &nn->flags) && + nn->reclaim_str_hashtbl_size == 0) goto skip_grace; printk(KERN_INFO "NFSD: starting %lld-second grace period (net %x)\n", nn->nfsd4_grace, net->ns.inum); diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index d0486f4a47ba..f73e30909559 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -1111,7 +1111,7 @@ static ssize_t write_v4_end_grace(struct file *file, char *buf, size_t size) } return scnprintf(buf, SIMPLE_TRANSACTION_LIMIT, "%c\n", - nn->grace_ended ? 'Y' : 'N'); + test_bit(NFSD_NET_GRACE_ENDED, &nn->flags) ? 'Y' : 'N'); } #endif diff --git a/fs/nfsd/nfssvc.c b/fs/nfsd/nfssvc.c index 4f1ab3222a4d..e45d46089959 100644 --- a/fs/nfsd/nfssvc.c +++ b/fs/nfsd/nfssvc.c @@ -351,7 +351,7 @@ static int nfsd_startup_net(struct net *net, const struct cred *cred) struct nfsd_net *nn = net_generic(net, nfsd_net_id); int ret; - if (nn->nfsd_net_up) + if (test_bit(NFSD_NET_UP, &nn->flags)) return 0; ret = nfsd_startup_generic(); @@ -364,11 +364,11 @@ static int nfsd_startup_net(struct net *net, const struct cred *cred) goto out_socks; } - if (nfsd_needs_lockd(nn) && !nn->lockd_up) { + if (nfsd_needs_lockd(nn) && !test_bit(NFSD_NET_LOCKD_UP, &nn->flags)) { ret = lockd_up(net, cred); if (ret) goto out_socks; - nn->lockd_up = true; + set_bit(NFSD_NET_LOCKD_UP, &nn->flags); } ret = nfsd_file_cache_start_net(net); @@ -386,7 +386,7 @@ static int nfsd_startup_net(struct net *net, const struct cred *cred) if (ret) goto out_reply_cache; - nn->nfsd_net_up = true; + set_bit(NFSD_NET_UP, &nn->flags); return 0; out_reply_cache: @@ -394,9 +394,9 @@ static int nfsd_startup_net(struct net *net, const struct cred *cred) out_filecache: nfsd_file_cache_shutdown_net(net); out_lockd: - if (nn->lockd_up) { + if (test_bit(NFSD_NET_LOCKD_UP, &nn->flags)) { lockd_down(net); - nn->lockd_up = false; + clear_bit(NFSD_NET_LOCKD_UP, &nn->flags); } out_socks: nfsd_shutdown_generic(); @@ -407,7 +407,7 @@ static void nfsd_shutdown_net(struct net *net) { struct nfsd_net *nn = net_generic(net, nfsd_net_id); - if (nn->nfsd_net_up) { + if (test_bit(NFSD_NET_UP, &nn->flags)) { percpu_ref_kill_and_confirm(&nn->nfsd_net_ref, nfsd_net_done); wait_for_completion(&nn->nfsd_net_confirm_done); @@ -415,18 +415,18 @@ static void nfsd_shutdown_net(struct net *net) nfs4_state_shutdown_net(net); nfsd_reply_cache_shutdown(nn); nfsd_file_cache_shutdown_net(net); - if (nn->lockd_up) { + if (test_bit(NFSD_NET_LOCKD_UP, &nn->flags)) { lockd_down(net); - nn->lockd_up = false; + clear_bit(NFSD_NET_LOCKD_UP, &nn->flags); } wait_for_completion(&nn->nfsd_net_free_done); } percpu_ref_exit(&nn->nfsd_net_ref); - if (nn->nfsd_net_up) + if (test_bit(NFSD_NET_UP, &nn->flags)) nfsd_shutdown_generic(); - nn->nfsd_net_up = false; + clear_bit(NFSD_NET_UP, &nn->flags); } static DEFINE_SPINLOCK(nfsd_notifier_lock); From f86d2720652784d46af0985c481d96a89f3ed0c4 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Sat, 30 May 2026 09:19:20 -0400 Subject: [PATCH 428/562] nfsd: dedup nfs4_client_to_reclaim inserts nfs4_client_to_reclaim() unconditionally allocates a new nfs4_client_reclaim, prepends it to reclaim_str_hashtbl[], and bumps reclaim_str_hashtbl_size with no check for an existing entry for the same client name. After a reboot with a populated recovery directory that inflates the counter by one for every client that reclaims: boot: load_recdir() nfs4_client_to_reclaim(name) /* entry #1, size++ */ grace: RECLAIM_COMPLETE __nfsd4_create_reclaim_record_grace() nfs4_client_to_reclaim(name) /* entry #2, size++ */ inc_reclaim_complete() ends the grace period early only when atomic_inc_return(&nn->nr_reclaim_complete) == nn->reclaim_str_hashtbl_size With reclaim_str_hashtbl_size at 2N and nr_reclaim_complete capped at N, the equality never holds and the fast end-of-grace path is dead. The grace period always runs out the full 90-second laundromat timer, and the shadow entry left in the hash table carries a dangling cr_clp for any reader that walks it. Fix nfs4_client_to_reclaim() to look the name up with nfsd4_find_reclaim_client() first and, on a hit, fold the new princhash into the existing record (if it lacks one) and return that record without allocating or touching reclaim_str_hashtbl_size. On kmemdup() failure during the fold-in, return NULL so __cld_pipe_inprogress_downcall() surfaces -EFAULT to nfsdcld, matching the miss-path contract. Add an rw_semaphore (reclaim_str_hashtbl_lock) to struct nfsd_net that serialises all access to reclaim_str_hashtbl[] and reclaim_str_hashtbl_size. Writers (nfs4_client_to_reclaim, nfs4_remove_reclaim_record callers) hold the write side; readers (nfsd4_cld_check*, inc_reclaim_complete, clients_still_reclaiming, nfs4_has_reclaimed_state, nfsd4_check_legacy_client) hold the read side. All call sites are in sleepable context, and none is a hot path, so the rwsem cost is negligible. Reported-by: Chris Mason Fixes: 362063a595be ("nfsd: keep a tally of RECLAIM_COMPLETE operations when using nfsdcld") Cc: stable@vger.kernel.org Assisted-by: kres:claude-opus-4-7 Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260530-nfsd-fixes-v2-4-f27e8eb4d974@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/netns.h | 6 ++++- fs/nfsd/nfs4recover.c | 36 ++++++++++++++++++++----- fs/nfsd/nfs4state.c | 61 ++++++++++++++++++++++++++++++++++++++----- 3 files changed, 89 insertions(+), 14 deletions(-) diff --git a/fs/nfsd/netns.h b/fs/nfsd/netns.h index 37dfecb9d49d..f6b8b340bf8e 100644 --- a/fs/nfsd/netns.h +++ b/fs/nfsd/netns.h @@ -93,6 +93,7 @@ struct nfsd_net { */ struct list_head *reclaim_str_hashtbl; int reclaim_str_hashtbl_size; + struct rw_semaphore reclaim_str_hashtbl_lock; struct list_head *conf_id_hashtbl; struct rb_root conf_name_tree; struct list_head *unconf_id_hashtbl; @@ -105,7 +106,10 @@ struct nfsd_net { * close_lru holds (open) stateowner queue ordered by nfs4_stateowner.so_time * for last close replay. * - * All of the above fields are protected by the client_mutex. + * reclaim_str_hashtbl[], reclaim_str_hashtbl_size are protected by + * reclaim_str_hashtbl_lock. + * + * All of the remaining fields are protected by the client_lock. */ struct list_head client_lru; struct list_head close_lru; diff --git a/fs/nfsd/nfs4recover.c b/fs/nfsd/nfs4recover.c index c841da585142..d513971fb119 100644 --- a/fs/nfsd/nfs4recover.c +++ b/fs/nfsd/nfs4recover.c @@ -285,10 +285,12 @@ __nfsd4_remove_reclaim_record_grace(const char *dname, int len, return; } name.len = len; + down_write(&nn->reclaim_str_hashtbl_lock); crp = nfsd4_find_reclaim_client(name, nn); - kfree(name.data); if (crp) nfs4_remove_reclaim_record(crp, nn); + up_write(&nn->reclaim_str_hashtbl_lock); + kfree(name.data); } static void @@ -484,6 +486,7 @@ nfs4_legacy_state_init(struct net *net) for (i = 0; i < CLIENT_HASH_SIZE; i++) INIT_LIST_HEAD(&nn->reclaim_str_hashtbl[i]); nn->reclaim_str_hashtbl_size = 0; + init_rwsem(&nn->reclaim_str_hashtbl_lock); return 0; } @@ -598,13 +601,16 @@ nfsd4_check_legacy_client(struct nfs4_client *clp) goto out_enoent; } name.len = HEXDIR_LEN; + down_read(&nn->reclaim_str_hashtbl_lock); crp = nfsd4_find_reclaim_client(name, nn); - kfree(name.data); if (crp) { set_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags); crp->cr_clp = clp; - return 0; } + up_read(&nn->reclaim_str_hashtbl_lock); + kfree(name.data); + if (crp) + return 0; out_enoent: return -ENOENT; @@ -1176,6 +1182,7 @@ nfsd4_cld_check(struct nfs4_client *clp) return 0; /* look for it in the reclaim hashtable otherwise */ + down_read(&nn->reclaim_str_hashtbl_lock); crp = nfsd4_find_reclaim_client(clp->cl_name, nn); if (crp) goto found; @@ -1191,6 +1198,7 @@ nfsd4_cld_check(struct nfs4_client *clp) if (!name.data) { dprintk("%s: failed to allocate memory for name.data!\n", __func__); + up_read(&nn->reclaim_str_hashtbl_lock); return -ENOENT; } name.len = HEXDIR_LEN; @@ -1201,9 +1209,11 @@ nfsd4_cld_check(struct nfs4_client *clp) } #endif + up_read(&nn->reclaim_str_hashtbl_lock); return -ENOENT; found: crp->cr_clp = clp; + up_read(&nn->reclaim_str_hashtbl_lock); return 0; } @@ -1215,6 +1225,7 @@ nfsd4_cld_check_v2(struct nfs4_client *clp) struct cld_net *cn = nn->cld_net; #endif struct nfs4_client_reclaim *crp; + unsigned int princhashlen; char *principal = NULL; /* did we already find that this client is stable? */ @@ -1222,6 +1233,7 @@ nfsd4_cld_check_v2(struct nfs4_client *clp) return 0; /* look for it in the reclaim hashtable otherwise */ + down_read(&nn->reclaim_str_hashtbl_lock); crp = nfsd4_find_reclaim_client(clp->cl_name, nn); if (crp) goto found; @@ -1237,6 +1249,7 @@ nfsd4_cld_check_v2(struct nfs4_client *clp) if (!name.data) { dprintk("%s: failed to allocate memory for name.data\n", __func__); + up_read(&nn->reclaim_str_hashtbl_lock); return -ENOENT; } name.len = HEXDIR_LEN; @@ -1247,23 +1260,31 @@ nfsd4_cld_check_v2(struct nfs4_client *clp) } #endif + up_read(&nn->reclaim_str_hashtbl_lock); return -ENOENT; found: - if (crp->cr_princhash.len) { + princhashlen = crp->cr_princhash.len; + if (princhashlen) { u8 digest[SHA256_DIGEST_SIZE]; + u8 *pdata; if (clp->cl_cred.cr_raw_principal) principal = clp->cl_cred.cr_raw_principal; else if (clp->cl_cred.cr_principal) principal = clp->cl_cred.cr_principal; - if (principal == NULL) + if (principal == NULL) { + up_read(&nn->reclaim_str_hashtbl_lock); return -ENOENT; + } sha256(principal, strlen(principal), digest); - if (memcmp(crp->cr_princhash.data, digest, - crp->cr_princhash.len)) + pdata = crp->cr_princhash.data; + if (memcmp(pdata, digest, princhashlen)) { + up_read(&nn->reclaim_str_hashtbl_lock); return -ENOENT; + } } crp->cr_clp = clp; + up_read(&nn->reclaim_str_hashtbl_lock); return 0; } @@ -1362,6 +1383,7 @@ nfs4_cld_state_init(struct net *net) for (i = 0; i < CLIENT_HASH_SIZE; i++) INIT_LIST_HEAD(&nn->reclaim_str_hashtbl[i]); nn->reclaim_str_hashtbl_size = 0; + init_rwsem(&nn->reclaim_str_hashtbl_lock); set_bit(NFSD_NET_TRACK_RECLAIM_COMPLETES, &nn->flags); atomic_set(&nn->nr_reclaim_complete, 0); diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index 2e9773b3a60c..c88637406773 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -2677,14 +2677,21 @@ static void inc_reclaim_complete(struct nfs4_client *clp) if (!test_bit(NFSD_NET_TRACK_RECLAIM_COMPLETES, &nn->flags)) return; - if (!nfsd4_find_reclaim_client(clp->cl_name, nn)) + + down_read(&nn->reclaim_str_hashtbl_lock); + if (!nfsd4_find_reclaim_client(clp->cl_name, nn)) { + up_read(&nn->reclaim_str_hashtbl_lock); return; + } if (atomic_inc_return(&nn->nr_reclaim_complete) == nn->reclaim_str_hashtbl_size) { + up_read(&nn->reclaim_str_hashtbl_lock); printk(KERN_INFO "NFSD: all clients done reclaiming, ending NFSv4 grace period (net %x)\n", clp->net->ns.inum); nfsd4_end_grace(nn); + return; } + up_read(&nn->reclaim_str_hashtbl_lock); } static void expire_client(struct nfs4_client *clp) @@ -6821,10 +6828,15 @@ static bool clients_still_reclaiming(struct nfsd_net *nn) if (test_bit(NFSD_NET_GRACE_END_FORCED, &nn->flags)) return false; - if (test_bit(NFSD_NET_TRACK_RECLAIM_COMPLETES, &nn->flags) && - atomic_read(&nn->nr_reclaim_complete) == - nn->reclaim_str_hashtbl_size) - return false; + if (test_bit(NFSD_NET_TRACK_RECLAIM_COMPLETES, &nn->flags)) { + int size; + + down_read(&nn->reclaim_str_hashtbl_lock); + size = nn->reclaim_str_hashtbl_size; + up_read(&nn->reclaim_str_hashtbl_lock); + if (atomic_read(&nn->nr_reclaim_complete) == size) + return false; + } if (!test_bit(NFSD_NET_SOMEBODY_RECLAIMED, &nn->flags)) return false; clear_bit(NFSD_NET_SOMEBODY_RECLAIMED, &nn->flags); @@ -8994,9 +9006,13 @@ bool nfs4_has_reclaimed_state(struct xdr_netobj name, struct nfsd_net *nn) { struct nfs4_client_reclaim *crp; + bool found; + down_read(&nn->reclaim_str_hashtbl_lock); crp = nfsd4_find_reclaim_client(name, nn); - return (crp && crp->cr_clp); + found = (crp && crp->cr_clp); + up_read(&nn->reclaim_str_hashtbl_lock); + return found; } /* @@ -9009,10 +9025,39 @@ nfs4_client_to_reclaim(struct xdr_netobj name, struct xdr_netobj princhash, unsigned int strhashval; struct nfs4_client_reclaim *crp; + down_write(&nn->reclaim_str_hashtbl_lock); + + /* + * A reclaim record for this client name may already exist (for + * example, populated at boot from the recovery directory before + * an in-grace RECLAIM_COMPLETE or an nfsdcld downcall delivers + * the same name). Dedup here so reclaim_str_hashtbl_size stays + * equal to the number of distinct client names; inc_reclaim_complete + * relies on that equality to end the grace period via the fast path. + */ + crp = nfsd4_find_reclaim_client(name, nn); + if (crp) { + if (princhash.len && crp->cr_princhash.len == 0) { + void *pdata = kmemdup(princhash.data, princhash.len, + GFP_KERNEL); + if (pdata) { + crp->cr_princhash.data = pdata; + crp->cr_princhash.len = princhash.len; + } else { + dprintk("%s: failed to allocate memory for princhash.data!\n", + __func__); + crp = NULL; + } + } + up_write(&nn->reclaim_str_hashtbl_lock); + return crp; + } + name.data = kmemdup(name.data, name.len, GFP_KERNEL); if (!name.data) { dprintk("%s: failed to allocate memory for name.data!\n", __func__); + up_write(&nn->reclaim_str_hashtbl_lock); return NULL; } if (princhash.len) { @@ -9021,6 +9066,7 @@ nfs4_client_to_reclaim(struct xdr_netobj name, struct xdr_netobj princhash, dprintk("%s: failed to allocate memory for princhash.data!\n", __func__); kfree(name.data); + up_write(&nn->reclaim_str_hashtbl_lock); return NULL; } } else @@ -9040,6 +9086,7 @@ nfs4_client_to_reclaim(struct xdr_netobj name, struct xdr_netobj princhash, kfree(name.data); kfree(princhash.data); } + up_write(&nn->reclaim_str_hashtbl_lock); return crp; } @@ -9059,6 +9106,7 @@ nfs4_release_reclaim(struct nfsd_net *nn) struct nfs4_client_reclaim *crp = NULL; int i; + down_write(&nn->reclaim_str_hashtbl_lock); for (i = 0; i < CLIENT_HASH_SIZE; i++) { while (!list_empty(&nn->reclaim_str_hashtbl[i])) { crp = list_entry(nn->reclaim_str_hashtbl[i].next, @@ -9067,6 +9115,7 @@ nfs4_release_reclaim(struct nfsd_net *nn) } } WARN_ON_ONCE(nn->reclaim_str_hashtbl_size); + up_write(&nn->reclaim_str_hashtbl_lock); } /* From 6175f7b733f8d4136c5d44288bb7ad4f41a7ab2f Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Sat, 30 May 2026 09:19:21 -0400 Subject: [PATCH 429/562] nfsd: gate nfs3 setacl by argp->mask nfsd3_proc_setacl() calls set_posix_acl() unconditionally for both ACL_TYPE_ACCESS and ACL_TYPE_DEFAULT, passing argp->acl_access and argp->acl_default verbatim. The NFSv3 ACL decoder only populates those pointers when the corresponding mask bit is set: nfs3svc_decode_setaclargs() if (args->mask & NFS_ACL) decode into acl_access if (args->mask & NFS_DFACL) decode into acl_default /* otherwise the pointer stays NULL (pc_argzero) */ nfsd3_proc_setacl() set_posix_acl(.., ACL_TYPE_ACCESS, argp->acl_access) set_posix_acl(.., ACL_TYPE_DEFAULT, argp->acl_default) set_posix_acl(idmap, dentry, type, NULL) is the VFS "remove this ACL type" operation. A NULL pointer that means "the client did not send this arm" is therefore indistinguishable from "the client asked to remove this ACL". A SETACL with mask=NFS_ACL silently drops the directory's default ACL; mask=0 drops both. The sibling nfsd3_proc_getacl() already consults argp->mask before touching each arm; mirror that in setacl. Fix by wrapping each set_posix_acl() call in the matching mask bit check and initializing error to 0 before inode_lock so that a request with neither bit set leaves the on-disk ACLs untouched and returns nfs_ok. The out_drop_lock path and the unconditional posix_acl_release() at out: are preserved; both NULL-tolerate the skipped arms. Fixes: a257cdd0e217 ("[PATCH] NFSD: Add server support for NFSv3 ACLs.") Cc: stable@vger.kernel.org Assisted-by: kres:claude-opus-4-7 Reported-by: Chris Mason Signed-off-by: Chris Mason Link: https://patch.msgid.link/20260530-nfsd-fixes-v2-5-f27e8eb4d974@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfs3acl.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/fs/nfsd/nfs3acl.c b/fs/nfsd/nfs3acl.c index e87731380be8..a87f9d7f32be 100644 --- a/fs/nfsd/nfs3acl.c +++ b/fs/nfsd/nfs3acl.c @@ -105,12 +105,17 @@ static __be32 nfsd3_proc_setacl(struct svc_rqst *rqstp) inode_lock(inode); - error = set_posix_acl(&nop_mnt_idmap, fh->fh_dentry, ACL_TYPE_ACCESS, - argp->acl_access); - if (error) - goto out_drop_lock; - error = set_posix_acl(&nop_mnt_idmap, fh->fh_dentry, ACL_TYPE_DEFAULT, - argp->acl_default); + error = 0; + if (argp->mask & NFS_ACL) { + error = set_posix_acl(&nop_mnt_idmap, fh->fh_dentry, + ACL_TYPE_ACCESS, argp->acl_access); + if (error) + goto out_drop_lock; + } + if (argp->mask & NFS_DFACL) { + error = set_posix_acl(&nop_mnt_idmap, fh->fh_dentry, + ACL_TYPE_DEFAULT, argp->acl_default); + } out_drop_lock: inode_unlock(inode); From 44246ebb2966cee7dbf1034abb6ce99a6a692f92 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Sat, 30 May 2026 09:19:22 -0400 Subject: [PATCH 430/562] NFSD: check truncate permission under inode lock nfsd_setattr() checks whether a size update needs NFSD_MAY_TRUNC before it takes inode_lock(). The comparison uses the file size sampled by that unlocked read, but the actual ATTR_SIZE update is applied later under inode_lock() by notify_change(). This leaves a TOCTOU window for append-only files. If a client sends a SETATTR that does not shrink the file at the time of the unlocked sample, a concurrent append can extend the file before nfsd_setattr() takes inode_lock(). notify_change() then applies a real truncation without the NFSD_MAY_TRUNC check that rejects IS_APPEND(inode). The VFS truncate syscall paths perform their own append-only checks before calling notify_change(), so NFSD must make this decision against the locked size it is about to change. Split the write-count acquisition from the truncation permission check. Keep get_write_access() before the locked setattr work, then recheck whether the requested size is below i_size_read(inode) after inode_lock() has been acquired and before notify_change(ATTR_SIZE). This also avoids the plain unlocked inode->i_size load. Fixes: 783112f7401f ("nfsd: special case truncates some more") Cc: stable@vger.kernel.org Assisted-by: kres:claude-opus-4-7 Reported-by: Chris Mason Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260530-nfsd-fixes-v2-6-f27e8eb4d974@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/vfs.c | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c index 1e89c7ff9493..a3940548136f 100644 --- a/fs/nfsd/vfs.c +++ b/fs/nfsd/vfs.c @@ -419,21 +419,22 @@ nfsd_sanitize_attrs(struct inode *inode, struct iattr *iap) } static __be32 -nfsd_get_write_access(struct svc_rqst *rqstp, struct svc_fh *fhp, - struct iattr *iap) +nfsd_may_truncate(struct svc_rqst *rqstp, struct svc_fh *fhp, + struct iattr *iap) { struct inode *inode = d_inode(fhp->fh_dentry); - if (iap->ia_size < inode->i_size) { - __be32 err; + if (iap->ia_size >= i_size_read(inode)) + return nfs_ok; - err = nfsd_permission(&rqstp->rq_cred, - fhp->fh_export, fhp->fh_dentry, - NFSD_MAY_TRUNC | NFSD_MAY_OWNER_OVERRIDE); - if (err) - return err; - } - return nfserrno(get_write_access(inode)); + return nfsd_permission(&rqstp->rq_cred, fhp->fh_export, fhp->fh_dentry, + NFSD_MAY_TRUNC | NFSD_MAY_OWNER_OVERRIDE); +} + +static __be32 +nfsd_get_write_access(struct svc_fh *fhp) +{ + return nfserrno(get_write_access(d_inode(fhp->fh_dentry))); } static int __nfsd_setattr(struct dentry *dentry, struct iattr *iap) @@ -560,12 +561,17 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, * setattr call. */ if (size_change) { - err = nfsd_get_write_access(rqstp, fhp, iap); + err = nfsd_get_write_access(fhp); if (err) return err; } inode_lock(inode); + if (size_change) { + err = nfsd_may_truncate(rqstp, fhp, iap); + if (err) + goto out_unlock; + } err = fh_fill_pre_attrs(fhp); if (err) goto out_unlock; From 2ff8eca04d2e6870595874aa8d5856fc5d0539dc Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Sat, 30 May 2026 09:19:23 -0400 Subject: [PATCH 431/562] nfsd: fix partial-write detection in nfsd_direct_write nfsd_direct_write() walks a list of write segments and, after each vfs_iocb_iter_write(), tries to detect a short write so the loop can stop before placing the next segment at a wrong file offset: host_err = vfs_iocb_iter_write(file, kiocb, &segments[i].iter); if (host_err < 0) return host_err; *cnt += host_err; if (host_err < segments[i].iter.count) break; /* partial write */ vfs_iocb_iter_write() runs the iter through ->write_iter(), which advances the iter by the number of bytes written. By the time the check runs, segments[i].iter.count is the residual, not the original request length: before write_iter: iter.count == original_len after write_iter: iter.count == original_len - host_err The condition then reduces to host_err < original_len - host_err, so the break fires only when less than half of the segment was written. Any short write completing between 50% and 99% of the segment slips through; the loop advances to the next segment with kiocb->ki_pos only bumped by the short amount, writing the next segment's payload at the wrong offset and over-reporting *cnt to the NFS client. Snapshot the segment's byte count before the write and compare host_err against that snapshot so any short write breaks the loop. Fixes: 06c5c97293e3 ("NFSD: Implement NFSD_IO_DIRECT for NFS WRITE") Cc: stable@vger.kernel.org Assisted-by: kres:claude-opus-4-7 Reported-by: Chris Mason Signed-off-by: Chris Mason Link: https://patch.msgid.link/20260530-nfsd-fixes-v2-7-f27e8eb4d974@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/vfs.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c index a3940548136f..0dcfaab95021 100644 --- a/fs/nfsd/vfs.c +++ b/fs/nfsd/vfs.c @@ -1380,6 +1380,7 @@ nfsd_direct_write(struct svc_rqst *rqstp, struct svc_fh *fhp, struct file *file = nf->nf_file; unsigned int nsegs, i; ssize_t host_err; + size_t expected; nsegs = nfsd_write_dio_iters_init(nf, rqstp->rq_bvec, nvecs, kiocb, *cnt, segments); @@ -1401,11 +1402,13 @@ nfsd_direct_write(struct svc_rqst *rqstp, struct svc_fh *fhp, kiocb->ki_flags |= IOCB_DONTCACHE; } + expected = iov_iter_count(&segments[i].iter); + host_err = vfs_iocb_iter_write(file, kiocb, &segments[i].iter); if (host_err < 0) return host_err; *cnt += host_err; - if (host_err < segments[i].iter.count) + if (host_err < (ssize_t)expected) break; /* partial write */ } From df742662ce7e21c4e8c32c0dcfee78c0234bef80 Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Sat, 30 May 2026 09:19:24 -0400 Subject: [PATCH 432/562] nfsd: cap decoded POSIX ACL count to bound sort cost nfsd4_decode_posixacl() reads a u32 entry count off the wire and passes it straight to posix_acl_alloc() and sort_pacl_range(). The latter is an O(n^2) bubble sort, so a client-chosen count drives unbounded CPU in the server's compound processing path. nfsd4_decode_posixacl() xdr_stream_decode_u32(&count) /* uncapped u32 */ posix_acl_alloc(count, GFP_KERNEL) sort_pacl_range(*acl, 0, count - 1) /* O(n^2) bubble sort */ The encoder side in the same file already rejects ACLs whose a_count exceeds NFS_ACL_MAX_ENTRIES, but the decoder introduced in commit 5fc51dfc2eb1 ("NFSD: Add support for XDR decoding POSIX draft ACLs") omitted the symmetric check. Fix by rejecting a wire count greater than NFS_ACL_MAX_ENTRIES with nfserr_inval, before any allocation, so the sort is bounded by NFS_ACL_MAX_ENTRIES^2 comparisons. While we're in here, also fix the nfserr_resource return if posix_acl_alloc() fails. That's not a legal error code for v4.1+. Change it to return nfserr_jukebox as that's more appropriate for memory allocation failures. Fixes: 5fc51dfc2eb1 ("NFSD: Add support for XDR decoding POSIX draft ACLs") Cc: stable@vger.kernel.org Assisted-by: kres:claude-opus-4-7 Reported-by: Chris Mason Signed-off-by: Chris Mason Link: https://patch.msgid.link/20260530-nfsd-fixes-v2-8-f27e8eb4d974@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfs4xdr.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c index e17488a911f7..09068ca61b00 100644 --- a/fs/nfsd/nfs4xdr.c +++ b/fs/nfsd/nfs4xdr.c @@ -449,9 +449,18 @@ nfsd4_decode_posixacl(struct nfsd4_compoundargs *argp, struct posix_acl **acl) if (xdr_stream_decode_u32(argp->xdr, &count) < 0) return nfserr_bad_xdr; + /* + * The NFSv4 POSIX ACL draft doesn't define a max number of ACE's, but + * the NFSACL spec does. For NFSv4, cap the number of entries to the v3 + * limit, as we want to ensure that ACLs set via NFSv4 POSIX ACL + * extensions are retrievable via NFSACL. + */ + if (count > NFS_ACL_MAX_ENTRIES) + return nfserr_inval; + *acl = posix_acl_alloc(count, GFP_KERNEL); if (*acl == NULL) - return nfserr_resource; + return nfserr_jukebox; (*acl)->a_count = count; for (ace = (*acl)->a_entries; ace < (*acl)->a_entries + count; ace++) { From b49f27ac5996a8b25f864ab9742817614e97b9a9 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Sat, 30 May 2026 09:19:25 -0400 Subject: [PATCH 433/562] nfsd: validate symlink target length in NFSv4 CREATE nfsd4_decode_create() accepts an unbounded cr_datalen from the wire for NF4LNK symlink targets, allowing a client to force a kmalloc of up to the maximum RPC payload size (several MiB) per COMPOUND op that persists until compound teardown. The VFS rejects oversized targets with ENAMETOOLONG, but the allocation has already occurred. Reject cr_datalen == 0 early with nfserr_inval and cr_datalen greater than NFS4_MAXPATHLEN (PATH_MAX) with nfserr_nametoolong to bound the allocation. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Assisted-by: kres:claude-opus-4-7 Reported-by: Chris Mason Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260530-nfsd-fixes-v2-9-f27e8eb4d974@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfs4xdr.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c index 09068ca61b00..c37c9722b8c6 100644 --- a/fs/nfsd/nfs4xdr.c +++ b/fs/nfsd/nfs4xdr.c @@ -964,6 +964,10 @@ nfsd4_decode_create(struct nfsd4_compoundargs *argp, union nfsd4_op_u *u) case NF4LNK: if (xdr_stream_decode_u32(argp->xdr, &create->cr_datalen) < 0) return nfserr_bad_xdr; + if (create->cr_datalen == 0) + return nfserr_inval; + if (create->cr_datalen > NFS4_MAXPATHLEN) + return nfserr_nametoolong; p = xdr_inline_decode(argp->xdr, create->cr_datalen); if (!p) return nfserr_bad_xdr; From 399af5f93e25d9f0245d846b9a330689cb155fbc Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Sat, 30 May 2026 16:58:16 -0400 Subject: [PATCH 434/562] nfsd: gate nfs2 setacl by argp->mask The NFSACL v2 SETACL path shares the decoder convention used by its v3 sibling: nfsaclsvc_decode_setaclargs() fills in argp->acl_access only when NFS_ACL is set in the request mask and argp->acl_default only when NFS_DFACL is set, leaving the other pointer NULL because the argument buffer is zeroed up to pc_argzero before decode. nfsacld_proc_setacl() then hands both pointers to set_posix_acl() unconditionally. set_posix_acl(idmap, dentry, type, NULL) is the VFS "remove this ACL type" operation, so an omitted arm is indistinguishable from an explicit request to delete that ACL. A SETACL carrying only NFS_ACL silently strips the directory's default ACL; mask=0 strips both. This is the same defect just fixed in nfsd3_proc_setacl(); apply the same remedy. Gate each set_posix_acl() call on its mask bit and initialize error to 0 so that a request with neither bit set leaves the on-disk ACLs untouched and returns success. The out_drop_lock path and the unconditional posix_acl_release() in nfsaclsvc_release_setacl() already tolerate the skipped arms. Fixes: a257cdd0e217 ("[PATCH] NFSD: Add server support for NFSv3 ACLs.") Cc: stable@vger.kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfs2acl.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/fs/nfsd/nfs2acl.c b/fs/nfsd/nfs2acl.c index 76305b86c1a9..827f90194c43 100644 --- a/fs/nfsd/nfs2acl.c +++ b/fs/nfsd/nfs2acl.c @@ -115,14 +115,19 @@ static __be32 nfsacld_proc_setacl(struct svc_rqst *rqstp) inode_lock(inode); - error = set_posix_acl(&nop_mnt_idmap, fh->fh_dentry, ACL_TYPE_ACCESS, - argp->acl_access); - if (error) - goto out_drop_lock; - error = set_posix_acl(&nop_mnt_idmap, fh->fh_dentry, ACL_TYPE_DEFAULT, - argp->acl_default); - if (error) - goto out_drop_lock; + error = 0; + if (argp->mask & NFS_ACL) { + error = set_posix_acl(&nop_mnt_idmap, fh->fh_dentry, + ACL_TYPE_ACCESS, argp->acl_access); + if (error) + goto out_drop_lock; + } + if (argp->mask & NFS_DFACL) { + error = set_posix_acl(&nop_mnt_idmap, fh->fh_dentry, + ACL_TYPE_DEFAULT, argp->acl_default); + if (error) + goto out_drop_lock; + } inode_unlock(inode); From da4f636f2c55c2dc71a0eda5d0a454783bfae871 Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Sat, 30 May 2026 20:42:52 -0400 Subject: [PATCH 435/562] sunrpc: init gssp_lock before publishing proc entry create_use_gss_proxy_proc_entry() publishes /proc/net/rpc/use-gss-proxy via proc_create_data() before init_gssp_clnt() runs mutex_init() on sn->gssp_lock. Once the dentry is linked under proc_subdir_lock it is immediately reachable from userspace, so a write that lands in the window drives set_gssp_clnt() into mutex_lock() on a zero-initialized struct mutex. create_use_gss_proxy_proc_entry(net) proc_create_data("use-gss-proxy", ...) /* dentry live */ init_gssp_clnt(sn) mutex_init(&sn->gssp_lock) /* too late */ write_gssp() set_gssp_clnt(net) mutex_lock(&sn->gssp_lock) /* uninitialized */ gssp_rpc_create(...) sn->gssp_clnt = clnt mutex_unlock(&sn->gssp_lock) The window spans only the two statements between proc_create_data() returning and init_gssp_clnt(), so a writer reaches it only if the registering thread is preempted there while another task is already opening the freshly published file. register_pernet_subsys() runs in preemptible context under pernet_ops_rwsem, so that preemption is possible, and the window widens on auth_rpcgss module load, when the proc entry is created for every live net namespace whose tasks are already running. A writer that wins the race locks a zero-filled struct mutex. On CONFIG_DEBUG_MUTEXES the missing magic value trips a "lock used without init" splat; on a production kernel the fast path acquires the lock via CMPXCHG(owner, 0, current). In the latter case a second writer that arrives before init_gssp_clnt() re-zeroes owner can enter set_gssp_clnt() concurrently, shut down the first writer's clnt while it is still in use, and leak the loser's clnt. Fix by initializing sn->gssp_lock in sunrpc_init_net() so its lifetime matches the sunrpc_net it lives in. sn->gssp_clnt is already NULL from the kzalloc that backs net_generic storage, so the lazy helper is no longer needed; drop init_gssp_clnt(), its prototype, and the call from create_use_gss_proxy_proc_entry(). sunrpc.ko is a build-time dependency of auth_rpcgss.ko, so sunrpc_init_net() has always run on every netns before any auth_gss pernet init can publish the proc entry. Fixes: 030d794bf498 ("SUNRPC: Use gssproxy upcall for server RPCGSS authentication.") Cc: stable@vger.kernel.org Assisted-by: kres:claude-opus-4-7 Signed-off-by: Chris Mason Reviewed-by: Jeff Layton Link: https://patch.msgid.link/20260530-tier2-local-v2-1-5a0fd532db57@oracle.com Signed-off-by: Chuck Lever --- net/sunrpc/auth_gss/gss_rpc_upcall.c | 6 ------ net/sunrpc/auth_gss/gss_rpc_upcall.h | 1 - net/sunrpc/auth_gss/svcauth_gss.c | 1 - net/sunrpc/sunrpc_syms.c | 1 + 4 files changed, 1 insertion(+), 8 deletions(-) diff --git a/net/sunrpc/auth_gss/gss_rpc_upcall.c b/net/sunrpc/auth_gss/gss_rpc_upcall.c index 0fa4778620d9..b7f70b1adb18 100644 --- a/net/sunrpc/auth_gss/gss_rpc_upcall.c +++ b/net/sunrpc/auth_gss/gss_rpc_upcall.c @@ -121,12 +121,6 @@ static int gssp_rpc_create(struct net *net, struct rpc_clnt **_clnt) return result; } -void init_gssp_clnt(struct sunrpc_net *sn) -{ - mutex_init(&sn->gssp_lock); - sn->gssp_clnt = NULL; -} - int set_gssp_clnt(struct net *net) { struct sunrpc_net *sn = net_generic(net, sunrpc_net_id); diff --git a/net/sunrpc/auth_gss/gss_rpc_upcall.h b/net/sunrpc/auth_gss/gss_rpc_upcall.h index 31e96344167e..b3c2b2b90798 100644 --- a/net/sunrpc/auth_gss/gss_rpc_upcall.h +++ b/net/sunrpc/auth_gss/gss_rpc_upcall.h @@ -29,7 +29,6 @@ int gssp_accept_sec_context_upcall(struct net *net, struct gssp_upcall_data *data); void gssp_free_upcall_data(struct gssp_upcall_data *data); -void init_gssp_clnt(struct sunrpc_net *); int set_gssp_clnt(struct net *); void clear_gssp_clnt(struct sunrpc_net *); diff --git a/net/sunrpc/auth_gss/svcauth_gss.c b/net/sunrpc/auth_gss/svcauth_gss.c index 764b4d82951d..967e9d53080d 100644 --- a/net/sunrpc/auth_gss/svcauth_gss.c +++ b/net/sunrpc/auth_gss/svcauth_gss.c @@ -1468,7 +1468,6 @@ static int create_use_gss_proxy_proc_entry(struct net *net) &use_gss_proxy_proc_ops, net); if (!*p) return -ENOMEM; - init_gssp_clnt(sn); return 0; } diff --git a/net/sunrpc/sunrpc_syms.c b/net/sunrpc/sunrpc_syms.c index ab88ce46afb5..1a3884a0376a 100644 --- a/net/sunrpc/sunrpc_syms.c +++ b/net/sunrpc/sunrpc_syms.c @@ -57,6 +57,7 @@ static __net_init int sunrpc_init_net(struct net *net) INIT_LIST_HEAD(&sn->all_clients); spin_lock_init(&sn->rpc_client_lock); spin_lock_init(&sn->rpcb_clnt_lock); + mutex_init(&sn->gssp_lock); return 0; err_pipefs: From 546f87f4fbbf22f5081d9f5e3e529cd8a72cc54a Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Sat, 30 May 2026 20:42:53 -0400 Subject: [PATCH 436/562] SUNRPC: Check svc pool percpu counter allocation __svc_create() initializes three per-pool percpu_counter stats and ignores every return value. On SMP, percpu_counter_init() fails when __alloc_percpu_gfp() cannot satisfy the allocation, leaving the failed counter with fbc->counters == NULL and its embedded raw_spinlock_t, list_head, and count never initialized. __svc_create() returns the half-constructed svc_serv to nfsd, lockd, or the NFS callback service anyway. Once that service is live, the hot-path increments in svc_xprt_enqueue(), svc_handle_xprt(), and svc_pool_wake_idle_thread() reach a counter whose backing pointer is NULL. The pointer is a per-cpu offset, so the access does not fault: it resolves to offset zero of the current CPU's per-cpu area and silently corrupts whatever variable lives there. A /proc/fs/nfsd/pool_stats read walks the same NULL per-cpu storage and returns garbage, and on CONFIG_DEBUG_SPINLOCK or lockdep it splats on the never-initialized lock. Creating the broken service requires a percpu allocation failure during RPC server startup, so it is reachable only by a local administrator under memory pressure or fault injection; a remote peer cannot induce the bad state on its own. Check each percpu_counter_init() return value in __svc_create() and fail when an allocation fails, unwinding the counters already set up in the current pool and in every pool initialized before it. A discrete percpu_counter_destroy() per counter at teardown frees each per-cpu allocation exactly once. Fixes: ccf08bed6e7a ("SUNRPC: Replace pool stats with per-CPU variables") Cc: stable@vger.kernel.org Reviewed-by: Jeff Layton Link: https://patch.msgid.link/20260530-tier2-local-v2-2-5a0fd532db57@oracle.com Signed-off-by: Chuck Lever --- net/sunrpc/svc.c | 45 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/net/sunrpc/svc.c b/net/sunrpc/svc.c index ae9ec4bf34f7..009373737ea9 100644 --- a/net/sunrpc/svc.c +++ b/net/sunrpc/svc.c @@ -476,6 +476,35 @@ __svc_init_bc(struct svc_serv *serv) } #endif +static int svc_pool_init_counters(struct svc_pool *pool) +{ + int err; + + err = percpu_counter_init(&pool->sp_messages_arrived, 0, GFP_KERNEL); + if (err) + return err; + err = percpu_counter_init(&pool->sp_sockets_queued, 0, GFP_KERNEL); + if (err) + goto err_sockets; + err = percpu_counter_init(&pool->sp_threads_woken, 0, GFP_KERNEL); + if (err) + goto err_threads; + return 0; + +err_threads: + percpu_counter_destroy(&pool->sp_sockets_queued); +err_sockets: + percpu_counter_destroy(&pool->sp_messages_arrived); + return err; +} + +static void svc_pool_destroy_counters(struct svc_pool *pool) +{ + percpu_counter_destroy(&pool->sp_messages_arrived); + percpu_counter_destroy(&pool->sp_sockets_queued); + percpu_counter_destroy(&pool->sp_threads_woken); +} + /* * Create an RPC service */ @@ -540,12 +569,18 @@ __svc_create(struct svc_program *prog, int nprogs, struct svc_stat *stats, INIT_LIST_HEAD(&pool->sp_all_threads); init_llist_head(&pool->sp_idle_threads); - percpu_counter_init(&pool->sp_messages_arrived, 0, GFP_KERNEL); - percpu_counter_init(&pool->sp_sockets_queued, 0, GFP_KERNEL); - percpu_counter_init(&pool->sp_threads_woken, 0, GFP_KERNEL); + if (svc_pool_init_counters(pool)) + goto out_err; } return serv; + +out_err: + while (i--) + svc_pool_destroy_counters(&serv->sv_pools[i]); + kfree(serv->sv_pools); + kfree(serv); + return NULL; } /** @@ -624,9 +659,7 @@ svc_destroy(struct svc_serv **servp) for (i = 0; i < serv->sv_nrpools; i++) { struct svc_pool *pool = &serv->sv_pools[i]; - percpu_counter_destroy(&pool->sp_messages_arrived); - percpu_counter_destroy(&pool->sp_sockets_queued); - percpu_counter_destroy(&pool->sp_threads_woken); + svc_pool_destroy_counters(pool); } kfree(serv->sv_pools); kfree(serv); From 6a51c4a53f2a53f1eb235d6ab253383e1b70982d Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Sun, 31 May 2026 08:06:58 -0400 Subject: [PATCH 437/562] nfsd: size fh_verify server sockaddr slot by xpt_locallen The nfsd_fh_verify and nfsd_fh_verify_err tracepoints declare the server sockaddr slot sized by xpt_remotelen but fill it from xpt_local using xpt_locallen: TP_STRUCT__entry( ... __sockaddr(server, rqstp->rq_xprt->xpt_remotelen) ... ) TP_fast_assign( ... __assign_sockaddr(server, &rqstp->rq_xprt->xpt_local, rqstp->rq_xprt->xpt_locallen); ... ) When xpt_locallen exceeds xpt_remotelen, __assign_sockaddr's memcpy writes past the reserved ring-buffer slot. In the reverse direction (xpt_locallen < xpt_remotelen) the slot is oversized and the unwritten tail leaks prior ring-buffer contents to trace consumers. The write-past-end case is reachable on NFS/UDP. svc_xprt_set_remote() is only called from svc_tcp_accept() (net/sunrpc/svcsock.c) and from the RDMA connect path; svc_create_socket() for UDP calls only svc_xprt_set_local(), so xpt_remotelen stays 0 for the xprt's lifetime. Every fh_verify trace for an NFSv2/v3-over-UDP request then copies 16 or 28 bytes from xpt_local into a zero-byte slot. The other NFSD tracepoints that record the server address (NFSD_TRACE_PROC_CALL_FIELDS, NFSD_TRACE_PROC_RES_FIELDS, SVC_RQST_ENDPOINT_FIELDS) already size the server slot by xpt_locallen; nfsd_fh_verify and nfsd_fh_verify_err were the only exceptions. Fix by sizing the server slot with xpt_locallen so the declared slot matches the copy length. The client slot and its assignment already agree on xpt_remotelen and are left untouched. Fixes: 051382885552 ("NFSD: Instrument fh_verify()") Fixes: 948755efc951 ("NFSD: Replace dprintk() call site in fh_verify()") Cc: stable@vger.kernel.org Assisted-by: kres:claude-opus-4-7 Signed-off-by: Chris Mason Link: https://patch.msgid.link/20260531-nfsd-testing-v1-1-7bfa481b0540@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/trace.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/nfsd/trace.h b/fs/nfsd/trace.h index 830d9ceb4fe3..33953d38314e 100644 --- a/fs/nfsd/trace.h +++ b/fs/nfsd/trace.h @@ -271,7 +271,7 @@ TRACE_EVENT_CONDITION(nfsd_fh_verify, TP_CONDITION(rqstp != NULL), TP_STRUCT__entry( __field(unsigned int, netns_ino) - __sockaddr(server, rqstp->rq_xprt->xpt_remotelen) + __sockaddr(server, rqstp->rq_xprt->xpt_locallen) __sockaddr(client, rqstp->rq_xprt->xpt_remotelen) __field(u32, xid) __field(u32, fh_hash) @@ -310,7 +310,7 @@ TRACE_EVENT_CONDITION(nfsd_fh_verify_err, TP_CONDITION(rqstp != NULL && error), TP_STRUCT__entry( __field(unsigned int, netns_ino) - __sockaddr(server, rqstp->rq_xprt->xpt_remotelen) + __sockaddr(server, rqstp->rq_xprt->xpt_locallen) __sockaddr(client, rqstp->rq_xprt->xpt_remotelen) __field(u32, xid) __field(u32, fh_hash) From f999298c024d7ae1c190f52d78e1856b49b51b91 Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Sun, 31 May 2026 08:06:59 -0400 Subject: [PATCH 438/562] nfsd: release path refs on follow_down() error nfsd_cross_mnt() initializes a local struct path with mntget() and dget() before calling follow_down(). On a negative return the error arm jumps to out without releasing those references: err = follow_down(&path, follow_flags); if (err < 0) goto out; follow_down() never drops the caller's entry-time refs on any error sub-case; for example a pre-cross d_manage() failure leaves path untouched, so the mntget()/dget() taken on entry survive the call. Every other early-exit arm in nfsd_cross_mnt() (other-namespace return, IS_ERR(exp2), and the success tail after the swap) already calls path_put(&path); the err < 0 arm is the lone omission. The leak inflates mnt_count and d_count on each failed cross-mount, blocking umount and pinning dentries against the shrinker, and is reachable by any authenticated NFS client through nfsd_lookup_dentry or the NFSv4 READDIR encode path. Fix by calling path_put(&path) before the goto out in the err < 0 arm so the entry-time refs are released on all follow_down() error returns. Fixes: cc53ce53c869 ("Add a dentry op to allow processes to be held during pathwalk transit") Cc: stable@vger.kernel.org Assisted-by: kres:claude-opus-4-7 Signed-off-by: Chris Mason Link: https://patch.msgid.link/20260531-nfsd-testing-v1-2-7bfa481b0540@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/vfs.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c index 0dcfaab95021..f73012bc742f 100644 --- a/fs/nfsd/vfs.c +++ b/fs/nfsd/vfs.c @@ -137,8 +137,10 @@ nfsd_cross_mnt(struct svc_rqst *rqstp, struct dentry **dpp, follow_flags = LOOKUP_AUTOMOUNT; err = follow_down(&path, follow_flags); - if (err < 0) + if (err < 0) { + path_put(&path); goto out; + } if (path.mnt == exp->ex_path.mnt && path.dentry == dentry && nfsd_mountpoint(dentry, exp) == 2) { /* This is only a mountpoint in some other namespace */ From 90314405569e9032afae3241042781dbd52de051 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Sun, 31 May 2026 08:07:00 -0400 Subject: [PATCH 439/562] nfsd: fix nfsd_file leak on inter-server COPY setup failure When nfsd4_setup_inter_ssc() fails, nfsd4_copy() returns nfserr_offload_denied directly, bypassing the out: label where release_copy_files() would drop the nf_dst reference taken by nfs4_preprocess_stateid_op(). Each failed inter-server COPY leaks one nfsd_file, pinning file/inode/dentry/vfsmount. Fix by setting status and jumping to out: instead of returning directly. Fixes: ce0887ac96d3 ("NFSD add nfs4 inter ssc to nfsd4_copy") Cc: stable@vger.kernel.org Assisted-by: kres:claude-opus-4-7 Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260531-nfsd-testing-v1-3-7bfa481b0540@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfs4proc.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c index 505c96c14d9e..4df4ad03db10 100644 --- a/fs/nfsd/nfs4proc.c +++ b/fs/nfsd/nfs4proc.c @@ -2159,16 +2159,14 @@ nfsd4_copy(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, } status = nfsd4_setup_inter_ssc(rqstp, cstate, copy); if (status) { - trace_nfsd_copy_done(copy, status); - return nfserr_offload_denied; + status = nfserr_offload_denied; + goto out; } } else { trace_nfsd_copy_intra(copy); status = nfsd4_setup_intra_ssc(rqstp, cstate, copy); - if (status) { - trace_nfsd_copy_done(copy, status); - return status; - } + if (status) + goto out; } memcpy(©->fh, &cstate->current_fh.fh_handle, From a9c5b814c478e0b2b2eb8597540c9015535b01dc Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Sun, 31 May 2026 08:07:01 -0400 Subject: [PATCH 440/562] nfsd: fix dentry ref leak on V4ROOT export filehandle lookup nfsd_set_fh_dentry() leaks the dentry reference from exportfs_decode_fh_raw() when the NFS3_FHSIZE or NFS_FHSIZE switch cases detect NFSEXP_V4ROOT and goto out. The out: label calls exp_put() but never dput(dentry), and fhp->fh_dentry was never assigned so fh_put() cannot compensate. A crafted NFSv3 filehandle targeting a V4ROOT export's fsid triggers the leak on every request. Fixes: ef7f6c4904d0 ("nfsd: move V4ROOT version check to nfsd_set_fh_dentry()") Cc: stable@vger.kernel.org Assisted-by: kres:claude-opus-4-7 Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260531-nfsd-testing-v1-4-7bfa481b0540@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfsfh.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/fs/nfsd/nfsfh.c b/fs/nfsd/nfsfh.c index 429ca5c6ec08..b36915401758 100644 --- a/fs/nfsd/nfsfh.c +++ b/fs/nfsd/nfsfh.c @@ -344,15 +344,19 @@ static __be32 nfsd_set_fh_dentry(struct svc_rqst *rqstp, struct net *net, if (dentry->d_sb->s_export_op->flags & EXPORT_OP_NOWCC) fhp->fh_no_wcc = true; fhp->fh_64bit_cookies = true; - if (exp->ex_flags & NFSEXP_V4ROOT) + if (exp->ex_flags & NFSEXP_V4ROOT) { + dput(dentry); goto out; + } break; case NFS_FHSIZE: fhp->fh_no_wcc = true; if (EX_WGATHER(exp)) fhp->fh_use_wgather = true; - if (exp->ex_flags & NFSEXP_V4ROOT) + if (exp->ex_flags & NFSEXP_V4ROOT) { + dput(dentry); goto out; + } } fhp->fh_dentry = dentry; From 1a8dba13f95a73c5ade4b19ea2486cdd663a4c5d Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Sun, 31 May 2026 08:07:03 -0400 Subject: [PATCH 441/562] nfsd: fix layout fence worker double-reference race The workqueue core clears WORK_STRUCT_PENDING before the callback is invoked, so delayed_work_pending() in lm_breaker_timedout() can return false while the fence worker is already running. This lets the breaker take a duplicate sc_count reference and schedule a new worker that coalesces with the in-progress one. The extra reference is never put, leaking the layout stateid. Replace the racy delayed_work_pending() check with an ls_fence_inflight boolean set atomically with refcount_inc_not_zero() under ls_lock, and cleared under ls_lock before the final nfs4_put_stid() on the dispose path; the retry path intentionally retains it. Remove the self-rearm mod_delayed_work() at the top of the worker. Fixes: f52792f484ba ("NFSD: Enforce timeout on layout recall and integrate lease manager fencing") Cc: stable@vger.kernel.org Assisted-by: kres:claude-opus-4-7 Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260531-nfsd-testing-v1-6-7bfa481b0540@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfs4layouts.c | 27 +++++++++++++++------------ fs/nfsd/state.h | 1 + 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/fs/nfsd/nfs4layouts.c b/fs/nfsd/nfs4layouts.c index 1de2f6cd1f09..279ff1e9dffb 100644 --- a/fs/nfsd/nfs4layouts.c +++ b/fs/nfsd/nfs4layouts.c @@ -260,6 +260,7 @@ nfsd4_alloc_layout_stateid(struct nfsd4_compound_state *cstate, } ls->ls_fenced = false; + ls->ls_fence_inflight = false; ls->ls_fence_delay = 0; INIT_DELAYED_WORK(&ls->ls_fence_work, nfsd4_layout_fence_worker); @@ -797,15 +798,6 @@ nfsd4_layout_fence_worker(struct work_struct *work) struct nfs4_client *clp; struct nfsd_net *nn; - /* - * The workqueue clears WORK_STRUCT_PENDING before invoking - * this callback. Re-arm immediately so that - * delayed_work_pending() returns true while the fence - * operation is in progress, preventing - * lm_breaker_timedout() from taking a duplicate reference. - */ - mod_delayed_work(system_dfl_wq, &ls->ls_fence_work, 0); - spin_lock(&ls->ls_lock); if (list_empty(&ls->ls_layouts)) { spin_unlock(&ls->ls_lock); @@ -815,6 +807,9 @@ nfsd4_layout_fence_worker(struct work_struct *work) nfsd4_close_layout(ls); ls->ls_fenced = true; + spin_lock(&ls->ls_lock); + ls->ls_fence_inflight = false; + spin_unlock(&ls->ls_lock); nfs4_put_stid(&ls->ls_stid); return; } @@ -900,18 +895,26 @@ nfsd4_layout_lm_breaker_timedout(struct file_lease *fl) if ((!nfsd4_layout_ops[ls->ls_layout_type]->fence_client) || ls->ls_fenced) return true; - if (delayed_work_pending(&ls->ls_fence_work)) - return false; /* * Make sure layout has not been returned yet before - * taking a reference count on the layout stateid. + * taking a reference count on the layout stateid. The + * ls_fence_inflight flag is set together with the sc_count + * increment under ls_lock so that a fence worker invocation + * already in progress (which has cleared WORK_STRUCT_PENDING + * but not yet reached dispose:) cannot be coalesced with a + * fresh schedule that takes an extra unmatched reference. */ spin_lock(&ls->ls_lock); + if (ls->ls_fence_inflight) { + spin_unlock(&ls->ls_lock); + return false; + } if (list_empty(&ls->ls_layouts) || !refcount_inc_not_zero(&ls->ls_stid.sc_count)) { spin_unlock(&ls->ls_lock); return true; } + ls->ls_fence_inflight = true; spin_unlock(&ls->ls_lock); mod_delayed_work(system_dfl_wq, &ls->ls_fence_work, 0); diff --git a/fs/nfsd/state.h b/fs/nfsd/state.h index ac6fd0d6d099..f44ea672670f 100644 --- a/fs/nfsd/state.h +++ b/fs/nfsd/state.h @@ -755,6 +755,7 @@ struct nfs4_layout_stateid { struct delayed_work ls_fence_work; unsigned int ls_fence_delay; bool ls_fenced; + bool ls_fence_inflight; }; static inline struct nfs4_layout_stateid *layoutstateid(struct nfs4_stid *s) From c07e294c639906b688e80a83998649f4e93dd620 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Mon, 1 Jun 2026 11:32:56 -0400 Subject: [PATCH 442/562] nfsd: release OPEN-decoded posix ACLs via op_release nfsd4_decode_createhow4() calls nfsd4_decode_fattr4(), which allocates refcounted struct posix_acl objects via posix_acl_alloc() and stores them in open->op_pacl and open->op_dpacl. These pointers must be released once the OPEN compound finishes. When nfsd4_decode_open_claim4() returns a non-seqid-mutating error, the dispatcher short-circuits before op_func runs: nfsd4_proc_compound() if (op->status && op->opnum == OP_OPEN) op->status = nfsd4_open_omfg(...) if (!seqid_mutating_err(ntohl(op->status))) return op->status; /* nfsd4_open() never runs */ ... opdesc->op_release(&op->u) /* must still release op_pacl/op_dpacl */ Before this change OP_OPEN had no .op_release in nfsd4_ops[], and the release pair lived inside nfsd4_open() at its out_err: label. On the short-circuit path nfsd4_open() is never invoked, so both posix_acl refs leak on every malformed OPEN compound that carries valid POSIX ACL createhow4 attributes. Add nfsd4_open_release() and wire it as .op_release for OP_OPEN. posix_acl_release() is NULL-safe, so the single release site covers both the normal path and the nfsd4_open_omfg short-circuit. Remove the matching posix_acl_release() pair from nfsd4_open()'s out_err: label to avoid double-releasing. The compound loop has two encoding branches: nfsd4_encode_operation() for normal ops, and nfsd4_encode_replay() for v4.0 replayed ops. op_release was only called from nfsd4_encode_operation(), so resources attached to op->u leak on the replay path. Move the op_release() call out of nfsd4_encode_operation() and the replay branch, placing it after the if-else in nfsd4_proc_compound(). This gives a single call site in a fairly obviously-correct place, covering both the normal encoding and replay paths. Fixes: 5fc51dfc2eb1 ("NFSD: Add support for XDR decoding POSIX draft ACLs") Cc: stable@vger.kernel.org Signed-off-by: Chris Mason Reviewed-by: NeilBrown Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260601-nfsd-testing-v3-1-a31cd10bdd4f@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfs4proc.c | 13 +++++++++++-- fs/nfsd/nfs4xdr.c | 3 --- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c index 4df4ad03db10..0c37d7c6d28c 100644 --- a/fs/nfsd/nfs4proc.c +++ b/fs/nfsd/nfs4proc.c @@ -681,8 +681,6 @@ nfsd4_open(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, nfsd4_cleanup_open_state(cstate, open); nfsd4_bump_seqid(cstate, status); out_err: - posix_acl_release(open->op_dpacl); - posix_acl_release(open->op_pacl); return status; } @@ -704,6 +702,13 @@ static __be32 nfsd4_open_omfg(struct svc_rqst *rqstp, struct nfsd4_compound_stat return nfsd4_open(rqstp, cstate, &op->u); } +static void +nfsd4_open_release(union nfsd4_op_u *u) +{ + posix_acl_release(u->open.op_dpacl); + posix_acl_release(u->open.op_pacl); +} + /* * filehandle-manipulating ops. */ @@ -3202,6 +3207,9 @@ nfsd4_proc_compound(struct svc_rqst *rqstp) status = op->status; } + if (op->opdesc && op->opdesc->op_release) + op->opdesc->op_release(&op->u); + trace_nfsd_compound_status(args->client_opcnt, resp->opcnt, status, nfsd4_op_name(op->opnum)); @@ -3701,6 +3709,7 @@ static const struct nfsd4_operation nfsd4_ops[] = { }, [OP_OPEN] = { .op_func = nfsd4_open, + .op_release = nfsd4_open_release, .op_flags = OP_HANDLES_WRONGSEC | OP_MODIFIES_SOMETHING, .op_name = "OP_OPEN", .op_rsize_bop = nfsd4_open_rsize, diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c index c37c9722b8c6..2e0097b2d1d9 100644 --- a/fs/nfsd/nfs4xdr.c +++ b/fs/nfsd/nfs4xdr.c @@ -6403,9 +6403,6 @@ nfsd4_encode_operation(struct nfsd4_compoundres *resp, struct nfsd4_op *op) write_bytes_to_xdr_buf(xdr->buf, op_status_offset, &op->status, XDR_UNIT); release: - if (opdesc && opdesc->op_release) - opdesc->op_release(&op->u); - /* * Account for pages consumed while encoding this operation. * The xdr_stream primitives don't manage rq_next_page. From 7b9e81ed1dacf2ed694fb82c907f01f17b17fbfa Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Mon, 1 Jun 2026 16:17:03 -0400 Subject: [PATCH 443/562] rpcrdma: arm rn_done before publishing the notification rpcrdma_rn_register() inserts @rn into rd_xa with xa_alloc() before storing the caller's callback in rn->rn_done. The xarray makes @rn reachable to rpcrdma_remove_one(), which walks rd_xa and invokes rn->rn_done(rn) for every registered notification. A device removal that races a fresh registration can therefore observe @rn with rn_done still NULL, because the notification objects are zero allocated by their owners, and call through a NULL function pointer. Store rn->rn_done before xa_alloc() publishes @rn. The xarray's store-side and load-side ordering then guarantees that any CPU which finds @rn in rd_xa also observes the armed callback. rpcrdma_rn_unregister() treats a non-NULL rn_done as the sentinel for a completed registration, so the early store must not survive a failed registration. Clear rn_done again when xa_alloc() fails. Were it left set, the failed-accept cleanup path would call rpcrdma_rn_unregister() on an @rn that was never inserted, erasing an unrelated rd_xa slot and underflowing rd_kref. Fixes: 7e86845a0346 ("rpcrdma: Implement generic device removal") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260601201703.46078-1-cel@kernel.org Signed-off-by: Chuck Lever --- net/sunrpc/xprtrdma/ib_client.c | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/net/sunrpc/xprtrdma/ib_client.c b/net/sunrpc/xprtrdma/ib_client.c index 69166d5d9987..188f7a13397f 100644 --- a/net/sunrpc/xprtrdma/ib_client.c +++ b/net/sunrpc/xprtrdma/ib_client.c @@ -52,8 +52,8 @@ static struct rpcrdma_device *rpcrdma_get_client_data(struct ib_device *device) * is unregistered first. * * On failure, a negative errno is returned. rn->rn_done is left - * NULL on every failure path (it is assigned only after xa_alloc - * and kref_get have both succeeded), so the @rn may safely be + * NULL on every failure path (it is armed before xa_alloc but + * cleared again if xa_alloc fails), so the @rn may safely be * passed to rpcrdma_rn_unregister() without a separate * registered/unregistered flag in the caller. */ @@ -66,10 +66,21 @@ int rpcrdma_rn_register(struct ib_device *device, if (!rd || test_bit(RPCRDMA_RD_F_REMOVING, &rd->rd_flags)) return -ENETUNREACH; - if (xa_alloc(&rd->rd_xa, &rn->rn_index, rn, xa_limit_32b, GFP_KERNEL) < 0) + /* + * Arm rn_done before xa_alloc() publishes @rn: once @rn is + * visible in rd_xa, a concurrent rpcrdma_remove_one() can + * call rn->rn_done(), so the pointer must already be set. + * + * Restore NULL if xa_alloc() fails. rn_done doubles as the + * registration sentinel for rpcrdma_rn_unregister(); a stale + * value would unregister an @rn that was never inserted. + */ + rn->rn_done = done; + if (xa_alloc(&rd->rd_xa, &rn->rn_index, rn, xa_limit_32b, GFP_KERNEL) < 0) { + rn->rn_done = NULL; return -ENOMEM; + } kref_get(&rd->rd_kref); - rn->rn_done = done; trace_rpcrdma_client_register(device, rn); return 0; } @@ -102,8 +113,9 @@ void rpcrdma_rn_unregister(struct ib_device *device, /* * rn_done is the registration sentinel: rpcrdma_rn_register - * assigns it last, after xa_alloc and kref_get have both - * succeeded. A NULL rn_done means this notification was + * leaves it NULL on every failure path, clearing it again if + * xa_alloc fails, so a non-NULL rn_done marks a completed + * registration. A NULL rn_done means this notification was * never registered (or its registration failed) or has * already been unregistered, and the call is a no-op. * Without this guard, rn_index == 0 from a kzalloc'd From b685fde66b2ff88961cc60c587f32d0e99919722 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Tue, 2 Jun 2026 12:23:13 -0400 Subject: [PATCH 444/562] nfsd: defer vfree of compound ops to fix rpc_status UAF MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The rpc_status netlink dumpit walks every in-flight svc_rqst under rcu_read_lock and, for NFSv4 requests, reads opnums out of args->ops[]. But args->ops is a separate vmalloc buffer freed synchronously by vfree() in nfsd4_release_compoundargs() at the end of every compound. The dumpit's rcu_read_lock pins the svc_rqst struct itself (freed via kfree_rcu), but nothing defers the vfree of the ops buffer across the RCU grace period. A concurrent compound completion can therefore free the buffer while the dumpit is reading it — a use-after-free on vmalloc memory. The trailing seqcount recheck (smp_load_acquire of rq_status_counter) cannot undo a load that already retired against freed memory. Fix by replacing vfree(args->ops) with kvfree_rcu_mightsleep(), which defers the free until after an RCU grace period. This makes the existing rcu_read_lock in the dumpit sufficient to protect the read. The tradeoff is that completed compound ops buffers (up to 200 * sizeof(struct nfsd4_op)) persist in memory slightly longer, across one grace period, before being reclaimed. Fixes: bd9d6a3efa97 ("NFSD: add rpc_status netlink support") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260602-nfsd-testing-v2-1-e4ea62e3cd5c@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfs4xdr.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c index 2e0097b2d1d9..b9037d99b564 100644 --- a/fs/nfsd/nfs4xdr.c +++ b/fs/nfsd/nfs4xdr.c @@ -6435,8 +6435,10 @@ void nfsd4_release_compoundargs(struct svc_rqst *rqstp) struct nfsd4_compoundargs *args = rqstp->rq_argp; if (args->ops != args->iops) { - vfree(args->ops); + void *old_ops = args->ops; + args->ops = args->iops; + kvfree_rcu_mightsleep(old_ops); } while (args->to_free) { struct svcxdr_tmpbuf *tb = args->to_free; From f27b533655b283368a6c5627f51978eb285702b1 Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Tue, 2 Jun 2026 12:23:14 -0400 Subject: [PATCH 445/562] nfsd: hold rcu across localio cmpxchg retry nfsd_file objects are freed via call_rcu (filecache.c:296), and nfsd_file_slab is created without SLAB_TYPESAFE_BY_RCU (KMEM_CACHE(nfsd_file, 0) at filecache.c:789), so the slab page backing a freed nfsd_file becomes freely reclaimable once the RCU grace period elapses. The again: retry block in nfsd_open_local_fh() loads a pointer with cmpxchg and then calls nfsd_file_get(new) (which is refcount_inc_not_zero) without holding rcu_read_lock. The sole caller nfs_open_local_fh() drops rcu_read_lock before invoking this helper, so no outer reader-side critical section covers the load. CPU 0 (nfsd_open_local_fh) CPU 1 (nfsd_file_put_local) ----- ----- new = cmpxchg(pnf, NULL, ...) nf = xchg(pnf, NULL) nfsd_file_put(nf) last ref -> call_rcu() /* grace period elapses; slab page recycled */ nfsd_file_get(new) refcount_inc_not_zero(&new->nf_ref) /* operates on recycled memory */ A non-zero word at the nf_ref offset of the recycled object makes the refcount bump appear to succeed, and the caller then dereferences new->nf_net and new->nf_file out of freed memory. Fix by taking rcu_read_lock() immediately before the cmpxchg and releasing it on all three exits of the if (new) block: the goto-again retry, the lost-race cleanup path, and the install-succeeded path. nfsd_file_put() and nfsd_net_put() stay outside the RCU section so they remain free to block. Fixes: e6f7e1487ab5 ("nfs_localio: simplify interface to nfsd for getting nfsd_file") Cc: stable@vger.kernel.org Assisted-by: kres:claude-opus-4-7 Signed-off-by: Chris Mason Link: https://patch.msgid.link/20260602-nfsd-testing-v2-2-e4ea62e3cd5c@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/localio.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/fs/nfsd/localio.c b/fs/nfsd/localio.c index be710d809a3b..c3eb0557b3e1 100644 --- a/fs/nfsd/localio.c +++ b/fs/nfsd/localio.c @@ -97,11 +97,15 @@ nfsd_open_local_fh(struct net *net, struct auth_domain *dom, } nfsd_file_get(localio); again: + rcu_read_lock(); new = unrcu_pointer(cmpxchg(pnf, NULL, RCU_INITIALIZER(localio))); if (new) { /* Some other thread installed an nfsd_file */ - if (nfsd_file_get(new) == NULL) + if (nfsd_file_get(new) == NULL) { + rcu_read_unlock(); goto again; + } + rcu_read_unlock(); /* * Drop the ref we were going to install (both file and * net) and the one we were going to return (only file). @@ -110,6 +114,8 @@ nfsd_open_local_fh(struct net *net, struct auth_domain *dom, nfsd_net_put(net); nfsd_file_put(localio); localio = new; + } else { + rcu_read_unlock(); } } else nfsd_net_put(net); From 48fb1383291314db9ac1bbeeb168f8e9bd0c4d7d Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Tue, 2 Jun 2026 12:23:15 -0400 Subject: [PATCH 446/562] NFS/localio: fix ref leak on nfs_uuid_add_file failure When nfs_uuid_add_file() races with nfs_uuid_put() tearing down uuid->net, it returns -ENXIO without publishing nfl->nfs_uuid via rcu_assign_pointer(). nfs_open_local_fh() then enters its error branch and only releases the slot's file ref and its paired net ref plus its own entry-time net ref, while the close path is a no-op: nfs_close_local_fh() nfs_uuid = rcu_dereference(nfl->nfs_uuid); if (!nfs_uuid) { rcu_read_unlock(); return; } /* always */ nfsd_open_local_fh() returns localio holding a caller-owned +1 nfsd_file reference (from nfsd_file_get() after nfsd_file_acquire_local()) and an entry-time nfsd_net reference (from its first nfsd_net_try_get()) embedded as nf->nf_net. Both are leaked on the failure path, pinning one nfsd_file (and the underlying struct file, dentry, inode) and one nfsd_net_ref per occurrence, which blocks nfsd_net and netns teardown. Fix by releasing the caller-owned file ref and its net ref through the existing helper, using a stack-local RCU pointer so the helper can xchg it out, then returning -ENXIO so callers do not dereference a localio whose slot has been cleared: struct nfsd_file __rcu *tmp = RCU_INITIALIZER(localio); nfs_to_nfsd_file_put_local(pnf); nfs_to_nfsd_file_put_local(&tmp); localio = ERR_PTR(-ENXIO); The trailing nfs_to_nfsd_net_put(net) continues to release the outer net ref, so all three nfsd_net_try_get() increments are balanced on the error branch. Fixes: fdd015de7679 ("NFS/localio: nfs_uuid_put() fix races with nfs_open/close_local_fh()") Cc: stable@vger.kernel.org Assisted-by: kres:claude-opus-4-7 Signed-off-by: Chris Mason Link: https://patch.msgid.link/20260602-nfsd-testing-v2-3-e4ea62e3cd5c@kernel.org Signed-off-by: Chuck Lever --- fs/nfs_common/nfslocalio.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/fs/nfs_common/nfslocalio.c b/fs/nfs_common/nfslocalio.c index dd715cdb6c04..85aa03a7b020 100644 --- a/fs/nfs_common/nfslocalio.c +++ b/fs/nfs_common/nfslocalio.c @@ -292,8 +292,22 @@ struct nfsd_file *nfs_open_local_fh(nfs_uuid_t *uuid, localio = nfs_to->nfsd_open_local_fh(net, uuid->dom, rpc_clnt, cred, nfs_fh, pnf, fmode); if (!IS_ERR(localio) && nfs_uuid_add_file(uuid, nfl) < 0) { - /* Delete the cached file when racing with nfs_uuid_put() */ + /* + * Delete the cached file when racing with nfs_uuid_put(). + * Since nfl->nfs_uuid was never published via + * rcu_assign_pointer(), nfs_close_local_fh() will early-return + * and cannot clean up after us. Drop the slot's file ref and + * its paired net ref, then drop the caller-owned nfsd_file ref + * (+1) and the entry-time nfsd_net ref carried via nf->nf_net, + * and return -ENXIO so the caller never dereferences the + * now-cleared localio. + */ + struct nfsd_file __rcu *tmp = + (struct nfsd_file __force __rcu *)localio; + nfs_to_nfsd_file_put_local(pnf); + nfs_to_nfsd_file_put_local(&tmp); + localio = ERR_PTR(-ENXIO); } nfs_to_nfsd_net_put(net); From 39fb70ced1196a3584bf3a04cf7b0e7d024cd5b3 Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Tue, 2 Jun 2026 12:23:16 -0400 Subject: [PATCH 447/562] nfsd: guard nfsd_serv deref in nfsd_file_net_dispose nfsd_file_net_dispose() is the consumer side of l->freeme: the nfsd service thread loop calls it to drain entries that the filecache garbage collector and shrinker append via nfsd_file_dispose_list_delayed(). During per-net teardown, nn->nfsd_serv is cleared before the filecache laundrette is shut down, so the service thread can still run a dispose pass that finds more than eight entries on l->freeme and dereferences a NULL svc_serv: nfsd service thread loop nfsd_file_net_dispose(nn) if (!list_empty(&l->freeme)) { ... svc_wake_up(nn->nfsd_serv); /* nn->nfsd_serv == NULL */ } The sibling helper nfsd_file_dispose_list_delayed() already documents this ordering and caches nn->nfsd_serv into a local before testing it for NULL. nfsd_file_net_dispose() was introduced with the same raw svc_wake_up(nn->nfsd_serv) call and never picked up the guard. Fix by loading nn->nfsd_serv into a local svc_serv pointer and only calling svc_wake_up() when it is non-NULL, matching the pattern in nfsd_file_dispose_list_delayed(). Fixes: ffb402596147 ("nfsd: Don't leave work of closing files to a work queue") Cc: stable@vger.kernel.org Assisted-by: kres:claude-opus-4-7 Signed-off-by: Chris Mason Link: https://patch.msgid.link/20260602-nfsd-testing-v2-4-e4ea62e3cd5c@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/filecache.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/fs/nfsd/filecache.c b/fs/nfsd/filecache.c index 3b5f18fb713f..7f100c41022a 100644 --- a/fs/nfsd/filecache.c +++ b/fs/nfsd/filecache.c @@ -471,11 +471,20 @@ void nfsd_file_net_dispose(struct nfsd_net *nn) for (i = 0; i < 8 && !list_empty(&l->freeme); i++) list_move(l->freeme.next, &dispose); spin_unlock(&l->lock); - if (!list_empty(&l->freeme)) - /* Wake up another thread to share the work + if (!list_empty(&l->freeme)) { + /* + * Wake up another thread to share the work * *before* doing any actual disposing. + * + * The filecache laundrette is shut down after + * the nn->nfsd_serv pointer is cleared, but + * before the svc_serv is freed. */ - svc_wake_up(nn->nfsd_serv); + struct svc_serv *serv = nn->nfsd_serv; + + if (serv) + svc_wake_up(serv); + } nfsd_file_dispose_list(&dispose); } } From 746e04a21b1dafb1991b63007cf40d2a5c261820 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Tue, 2 Jun 2026 12:23:17 -0400 Subject: [PATCH 448/562] nfsd: widen nfsd_genl_rqstp address fields to sockaddr_storage struct nfsd_genl_rqstp declares rq_daddr and rq_saddr as plain "struct sockaddr" (16 bytes). When an IPv6 NFS client is connected, nfsd_genl_rpc_status_compose_msg() casts these fields to "struct sockaddr_in6 *" (28 bytes) and reads sin6_addr at offset 8..24, which extends 8 bytes past the end of the 16-byte sockaddr field into the adjacent rq_flags member. The 16-byte nla_put_in6_addr then ships 8 bytes of truncated IPv6 address followed by 8 bytes of rq_flags to userspace via the NFSD_A_RPC_STATUS_SADDR6/DADDR6 netlink attributes. This is reachable by any unprivileged process in the network namespace because NFSD_CMD_RPC_STATUS_GET uses GENL_CMD_CAP_DUMP without GENL_ADMIN_PERM. Fix by widening rq_daddr and rq_saddr to struct sockaddr_storage so the IPv6 casts operate within bounds, copying sizeof(struct sockaddr_storage) bytes in the memcpy calls so the full address is captured, and zero-initializing the genl_rqstp stack variable to prevent leaking uninitialized tail bytes through netlink. Fixes: bd9d6a3efa97 ("NFSD: add rpc_status netlink support") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260602-nfsd-testing-v2-5-e4ea62e3cd5c@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfsctl.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index f73e30909559..bf360a5a3ee9 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -1414,8 +1414,8 @@ static int create_proc_exports_entry(void) unsigned int nfsd_net_id; struct nfsd_genl_rqstp { - struct sockaddr rq_daddr; - struct sockaddr rq_saddr; + struct sockaddr_storage rq_daddr; + struct sockaddr_storage rq_saddr; unsigned long rq_flags; ktime_t rq_stime; __be32 rq_xid; @@ -1450,7 +1450,7 @@ static int nfsd_genl_rpc_status_compose_msg(struct sk_buff *skb, NFSD_A_RPC_STATUS_PAD)) return -ENOBUFS; - switch (genl_rqstp->rq_saddr.sa_family) { + switch (genl_rqstp->rq_saddr.ss_family) { case AF_INET: { const struct sockaddr_in *s_in, *d_in; @@ -1527,7 +1527,7 @@ int nfsd_nl_rpc_status_get_dumpit(struct sk_buff *skb, list_for_each_entry_rcu(rqstp, &nn->nfsd_serv->sv_pools[i].sp_all_threads, rq_all) { - struct nfsd_genl_rqstp genl_rqstp; + struct nfsd_genl_rqstp genl_rqstp = {}; unsigned int status_counter; if (rqstp_index++ < cb->args[1]) /* already consumed */ @@ -1551,9 +1551,9 @@ int nfsd_nl_rpc_status_get_dumpit(struct sk_buff *skb, genl_rqstp.rq_stime = rqstp->rq_stime; genl_rqstp.rq_opcnt = 0; memcpy(&genl_rqstp.rq_daddr, svc_daddr(rqstp), - sizeof(struct sockaddr)); + sizeof(struct sockaddr_storage)); memcpy(&genl_rqstp.rq_saddr, svc_addr(rqstp), - sizeof(struct sockaddr)); + sizeof(struct sockaddr_storage)); #ifdef CONFIG_NFSD_V4 if (rqstp->rq_vers == NFS4_VERSION && From 357ed2011422263995b1f3313b1c18623000ba81 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Tue, 2 Jun 2026 12:23:18 -0400 Subject: [PATCH 449/562] nfsd: fix refcount leak in nfsd_file_lru_add on insertion failure nfsd_file_lru_add() unconditionally increments nf_ref before attempting to insert the nfsd_file into the LRU via list_lru_add_obj(). If the insertion fails (the item is already linked), the incremented reference is never released, permanently inflating the refcount. The LRU shrinker callback (nfsd_file_lru_cb) uses refcount_dec_if_one() to reclaim entries, which requires nf_ref == 1. An inflated refcount therefore blocks eviction of the affected file cache entry for the lifetime of the nfsd instance. While this failure path is currently unreachable -- the sole caller in nfsd_file_do_acquire() operates on freshly-allocated objects that cannot already be on the LRU -- it represents a latent bug that would become exploitable if a future change adds another call site or alters the PENDING protocol. Fix this by: - Adding a compensating refcount_dec() on the failure path. Bare refcount_dec (rather than nfsd_file_put) is correct here because the caller in nfsd_file_do_acquire still holds its own construction reference, so the count goes from 2 back to 1 without risk of reaching zero. - Changing WARN_ON(1) to WARN_ON_ONCE(1) to prevent log flooding if this path is ever hit repeatedly. - Returning early on failure to skip the unnecessary call to nfsd_file_schedule_laundrette(), since no entry was added to the LRU. Fixes: 56221b42d717 ("nfsd: filecache: don't repeatedly add/remove files on the lru list") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260602-nfsd-testing-v2-6-e4ea62e3cd5c@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/filecache.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/fs/nfsd/filecache.c b/fs/nfsd/filecache.c index 7f100c41022a..19beb4d482be 100644 --- a/fs/nfsd/filecache.c +++ b/fs/nfsd/filecache.c @@ -327,8 +327,11 @@ static void nfsd_file_lru_add(struct nfsd_file *nf) refcount_inc(&nf->nf_ref); if (list_lru_add_obj(&nfsd_file_lru, &nf->nf_lru)) trace_nfsd_file_lru_add(nf); - else - WARN_ON(1); + else { + refcount_dec(&nf->nf_ref); + WARN_ON_ONCE(1); + return; + } nfsd_file_schedule_laundrette(); } From 74543ceafef4f620e5016a2d2ece2387dfa13f2d Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Tue, 2 Jun 2026 12:23:19 -0400 Subject: [PATCH 450/562] nfsd: fix fcache_disposal UAF by inlining dispose state into nfsd_net nfsd_file_dispose_list_delayed() defers fput() to nfsd service threads via a per-net freeme queue, preventing the shrinker and GC worker from bearing the cost of closing files (see ffb402596147). However, the queue lives in a separately-allocated struct nfsd_fcache_disposal that is freed by nfsd_free_fcache_disposal_net() during per-net teardown. The global shrinker, laundrette, and fsnotify callbacks can still be inside nfsd_file_dispose_list_delayed() dereferencing that pointer, causing a use-after-free. Inline the spinlock and freeme list directly into struct nfsd_net (as fcache_dispose_lock and fcache_dispose_list), eliminating the separately allocated struct nfsd_fcache_disposal entirely. These fields now have the same lifetime as the net namespace itself, so there is no dangling pointer to chase. nfsd_file_cache_start_net() now just initializes the inline fields and cannot fail due to allocation. nfsd_file_cache_shutdown_net() drains the inline list directly instead of freeing a separate struct. The alloc/free helpers are removed. Fixes: 1463b38e7cf3 ("NFSD: simplify per-net file cache management") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260602-nfsd-testing-v2-7-e4ea62e3cd5c@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/filecache.c | 77 +++++++++++++-------------------------------- fs/nfsd/netns.h | 3 +- 2 files changed, 24 insertions(+), 56 deletions(-) diff --git a/fs/nfsd/filecache.c b/fs/nfsd/filecache.c index 19beb4d482be..da5a44c31acb 100644 --- a/fs/nfsd/filecache.c +++ b/fs/nfsd/filecache.c @@ -62,11 +62,6 @@ static DEFINE_PER_CPU(unsigned long, nfsd_file_releases); static DEFINE_PER_CPU(unsigned long, nfsd_file_total_age); static DEFINE_PER_CPU(unsigned long, nfsd_file_evictions); -struct nfsd_fcache_disposal { - spinlock_t lock; - struct list_head freeme; -}; - static struct kmem_cache *nfsd_file_slab; static struct kmem_cache *nfsd_file_mark_slab; static struct list_lru nfsd_file_lru; @@ -422,25 +417,25 @@ nfsd_file_dispose_list(struct list_head *dispose) } /** - * nfsd_file_dispose_list_delayed - move list of dead files to net's freeme list + * nfsd_file_dispose_list_delayed - queue dead files for nfsd thread disposal * @dispose: list of nfsd_files to be disposed * - * Transfers each file to the "freeme" list for its nfsd_net, to eventually - * be disposed of by the per-net garbage collector. + * Transfers each file to the dispose list in its nfsd_net and wakes an nfsd + * thread to do the actual close. This keeps the cost of fput() in the nfsd + * threads rather than in the shrinker or GC worker. */ static void nfsd_file_dispose_list_delayed(struct list_head *dispose) { - while(!list_empty(dispose)) { + while (!list_empty(dispose)) { struct nfsd_file *nf = list_first_entry(dispose, struct nfsd_file, nf_gc); struct nfsd_net *nn = net_generic(nf->nf_net, nfsd_net_id); - struct nfsd_fcache_disposal *l = nn->fcache_disposal; struct svc_serv *serv; - spin_lock(&l->lock); - list_move_tail(&nf->nf_gc, &l->freeme); - spin_unlock(&l->lock); + spin_lock(&nn->fcache_dispose_lock); + list_move_tail(&nf->nf_gc, &nn->fcache_dispose_list); + spin_unlock(&nn->fcache_dispose_lock); /* * The filecache laundrette is shut down after the @@ -464,17 +459,15 @@ nfsd_file_dispose_list_delayed(struct list_head *dispose) */ void nfsd_file_net_dispose(struct nfsd_net *nn) { - struct nfsd_fcache_disposal *l = nn->fcache_disposal; - - if (!list_empty(&l->freeme)) { + if (!list_empty(&nn->fcache_dispose_list)) { LIST_HEAD(dispose); int i; - spin_lock(&l->lock); - for (i = 0; i < 8 && !list_empty(&l->freeme); i++) - list_move(l->freeme.next, &dispose); - spin_unlock(&l->lock); - if (!list_empty(&l->freeme)) { + spin_lock(&nn->fcache_dispose_lock); + for (i = 0; i < 8 && !list_empty(&nn->fcache_dispose_list); i++) + list_move(nn->fcache_dispose_list.next, &dispose); + spin_unlock(&nn->fcache_dispose_lock); + if (!list_empty(&nn->fcache_dispose_list)) { /* * Wake up another thread to share the work * *before* doing any actual disposing. @@ -698,11 +691,11 @@ nfsd_file_queue_for_close(struct inode *inode, struct list_head *dispose) } /** - * nfsd_file_close_inode - attempt a delayed close of a nfsd_file + * nfsd_file_close_inode - attempt a deferred close of a nfsd_file * @inode: inode of the file to attempt to remove * * Close out any open nfsd_files that can be reaped for @inode. The - * actual freeing is deferred to the dispose_list_delayed infrastructure. + * actual freeing is deferred to the nfsd service threads. * * This is used by the fsnotify callbacks and setlease notifier. */ @@ -952,42 +945,14 @@ __nfsd_file_cache_purge(struct net *net) nfsd_file_dispose_list(&dispose); } -static struct nfsd_fcache_disposal * -nfsd_alloc_fcache_disposal(void) -{ - struct nfsd_fcache_disposal *l; - - l = kmalloc_obj(*l); - if (!l) - return NULL; - spin_lock_init(&l->lock); - INIT_LIST_HEAD(&l->freeme); - return l; -} - -static void -nfsd_free_fcache_disposal(struct nfsd_fcache_disposal *l) -{ - nfsd_file_dispose_list(&l->freeme); - kfree(l); -} - -static void -nfsd_free_fcache_disposal_net(struct net *net) -{ - struct nfsd_net *nn = net_generic(net, nfsd_net_id); - struct nfsd_fcache_disposal *l = nn->fcache_disposal; - - nfsd_free_fcache_disposal(l); -} - int nfsd_file_cache_start_net(struct net *net) { struct nfsd_net *nn = net_generic(net, nfsd_net_id); - nn->fcache_disposal = nfsd_alloc_fcache_disposal(); - return nn->fcache_disposal ? 0 : -ENOMEM; + spin_lock_init(&nn->fcache_dispose_lock); + INIT_LIST_HEAD(&nn->fcache_dispose_list); + return 0; } /** @@ -1006,8 +971,10 @@ nfsd_file_cache_purge(struct net *net) void nfsd_file_cache_shutdown_net(struct net *net) { + struct nfsd_net *nn = net_generic(net, nfsd_net_id); + nfsd_file_cache_purge(net); - nfsd_free_fcache_disposal_net(net); + nfsd_file_dispose_list(&nn->fcache_dispose_list); } void diff --git a/fs/nfsd/netns.h b/fs/nfsd/netns.h index f6b8b340bf8e..5c33c96da28e 100644 --- a/fs/nfsd/netns.h +++ b/fs/nfsd/netns.h @@ -216,7 +216,8 @@ struct nfsd_net { /* utsname taken from the process that starts the server */ char nfsd_name[UNX_MAXNODENAME+1]; - struct nfsd_fcache_disposal *fcache_disposal; + spinlock_t fcache_dispose_lock; + struct list_head fcache_dispose_list; siphash_key_t siphash_key; From 3c9fd0b3b2964d2e60e76ba62b4603eeff999e76 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Tue, 2 Jun 2026 12:23:21 -0400 Subject: [PATCH 451/562] nfsd: unify cleanups in nfsd_cross_mnt() exits Instead of having a separate path_put() on each failure exit, as well as on the normal path, let's move all of those past the point where these codepaths join. We want to keep the ordering between path_put() and exp_put(), so move that one as well. Signed-off-by: Al Viro Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260602-nfsd-testing-v2-9-e4ea62e3cd5c@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/vfs.c | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c index f73012bc742f..c81aea23363a 100644 --- a/fs/nfsd/vfs.c +++ b/fs/nfsd/vfs.c @@ -137,20 +137,19 @@ nfsd_cross_mnt(struct svc_rqst *rqstp, struct dentry **dpp, follow_flags = LOOKUP_AUTOMOUNT; err = follow_down(&path, follow_flags); - if (err < 0) { - path_put(&path); + if (err < 0) goto out; - } + if (path.mnt == exp->ex_path.mnt && path.dentry == dentry && nfsd_mountpoint(dentry, exp) == 2) { /* This is only a mountpoint in some other namespace */ - path_put(&path); goto out; } exp2 = rqst_exp_get_by_name(rqstp, &path); if (IS_ERR(exp2)) { err = PTR_ERR(exp2); + exp2 = NULL; /* * We normally allow NFS clients to continue * "underneath" a mountpoint that is not exported. @@ -160,10 +159,7 @@ nfsd_cross_mnt(struct svc_rqst *rqstp, struct dentry **dpp, */ if (err == -ENOENT && !(exp->ex_flags & NFSEXP_V4ROOT)) err = 0; - path_put(&path); - goto out; - } - if (nfsd_v4client(rqstp) || + } else if (nfsd_v4client(rqstp) || (exp->ex_flags & NFSEXP_CROSSMOUNT) || EX_NOHIDE(exp2)) { /* successfully crossed mount point */ /* @@ -177,9 +173,10 @@ nfsd_cross_mnt(struct svc_rqst *rqstp, struct dentry **dpp, *expp = exp2; exp2 = exp; } - path_put(&path); - exp_put(exp2); out: + path_put(&path); + if (exp2) + exp_put(exp2); return err; } From 77f499f88196e53911d3d20b473ad68f2f7043dd Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 4 Jun 2026 10:31:09 -0400 Subject: [PATCH 452/562] nfsd: close shrinker/GC/fsnotify vs per-net shutdown race in filecache The shrinker, GC worker, and fsnotify/lease callbacks can unhash an nfsd_file from the rhashtable and then call nfsd_file_dispose_list_delayed() to move it to the per-net dispose list. If nfsd_file_cache_shutdown_net() runs concurrently, its rhashtable walk misses the already-unhashed file, and its drain of the per-net dispose list can run before the file has been queued. The file then sits on the per-net list with no thread to drain it, leaking both the file and its associated state. The GC worker and shrinker already hold nfsd_gc_lock while walking the LRU, but in the original code they release it before calling nfsd_file_dispose_list_delayed(). The fsnotify/lease path (nfsd_file_close_inode) has no synchronization at all. Fix this by: 1. Widening nfsd_gc_lock in both nfsd_file_gc() and nfsd_file_lru_scan() to cover the nfsd_file_dispose_list_delayed() call. 2. Wrapping nfsd_file_close_inode() in nfsd_gc_lock so that all three callers of nfsd_file_dispose_list_delayed() hold the lock. 3. Adding a spin_lock/unlock(nfsd_gc_lock) barrier in nfsd_file_cache_shutdown_net() after the purge, so that any in-progress disposal has fully completed before the per-net list is drained. All operations inside the lock are non-sleeping (rhashtable lookups, atomic bit/refcount ops, list moves, svc_wake_up), so the spinlock is appropriate. Fixes: ffb402596147 ("nfsd: Don't leave work of closing files to a work queue") Cc: stable@vger.kernel.org # v6.15+ Signed-off-by: Jeff Layton Assisted-by: Claude:claude-opus-4-8 Link: https://patch.msgid.link/20260604-nfsd-testing-v4-1-3aeb1479c5bb@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/filecache.c | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/fs/nfsd/filecache.c b/fs/nfsd/filecache.c index da5a44c31acb..1ea2bfd51825 100644 --- a/fs/nfsd/filecache.c +++ b/fs/nfsd/filecache.c @@ -55,6 +55,17 @@ /* We only care about NFSD_MAY_READ/WRITE for this cache */ #define NFSD_FILE_MAY_MASK (NFSD_MAY_READ|NFSD_MAY_WRITE|NFSD_MAY_LOCALIO) +/* If the shrinker runs between calls to list_lru_walk_node() in + * nfsd_file_gc(), the "remaining" count will be wrong. This could + * result in premature freeing of some files. This may not matter much + * but is easy to fix with this spinlock which temporarily disables + * the shrinker. + * + * It also serializes callers of nfsd_file_dispose_list_delayed() + * against per-net shutdown. + */ +static DEFINE_SPINLOCK(nfsd_gc_lock); + static DEFINE_PER_CPU(unsigned long, nfsd_file_cache_hits); static DEFINE_PER_CPU(unsigned long, nfsd_file_acquisitions); static DEFINE_PER_CPU(unsigned long, nfsd_file_allocations); @@ -423,10 +434,16 @@ nfsd_file_dispose_list(struct list_head *dispose) * Transfers each file to the dispose list in its nfsd_net and wakes an nfsd * thread to do the actual close. This keeps the cost of fput() in the nfsd * threads rather than in the shrinker or GC worker. + * + * All callers must hold nfsd_gc_lock, so that nfsd_file_cache_shutdown_net() + * can synchronize against them before draining the per-net dispose list. + * This guarantees nf_net is still live when we call net_generic(). */ static void nfsd_file_dispose_list_delayed(struct list_head *dispose) { + lockdep_assert_held(&nfsd_gc_lock); + while (!list_empty(dispose)) { struct nfsd_file *nf = list_first_entry(dispose, struct nfsd_file, nf_gc); @@ -557,13 +574,6 @@ nfsd_file_gc_cb(struct list_head *item, struct list_lru_one *lru, return nfsd_file_lru_cb(item, lru, arg); } -/* If the shrinker runs between calls to list_lru_walk_node() in - * nfsd_file_gc(), the "remaining" count will be wrong. This could - * result in premature freeing of some files. This may not matter much - * but is easy to fix with this spinlock which temporarily disables - * the shrinker. - */ -static DEFINE_SPINLOCK(nfsd_gc_lock); static void nfsd_file_gc(void) { @@ -586,9 +596,9 @@ nfsd_file_gc(void) remaining = 0; } } + nfsd_file_dispose_list_delayed(&dispose); spin_unlock(&nfsd_gc_lock); trace_nfsd_file_gc_removed(ret, list_lru_count(&nfsd_file_lru)); - nfsd_file_dispose_list_delayed(&dispose); } static void @@ -616,9 +626,9 @@ nfsd_file_lru_scan(struct shrinker *s, struct shrink_control *sc) ret = list_lru_shrink_walk(&nfsd_file_lru, sc, nfsd_file_lru_cb, &dispose); + nfsd_file_dispose_list_delayed(&dispose); spin_unlock(&nfsd_gc_lock); trace_nfsd_file_shrinker_removed(ret, list_lru_count(&nfsd_file_lru)); - nfsd_file_dispose_list_delayed(&dispose); return ret; } @@ -704,8 +714,10 @@ nfsd_file_close_inode(struct inode *inode) { LIST_HEAD(dispose); + spin_lock(&nfsd_gc_lock); nfsd_file_queue_for_close(inode, &dispose); nfsd_file_dispose_list_delayed(&dispose); + spin_unlock(&nfsd_gc_lock); } /** @@ -974,6 +986,14 @@ nfsd_file_cache_shutdown_net(struct net *net) struct nfsd_net *nn = net_generic(net, nfsd_net_id); nfsd_file_cache_purge(net); + /* + * Ensure any in-progress shrinker, GC, or fsnotify/lease callback + * (all of which hold nfsd_gc_lock while calling + * nfsd_file_dispose_list_delayed()) has fully completed before + * draining the per-net dispose list. + */ + spin_lock(&nfsd_gc_lock); + spin_unlock(&nfsd_gc_lock); nfsd_file_dispose_list(&nn->fcache_dispose_list); } From dc91945d5064986b8b52f48709bdd4c61978254e Mon Sep 17 00:00:00 2001 From: David Laight Date: Mon, 8 Jun 2026 10:55:00 +0100 Subject: [PATCH 453/562] net/sunrpc/svcauth_unix: Use strscpy() to copy strings into arrays Replacing strcpy() with strscpy() ensures that overflow of the target buffer cannot happen. Signed-off-by: David Laight Link: https://patch.msgid.link/20260608095523.2606-16-david.laight.linux@gmail.com Signed-off-by: Chuck Lever --- net/sunrpc/svcauth_unix.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/sunrpc/svcauth_unix.c b/net/sunrpc/svcauth_unix.c index 64a2658faddb..aebd97e7f66c 100644 --- a/net/sunrpc/svcauth_unix.c +++ b/net/sunrpc/svcauth_unix.c @@ -133,7 +133,7 @@ static void ip_map_init(struct cache_head *cnew, struct cache_head *citem) struct ip_map *new = container_of(cnew, struct ip_map, h); struct ip_map *item = container_of(citem, struct ip_map, h); - strcpy(new->m_class, item->m_class); + strscpy(new->m_class, item->m_class); new->m_addr = item->m_addr; } static void update(struct cache_head *cnew, struct cache_head *citem) @@ -296,7 +296,7 @@ static struct ip_map *__ip_map_lookup(struct cache_detail *cd, char *class, struct ip_map ip; struct cache_head *ch; - strcpy(ip.m_class, class); + strscpy(ip.m_class, class); ip.m_addr = *addr; ch = sunrpc_cache_lookup_rcu(cd, &ip.h, hash_str(class, IP_HASHBITS) ^ From a0a69f19dc5a30f9e81d3cac039dcd0c16b4656e Mon Sep 17 00:00:00 2001 From: Scott Mayhew Date: Mon, 8 Jun 2026 09:14:02 -0400 Subject: [PATCH 454/562] NFSD: fix up error returned by write_threads() Previously, writing 0 to /proc/fs/nfsd/threads would return 0 if the NFS server wasn't running. After commit 14282cc3cfa2 ("NFSD: don't start nfsd if sv_permsocks is empty"), -EIO is returned. Existing scripts don't expect this behavior. Add a check to bypass the call to nfsd_svc() when newthreads is 0 and the NFS server is already stopped. Fixes: 14282cc3cfa2 ("NFSD: don't start nfsd if sv_permsocks is empty") Cc: stable@vger.kernel.org Signed-off-by: Scott Mayhew Reviewed-by: Jeff Layton Link: https://patch.msgid.link/20260608131402.95625-1-smayhew@redhat.com Signed-off-by: Chuck Lever --- fs/nfsd/nfsctl.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index bf360a5a3ee9..c06d25c06f06 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -420,6 +420,7 @@ static ssize_t write_threads(struct file *file, char *buf, size_t size) char *mesg = buf; int rv; struct net *net = netns(file); + struct nfsd_net *nn = net_generic(net, nfsd_net_id); if (size > 0) { int newthreads; @@ -430,7 +431,10 @@ static ssize_t write_threads(struct file *file, char *buf, size_t size) return -EINVAL; trace_nfsd_ctl_threads(net, newthreads); mutex_lock(&nfsd_mutex); - rv = nfsd_svc(1, &newthreads, net, file->f_cred, NULL); + if (newthreads > 0 || nn->nfsd_serv != NULL) + rv = nfsd_svc(1, &newthreads, net, file->f_cred, NULL); + else + rv = 0; mutex_unlock(&nfsd_mutex); if (rv < 0) return rv; From 470ea9c96f672767c3bb678392b2ff99cdef7ba4 Mon Sep 17 00:00:00 2001 From: David Laight Date: Mon, 8 Jun 2026 22:20:42 +0100 Subject: [PATCH 455/562] lockd: Use "%*phN" to dprintk() a cookie Simplifies the code and removes a 'not obviously bounded' strcpy(). Delete the local function nlmdbg_cookie2a() that did the equivalent. There is no need to worry about cookie->len being more than NLM_MAXCOOKIELEN (32), the buffer holding it is only that long. The existing length checks must pre-date this code being added in 2.4.26. Signed-off-by: David Laight Link: https://patch.msgid.link/20260608212042.25476-1-david.laight.linux@gmail.com Signed-off-by: Chuck Lever --- fs/lockd/svclock.c | 42 +++++------------------------------------- 1 file changed, 5 insertions(+), 37 deletions(-) diff --git a/fs/lockd/svclock.c b/fs/lockd/svclock.c index e48d31f14a65..e628b5d35507 100644 --- a/fs/lockd/svclock.c +++ b/fs/lockd/svclock.c @@ -47,40 +47,6 @@ static const struct rpc_call_ops nlmsvc_grant_ops; static LIST_HEAD(nlm_blocked); static DEFINE_SPINLOCK(nlm_blocked_lock); -#if IS_ENABLED(CONFIG_SUNRPC_DEBUG) -static const char *nlmdbg_cookie2a(const struct lockd_cookie *cookie) -{ - /* - * We can get away with a static buffer because this is only called - * from lockd, which is single-threaded. - */ - static char buf[2*NLM_MAXCOOKIELEN+1]; - unsigned int i, len = sizeof(buf); - char *p = buf; - - len--; /* allow for trailing \0 */ - if (len < 3) - return "???"; - for (i = 0 ; i < cookie->len ; i++) { - if (len < 2) { - strcpy(p-3, "..."); - break; - } - sprintf(p, "%02x", cookie->data[i]); - p += 2; - len -= 2; - } - *p = '\0'; - - return buf; -} -#else -static inline const char *nlmdbg_cookie2a(const struct lockd_cookie *cookie) -{ - return "???"; -} -#endif - /* * Insert a blocked lock into the global list */ @@ -155,11 +121,12 @@ nlmsvc_lookup_block(struct nlm_file *file, struct lockd_lock *lock) spin_lock(&nlm_blocked_lock); list_for_each_entry(block, &nlm_blocked, b_list) { fl = &block->b_call->a_args.lock.fl; - dprintk("lockd: check f=%p pd=%d %Ld-%Ld ty=%d cookie=%s\n", + dprintk("lockd: check f=%p pd=%d %Ld-%Ld ty=%d cookie=%*phN\n", block->b_file, fl->c.flc_pid, (long long)fl->fl_start, (long long)fl->fl_end, fl->c.flc_type, - nlmdbg_cookie2a(&block->b_call->a_args.cookie)); + block->b_call->a_args.cookie.len, + block->b_call->a_args.cookie.data); if (block->b_file == file && nlm_compare_locks(fl, &lock->fl)) { kref_get(&block->b_count); spin_unlock(&nlm_blocked_lock); @@ -198,7 +165,8 @@ nlmsvc_find_block(struct lockd_cookie *cookie) return NULL; found: - dprintk("nlmsvc_find_block(%s): block=%p\n", nlmdbg_cookie2a(cookie), block); + dprintk("nlmsvc_find_block(%*phN): block=%p\n", + cookie->len, cookie->data, block); kref_get(&block->b_count); spin_unlock(&nlm_blocked_lock); return block; From 91a6b5f0b47e880ef63318739bca99622778cf42 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Wed, 10 Jun 2026 21:58:53 -0400 Subject: [PATCH 456/562] SUNRPC: Add svc_serv_maxthreads() to report the thread ceiling A pooled RPC service sizes its threads dynamically, growing and shrinking each pool between its minimum and maximum bounds as load varies. The count of running threads therefore reflects recent demand, not the service's capacity. A consumer that sizes a data structure against the concurrency the service can sustain -- NFSD's NFSv4 session slot tables, for one -- needs that stable ceiling, and computing it means summing sp_nrthrmax across every pool. Add svc_serv_maxthreads() so the summation, and its dependence on the layout of struct svc_serv and struct svc_pool, stays within sunrpc. The read is lock-free: pool maxima change only when a service is reconfigured, a path callers already serialize against startup and shutdown, so a racing reader observes at worst a transient value. This is acceptable for the sizing heuristics that will consume it. nfsd_nrthreads() already sums sp_nrthrmax across pools by hand; convert it to svc_serv_maxthreads(), giving the new export an in-tree consumer and removing a copy of the dependence on svc_serv internals. Reviewed-by: NeilBrown Reviewed-by: Jeff Layton Reviewed-by: Benjamin Coddington Link: https://patch.msgid.link/20260610-nfsd-slot-growth-clamp-v1-1-7b966700df0b@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfssvc.c | 12 +++++++++--- include/linux/sunrpc/svc.h | 1 + net/sunrpc/svc.c | 23 +++++++++++++++++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/fs/nfsd/nfssvc.c b/fs/nfsd/nfssvc.c index e45d46089959..0d3838dd59c1 100644 --- a/fs/nfsd/nfssvc.c +++ b/fs/nfsd/nfssvc.c @@ -237,15 +237,21 @@ static void nfsd_net_free(struct percpu_ref *ref) */ #define NFSD_MAXSERVS 8192 +/** + * nfsd_nrthreads - report a namespace's configured nfsd thread count + * @net: network namespace to query + * + * Return: the configured thread ceiling, or 0 when no service runs. + */ int nfsd_nrthreads(struct net *net) { - int i, rv = 0; + int rv = 0; struct nfsd_net *nn = net_generic(net, nfsd_net_id); + /* nfsd_mutex keeps nn->nfsd_serv valid across the read. */ mutex_lock(&nfsd_mutex); if (nn->nfsd_serv) - for (i = 0; i < nn->nfsd_serv->sv_nrpools; ++i) - rv += nn->nfsd_serv->sv_pools[i].sp_nrthrmax; + rv = svc_serv_maxthreads(nn->nfsd_serv); mutex_unlock(&nfsd_mutex); return rv; } diff --git a/include/linux/sunrpc/svc.h b/include/linux/sunrpc/svc.h index 4be6204f6630..3a0152d926fb 100644 --- a/include/linux/sunrpc/svc.h +++ b/include/linux/sunrpc/svc.h @@ -469,6 +469,7 @@ int svc_set_pool_threads(struct svc_serv *serv, struct svc_pool *pool, unsigned int min_threads, unsigned int max_threads); int svc_set_num_threads(struct svc_serv *serv, unsigned int min_threads, unsigned int nrservs); +unsigned int svc_serv_maxthreads(const struct svc_serv *serv); int svc_pool_stats_open(struct svc_info *si, struct file *file); void svc_process(struct svc_rqst *rqstp); void svc_process_bc(struct rpc_rqst *req, struct svc_rqst *rqstp); diff --git a/net/sunrpc/svc.c b/net/sunrpc/svc.c index 009373737ea9..86d39610cf0a 100644 --- a/net/sunrpc/svc.c +++ b/net/sunrpc/svc.c @@ -987,6 +987,29 @@ svc_set_num_threads(struct svc_serv *serv, unsigned int min_threads, } EXPORT_SYMBOL_GPL(svc_set_num_threads); +/** + * svc_serv_maxthreads - report a service's configured thread ceiling + * @serv: RPC service to query + * + * A pooled service sizes its threads dynamically, so the number of + * threads running at any moment tracks recent load rather than the + * service's capacity. The per-pool maximum is the stable figure a + * consumer should size against. + * + * The caller must keep @serv valid for the duration of the call. + * + * Return: the sum of every pool's maximum thread count. + */ +unsigned int svc_serv_maxthreads(const struct svc_serv *serv) +{ + unsigned int i, max = 0; + + for (i = 0; i < serv->sv_nrpools; i++) + max += data_race(serv->sv_pools[i].sp_nrthrmax); + return max; +} +EXPORT_SYMBOL_GPL(svc_serv_maxthreads); + /** * svc_rqst_replace_page - Replace one page in rq_respages[] * @rqstp: svc_rqst with pages to replace From 5b83ddee37ac249d92eceed7661338e0652b1068 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Wed, 10 Jun 2026 21:58:54 -0400 Subject: [PATCH 457/562] NFSD: Count slot 0 in nfsd_total_target_slots nfsd_total_target_slots sums "target_slots - 1" across sessions rather than the full target. Its sole consumer, the NFSv4.1 session slot shrinker's count callback, must report only reclaimable slots, and slot 0 is never reclaimable while a session is active. That correction is open-coded where a session's full target enters and leaves the counter, as "i - 1" on alloc and "from ?: 1" on free, and reads as an unexplained fudge. Give nfsd_total_target_slots the full-target meaning its name implies, and move the reclaimability correction to the single place that consumes it: nfsd_slot_count() subtracts nfsd_total_sessions, a new tally of the sessions on nfsd_session_list. One correction at the consumer is clearer than repeating it wherever a session's target enters or leaves the counter. The reclaimable figure the shrinker sees is unchanged: slot 0 was never reclaimable and still is not. The change only relocates the minus-slot-0 correction. Reviewed-by: NeilBrown Reviewed-by: Jeff Layton Reviewed-by: Benjamin Coddington Link: https://patch.msgid.link/20260610-nfsd-slot-growth-clamp-v1-2-7b966700df0b@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfs4state.c | 44 ++++++++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index c88637406773..8b611c7b3d4d 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -2052,12 +2052,10 @@ gen_sessionid(struct nfsd4_session *ses) static struct shrinker *nfsd_slot_shrinker; static DEFINE_SPINLOCK(nfsd_session_list_lock); static LIST_HEAD(nfsd_session_list); -/* The sum of "target_slots-1" on every session. The shrinker can push this - * down, though it can take a little while for the memory to actually - * be freed. The "-1" is because we can never free slot 0 while the - * session is active. - */ +/* The sum of "target_slots" on every session, slot 0 included. */ static atomic_t nfsd_total_target_slots = ATOMIC_INIT(0); +/* Session count, subtracted from the sum to exclude slot 0. */ +static atomic_t nfsd_total_sessions = ATOMIC_INIT(0); static void free_session_slots(struct nfsd4_session *ses, int from) @@ -2081,9 +2079,11 @@ free_session_slots(struct nfsd4_session *ses, int from) } ses->se_fchannel.maxreqs = from; if (ses->se_target_maxslots > from) { - int new_target = from ?: 1; - atomic_sub(ses->se_target_maxslots - new_target, &nfsd_total_target_slots); - ses->se_target_maxslots = new_target; + int delta = ses->se_target_maxslots - from; + + atomic_sub(delta, &nfsd_total_target_slots); + /* Retain one slot so the session can make forward progress. */ + ses->se_target_maxslots = from ?: 1; } } @@ -2179,7 +2179,7 @@ static struct nfsd4_session *alloc_session(struct nfsd4_channel_attrs *fattrs, fattrs->maxreqs = i; memcpy(&new->se_fchannel, fattrs, sizeof(struct nfsd4_channel_attrs)); new->se_target_maxslots = i; - atomic_add(i - 1, &nfsd_total_target_slots); + atomic_add(i, &nfsd_total_target_slots); new->se_cb_slot_avail = ~0U; new->se_cb_highest_slot = min(battrs->maxreqs - 1, NFSD_BC_SLOT_TABLE_SIZE - 1); @@ -2307,9 +2307,17 @@ static void free_session(struct nfsd4_session *ses) static unsigned long nfsd_slot_count(struct shrinker *s, struct shrink_control *sc) { - unsigned long cnt = atomic_read(&nfsd_total_target_slots); + int count; + + /* + * To prevent session deadlock, one slot of each session (slot 0) + * is not reclaimable while the session is active. Thus the number + * of sessions is subtracted from the total number of target slots. + */ + count = atomic_read(&nfsd_total_target_slots) - + atomic_read(&nfsd_total_sessions); - return cnt ? cnt : SHRINK_EMPTY; + return count > 0 ? count : SHRINK_EMPTY; } static unsigned long @@ -2360,6 +2368,7 @@ static void init_session(struct svc_rqst *rqstp, struct nfsd4_session *new, stru spin_lock(&nfsd_session_list_lock); list_add_tail(&new->se_all_sessions, &nfsd_session_list); + atomic_inc(&nfsd_total_sessions); spin_unlock(&nfsd_session_list_lock); { @@ -2433,6 +2442,7 @@ unhash_session(struct nfsd4_session *ses) spin_unlock(&ses->se_client->cl_lock); spin_lock(&nfsd_session_list_lock); list_del(&ses->se_all_sessions); + atomic_dec(&nfsd_total_sessions); spin_unlock(&nfsd_session_list_lock); } @@ -2581,7 +2591,17 @@ unhash_client_locked(struct nfs4_client *clp) spin_lock(&nfsd_session_list_lock); list_for_each_entry(ses, &clp->cl_sessions, se_perclnt) { list_del_init(&ses->se_hash); - list_del_init(&ses->se_all_sessions); + /* + * unhash_client_locked() can run more than once for a + * client; the session stays on cl_sessions across calls. + * The first pass empties se_all_sessions via + * list_del_init(), so skip the decrement on later passes + * to keep nfsd_total_sessions from being double-counted. + */ + if (!list_empty(&ses->se_all_sessions)) { + list_del_init(&ses->se_all_sessions); + atomic_dec(&nfsd_total_sessions); + } } spin_unlock(&nfsd_session_list_lock); spin_unlock(&clp->cl_lock); From 6e25e9a9b592c135701bab37612c0bfa10f88815 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Wed, 10 Jun 2026 21:58:55 -0400 Subject: [PATCH 458/562] NFSD: Clean up documenting comment for reduce_session_slots() Fix typos. The usual convention is to not use kdoc-style for internal (static) functions. Reviewed-by: NeilBrown Reviewed-by: Jeff Layton Reviewed-by: Benjamin Coddington Link: https://patch.msgid.link/20260610-nfsd-slot-growth-clamp-v1-3-7b966700df0b@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfs4state.c | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index 8b611c7b3d4d..5673fd0da589 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -2087,20 +2087,13 @@ free_session_slots(struct nfsd4_session *ses, int from) } } -/** - * reduce_session_slots - reduce the target max-slots of a session if possible - * @ses: The session to affect - * @dec: how much to decrease the target by - * +/* * This interface can be used by a shrinker to reduce the target max-slots * for a session so that some slots can eventually be freed. * It uses spin_trylock() as it may be called in a context where another * spinlock is held that has a dependency on client_lock. As shrinkers are - * best-effort, skiping a session is client_lock is already held has no - * great coast - * - * Return value: - * The number of slots that the target was reduced by. + * best-effort, skipping a session with the client_lock already held has no + * great cost. */ static int reduce_session_slots(struct nfsd4_session *ses, int dec) From e20e5d9d9f353118e8f024d94b24b227f7e8871b Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Wed, 10 Jun 2026 21:58:56 -0400 Subject: [PATCH 459/562] NFSD: Document and rename the NFSv4.1 session slot shrinker callbacks Clean up: To prevent their reuse by generic code, rename the NFSv4.1 session slot shrinker's callback functions to make it clear they are for use only by the shrinker. Though they are static, callbacks are invoked from outside nfsd.ko, so they need appropriate kdoc comments that document their API contracts. Reviewed-by: NeilBrown Reviewed-by: Jeff Layton Reviewed-by: Benjamin Coddington Link: https://patch.msgid.link/20260610-nfsd-slot-growth-clamp-v1-4-7b966700df0b@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfs4state.c | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index 5673fd0da589..b4c9ebec6c96 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -2297,8 +2297,16 @@ static void free_session(struct nfsd4_session *ses) __free_session(ses); } +/** + * nfsd_slot_shrinker_count - report reclaimable DRC slots + * @s: shrinker descriptor (unused) + * @sc: shrink control (unused) + * + * Return: a positive count of reclaimable slots, or SHRINK_EMPTY when + * there is nothing to reclaim. + */ static unsigned long -nfsd_slot_count(struct shrinker *s, struct shrink_control *sc) +nfsd_slot_shrinker_count(struct shrinker *s, struct shrink_control *sc) { int count; @@ -2313,13 +2321,27 @@ nfsd_slot_count(struct shrinker *s, struct shrink_control *sc) return count > 0 ? count : SHRINK_EMPTY; } +/** + * nfsd_slot_shrinker_scan - reclaim DRC slots under memory pressure + * @s: shrinker descriptor (unused) + * @sc: shrink control; @sc->nr_to_scan bounds the sessions visited, + * @sc->nr_scanned reports how many were visited + * + * Return: the number of session slots NFSD will release. + */ static unsigned long -nfsd_slot_scan(struct shrinker *s, struct shrink_control *sc) +nfsd_slot_shrinker_scan(struct shrinker *s, struct shrink_control *sc) { struct nfsd4_session *ses; unsigned long scanned = 0; unsigned long freed = 0; + /* + * Each visited session releases at most one slot. After + * nr_to_scan sessions have been visited, the list head is + * rotated past the last visited session so the next scan + * resumes from there. + */ spin_lock(&nfsd_session_list_lock); list_for_each_entry(ses, &nfsd_session_list, se_all_sessions) { freed += reduce_session_slots(ses, 1); @@ -9325,8 +9347,8 @@ nfs4_state_start(void) rhltable_destroy(&nfs4_file_rhltable); return -ENOMEM; } - nfsd_slot_shrinker->count_objects = nfsd_slot_count; - nfsd_slot_shrinker->scan_objects = nfsd_slot_scan; + nfsd_slot_shrinker->count_objects = nfsd_slot_shrinker_count; + nfsd_slot_shrinker->scan_objects = nfsd_slot_shrinker_scan; shrinker_register(nfsd_slot_shrinker); set_max_delegations(); From 1a162ae5ab5fb1d69a4773c9288741ea065a2985 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Wed, 10 Jun 2026 21:58:57 -0400 Subject: [PATCH 460/562] NFSD: Bound on-demand DRC slot growth by the thread ceiling When a client uses its highest session slot, nfsd4_sequence() grows the session's slot table by 20%, up to NFSD_MAX_SLOTS_PER_SESSION, on the theory that a client at its ceiling can put more requests in flight. The heuristic keys only on the client's appetite, so its incentive runs backwards: the client that keeps every slot busy -- already the largest consumer of the thread pool -- is the one the server rewards with still more slots. A single session's table can climb toward 2048 slots even on a server with far fewer threads to run them. A slot stays occupied for a full round trip -- request out, server processing, reply back -- but ties up an nfsd thread only during the processing. When the round trip is short, one slot per thread keeps the pool busy and further slots add only backlog. Across a high-RTT link more slots are in flight than the pool serves at any instant, so there extra slots do raise throughput by masking link latency -- but that is the client's call to make by sizing its session at CREATE_SESSION, not a reason for the server to grow every busy session toward 2048. Cap on-demand growth at the thread ceiling, the point past which added slots stop buying concurrency on a short round trip, so a table stops climbing once it can keep every thread busy. Apply the cap per session rather than across the namespace. A session cannot use another session's slots, so one client's table size has no bearing on what a second client may grow to. A shared per-namespace budget would also misbehave at the floor: every active session holds one slot that cannot be reclaimed, so once the session count reaches the thread ceiling those floors alone exhaust the budget, pinning the one busy client small while most of the pool sits idle. NFSD sizes its pool dynamically, so compare against svc_serv_maxthreads(), the configured maximum, rather than the running thread count, which tracks recent load and would deny a client resuming from idle the slots it needs to ramp up. This removes a perverse incentive without becoming slot admission control. A client still sizes its sessions directly at CREATE_SESSION, bounded by NFSD_MAX_SLOTS_PER_SESSION, and a client determined to monopolize threads can do so through that path regardless of this change. Enforcing per-client fairness against thread starvation belongs in the dispatch layer, not in slot accounting. Reviewed-by: NeilBrown Reviewed-by: Jeff Layton Reviewed-by: Benjamin Coddington Link: https://patch.msgid.link/20260610-nfsd-slot-growth-clamp-v1-5-7b966700df0b@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfs4state.c | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index b4c9ebec6c96..e59aec57e9e8 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -4665,15 +4665,26 @@ nfsd4_sequence(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, * gently try to allocate another 20%. This allows * fairly quick growth without grossly over-shooting what * the client might use. + * + * Bound that growth by the service's thread ceiling: + * slots beyond the nfsd thread count cannot raise this + * client's throughput, only deepen its backlog. Cap each + * session independently, since a session cannot use + * another's slots; a shared budget would let idle sessions + * pin an active client small. Compare against the + * configured maximum, not the running thread count, so a + * client resuming from idle can grow back before the pool + * scales up. */ if (seq->slotid == session->se_fchannel.maxreqs - 1 && - session->se_target_maxslots >= session->se_fchannel.maxreqs && - session->se_fchannel.maxreqs < NFSD_MAX_SLOTS_PER_SESSION) { + session->se_target_maxslots >= session->se_fchannel.maxreqs) { int s = session->se_fchannel.maxreqs; - int cnt = DIV_ROUND_UP(s, 5); + int ceiling = min_t(int, NFSD_MAX_SLOTS_PER_SESSION, + svc_serv_maxthreads(rqstp->rq_server)); + int cnt = min(DIV_ROUND_UP(s, 5), ceiling - s); void *prev_slot; - do { + while (cnt-- > 0) { /* * GFP_NOWAIT both allows allocation under a * spinlock, and only succeeds if there is @@ -4681,13 +4692,14 @@ nfsd4_sequence(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, */ slot = nfsd4_alloc_slot(&session->se_fchannel, s, GFP_NOWAIT); + if (!slot) + break; prev_slot = xa_load(&session->se_slots, s); - if (xa_is_value(prev_slot) && slot) { + if (xa_is_value(prev_slot)) { slot->sl_seqid = xa_to_value(prev_slot); slot->sl_flags |= NFSD4_SLOT_REUSED; } - if (slot && - !xa_is_err(xa_store(&session->se_slots, s, slot, + if (!xa_is_err(xa_store(&session->se_slots, s, slot, GFP_NOWAIT))) { s += 1; session->se_fchannel.maxreqs = s; @@ -4696,9 +4708,9 @@ nfsd4_sequence(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, session->se_target_maxslots = s; } else { kfree(slot); - slot = NULL; + break; } - } while (slot && --cnt > 0); + } } out: From bd07c932ed32d8f1280fa6c05da9c2c9f2d5d9b2 Mon Sep 17 00:00:00 2001 From: Mike Snitzer Date: Fri, 12 Jun 2026 15:14:10 -0400 Subject: [PATCH 461/562] NFSD: remove flawed WARN_ON_ONCE from nfsd_mode_check The header for commit e75b23f9e323 ("nfsd: check d_can_lookup in fh_verify of directories") details the assumption that justified adding the WARN_ON_ONCE to nfsd_mode_check(), that assumption is invalid (in the case of NFS reexport). When NFSD exports an NFS filesystem it is very possible for nfsd_mode_check() to encounter a @dentry that doesn't have i_op->lookup (see nfs_fhget()'s NFS_ATTR_FATTR_MOUNTPOINT and NFS_ATTR_FATTR_V4_REFERRAL handling, and d_flags_for_inode()). So remove nfsd_mode_check()'s WARN_ON_ONCE(). The nfserr_notdir return on that branch must stay. It guards the subsequent lookup_one_unlocked() -> __lookup_slow() path, which calls inode->i_op->lookup() with no NULL check, so returning nfserr_notdir is what keeps a client LOOKUP into such a @dentry from dereferencing a NULL method pointer. Fixes: e75b23f9e323 ("nfsd: check d_can_lookup in fh_verify of directories") Cc: stable@vger.kernel.org Signed-off-by: Mike Snitzer Link: https://patch.msgid.link/20260612191410.50177-1-snitzer@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfsfh.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/fs/nfsd/nfsfh.c b/fs/nfsd/nfsfh.c index b36915401758..ab53de1c280d 100644 --- a/fs/nfsd/nfsfh.c +++ b/fs/nfsd/nfsfh.c @@ -70,10 +70,8 @@ nfsd_mode_check(struct dentry *dentry, umode_t requested) if (requested == 0) /* the caller doesn't care */ return nfs_ok; if (mode == requested) { - if (mode == S_IFDIR && !d_can_lookup(dentry)) { - WARN_ON_ONCE(1); + if (mode == S_IFDIR && !d_can_lookup(dentry)) return nfserr_notdir; - } return nfs_ok; } if (mode == S_IFLNK) { From 012936d0808ed99d31ecf023561de7f684decbf7 Mon Sep 17 00:00:00 2001 From: Nikol Kuklev Date: Sat, 13 Jun 2026 11:24:20 +0300 Subject: [PATCH 462/562] nfsd: fix null dereference in nfsd4_setattr for deleg timestamp attrs When a SETATTR request includes FATTR4_WORD2_TIME_DELEG_ACCESS or FATTR4_WORD2_TIME_DELEG_MODIFY in the attribute bitmap, nfsd4_setattr() sets deleg_attrs=true and calls nfs4_preprocess_stateid_op() to validate the stateid. If the client supplies the NFSv4 "one stateid" (all-0xFF bytes), check_special_stateids() returns nfs_ok without populating the output nfs4_stid pointer, because the special-stateid path in nfs4_preprocess_stateid_op() jumps to done: with s==NULL, and the "if (s)" block that would set *cstid is skipped. The local variable `st` remains NULL. Back in nfsd4_setattr(), the if (deleg_attrs) block then unconditionally dereferences st->sc_type (at offset 4 from NULL), causing a kernel oops. This is remotely triggerable by any NFSv4 client: send COMPOUND [PUTROOTFH, SETATTR(ONE_STATEID, {bmval2=FATTR4_WORD2_TIME_DELEG_ACCESS, ...})]. No authentication, delegation, or prior state is required. Fix by adding a NULL check before the dereference. A special stateid is not a delegation stateid, so the existing nfserr_bad_stateid return value is already correct; we only need to guard the pointer dereference itself. Fixes: 7e13f4f8d27d ("nfsd: handle delegated timestamps in SETATTR") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-sonnet-4-6 Signed-off-by: Nikol Kuklev Signed-off-by: Chuck Lever --- fs/nfsd/nfs4proc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c index 0c37d7c6d28c..623a89a1f34e 100644 --- a/fs/nfsd/nfs4proc.c +++ b/fs/nfsd/nfs4proc.c @@ -1262,7 +1262,7 @@ nfsd4_setattr(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, if (deleg_attrs) { status = nfserr_bad_stateid; - if (st->sc_type & SC_TYPE_DELEG) { + if (st && (st->sc_type & SC_TYPE_DELEG)) { struct nfs4_delegation *dp = delegstateid(st); /* Only for *_ATTRS_DELEG flavors */ From 323dfadf82f40e478f0355b2c29e8e130b86ad9e Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 11 Jun 2026 16:00:44 -0400 Subject: [PATCH 463/562] nfsd: clear opcnt on compound arg release to prevent OOB read nfsd4_release_compoundargs() resets args->ops to the inline iops[8] array when the dynamically-allocated ops buffer is freed, but leaves args->opcnt at its original value (which can be up to 200 for NFSv4.1+ compounds). If rq_status_counter is stuck at an odd value (which can happen when nfsd_dispatch() hits an error path after setting it odd), the RPC status dumpit handler reads min(opcnt, 16) entries from args->ops[]. Since iops only has 8 elements and is the last field in struct nfsd4_compoundargs, reading indices 8-15 accesses adjacent slab memory and leaks it to userspace via netlink. Zero opcnt unconditionally in nfsd4_release_compoundargs() so stale compound metadata is never exposed through the status interface. Fixes: bd9d6a3efa97 ("NFSD: add rpc_status netlink support") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Jeff Layton [ cel: Remove the kvfree_rcu_mightsleep() sleep from the exposure window ] Link: https://patch.msgid.link/20260611-nfsd-testing-v2-1-5b90e276f2d9@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfs4xdr.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c index b9037d99b564..6f356c71e234 100644 --- a/fs/nfsd/nfs4xdr.c +++ b/fs/nfsd/nfs4xdr.c @@ -6434,6 +6434,7 @@ void nfsd4_release_compoundargs(struct svc_rqst *rqstp) { struct nfsd4_compoundargs *args = rqstp->rq_argp; + args->opcnt = 0; if (args->ops != args->iops) { void *old_ops = args->ops; From eab515426a3d9d5ba16268eb2e68330432f423a5 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 11 Jun 2026 16:00:45 -0400 Subject: [PATCH 464/562] nfsd: add missing read barrier to rpc_status_get dumpit seqcount retry The hand-rolled seqcount-like protocol in nfsd_nl_rpc_status_get_dumpit() is missing a read memory barrier (smp_rmb) before its second counter check. The standard kernel read_seqcount_retry() includes smp_rmb() to ensure that all data reads complete before the counter is re-checked. Without this barrier, on weakly-ordered architectures (ARM, POWER), the CPU may reorder field reads past the second counter check, making the retry logic ineffective: it could observe a consistent counter pair while reading fields that have been concurrently modified by the writer. Add smp_rmb() before the second counter check to order the field reads ahead of it, matching the barrier semantics of the standard seqcount read-side. The begin-side smp_load_acquire() already pairs with the smp_store_release() in nfsd_dispatch(); with the smp_rmb() now ordering the field reads, the retry check no longer needs acquire semantics and reads the counter with a plain READ_ONCE(), as read_seqcount_retry() does. Fixes: bd9d6a3efa97 ("NFSD: add rpc_status netlink support") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Jeff Layton [ cel: Use READ_ONCE instead of smp_load_acquire() ] Link: https://patch.msgid.link/20260611-nfsd-testing-v2-2-5b90e276f2d9@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfsctl.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index c06d25c06f06..a0c46dc8c68b 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -1576,11 +1576,14 @@ int nfsd_nl_rpc_status_get_dumpit(struct sk_buff *skb, #endif /* CONFIG_NFSD_V4 */ /* - * Acquire rq_status_counter before reporting the rqst - * fields to the user. + * Read-side load-load fence: order the field reads + * above before the counter re-read below, mirroring + * the smp_rmb() in the standard seqcount retry. The + * begin-side smp_load_acquire() above pairs with the + * smp_store_release() in nfsd_dispatch(). */ - if (smp_load_acquire(&rqstp->rq_status_counter) != - status_counter) + smp_rmb(); + if (READ_ONCE(rqstp->rq_status_counter) != status_counter) continue; ret = nfsd_genl_rpc_status_compose_msg(skb, cb, From 616a95b09c186de8a4f89fb58681ec6795f2fadd Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 11 Jun 2026 16:00:46 -0400 Subject: [PATCH 465/562] nfsd: fix netlink dumpit error handling for rpc_status_get nfsd_genl_rpc_status_compose_msg() returns -ENOBUFS on nla_put failure without calling genlmsg_cancel(), leaving a partial message in the skb. The caller then propagates -ENOBUFS directly, which the netlink dump infrastructure treats as a fatal error, aborting the entire dump. The correct netlink dump convention is: - Cancel any partial message with genlmsg_cancel() - If prior messages were added to the skb (skb->len > 0), save the current iterator position and return skb->len to paginate - Only return a negative errno when no messages fit at all Fix compose_msg to cancel the partial message on all nla_put failure paths, and fix the caller to paginate when possible rather than returning a fatal error. A second defect surfaces once pagination actually works: cb->args[1] records the resume index within the pool named by cb->args[0], but the inner loop applied it to every pool from cb->args[0] onward. After a mid-pool pause, a later dump call drains the resume pool and continues into subsequent pools within the same call, where the stale cb->args[1] caused the first N threads of each following pool to be skipped. On per-CPU or per-node pool configurations this silently dropped active requests from the dump. Apply the saved thread index only to the pool matching cb->args[0], and start every subsequent pool from thread 0. Fixes: bd9d6a3efa97 ("NFSD: add rpc_status netlink support") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Jeff Layton [ cel: fold in 20/21 to avoid bisect hazard ] Link: https://patch.msgid.link/20260611-nfsd-testing-v2-3-5b90e276f2d9@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfsctl.c | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index a0c46dc8c68b..c9004f1dab0b 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -1452,7 +1452,7 @@ static int nfsd_genl_rpc_status_compose_msg(struct sk_buff *skb, nla_put_s64(skb, NFSD_A_RPC_STATUS_SERVICE_TIME, ktime_to_us(genl_rqstp->rq_stime), NFSD_A_RPC_STATUS_PAD)) - return -ENOBUFS; + goto out_cancel; switch (genl_rqstp->rq_saddr.ss_family) { case AF_INET: { @@ -1468,7 +1468,7 @@ static int nfsd_genl_rpc_status_compose_msg(struct sk_buff *skb, s_in->sin_port) || nla_put_be16(skb, NFSD_A_RPC_STATUS_DPORT, d_in->sin_port)) - return -ENOBUFS; + goto out_cancel; break; } case AF_INET6: { @@ -1484,7 +1484,7 @@ static int nfsd_genl_rpc_status_compose_msg(struct sk_buff *skb, s_in->sin6_port) || nla_put_be16(skb, NFSD_A_RPC_STATUS_DPORT, d_in->sin6_port)) - return -ENOBUFS; + goto out_cancel; break; } } @@ -1492,10 +1492,14 @@ static int nfsd_genl_rpc_status_compose_msg(struct sk_buff *skb, for (i = 0; i < genl_rqstp->rq_opcnt; i++) if (nla_put_u32(skb, NFSD_A_RPC_STATUS_COMPOUND_OPS, genl_rqstp->rq_opnum[i])) - return -ENOBUFS; + goto out_cancel; genlmsg_end(skb, hdr); return 0; + +out_cancel: + genlmsg_cancel(skb, hdr); + return -ENOBUFS; } /** @@ -1523,10 +1527,20 @@ int nfsd_nl_rpc_status_get_dumpit(struct sk_buff *skb, for (i = 0; i < nn->nfsd_serv->sv_nrpools; i++) { struct svc_rqst *rqstp; + long thread_skip = 0; if (i < cb->args[0]) /* already consumed */ continue; + /* + * The saved thread index only applies to the pool the dump + * was resumed in. Subsequent pools must start from thread 0, + * otherwise their first cb->args[1] threads are silently + * skipped. + */ + if (i == cb->args[0]) + thread_skip = cb->args[1]; + rqstp_index = 0; list_for_each_entry_rcu(rqstp, &nn->nfsd_serv->sv_pools[i].sp_all_threads, @@ -1534,7 +1548,7 @@ int nfsd_nl_rpc_status_get_dumpit(struct sk_buff *skb, struct nfsd_genl_rqstp genl_rqstp = {}; unsigned int status_counter; - if (rqstp_index++ < cb->args[1]) /* already consumed */ + if (rqstp_index++ < thread_skip) /* already consumed */ continue; /* * Acquire rq_status_counter before parsing the rqst @@ -1588,8 +1602,14 @@ int nfsd_nl_rpc_status_get_dumpit(struct sk_buff *skb, ret = nfsd_genl_rpc_status_compose_msg(skb, cb, &genl_rqstp); - if (ret) + if (ret) { + if (skb->len) { + cb->args[0] = i; + cb->args[1] = rqstp_index - 1; + ret = skb->len; + } goto out; + } } } From 3d1f7950ee03fc0c39bd355f67d335063f76a5e4 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 11 Jun 2026 16:00:47 -0400 Subject: [PATCH 466/562] sunrpc: defer rq_argp and rq_resp free until after RCU grace period MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit svc_rqst_free() frees rqstp->rq_argp and rqstp->rq_resp synchronously via kfree(), but defers the rqstp struct free via kfree_rcu(). After svc_exit_thread() calls list_del_rcu() and svc_rqst_free(), there is a window where RCU readers that started before list_del_rcu() can still traverse the thread list and find the rqstp. These readers (e.g. nfsd_nl_rpc_status_get_dumpit()) dereference rqstp->rq_argp, which has already been freed — a use-after-free. Fix this by moving the kfree of rq_argp and rq_resp into an explicit call_rcu() callback alongside the struct free. Resources not accessed by RCU readers (bvec, buffer pages, scratch folio, auth_data) remain synchronously freed. Fixes: 812443865c5f ("sunrpc: add a rcu_head to svc_rqst and use kfree_rcu to free it") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260611-nfsd-testing-v2-4-5b90e276f2d9@kernel.org Signed-off-by: Chuck Lever --- net/sunrpc/svc.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/net/sunrpc/svc.c b/net/sunrpc/svc.c index 86d39610cf0a..dd80a2eaaa74 100644 --- a/net/sunrpc/svc.c +++ b/net/sunrpc/svc.c @@ -716,6 +716,15 @@ svc_release_buffer(struct svc_rqst *rqstp) } } +static void svc_rqst_free_rcu(struct rcu_head *head) +{ + struct svc_rqst *rqstp = container_of(head, struct svc_rqst, rq_rcu_head); + + kfree(rqstp->rq_resp); + kfree(rqstp->rq_argp); + kfree(rqstp); +} + static void svc_rqst_free(struct svc_rqst *rqstp) { @@ -724,10 +733,8 @@ svc_rqst_free(struct svc_rqst *rqstp) svc_release_buffer(rqstp); if (rqstp->rq_scratch_folio) folio_put(rqstp->rq_scratch_folio); - kfree(rqstp->rq_resp); - kfree(rqstp->rq_argp); kfree(rqstp->rq_auth_data); - kfree_rcu(rqstp, rq_rcu_head); + call_rcu(&rqstp->rq_rcu_head, svc_rqst_free_rcu); } static struct svc_rqst * From 040576f366d938b10038adb3b3f08b6fec4e93a0 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 11 Jun 2026 16:00:48 -0400 Subject: [PATCH 467/562] nfsd: check nfsd4_acl_to_attr() return value in nfsd4_create() nfsd4_create() stores the return value of nfsd4_acl_to_attr() in status, but the switch(create->cr_type) block unconditionally overwrites it in every branch. ACL translation errors are silently discarded, and the CREATE proceeds without the requested ACL. Add an early exit check after nfsd4_acl_to_attr(), matching the pattern already used in nfsd4_setattr(). Fixes: c0cbe70742f4 ("NFSD: add posix ACLs to struct nfsd_attrs") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Jeff Layton [ cel: prefer NFS4ERR_BADTYPE over NFS4ERR_ATTRNOTSUPP ] Link: https://patch.msgid.link/20260611-nfsd-testing-v2-5-5b90e276f2d9@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfs4proc.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c index 623a89a1f34e..eb8a2a16839f 100644 --- a/fs/nfsd/nfs4proc.c +++ b/fs/nfsd/nfs4proc.c @@ -848,6 +848,20 @@ nfsd4_create(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, if (status) goto out_aftermask; + /* Sanitize cr_type to avoid returning ATTRNOTSUPP. */ + switch (create->cr_type) { + case NF4LNK: + case NF4BLK: + case NF4CHR: + case NF4SOCK: + case NF4FIFO: + case NF4DIR: + break; + default: + status = nfserr_badtype; + goto out_aftermask; + } + if (create->cr_acl) { if (attrs.na_dpacl || attrs.na_pacl) { status = nfserr_inval; @@ -855,6 +869,8 @@ nfsd4_create(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, } status = nfsd4_acl_to_attr(create->cr_type, create->cr_acl, &attrs); + if (status != nfs_ok) + goto out_aftermask; } current->fs->umask = create->cr_umask; switch (create->cr_type) { From a38d0526cc99154923a305be5fec7c6bcba85435 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 11 Jun 2026 16:00:49 -0400 Subject: [PATCH 468/562] nfsd: add filehandle match check to nfsd4_delegreturn() nfsd4_delegreturn() is the only stateful NFSv4 operation that does not call nfs4_check_fh() to verify the delegation's file matches cstate->current_fh. A client can DELEGRETURN with a mismatched filehandle, destroying the correct delegation but waking the wrong inode's waiters. Add the missing nfs4_check_fh() call after the generation check. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260611-nfsd-testing-v2-6-5b90e276f2d9@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfs4state.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index e59aec57e9e8..eb832e996364 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -8126,6 +8126,10 @@ nfsd4_delegreturn(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, if (status) goto put_stateid; + status = nfs4_check_fh(&cstate->current_fh, &dp->dl_stid); + if (status) + goto put_stateid; + trace_nfsd_deleg_return(stateid); destroy_delegation(dp); smp_mb__after_atomic(); From aea3941ef4506bb1e58b728118262f1283a1fd4b Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 11 Jun 2026 16:00:50 -0400 Subject: [PATCH 469/562] nfsd: validate nseconds in TIME_DELEG decode paths The xdrgen-based TIME_DELEG_ACCESS and TIME_DELEG_MODIFY decode arms store a raw uint32_t nseconds directly into tv_nsec without enforcing nseconds < NSEC_PER_SEC. The legacy nfsd4_decode_nfstime4 has this check but the TIME_DELEG paths do not. A malformed timespec can propagate through notify_change() to disk. Add range checks in both nfs4xdr.c (SETATTR path) and nfs4callback.c (CB_GETATTR path). Fixes: 6ae30d6eb26b ("nfsd: add support for delegated timestamps") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260611-nfsd-testing-v2-7-5b90e276f2d9@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfs4callback.c | 4 ++++ fs/nfsd/nfs4xdr.c | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/fs/nfsd/nfs4callback.c b/fs/nfsd/nfs4callback.c index 1628bb9ef9dd..7c868afc329e 100644 --- a/fs/nfsd/nfs4callback.c +++ b/fs/nfsd/nfs4callback.c @@ -108,6 +108,8 @@ static int decode_cb_fattr4(struct xdr_stream *xdr, uint32_t *bitmap, if (!xdrgen_decode_fattr4_time_deleg_access(xdr, &access)) return -EIO; + if (access.nseconds >= NSEC_PER_SEC) + return -EIO; fattr->ncf_cb_atime.tv_sec = access.seconds; fattr->ncf_cb_atime.tv_nsec = access.nseconds; @@ -117,6 +119,8 @@ static int decode_cb_fattr4(struct xdr_stream *xdr, uint32_t *bitmap, if (!xdrgen_decode_fattr4_time_deleg_modify(xdr, &modify)) return -EIO; + if (modify.nseconds >= NSEC_PER_SEC) + return -EIO; fattr->ncf_cb_mtime.tv_sec = modify.seconds; fattr->ncf_cb_mtime.tv_nsec = modify.nseconds; diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c index 6f356c71e234..ad192d25724c 100644 --- a/fs/nfsd/nfs4xdr.c +++ b/fs/nfsd/nfs4xdr.c @@ -637,6 +637,8 @@ nfsd4_decode_fattr4(struct nfsd4_compoundargs *argp, u32 *bmval, u32 bmlen, if (!xdrgen_decode_fattr4_time_deleg_access(argp->xdr, &access)) return nfserr_bad_xdr; + if (access.nseconds >= NSEC_PER_SEC) + return nfserr_inval; iattr->ia_atime.tv_sec = access.seconds; iattr->ia_atime.tv_nsec = access.nseconds; iattr->ia_valid |= ATTR_ATIME | ATTR_ATIME_SET | ATTR_DELEG; @@ -646,6 +648,8 @@ nfsd4_decode_fattr4(struct nfsd4_compoundargs *argp, u32 *bmval, u32 bmlen, if (!xdrgen_decode_fattr4_time_deleg_modify(argp->xdr, &modify)) return nfserr_bad_xdr; + if (modify.nseconds >= NSEC_PER_SEC) + return nfserr_inval; iattr->ia_mtime.tv_sec = modify.seconds; iattr->ia_mtime.tv_nsec = modify.nseconds; iattr->ia_ctime.tv_sec = modify.seconds; From 86ae09cea43ad766b89d70998af86dee389966af Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 11 Jun 2026 16:00:52 -0400 Subject: [PATCH 470/562] nfsd: fix version mismatch loops in nfsd_acl_init_request() The loops that compute the supported version range for PROG_MISMATCH test nfsd_support_acl_version(rqstp->rq_vers) instead of nfsd_support_acl_version(i), so every iteration fails and the function returns rpc_prog_unavail instead of rpc_prog_mismatch. Replace rqstp->rq_vers with the loop variable i, matching the pattern used by the sibling nfsd_init_request() function. Fixes: e333f3bbefe3 ("nfsd: Allow containers to set supported nfs versions") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260611-nfsd-testing-v2-9-5b90e276f2d9@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfssvc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/nfsd/nfssvc.c b/fs/nfsd/nfssvc.c index 0d3838dd59c1..b8e8d80e984c 100644 --- a/fs/nfsd/nfssvc.c +++ b/fs/nfsd/nfssvc.c @@ -821,7 +821,7 @@ nfsd_acl_init_request(struct svc_rqst *rqstp, ret->mismatch.lovers = NFSD_ACL_NRVERS; for (i = NFSD_ACL_MINVERS; i < NFSD_ACL_NRVERS; i++) { - if (nfsd_support_acl_version(rqstp->rq_vers) && + if (nfsd_support_acl_version(i) && nfsd_vers(nn, i, NFSD_TEST)) { ret->mismatch.lovers = i; break; @@ -831,7 +831,7 @@ nfsd_acl_init_request(struct svc_rqst *rqstp, return rpc_prog_unavail; ret->mismatch.hivers = NFSD_ACL_MINVERS; for (i = NFSD_ACL_NRVERS - 1; i >= NFSD_ACL_MINVERS; i--) { - if (nfsd_support_acl_version(rqstp->rq_vers) && + if (nfsd_support_acl_version(i) && nfsd_vers(nn, i, NFSD_TEST)) { ret->mismatch.hivers = i; break; From fa9fa2508ef0492040e78bd81943d15d10181c0b Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 11 Jun 2026 16:00:53 -0400 Subject: [PATCH 471/562] nfsd: fix FL_SLEEP being set unconditionally for all LOCK types The FL_SLEEP guard uses lk_type & (NFS4_READW_LT | NFS4_WRITEW_LT) which computes lk_type & 7, non-zero for all valid lock types including non-blocking ones. This was introduced by commit 7e64c5bc497c ("NLM/NFSD: Fix lock notifications for async-capable filesystems") when refactoring from per-case switch arms. Replace the bitmask test with explicit equality checks. Fixes: 7e64c5bc497c ("NLM/NFSD: Fix lock notifications for async-capable filesystems") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260611-nfsd-testing-v2-10-5b90e276f2d9@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfs4state.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index eb832e996364..3dc0c0f6eb5d 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -8636,10 +8636,11 @@ nfsd4_lock(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, goto out; } - if (lock->lk_type & (NFS4_READW_LT | NFS4_WRITEW_LT) && - nfsd4_has_session(cstate) && - locks_can_async_lock(nf->nf_file->f_op)) - flags |= FL_SLEEP; + if ((lock->lk_type == NFS4_READW_LT || + lock->lk_type == NFS4_WRITEW_LT) && + nfsd4_has_session(cstate) && + locks_can_async_lock(nf->nf_file->f_op)) + flags |= FL_SLEEP; nbl = find_or_allocate_block(lock_sop, &fp->fi_fhandle, nn); if (!nbl) { From 446c45ea1756ef21b2e6b6f784b769657b3f5780 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 11 Jun 2026 16:00:54 -0400 Subject: [PATCH 472/562] nfsd: add fh_want_write() for early-verified SETATTR in nfsd_proc_setattr() The BOTH_TIME_SET branch calls fh_verify() early so setattr_prepare() can inspect the dentry. This causes nfsd_setattr() to skip fh_want_write(), so notify_change() runs without a mount write reference. Add the missing fh_want_write() call after the early fh_verify(). Fixes: cc265089ce1b ("nfsd: Disable NFSv2 timestamp workaround for NFSv3+") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260611-nfsd-testing-v2-11-5b90e276f2d9@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfsproc.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c index 8873033d1e82..a73d5c259cd9 100644 --- a/fs/nfsd/nfsproc.c +++ b/fs/nfsd/nfsproc.c @@ -82,6 +82,7 @@ nfsd_proc_setattr(struct svc_rqst *rqstp) .na_iattr = iap, }; struct svc_fh *fhp; + int hosterr; dprintk("nfsd: SETATTR %s, valid=%x, size=%ld\n", SVCFH_fmt(&argp->fh), @@ -117,6 +118,12 @@ nfsd_proc_setattr(struct svc_rqst *rqstp) if (resp->status != nfs_ok) goto out; + hosterr = fh_want_write(fhp); + if (hosterr) { + resp->status = nfserrno(hosterr); + goto out; + } + if (delta < 0) delta = -delta; if (delta < MAX_TOUCH_TIME_ERROR && From 59586c9e2f011e9cc28da30f3f682be7086a3f21 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 11 Jun 2026 16:00:55 -0400 Subject: [PATCH 473/562] nfsd: fix clock domain mismatch in clients_still_reclaiming() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit clients_still_reclaiming() computes a deadline from nn->boot_time (CLOCK_REALTIME, ~1.7 billion) but compares it against ktime_get_boottime_seconds() (CLOCK_BOOTTIME, seconds since boot). The comparison is always false — it would take ~54 years of uptime for BOOTTIME to exceed the REALTIME-derived deadline. This means any client can hold the server in grace indefinitely by sending CLAIM_PREVIOUS OPEN requests, blocking all non-reclaim operations for all other clients. Add boot_time_bt (CLOCK_BOOTTIME) alongside the existing boot_time and use it for the deadline computation. boot_time (CLOCK_REALTIME) is preserved for its cl_boot clientid-nonce role. Fixes: 20b7d86f29d3 ("nfsd: use boottime for lease expiry calculation") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260611-nfsd-testing-v2-12-5b90e276f2d9@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/netns.h | 1 + fs/nfsd/nfs4state.c | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/nfsd/netns.h b/fs/nfsd/netns.h index 5c33c96da28e..03724bef10a7 100644 --- a/fs/nfsd/netns.h +++ b/fs/nfsd/netns.h @@ -78,6 +78,7 @@ struct nfsd_net { struct lock_manager nfsd4_manager; unsigned long flags; time64_t boot_time; + time64_t boot_time_bt; /* same instant in CLOCK_BOOTTIME */ struct dentry *nfsd_client_dir; diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index 3dc0c0f6eb5d..17cb3b0ad956 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -6870,7 +6870,7 @@ bool nfsd4_force_end_grace(struct nfsd_net *nn) */ static bool clients_still_reclaiming(struct nfsd_net *nn) { - time64_t double_grace_period_end = nn->boot_time + + time64_t double_grace_period_end = nn->boot_time_bt + 2 * nn->nfsd4_lease; if (test_bit(NFSD_NET_GRACE_END_FORCED, &nn->flags)) @@ -9245,6 +9245,7 @@ static int nfs4_state_create_net(struct net *net) nn->conf_name_tree = RB_ROOT; nn->unconf_name_tree = RB_ROOT; nn->boot_time = ktime_get_real_seconds(); + nn->boot_time_bt = ktime_get_boottime_seconds(); clear_bit(NFSD_NET_GRACE_ENDED, &nn->flags); clear_bit(NFSD_NET_GRACE_END_FORCED, &nn->flags); nn->nfsd4_manager.block_opens = true; From b4b950a3f23cebd9d9655ec990db221d19ed5c90 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 11 Jun 2026 16:00:56 -0400 Subject: [PATCH 474/562] nfsd: use test_and_clear_bit for somebody_reclaimed to prevent lost update clients_still_reclaiming() uses separate test_bit() and clear_bit() calls on NFSD_NET_SOMEBODY_RECLAIMED. A concurrent set_bit() from the OPEN or LOCK reclaim path arriving between the test and clear is silently lost, causing the next laundromat tick to end grace prematurely. Replace with test_and_clear_bit() to make the read-and-clear atomic. Fixes: 8c67a210c90c ("nfsd: convert nfsd_net boolean flags to unsigned long flags word") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260611-nfsd-testing-v2-13-5b90e276f2d9@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfs4state.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index 17cb3b0ad956..0735a3bafa58 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -6884,9 +6884,8 @@ static bool clients_still_reclaiming(struct nfsd_net *nn) if (atomic_read(&nn->nr_reclaim_complete) == size) return false; } - if (!test_bit(NFSD_NET_SOMEBODY_RECLAIMED, &nn->flags)) + if (!test_and_clear_bit(NFSD_NET_SOMEBODY_RECLAIMED, &nn->flags)) return false; - clear_bit(NFSD_NET_SOMEBODY_RECLAIMED, &nn->flags); /* * If we've given them *two* lease times to reclaim, and they're * still not done, give up: From 0b8c4bd82669fb93982dac147063afa599be9302 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 11 Jun 2026 16:00:57 -0400 Subject: [PATCH 475/562] nfsd: reject reclaim LOCK after RECLAIM_COMPLETE nfsd4_lock() only checks the namespace-wide grace flag when deciding whether to accept a reclaim LOCK. It does not check the per-client NFSD4_CLIENT_RECLAIM_COMPLETE bit. An NFSv4.1+ client that has already sent RECLAIM_COMPLETE can submit lk_reclaim=1 while grace is still active (e.g. lockd holds the grace list open), and the server accepts it instead of returning NFS4ERR_NO_GRACE as required by RFC 8881 section 18.51.3. The OPEN path already enforces both tiers: the grace check plus the per-client RECLAIM_COMPLETE check in nfs4_check_open_reclaim(). Add the equivalent per-client check to the LOCK path. Fixes: 3b3e7b72239a ("nfsd: reject reclaim request when client has already sent RECLAIM_COMPLETE") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Jeff Layton [ cel: Correct the RFC citations in the commit message ] Link: https://patch.msgid.link/20260611-nfsd-testing-v2-14-5b90e276f2d9@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfs4state.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index 0735a3bafa58..a0c97bff3cff 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -8599,6 +8599,9 @@ nfsd4_lock(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, status = nfserr_no_grace; if (!locks_in_grace(net) && lock->lk_reclaim) goto out; + if (lock->lk_reclaim && + test_bit(NFSD4_CLIENT_RECLAIM_COMPLETE, &cstate->clp->cl_flags)) + goto out; if (lock->lk_reclaim) flags |= FL_RECLAIM; From a6635b861a56596cb1aea25b009c369406e5446d Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 11 Jun 2026 16:00:59 -0400 Subject: [PATCH 476/562] lockd, nfsd: RCU-protect nlmsvc_ops dispatch nlmsvc_ops is published by nfsd_lockd_init() and cleared by nfsd_lockd_shutdown() with plain stores, while lockd dereferences it unguarded from dispatch sites in fs/lockd/svcsubs.c. The pointer targets nfsd's .rodata and the fopen/fclose callbacks live in nfsd's .text, so a stale load after rmmod nfsd results in either a NULL deref or a module-text use-after-free. Declare nlmsvc_ops as __rcu, publish via rcu_assign_pointer(), clear via RCU_INIT_POINTER() + synchronize_rcu(). Add a struct module *owner field to nlmsvc_binding and pin the module across indirect calls with try_module_get/module_put. When the binding is torn down, fall back to fput() to avoid leaking struct file references. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260611-nfsd-testing-v2-16-5b90e276f2d9@kernel.org Signed-off-by: Chuck Lever --- fs/lockd/svc.c | 4 +-- fs/lockd/svc4proc.c | 4 +-- fs/lockd/svcproc.c | 4 +-- fs/lockd/svcsubs.c | 52 +++++++++++++++++++++++++++++++++----- fs/nfsd/lockd.c | 6 +++-- include/linux/lockd/bind.h | 12 ++++++--- 6 files changed, 64 insertions(+), 18 deletions(-) diff --git a/fs/lockd/svc.c b/fs/lockd/svc.c index 490551369ef2..ee90e743064a 100644 --- a/fs/lockd/svc.c +++ b/fs/lockd/svc.c @@ -47,7 +47,7 @@ static struct svc_program nlmsvc_program; -const struct nlmsvc_binding *nlmsvc_ops; +const struct nlmsvc_binding __rcu *nlmsvc_ops; EXPORT_SYMBOL_GPL(nlmsvc_ops); static DEFINE_MUTEX(nlmsvc_mutex); @@ -142,7 +142,7 @@ lockd(void *vrqstp) nlmsvc_retry_blocked(rqstp); svc_recv(rqstp, 0); } - if (nlmsvc_ops) + if (rcu_access_pointer(nlmsvc_ops)) nlmsvc_invalidate_all(); nlm_shutdown_hosts(); cancel_delayed_work_sync(&ln->grace_period_end); diff --git a/fs/lockd/svc4proc.c b/fs/lockd/svc4proc.c index 78e675470c4b..080dffce9d8e 100644 --- a/fs/lockd/svc4proc.c +++ b/fs/lockd/svc4proc.c @@ -128,7 +128,7 @@ nlm4svc_lookup_host(struct svc_rqst *rqstp, string caller, bool monitored) { struct nlm_host *host; - if (!nlmsvc_ops) + if (!rcu_access_pointer(nlmsvc_ops)) return NULL; host = nlmsvc_lookup_host(rqstp, caller.data, caller.len); if (!host) @@ -894,7 +894,7 @@ static __be32 nlm4svc_proc_granted_res(struct svc_rqst *rqstp) { struct nlm4_res_wrapper *argp = rqstp->rq_argp; - if (!nlmsvc_ops) + if (!rcu_access_pointer(nlmsvc_ops)) return rpc_success; if (nlm4_netobj_to_cookie(&argp->cookie, &argp->xdrgen.cookie)) diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index 4836887f11ef..dce6f6e3fd40 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -133,7 +133,7 @@ nlm3svc_lookup_host(struct svc_rqst *rqstp, string caller, bool monitored) { struct nlm_host *host; - if (!nlmsvc_ops) + if (!rcu_access_pointer(nlmsvc_ops)) return NULL; host = nlmsvc_lookup_host(rqstp, caller.data, caller.len); if (!host) @@ -923,7 +923,7 @@ static __be32 nlmsvc_proc_granted_res(struct svc_rqst *rqstp) { struct nlm_res_wrapper *argp = rqstp->rq_argp; - if (!nlmsvc_ops) + if (!rcu_access_pointer(nlmsvc_ops)) return rpc_success; if (nlm_netobj_to_cookie(&argp->cookie, &argp->xdrgen.cookie)) diff --git a/fs/lockd/svcsubs.c b/fs/lockd/svcsubs.c index d7ada90dc048..e44eb20d3453 100644 --- a/fs/lockd/svcsubs.c +++ b/fs/lockd/svcsubs.c @@ -90,22 +90,35 @@ int lock_to_openmode(struct file_lock *lock) static __be32 nlm_do_fopen(struct svc_rqst *rqstp, struct nlm_file *file, int mode) { + const struct nlmsvc_binding *ops; __be32 nlmerr = nlm__int__failed; __be32 deferred = 0; int error; int m; + rcu_read_lock(); + ops = rcu_dereference(nlmsvc_ops); + if (!ops || !try_module_get(ops->owner)) { + rcu_read_unlock(); + return nlm__int__failed; + } + rcu_read_unlock(); + for (m = O_RDONLY; m <= O_WRONLY; m++) { struct file **fp = &file->f_file[m]; if (mode != O_RDWR && mode != m) continue; - if (*fp) + if (*fp) { + module_put(ops->owner); return nlm_granted; + } - error = nlmsvc_ops->fopen(rqstp, &file->f_handle, fp, m); - if (!error) + error = ops->fopen(rqstp, &file->f_handle, fp, m); + if (!error) { + module_put(ops->owner); return nlm_granted; + } dprintk("lockd: open failed (errno %d)\n", error); switch (error) { @@ -122,6 +135,7 @@ static __be32 nlm_do_fopen(struct svc_rqst *rqstp, } } + module_put(ops->owner); return deferred ? deferred : nlmerr; } @@ -185,6 +199,33 @@ nlm_lookup_file(struct svc_rqst *rqstp, struct nlm_file **result, goto out_unlock; } +/* + * Release the struct file references held by a nlm_file. + */ +static void nlm_release_files(struct nlm_file *file) +{ + const struct nlmsvc_binding *ops; + bool have_ops; + + rcu_read_lock(); + ops = rcu_dereference(nlmsvc_ops); + have_ops = ops && try_module_get(ops->owner); + rcu_read_unlock(); + + if (have_ops) { + if (file->f_file[O_RDONLY]) + ops->fclose(file->f_file[O_RDONLY]); + if (file->f_file[O_WRONLY]) + ops->fclose(file->f_file[O_WRONLY]); + module_put(ops->owner); + } else { + if (file->f_file[O_RDONLY]) + fput(file->f_file[O_RDONLY]); + if (file->f_file[O_WRONLY]) + fput(file->f_file[O_WRONLY]); + } +} + /* * Delete a file after having released all locks, blocks and shares */ @@ -194,10 +235,7 @@ nlm_delete_file(struct nlm_file *file) nlm_debug_print_file("closing file", file); if (!hlist_unhashed(&file->f_list)) { hlist_del(&file->f_list); - if (file->f_file[O_RDONLY]) - nlmsvc_ops->fclose(file->f_file[O_RDONLY]); - if (file->f_file[O_WRONLY]) - nlmsvc_ops->fclose(file->f_file[O_WRONLY]); + nlm_release_files(file); kfree(file); } else { printk(KERN_WARNING "lockd: attempt to release unknown file!\n"); diff --git a/fs/nfsd/lockd.c b/fs/nfsd/lockd.c index 6fe1325815e0..72a5b499839d 100644 --- a/fs/nfsd/lockd.c +++ b/fs/nfsd/lockd.c @@ -92,6 +92,7 @@ nlm_fclose(struct file *filp) } static const struct nlmsvc_binding nfsd_nlm_ops = { + .owner = THIS_MODULE, .fopen = nlm_fopen, /* open file for locking */ .fclose = nlm_fclose, /* close file */ }; @@ -100,11 +101,12 @@ void nfsd_lockd_init(void) { dprintk("nfsd: initializing lockd\n"); - nlmsvc_ops = &nfsd_nlm_ops; + rcu_assign_pointer(nlmsvc_ops, &nfsd_nlm_ops); } void nfsd_lockd_shutdown(void) { - nlmsvc_ops = NULL; + RCU_INIT_POINTER(nlmsvc_ops, NULL); + synchronize_rcu(); } diff --git a/include/linux/lockd/bind.h b/include/linux/lockd/bind.h index b614e0deea72..db8207d4059f 100644 --- a/include/linux/lockd/bind.h +++ b/include/linux/lockd/bind.h @@ -16,17 +16,23 @@ struct svc_rqst; struct rpc_task; struct rpc_clnt; struct super_block; +struct module; -/* - * This is the set of functions for lockd->nfsd communication +/** + * struct nlmsvc_binding - lockd -> nfsd callback table + * @owner: module that provides this binding. + * @fopen: open a file by NFS file handle on behalf of an NLM request. + * @fclose: close a file that was previously opened via @fopen. + * Implementations MUST be semantically equivalent to fput(). */ struct nlmsvc_binding { + struct module *owner; int (*fopen)(struct svc_rqst *rqstp, struct nfs_fh *f, struct file **filp, int flags); void (*fclose)(struct file *filp); }; -extern const struct nlmsvc_binding *nlmsvc_ops; +extern const struct nlmsvc_binding __rcu *nlmsvc_ops; /* * Similar to nfs_client_initdata, but without the NFS-specific From 1b0b3f1f0ce33bcc41a82b5910bb51d300343e7f Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 11 Jun 2026 16:01:00 -0400 Subject: [PATCH 477/562] nfsd: move nfsd_debugfs_init() after nfsd4_init_slabs() in init_nfsd() nfsd_debugfs_init() runs before nfsd4_init_slabs() in init_nfsd(). If the slab allocation fails, the bare "return retval" bypasses nfsd_debugfs_exit(), leaving orphan debugfs files with stale fops pointers into the freed module text. Move nfsd_debugfs_init() to after the slab init succeeds, so the early return has no debugfs state to clean up. Since debugfs is now the more recently initialized of the two, also update the unwind paths to match reverse-initialization (LIFO) order: run nfsd_debugfs_exit() before nfsd4_free_slabs() in both the init_nfsd() error path and exit_nfsd(). The nfsd debugfs files only reference module-global state and have no dependency on the slab caches, so that reordering is a cleanup with no functional change. Fixes: 9fe5ea760e64 ("NFSD: Add /sys/kernel/debug/nfsd") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260611-nfsd-testing-v2-17-5b90e276f2d9@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfsctl.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index c9004f1dab0b..11bbc7e8210c 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -2538,11 +2538,12 @@ static int __init init_nfsd(void) { int retval; - nfsd_debugfs_init(); - retval = nfsd4_init_slabs(); if (retval) return retval; + + nfsd_debugfs_init(); + retval = nfsd4_init_pnfs(); if (retval) goto out_free_slabs; @@ -2587,8 +2588,8 @@ static int __init init_nfsd(void) out_free_pnfs: nfsd4_exit_pnfs(); out_free_slabs: - nfsd4_free_slabs(); nfsd_debugfs_exit(); + nfsd4_free_slabs(); return retval; } @@ -2603,9 +2604,9 @@ static void __exit exit_nfsd(void) unregister_pernet_subsys(&nfsd_net_ops); nfsd_drc_slab_free(); nfsd_lockd_shutdown(); - nfsd4_free_slabs(); nfsd4_exit_pnfs(); nfsd_debugfs_exit(); + nfsd4_free_slabs(); } MODULE_AUTHOR("Olaf Kirch "); From 56873ee8e007847f822c37d33b996c2a47588160 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 11 Jun 2026 16:01:01 -0400 Subject: [PATCH 478/562] nfsd: initialize DRC hash table before registering shrinker shrinker_register() precedes the INIT_LIST_HEAD loop and the drc_hashsize store. On weakly-ordered architectures (arm64, ppc), a shrinker scan can observe drc_hashsize before the bucket list heads are initialized, causing a NULL deref in the DRC shrinker callback. Move bucket initialization and the drc_hashsize store before shrinker_register() so the hash table is fully initialized before it becomes visible to the shrinker. Fixes: 8eea99a81c6f ("nfsd: dynamically allocate the nfsd-reply shrinker") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260611-nfsd-testing-v2-18-5b90e276f2d9@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfscache.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/nfsd/nfscache.c b/fs/nfsd/nfscache.c index 154468ceccdc..18f8556d33dd 100644 --- a/fs/nfsd/nfscache.c +++ b/fs/nfsd/nfscache.c @@ -200,14 +200,14 @@ int nfsd_reply_cache_init(struct nfsd_net *nn) nn->nfsd_reply_cache_shrinker->seeks = 1; nn->nfsd_reply_cache_shrinker->private_data = nn; - shrinker_register(nn->nfsd_reply_cache_shrinker); - for (i = 0; i < hashsize; i++) { INIT_LIST_HEAD(&nn->drc_hashtbl[i].lru_head); spin_lock_init(&nn->drc_hashtbl[i].cache_lock); } nn->drc_hashsize = hashsize; + shrinker_register(nn->nfsd_reply_cache_shrinker); + return 0; out_shrinker: kvfree(nn->drc_hashtbl); From fb95299207c8adb0502c061fe466927d2e1595c0 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 11 Jun 2026 16:01:02 -0400 Subject: [PATCH 479/562] nfsd: restore rq_status_counter to even on all nfsd_dispatch() exit paths nfsd_dispatch() sets rq_status_counter to an odd value once a request has been decoded, and back to an even value once it has been fully processed, forming a seq-lock like protocol with the lockless reader in nfsd_nl_rpc_status_get_dumpit(). Only the fully successful path restored the counter to even. The cache-hit (RC_REPLY), drop (RC_DROPIT / RQ_DROPME) and encode-error paths all return after the odd-valued store without ever bringing the counter back to even. Once one of those paths is taken, rq_status_counter is left odd: the next request's decode ORs in 1 (still odd) and only a subsequent successful encode restores even. While stuck odd, the dumpit reader treats the rqstp fields as stable and its retry check compares against the same unchanging odd value, so it never detects concurrent mutation. This exposes actively mutating fields (e.g. args->ops / args->opcnt during compound decode and release) to the lockless reader, which can read past the end of the 8-element inline ops array. Add a helper that advances the counter to the next even value and call it on every return path that follows the odd-valued store. The decode-error path is left untouched as it is reached before the counter is set odd. Fixes: bd9d6a3efa97 ("NFSD: add rpc_status netlink support") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260611-nfsd-testing-v2-19-5b90e276f2d9@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfssvc.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/fs/nfsd/nfssvc.c b/fs/nfsd/nfssvc.c index b8e8d80e984c..a8ea4dbfa56b 100644 --- a/fs/nfsd/nfssvc.c +++ b/fs/nfsd/nfssvc.c @@ -966,6 +966,20 @@ nfsd(void *vrqstp) return 0; } +/* + * Set rq_status_counter back to an even value, indicating that the rqstp + * fields are no longer meaningful to a lockless reader. This pairs with the + * odd-valued store made once the request has been decoded, and must run on + * every return path that follows it so that the seq-lock like protocol used + * by nfsd_nl_rpc_status_get_dumpit() is not left permanently odd. The store + * also advances the counter so a concurrent reader detects the transition. + */ +static void nfsd_status_counter_set_idle(struct svc_rqst *rqstp) +{ + smp_store_release(&rqstp->rq_status_counter, + (rqstp->rq_status_counter | 1) + 1); +} + /** * nfsd_dispatch - Process an NFS or NFSACL or LOCALIO Request * @rqstp: incoming request @@ -1028,14 +1042,9 @@ int nfsd_dispatch(struct svc_rqst *rqstp) if (!proc->pc_encode(rqstp, &rqstp->rq_res_stream)) goto out_encode_err; - /* - * Release rq_status_counter setting it to an even value after the rpc - * request has been properly processed. - */ - smp_store_release(&rqstp->rq_status_counter, rqstp->rq_status_counter + 1); - nfsd_cache_update(rqstp, rp, ntli->ntli_cachetype, nfs_reply); out_cached_reply: + nfsd_status_counter_set_idle(rqstp); return 1; out_decode_err: @@ -1046,12 +1055,14 @@ int nfsd_dispatch(struct svc_rqst *rqstp) out_update_drop: nfsd_cache_update(rqstp, rp, RC_NOCACHE, NULL); out_dropit: + nfsd_status_counter_set_idle(rqstp); return 0; out_encode_err: trace_nfsd_cant_encode_err(rqstp); nfsd_cache_update(rqstp, rp, RC_NOCACHE, NULL); *statp = rpc_system_err; + nfsd_status_counter_set_idle(rqstp); return 1; } From 06da7b592dad2b7065ebcf7f71b051ab95155035 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 11 Jun 2026 16:01:04 -0400 Subject: [PATCH 480/562] nfsd: drop the stateid, not the stateowner, on seqid_op replay retry In nfs4_preprocess_seqid_op() the stateid is obtained from nfsd4_lookup_stateid(), which holds a reference on the nfs4_stid (sc_count) but takes no reference on the stateowner. openlockstateid() merely casts that stid and likewise takes no reference. When nfsd4_cstate_assign_replay() returns -EAGAIN (the replay owner is being torn down, RP_UNHASHED) it has not taken a stateowner reference on that path. The error handling nevertheless called nfs4_put_stateowner(stp->st_stateowner), dropping an so_count reference the function never acquired -- risking a stateowner refcount underflow and use-after-free -- while leaking the sc_count reference held on the stid. The leaked stid reference can also stall a concurrent nfsd4_close_open_stateid() waiting for sc_count to drop. Drop the reference actually held -- the stid -- before retrying. The stateowner stays alive through the reference held by the stid. This mirrors the open path in nfsd4_process_open1(), where the put balances a reference that path explicitly holds on the stateowner. Fixes: eec762080008 ("nfsd: replace rp_mutex to avoid deadlock in move_to_close_lru()") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260611-nfsd-testing-v2-21-5b90e276f2d9@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfs4state.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index a0c97bff3cff..bef0ec9be459 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -7876,7 +7876,7 @@ nfs4_preprocess_seqid_op(struct nfsd4_compound_state *cstate, u32 seqid, return status; stp = openlockstateid(s); if (nfsd4_cstate_assign_replay(cstate, stp->st_stateowner) == -EAGAIN) { - nfs4_put_stateowner(stp->st_stateowner); + nfs4_put_stid(&stp->st_stid); goto retry; } From c1d3c69721ceeba9942cfadaa0e31f96b15259a7 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Sat, 13 Jun 2026 18:16:32 -0400 Subject: [PATCH 481/562] NFSD: Prevent post-shutdown use-after-free in unlock_filesystem Writing a filesystem path to /proc/fs/nfsd/unlock_filesystem runs nfsd4_cancel_copy_by_sb() before nfsd_mutex is held and before the handler confirms that nn->nfsd_serv is set. Once nfsd has shut down, nfs4_state_destroy_net() has freed nn->conf_id_hashtbl but left the pointer intact, so the cancel helper iterates freed slab memory as an array of struct list_head and then dereferences a bogus nfs4_client when it takes clp->async_lock. A local administrator holding CAP_SYS_ADMIN can reach this use-after-free by stopping the server and then writing to unlock_filesystem; KASAN reports a slab-use-after-free read in nfsd4_cancel_copy_by_sb(). nfsd4_revoke_states() walks the same state tables and for that reason already runs only under nfsd_mutex with nn->nfsd_serv confirmed present. Move the async COPY cancel into that protected section so every NFSv4 state-table walker on this path observes a running server. Async copies exist only while the server runs, so gating the cancel on nn->nfsd_serv loses nothing. Reported-by: Musaab Khan Fixes: 3daab3112f03 ("nfsd: cancel async COPY operations when admin revokes filesystem state") Cc: stable@vger.kernel.org Reviewed-by: Jeff Layton Link: https://patch.msgid.link/20260613-unlock-filesystem-uaf-v1-1-462b9bec8c84@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfsctl.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index 11bbc7e8210c..29d68abfa5c8 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -296,14 +296,15 @@ static ssize_t write_unlock_fs(struct file *file, char *buf, size_t size) * 2. Is that directory a mount point, or * 3. Is that directory the root of an exported file system? */ - nfsd4_cancel_copy_by_sb(netns(file), path.dentry->d_sb); error = nlmsvc_unlock_all_by_sb(path.dentry->d_sb); mutex_lock(&nfsd_mutex); nn = net_generic(netns(file), nfsd_net_id); - if (nn->nfsd_serv) + if (nn->nfsd_serv) { + nfsd4_cancel_copy_by_sb(netns(file), path.dentry->d_sb); nfsd4_revoke_states(nn, path.dentry->d_sb); - else + } else { error = -EINVAL; + } mutex_unlock(&nfsd_mutex); path_put(&path); From 26ddc8a26f00b67fd9b40e134f06ad269b46c1c7 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Sat, 13 Jun 2026 18:16:34 -0400 Subject: [PATCH 482/562] NFSD: Annotate caller preconditions for the state-table walkers The state-table walkers now assert nfsd_mutex with lockdep_assert_held() and document the nfsd_mutex / nn->nfsd_serv precondition in a Context: kdoc section, so the next caller added to this path cannot silently reintroduce the same use-after-free. Reviewed-by: Jeff Layton Link: https://patch.msgid.link/20260613-unlock-filesystem-uaf-v1-3-462b9bec8c84@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfs4proc.c | 6 ++++++ fs/nfsd/nfs4state.c | 16 +++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c index eb8a2a16839f..3e4de45aa360 100644 --- a/fs/nfsd/nfs4proc.c +++ b/fs/nfsd/nfs4proc.c @@ -1587,6 +1587,11 @@ static bool nfsd4_copy_on_sb(const struct nfsd4_copy *copy, * nfsd4_cancel_copy_by_sb - cancel async copy operations on @sb * @net: net namespace containing the copy operations * @sb: targeted superblock + * + * Context: Caller must hold nfsd_mutex with nn->nfsd_serv confirmed + * non-NULL. nfs4_state_destroy_net() frees conf_id_hashtbl + * at server shutdown without clearing the pointer, so a + * walk without these guarantees iterates freed slab memory. */ void nfsd4_cancel_copy_by_sb(struct net *net, struct super_block *sb) { @@ -1596,6 +1601,7 @@ void nfsd4_cancel_copy_by_sb(struct net *net, struct super_block *sb) unsigned int idhashval; LIST_HEAD(to_cancel); + lockdep_assert_held(&nfsd_mutex); spin_lock(&nn->client_lock); for (idhashval = 0; idhashval < CLIENT_HASH_SIZE; idhashval++) { struct list_head *head = &nn->conf_id_hashtbl[idhashval]; diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index bef0ec9be459..2a6a0c9ef65f 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -1873,14 +1873,21 @@ static void revoke_one_stid(struct nfsd_net *nn, struct nfs4_client *clp, * being released. Thus nfsd will no longer prevent the filesystem from being * unmounted. * - * The clients which own the states will subsequently being notified that the + * The clients which own the states will subsequently be notified that the * states have been "admin-revoked". + * + * Context: Caller must hold nfsd_mutex with nn->nfsd_serv confirmed + * non-NULL. nfs4_state_destroy_net() frees conf_id_hashtbl + * at server shutdown without clearing the pointer, so a + * walk without these guarantees iterates freed slab memory. */ void nfsd4_revoke_states(struct nfsd_net *nn, struct super_block *sb) { unsigned int idhashval; unsigned int sc_types; + lockdep_assert_held(&nfsd_mutex); + sc_types = SC_TYPE_OPEN | SC_TYPE_LOCK | SC_TYPE_DELEG | SC_TYPE_LAYOUT; spin_lock(&nn->client_lock); @@ -1946,12 +1953,19 @@ static struct nfs4_stid *find_one_export_stid(struct nfs4_client *clp, * * Userspace (exportfs -u) sends this after removing the last client * for a path, enabling the underlying filesystem to be unmounted. + * + * Context: Caller must hold nfsd_mutex with nn->nfsd_serv confirmed + * non-NULL. nfs4_state_destroy_net() frees conf_id_hashtbl + * at server shutdown without clearing the pointer, so a + * walk without these guarantees iterates freed slab memory. */ void nfsd4_revoke_export_states(struct nfsd_net *nn, const struct path *path) { unsigned int idhashval; unsigned int sc_types; + lockdep_assert_held(&nfsd_mutex); + sc_types = SC_TYPE_OPEN | SC_TYPE_LOCK | SC_TYPE_DELEG | SC_TYPE_LAYOUT; spin_lock(&nn->client_lock); From 22305d27bff545be97b088f5d50d37c4c3073b6f Mon Sep 17 00:00:00 2001 From: Steve French Date: Sun, 5 Jul 2026 16:04:09 -0500 Subject: [PATCH 483/562] smb: client: preserve leading slash for POSIX absolute symlink targets When creating a native SMB symbolic link (CIFS_SYMLINK_TYPE_NATIVE) whose target is an absolute path on a mount that uses POSIX paths, the leading path separator was silently dropped from the stored symlink target. create_native_symlink() converted the target to UTF-16 with cifs_convert_path_to_utf16(). That helper was intended for share-relative SMB paths and therefore unconditionally strips a leading path separator. For an absolute POSIX symlink target the leading '/' is significant, so a target of "/foo/bar" was stored and read back as "foo/bar", even though the reparse point was still flagged as absolute (SYMLINK_FLAG_RELATIVE cleared). On a POSIX paths mount the symlink target is stored verbatim, so convert it directly with cifs_strndup_to_utf16() instead. This preserves the leading separator, avoids the leading-backslash stripping that cifs_convert_path_to_utf16() also performs (a backslash is a valid POSIX filename character), and uses NO_MAP_UNI_RSVD to match the readback path in smb2_parse_native_symlink(), which always converts the target with cifs_strndup_from_utf16() / NO_MAP_UNI_RSVD. This mirrors how the NFS and WSL reparse symlink creators convert their targets. The NT-style absolute symlink handling, which needs the "\??\" prefix and drive-letter colon preserved, continues to use cifs_convert_path_to_utf16() together with the existing masking of those bytes. Fixes: 12b466eb52d9 ("cifs: Fix creating and resolving absolute NT-style symlinks") Reviewed-by: Paulo Alcantara (Red Hat) Signed-off-by: Steve French --- fs/smb/client/reparse.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c index cd1e1eaee67a..5cc5b0410d48 100644 --- a/fs/smb/client/reparse.c +++ b/fs/smb/client/reparse.c @@ -67,6 +67,7 @@ static int create_native_symlink(const unsigned int xid, struct inode *inode, char *sym = NULL; struct kvec iov; bool directory; + int path_len; int rc = 0; if (strlen(symname) > REPARSE_SYM_PATH_MAX) @@ -168,7 +169,21 @@ static int create_native_symlink(const unsigned int xid, struct inode *inode, if (!(sbflags & CIFS_MOUNT_POSIX_PATHS) && symname[0] == '/') sym[0] = sym[1] = sym[2] = sym[5] = '_'; - path = cifs_convert_path_to_utf16(sym, cifs_sb); + /* + * On a POSIX paths mount the symlink target is stored verbatim, so + * convert it with cifs_strndup_to_utf16(). cifs_convert_path_to_utf16() + * must not be used here: it strips a leading path separator (it is + * meant for share-relative SMB paths), which would corrupt an absolute + * POSIX symlink target such as "/foo/bar". Using NO_MAP_UNI_RSVD also + * matches the readback path in smb2_parse_native_symlink(). + */ + if (sbflags & CIFS_MOUNT_POSIX_PATHS) + path = cifs_strndup_to_utf16(sym, strlen(sym), &path_len, + cifs_sb->local_nls, + NO_MAP_UNI_RSVD); + else + path = cifs_convert_path_to_utf16(sym, cifs_sb); + if (!path) { rc = -ENOMEM; goto out; From 816059941cb6421d759b6abf1aac5d8b4d174617 Mon Sep 17 00:00:00 2001 From: Zizhi Wo Date: Sat, 4 Jul 2026 09:53:20 +0800 Subject: [PATCH 484/562] smb: client: fix busy dentry warning on unmount after DIO Commit c68337442f03 ("cifs: Fix busy dentry used after unmounting") fixed the issue in cifs where deferred close of a file led to a dentry reference count not being released in umount, by flushing deferredclose_wq in cifs_kill_sb() to solve it. However, the cifs DIO path suffers from the same busy-dentry problem caused by a delayed dentry reference-count release: [dio] [cifsd] [close + umount] netfs_unbuffered_write_iter_locked ... cifs_demultiplex_thread netfs_unbuffered_write cifs_issue_write netfs_wait_for_in_progress_stream [1] ... netfs_write_subrequest_terminated netfs_subreq_clear_in_progress netfs_wake_collector // wake [1] netfs_put_subrequest netfs_put_request queue_work(system_dfl_wq, xxx) [2] // dio write return cifs_close _cifsFileInfo_put // cfile->count 2->1 --cfile->count [3] // umount cifs_kill_sb kill_anon_super // warning triggered! shrink_dcache_for_umount [4] [system_dfl_wq] [5] netfs_free_request ... _cifsFileInfo_put // cfile->count 1->0 --cfile->count queue_work(fileinfo_put_wq, xxx) [fileinfo_put_wq] [6] cifsFileInfo_put_work cifsFileInfo_put_final dput If the umount path is triggered before [5], it results warning: BUG: Dentry 00000000eab1f070{i=9a917b66ae404fec,n=test} still in use (1) [unmount of cifs cifs] The existing per-inode ictx->io_count wait in cifs_evict_inode() does not help: it lives in the inode eviction path, which runs after shrink_dcache_for_umount() has already warned about the busy dentries. Fix it by adding a per-superblock outstanding-rreq counter that is incremented in cifs_init_request() and decremented in cifs_free_request(). In cifs_kill_sb(), before kill_anon_super(), wait for this counter to reach 0 - which guarantees that all cleanup_work for this sb have run and thus all relevant cfile puts are queued on fileinfo_put_wq or serverclose_wq. Then drain the workqueue so the dentry refs are dropped. This is a targeted wait, not a flush of the system-wide system_dfl_wq. Fixes: 340cea84f691c ("cifs: open files should not hold ref on superblock") Signed-off-by: Zizhi Wo Signed-off-by: Steve French --- fs/smb/client/cifs_fs_sb.h | 1 + fs/smb/client/cifsfs.c | 12 ++++++++++++ fs/smb/client/connect.c | 1 + fs/smb/client/file.c | 5 +++++ 4 files changed, 19 insertions(+) diff --git a/fs/smb/client/cifs_fs_sb.h b/fs/smb/client/cifs_fs_sb.h index 84e7e366b0ff..d6494e1d93cc 100644 --- a/fs/smb/client/cifs_fs_sb.h +++ b/fs/smb/client/cifs_fs_sb.h @@ -56,6 +56,7 @@ struct cifs_sb_info { struct smb3_fs_context *ctx; atomic_t active; atomic_t mnt_cifs_flags; + atomic_t outstanding_rreq; /* nr of rreqs not yet fully deinitialized */ struct delayed_work prune_tlinks; struct rcu_head rcu; diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c index ea4fc0fa68ca..4df6ca03a8de 100644 --- a/fs/smb/client/cifsfs.c +++ b/fs/smb/client/cifsfs.c @@ -311,6 +311,18 @@ static void cifs_kill_sb(struct super_block *sb) /* Wait for all opened files to release */ flush_workqueue(deferredclose_wq); + /* + * Wait for all in-flight netfs I/O requests to finish their + * cleanup_work so that any cifsFileInfo final puts they queue + * to fileinfo_put_wq/serverclose_wq have been queued, then + * drain the workqueue so the cfile dentry refs are dropped to + * avoid the busy dentry warning. + */ + wait_var_event(&cifs_sb->outstanding_rreq, + !atomic_read(&cifs_sb->outstanding_rreq)); + flush_workqueue(serverclose_wq); + flush_workqueue(fileinfo_put_wq); + /* finally release root dentry */ dput(cifs_sb->root); cifs_sb->root = NULL; diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c index 85aec302c89e..a187398fbabd 100644 --- a/fs/smb/client/connect.c +++ b/fs/smb/client/connect.c @@ -3485,6 +3485,7 @@ int cifs_setup_cifs_sb(struct cifs_sb_info *cifs_sb) spin_lock_init(&cifs_sb->tlink_tree_lock); cifs_sb->tlink_tree = RB_ROOT; + atomic_set(&cifs_sb->outstanding_rreq, 0); cifs_dbg(FYI, "file mode: %04ho dir mode: %04ho\n", ctx->file_mode, ctx->dir_mode); diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c index 5a25635bc62a..0f2dcc444c22 100644 --- a/fs/smb/client/file.c +++ b/fs/smb/client/file.c @@ -288,6 +288,7 @@ static int cifs_init_request(struct netfs_io_request *rreq, struct file *file) return smb_EIO1(smb_eio_trace_not_netfs_writeback, rreq->origin); } + atomic_inc(&cifs_sb->outstanding_rreq); return 0; } @@ -309,9 +310,13 @@ static void cifs_rreq_done(struct netfs_io_request *rreq) static void cifs_free_request(struct netfs_io_request *rreq) { struct cifs_io_request *req = container_of(rreq, struct cifs_io_request, rreq); + struct cifs_sb_info *cifs_sb = CIFS_SB(rreq->inode->i_sb); if (req->cfile) cifsFileInfo_put(req->cfile); + + if (atomic_dec_and_test(&cifs_sb->outstanding_rreq)) + wake_up_var(&cifs_sb->outstanding_rreq); } static void cifs_free_subrequest(struct netfs_io_subrequest *subreq) From 61957924eccf601c88cddb362931bb75e066497b Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Sat, 6 Jun 2026 15:00:08 +0100 Subject: [PATCH 485/562] kconfig: warn on dead default The dead default check was originally introduced with kconfirm: https://lore.kernel.org/all/6ec4df6d-1445-48ca-8f54-1d1a83c4716d@gmail.com/ While I'm still working on that tool, it's not yet ready for inclusion into the tree. I am currently waiting for common distro packagers to package the parsing library before submitting the next RFC iteration. However, the dead default check is more impactful than the other checks: all 4 dead defaults that were detected should not have been dead and could cause misconfiguration bugs. But fortunately, these were just for kunit tests. The 3 patches to fix them have all since been merged: commit aef656a0e6c0 ("powerpc: fix dead default for GUEST_STATE_BUFFER_TEST") commit 30cc5e2ad826 ("s390/Kconfig: Cleanup defaults for selftests") commit df75430515c3 ("drm: fix dead default for DRM_TTM_KUNIT_TEST") We can actually check for dead defaults while evaluating Kconfig, which should be even more effective at preventing future instances than keeping it in a static checker. Note that this patch will only trigger a warning when the default values are different, in other words, pure duplicate defaults won't cause a warning, as they are simply redundant. Signed-off-by: Julian Braha Link: https://patch.msgid.link/20260606140008.271929-1-julianbraha@gmail.com Signed-off-by: Nicolas Schier --- scripts/kconfig/menu.c | 22 +++++++++- .../kconfig/tests/warn_dead_default/Kconfig | 40 +++++++++++++++++++ .../tests/warn_dead_default/__init__.py | 8 ++++ .../tests/warn_dead_default/expected_stderr | 4 ++ 4 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 scripts/kconfig/tests/warn_dead_default/Kconfig create mode 100644 scripts/kconfig/tests/warn_dead_default/__init__.py create mode 100644 scripts/kconfig/tests/warn_dead_default/expected_stderr diff --git a/scripts/kconfig/menu.c b/scripts/kconfig/menu.c index b2d8d4e11e07..8c280292f9cd 100644 --- a/scripts/kconfig/menu.c +++ b/scripts/kconfig/menu.c @@ -242,13 +242,33 @@ static int menu_validate_number(struct symbol *sym, struct symbol *sym2) static void sym_check_prop(struct symbol *sym) { - struct property *prop; + struct property *prev, *prop; struct symbol *sym2; char *use; for (prop = sym->prop; prop; prop = prop->next) { switch (prop->type) { case P_DEFAULT: + for_all_defaults(sym, prev) { + if (prev == prop) + break; + if (expr_is_yes(prev->visible.expr)) { + if (!expr_eq(prev->expr, prop->expr)) + prop_warn(prop, + "default for '%s' is unreachable: earlier default at %s:%d is unconditional", + sym->name ? sym->name : "", + prev->filename, prev->lineno); + break; + } + if (expr_eq(prev->visible.expr, prop->visible.expr)) { + if (!expr_eq(prev->expr, prop->expr)) + prop_warn(prop, + "default for '%s' has the same condition as the earlier default at %s:%d", + sym->name ? sym->name : "", + prev->filename, prev->lineno); + break; + } + } if ((sym->type == S_STRING || sym->type == S_INT || sym->type == S_HEX) && prop->expr->type != E_SYMBOL) prop_warn(prop, diff --git a/scripts/kconfig/tests/warn_dead_default/Kconfig b/scripts/kconfig/tests/warn_dead_default/Kconfig new file mode 100644 index 000000000000..adf421d73dbd --- /dev/null +++ b/scripts/kconfig/tests/warn_dead_default/Kconfig @@ -0,0 +1,40 @@ +# SPDX-License-Identifier: GPL-2.0 + +config A + bool + +config B + bool + +config UNCONDITIONAL + int + default 1 + default 2 + +config CONDITIONAL + int + default 1 if A + default 2 if A + default 3 if B + +config CONDITIONAL_COMMUTATIVE + int + default 1 if A && B + default 2 if B && A + +config CONTROL + int + default 1 if A + default 2 if B + default 3 + +choice + prompt "test choice" + default C + default D + + config C + bool "C" + config D + bool "D" +endchoice diff --git a/scripts/kconfig/tests/warn_dead_default/__init__.py b/scripts/kconfig/tests/warn_dead_default/__init__.py new file mode 100644 index 000000000000..911b30ce19fe --- /dev/null +++ b/scripts/kconfig/tests/warn_dead_default/__init__.py @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +""" +Test detection of dead defaults (different defaults that can never be active). +""" + +def test(conf): + assert conf.olddefconfig() == 0 + assert conf.stderr_contains('expected_stderr') diff --git a/scripts/kconfig/tests/warn_dead_default/expected_stderr b/scripts/kconfig/tests/warn_dead_default/expected_stderr new file mode 100644 index 000000000000..baa20bf33910 --- /dev/null +++ b/scripts/kconfig/tests/warn_dead_default/expected_stderr @@ -0,0 +1,4 @@ +Kconfig:12:warning: default for 'UNCONDITIONAL' is unreachable: earlier default at Kconfig:11 is unconditional +Kconfig:17:warning: default for 'CONDITIONAL' has the same condition as the earlier default at Kconfig:16 +Kconfig:23:warning: default for 'CONDITIONAL_COMMUTATIVE' has the same condition as the earlier default at Kconfig:22 +Kconfig:34:warning: default for '' is unreachable: earlier default at Kconfig:33 is unconditional From 078f4b01a0cffa1873e37d6f0575274df3149f51 Mon Sep 17 00:00:00 2001 From: Ben Dooks Date: Mon, 22 Jun 2026 14:26:53 +0100 Subject: [PATCH 486/562] kbuild: remove srctree path from CHECK output The build does not put the full kernel path in when building outputs, so do the same when the check is run to make the output more consistent. turn the following: CC arch/riscv/lib/delay.o CHECK /home/ben/linux/arch/riscv/lib/delay.c into: CC arch/riscv/lib/delay.o CHECK arch/riscv/lib/delay.c Signed-off-by: Ben Dooks Acked-by: Nathan Chancellor Link: https://patch.msgid.link/20260622132653.446868-1-ben.dooks@codethink.co.uk [nsc: Fixed typo in subject line] Signed-off-by: Nicolas Schier --- scripts/Makefile.build | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/Makefile.build b/scripts/Makefile.build index 911745743246..d432693e5367 100644 --- a/scripts/Makefile.build +++ b/scripts/Makefile.build @@ -159,10 +159,10 @@ targets += $(targets-for-builtin) $(targets-for-modules) # Linus' kernel sanity checking tool ifeq ($(KBUILD_CHECKSRC),1) - quiet_cmd_checksrc = CHECK $< + quiet_cmd_checksrc = CHECK $(patsubst $(srctree)/%,%,$<) cmd_checksrc = $(CHECK) $(CHECKFLAGS) $(c_flags) $< else ifeq ($(KBUILD_CHECKSRC),2) - quiet_cmd_force_checksrc = CHECK $< + quiet_cmd_force_checksrc = CHECK $(patsubst $(srctree)/%,%,$<) cmd_force_checksrc = $(CHECK) $(CHECKFLAGS) $(c_flags) $< endif From e4fa2545a610dd36d3fdcd685c404f593329c27b Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Sat, 20 Jun 2026 00:25:42 +0800 Subject: [PATCH 487/562] wifi: cfg80211: cancel sched scan results work on unregister cfg80211_sched_scan_results() can queue rdev->sched_scan_res_wk from a driver result notification while a scheduled scan request is present. The work callback recovers the containing cfg80211_registered_device and then locks the wiphy and walks the scheduled-scan request list. wiphy_unregister() already makes the wiphy unreachable and drains rdev work items before cfg80211_dev_free() can release the object, but it does not drain sched_scan_res_wk. A queued or running result work item can therefore cross the unregister/free boundary and access freed rdev state. The buggy scenario involves two paths, with each column showing the order within that path: scheduled-scan result path: unregister/free path: 1. cfg80211_sched_scan_results() 1. interface teardown stops and queues rdev->sched_scan_res_wk. removes the scheduled scan request. 2. cfg80211_wq starts the work 2. wiphy_unregister() drains other item and recovers rdev. rdev work items. 3. The worker locks rdev->wiphy 3. cfg80211_dev_free() destroys and and walks rdev state. frees rdev. Cancel sched_scan_res_wk in wiphy_unregister() alongside the other rdev work items. cancel_work_sync() removes a pending result notification and waits for an already running callback, so cfg80211_dev_free() cannot free rdev while this work item is still active. Validation reproduced this kernel report: BUG: KASAN: use-after-free in cfg80211_sched_scan_results_wk+0x4a6/0x530 Workqueue: cfg80211 cfg80211_sched_scan_results_wk [cfg80211] Read of size 8 Call trace: dump_stack_lvl+0x66/0xa0 print_report+0xce/0x630 cfg80211_sched_scan_results_wk+0x4a6/0x530 srso_alias_return_thunk+0x5/0xfbef5 __virt_addr_valid+0x224/0x430 kasan_report+0xac/0xe0 lockdep_hardirqs_on_prepare+0xea/0x1a0 process_one_work+0x8d0/0x18f0 (kernel/workqueue.c:3212) lock_is_held_type+0x8f/0x100 worker_thread+0x5ad/0xfd0 __kthread_parkme+0xc6/0x200 kthread+0x31e/0x410 trace_hardirqs_on+0x1a/0x170 ret_from_fork+0x576/0x810 __switch_to+0x57e/0xe20 __switch_to_asm+0x33/0x70 ret_from_fork_asm+0x1a/0x30 Fixes: 807f8a8c3004 ("cfg80211/nl80211: add support for scheduled scans") Assisted-by: Codex:gpt-5.5 Signed-off-by: Cen Zhang Link: https://patch.msgid.link/20260619162542.3878296-1-zzzccc427@gmail.com --- net/wireless/core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/wireless/core.c b/net/wireless/core.c index 3dcf63b04c41..2c729a7aca12 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -1335,6 +1335,7 @@ void wiphy_unregister(struct wiphy *wiphy) /* this has nothing to do now but make sure it's gone */ cancel_work_sync(&rdev->wiphy_work); + cancel_work_sync(&rdev->sched_scan_res_wk); cancel_work_sync(&rdev->rfkill_block); cancel_work_sync(&rdev->conn_work); flush_work(&rdev->event_work); From 2260da8fd38aecbb4bc99660cd508312831cfa0b Mon Sep 17 00:00:00 2001 From: Abdun Nihaal Date: Sat, 20 Jun 2026 12:22:39 +0530 Subject: [PATCH 488/562] wifi: ipw2100: fix potential memory leak in ipw2100_pci_init_one() The memory allocated in the ipw2100_alloc_device() function is not freed in some of the error paths in ipw2100_pci_init_one(). Fix that by converting the direct return into a goto to the error path return. The error path when pci_enable_device() fails cannot jump to fail, since at this point priv is not set, so perform error handling inline. Fixes: 2c86c275015c ("Add ipw2100 wireless driver.") Signed-off-by: Abdun Nihaal Link: https://patch.msgid.link/20260620065242.93798-1-nihaal@cse.iitm.ac.in Signed-off-by: Johannes Berg --- drivers/net/wireless/intel/ipw2x00/ipw2100.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/intel/ipw2x00/ipw2100.c b/drivers/net/wireless/intel/ipw2x00/ipw2100.c index c11428485dcc..2b8a23865bfb 100644 --- a/drivers/net/wireless/intel/ipw2x00/ipw2100.c +++ b/drivers/net/wireless/intel/ipw2x00/ipw2100.c @@ -6157,6 +6157,8 @@ static int ipw2100_pci_init_one(struct pci_dev *pci_dev, if (err) { printk(KERN_WARNING DRV_NAME "Error calling pci_enable_device.\n"); + free_libipw(dev, 0); + pci_iounmap(pci_dev, ioaddr); return err; } @@ -6169,16 +6171,14 @@ static int ipw2100_pci_init_one(struct pci_dev *pci_dev, if (err) { printk(KERN_WARNING DRV_NAME "Error calling pci_set_dma_mask.\n"); - pci_disable_device(pci_dev); - return err; + goto fail; } err = pci_request_regions(pci_dev, DRV_NAME); if (err) { printk(KERN_WARNING DRV_NAME "Error calling pci_request_regions.\n"); - pci_disable_device(pci_dev); - return err; + goto fail; } /* We disable the RETRY_TIMEOUT register (0x41) to keep From 3425da27695d0afd38892a306ceeb485208c4cf9 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sat, 20 Jun 2026 21:48:56 +0200 Subject: [PATCH 489/562] wifi: cfg80211: Fix an error handling path in cfg80211_wext_siwscan() If the test against IEEE80211_MAX_SSID_LEN fails, then 'creq' leaks. Use the existing error handling path to fix it. Fixes: 2a5193119269 ("cfg80211/nl80211: scanning (and mac80211 update to use it)") Signed-off-by: Christophe JAILLET Link: https://patch.msgid.link/a1be7eea4da0da18f90589af252bb76a18a61978.1781984889.git.christophe.jaillet@wanadoo.fr Signed-off-by: Johannes Berg --- net/wireless/scan.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/net/wireless/scan.c b/net/wireless/scan.c index 05b7dc6b766c..38001684014d 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c @@ -3612,8 +3612,10 @@ int cfg80211_wext_siwscan(struct net_device *dev, /* translate "Scan for SSID" request */ if (wreq) { if (wrqu->data.flags & IW_SCAN_THIS_ESSID) { - if (wreq->essid_len > IEEE80211_MAX_SSID_LEN) - return -EINVAL; + if (wreq->essid_len > IEEE80211_MAX_SSID_LEN) { + err = -EINVAL; + goto out; + } memcpy(creq->req.ssids[0].ssid, wreq->essid, wreq->essid_len); creq->req.ssids[0].ssid_len = wreq->essid_len; From 8bc0195c26e039ba3b8257bbca67ca3aa298f85c Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Sat, 20 Jun 2026 21:45:18 -0500 Subject: [PATCH 490/562] wifi: mac80211_hwsim: clamp virtio RX length before skb_put hwsim_virtio_rx_work() passes the virtqueue used-ring length reported by the device straight to skb_put() on a fixed-size receive skb. A backend reporting a length larger than the skb tailroom drives skb_put() past the buffer end and hits skb_over_panic() -- a host-triggerable guest panic (denial of service). Clamp the length to the skb's available room before skb_put(). A conforming device never reports more than the posted buffer size, so valid frames are unaffected; a truncated over-report then fails the length/header checks in hwsim_virtio_handle_cmd() and is dropped, so truncating rather than dropping here cannot be turned into a parsing problem. Fixes: 5d44fe7c9808 ("mac80211_hwsim: add frame transmission support over virtio") Signed-off-by: Bryam Vargas Link: https://patch.msgid.link/20260620-b4-disp-474bee37-v1-1-1a4d37f3e2d4@proton.me Signed-off-by: Johannes Berg --- drivers/net/wireless/virtual/mac80211_hwsim_main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c index 0dd8a6c85953..5c1718277599 100644 --- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c +++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c @@ -7289,6 +7289,7 @@ static void hwsim_virtio_rx_work(struct work_struct *work) skb->data = skb->head; skb_reset_tail_pointer(skb); + len = min(len, skb_end_offset(skb)); skb_put(skb, len); hwsim_virtio_handle_cmd(skb); From b78bf0aa6f4375233dfd5cc22e027e6c5591ff76 Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Sun, 21 Jun 2026 02:35:31 -0700 Subject: [PATCH 491/562] wifi: mac80211: fix unsol_bcast_probe_resp double free on alloc failure ieee80211_set_unsol_bcast_probe_resp() calls kfree_rcu() on the old template before allocating the replacement. If the kzalloc() then fails, it returns -ENOMEM while link->u.ap.unsol_bcast_probe_resp still points at the object already queued for freeing. A later update or AP teardown re-queues that same rcu_head; the second free is caught by KASAN when the RCU sheaf is processed in softirq: BUG: KASAN: double-free in rcu_free_sheaf (mm/slub.c:5850) Free of addr ffff88800d06f300 by task exploit/145 ... __rcu_free_sheaf_prepare (mm/slub.c:2634 mm/slub.c:2940) rcu_free_sheaf (mm/slub.c:5850) rcu_core (kernel/rcu/tree.c:2617 kernel/rcu/tree.c:2869) handle_softirqs (kernel/softirq.c:622) The buggy address belongs to the cache kmalloc-128 of size 128 Queue the old object for kfree_rcu() only after the new one is published, matching ieee80211_set_probe_resp() and ieee80211_set_s1g_short_beacon(). Fixes: 3b1c256eb4ae ("wifi: mac80211: fixes in FILS discovery updates") Reported-by: Weiming Shi Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Xiang Mei Link: https://patch.msgid.link/20260621093532.884188-1-xmei5@asu.edu Signed-off-by: Johannes Berg --- net/mac80211/cfg.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 3b58af59f7e4..932cf20785bc 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -1179,8 +1179,6 @@ ieee80211_set_unsol_bcast_probe_resp(struct ieee80211_sub_if_data *sdata, link_conf->unsol_bcast_probe_resp_interval = params->interval; old = sdata_dereference(link->u.ap.unsol_bcast_probe_resp, sdata); - if (old) - kfree_rcu(old, rcu_head); if (params->tmpl && params->tmpl_len) { new = kzalloc(sizeof(*new) + params->tmpl_len, GFP_KERNEL); @@ -1193,6 +1191,9 @@ ieee80211_set_unsol_bcast_probe_resp(struct ieee80211_sub_if_data *sdata, RCU_INIT_POINTER(link->u.ap.unsol_bcast_probe_resp, NULL); } + if (old) + kfree_rcu(old, rcu_head); + *changed |= BSS_CHANGED_UNSOL_BCAST_PROBE_RESP; return 0; } From 2120acdfc9460c5344ecabf1041d3541084ff84f Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Sun, 21 Jun 2026 02:35:32 -0700 Subject: [PATCH 492/562] wifi: mac80211: fix fils_discovery double free on alloc failure ieee80211_set_fils_discovery() calls kfree_rcu() on the old template before allocating the replacement. If the kzalloc() then fails, it returns -ENOMEM while link->u.ap.fils_discovery still points at the object already queued for freeing. A later update or AP teardown (ieee80211_stop_ap()) re-queues that same rcu_head; the second free is caught by KASAN when the RCU sheaf is processed in softirq: BUG: KASAN: double-free in rcu_free_sheaf (mm/slub.c:5850) Free of addr ffff88800c065280 by task swapper/0/0 ... __rcu_free_sheaf_prepare (mm/slub.c:2634 mm/slub.c:2940) rcu_free_sheaf (mm/slub.c:5850) rcu_core (kernel/rcu/tree.c:2617 kernel/rcu/tree.c:2869) handle_softirqs (kernel/softirq.c:622) The buggy address belongs to the cache kmalloc-96 of size 96 Queue the old object for kfree_rcu() only after the new one is published, matching ieee80211_set_probe_resp() and ieee80211_set_s1g_short_beacon(). Fixes: 3b1c256eb4ae ("wifi: mac80211: fixes in FILS discovery updates") Reported-by: Weiming Shi Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Xiang Mei Link: https://patch.msgid.link/20260621093532.884188-2-xmei5@asu.edu Signed-off-by: Johannes Berg --- net/mac80211/cfg.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 932cf20785bc..b00191e02a63 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -1146,9 +1146,6 @@ static int ieee80211_set_fils_discovery(struct ieee80211_sub_if_data *sdata, fd->max_interval = params->max_interval; old = sdata_dereference(link->u.ap.fils_discovery, sdata); - if (old) - kfree_rcu(old, rcu_head); - if (params->tmpl && params->tmpl_len) { new = kzalloc(sizeof(*new) + params->tmpl_len, GFP_KERNEL); if (!new) @@ -1160,6 +1157,9 @@ static int ieee80211_set_fils_discovery(struct ieee80211_sub_if_data *sdata, RCU_INIT_POINTER(link->u.ap.fils_discovery, NULL); } + if (old) + kfree_rcu(old, rcu_head); + *changed |= BSS_CHANGED_FILS_DISCOVERY; return 0; } From 8de1106f05062f7d5882d49c9fd3a4cedae20876 Mon Sep 17 00:00:00 2001 From: Maoyi Xie Date: Mon, 22 Jun 2026 15:53:38 +0800 Subject: [PATCH 493/562] wifi: libertas_tf: fix use-after-free in lbtf_free_adapter() lbtf_free_adapter() calls timer_delete(&priv->command_timer), which does not wait for a running command_timer_fn() callback. lbtf_free_adapter() runs on the teardown path right before ieee80211_free_hw() frees priv, both in lbtf_remove_card() and in the probe error path. command_timer is armed by mod_timer() in lbtf_cmd() whenever a firmware command is sent. command_timer_fn() dereferences priv. If a command times out as the device is removed, command_timer_fn() runs concurrently with teardown and dereferences priv after it has been freed. This is the same use-after-free that commit 03cc8f90d053 ("wifi: libertas: fix use-after-free in lbs_free_adapter()") fixed in the sibling libertas driver. The libertas_tf variant has the identical pattern and was left unchanged. Use timer_delete_sync() so any in-flight callback completes before priv is freed. Fixes: 06b16ae53192 ("libertas_tf: main.c, data paths and mac80211 handlers") Cc: stable@vger.kernel.org Signed-off-by: Maoyi Xie Link: https://patch.msgid.link/178211481807.2212567.8773346114561900100@maoyixie.com Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/libertas_tf/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/marvell/libertas_tf/main.c b/drivers/net/wireless/marvell/libertas_tf/main.c index fb20fe31cd36..42be6fa22f9c 100644 --- a/drivers/net/wireless/marvell/libertas_tf/main.c +++ b/drivers/net/wireless/marvell/libertas_tf/main.c @@ -174,7 +174,7 @@ static void lbtf_free_adapter(struct lbtf_private *priv) { lbtf_deb_enter(LBTF_DEB_MAIN); lbtf_free_cmd_buffer(priv); - timer_delete(&priv->command_timer); + timer_delete_sync(&priv->command_timer); lbtf_deb_leave(LBTF_DEB_MAIN); } From ca1133bb9be2f35ba7482bf4466f0db1f2c5f3a5 Mon Sep 17 00:00:00 2001 From: Dawei Feng Date: Wed, 24 Jun 2026 16:53:43 +0800 Subject: [PATCH 494/562] wifi: libertas: fix memory leak in helper_firmware_cb() helper_firmware_cb() neglects to free the single-stage firmware image after a successful async load, leading to a memory leak in the USB firmware-download path. Fix this memory leak by calling release_firmware() immediately after lbs_fw_loaded() returns. The bug was first flagged by an experimental analysis tool we are developing for kernel memory-management bugs while analyzing v6.13-rc1. The tool is still under development and is not yet publicly available. Manual inspection confirms that the bug is still present in the current wireless tree. An x86_64 allyesconfig build showed no new warnings. As we do not have compatible Libertas USB hardware for exercising this firmware-download path, no runtime testing was able to be performed. Fixes: 1dfba3060fe7 ("libertas: move firmware lifetime handling to firmware.c") Signed-off-by: Dawei Feng Link: https://patch.msgid.link/20260624085343.575508-1-dawei.feng@seu.edu.cn Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/libertas/firmware.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/marvell/libertas/firmware.c b/drivers/net/wireless/marvell/libertas/firmware.c index f124110944b7..9bf7d4c207b9 100644 --- a/drivers/net/wireless/marvell/libertas/firmware.c +++ b/drivers/net/wireless/marvell/libertas/firmware.c @@ -78,6 +78,7 @@ static void helper_firmware_cb(const struct firmware *firmware, void *context) } else { /* No main firmware needed for this helper --> success! */ lbs_fw_loaded(priv, 0, firmware, NULL); + release_firmware(firmware); } } From 0f05858bd8864eb616221772f6b9dc3eaaebf2b3 Mon Sep 17 00:00:00 2001 From: Yousef Alhouseen Date: Sun, 28 Jun 2026 02:25:37 +0200 Subject: [PATCH 495/562] wifi: mac80211_hwsim: avoid treating MCS as legacy rate index Injected HT and VHT rates store an MCS value in rates[0].idx rather than an index into the legacy bitrate table. hwsim nevertheless passes these rates to ieee80211_get_tx_rate() while generating monitor frames and timestamps. A crafted injected frame can therefore read beyond the bitrate table. If the resulting bitrate is zero, mac80211_hwsim_write_tsf() also divides by zero, as observed by syzbot. Use ieee80211_get_tx_rate() only for legacy rates. The existing fallback continues to supply a conservative bitrate where hwsim does not yet calculate MCS rates. Reported-by: syzbot+21629c14aa749636db9d@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=21629c14aa749636db9d Signed-off-by: Yousef Alhouseen Link: https://patch.msgid.link/20260628002537.23550-1-alhouseenyousef@gmail.com [drop wrong Fixes tag] Signed-off-by: Johannes Berg --- .../net/wireless/virtual/mac80211_hwsim_main.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c index 5c1718277599..956ff9b94526 100644 --- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c +++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c @@ -1324,6 +1324,17 @@ static void mac80211_hwsim_set_tsf(struct ieee80211_hw *hw, } } +static struct ieee80211_rate * +mac80211_hwsim_get_tx_rate(struct ieee80211_hw *hw, + struct ieee80211_tx_info *info) +{ + if (info->control.rates[0].flags & + (IEEE80211_TX_RC_MCS | IEEE80211_TX_RC_VHT_MCS)) + return NULL; + + return ieee80211_get_tx_rate(hw, info); +} + static void mac80211_hwsim_monitor_rx(struct ieee80211_hw *hw, struct sk_buff *tx_skb, struct ieee80211_channel *chan) @@ -1333,7 +1344,7 @@ static void mac80211_hwsim_monitor_rx(struct ieee80211_hw *hw, struct hwsim_radiotap_hdr *hdr; u16 flags, bitrate; struct ieee80211_tx_info *info = IEEE80211_SKB_CB(tx_skb); - struct ieee80211_rate *txrate = ieee80211_get_tx_rate(hw, info); + struct ieee80211_rate *txrate = mac80211_hwsim_get_tx_rate(hw, info); if (!txrate) bitrate = 0; @@ -1603,7 +1614,7 @@ static void mac80211_hwsim_write_tsf(struct mac80211_hwsim_data *data, spin_lock_bh(&data->tsf_offset_lock); - txrate = ieee80211_get_tx_rate(data->hw, info); + txrate = mac80211_hwsim_get_tx_rate(data->hw, info); if (txrate) bitrate = txrate->bitrate; From 5ac09699ec55bc39a1ce92eceaa293cc308e4352 Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Sat, 27 Jun 2026 00:58:30 +0800 Subject: [PATCH 496/562] wifi: mac80211: free ack status frame on TX header build failure ieee80211_build_hdr() stores an ACK status frame before it has finished all validation and header construction. If a later error path is taken, the transmit skb is freed but the stored ACK status frame remains in local->ack_status_frames. This can happen for control port frames when the requested MLO link ID does not match the link selected for a non-MLO station. Repeated failures can fill the ACK status IDR and leave pending ACK frames until hardware teardown. Remove any stored ACK status frame before returning an error after it has been inserted into the IDR. Fixes: a729cff8ad51 ("mac80211: implement wifi TX status") Cc: stable@vger.kernel.org Reported-by: Yuan Tan Reported-by: Yifan Wu Reported-by: Juefei Pu Reported-by: Xin Liu Assisted-by: Codex:gpt-5.4 Signed-off-by: Zhiling Zou Signed-off-by: Ren Wei Link: https://patch.msgid.link/9de0423da840e92084915b8f92e66a421245c4b8.1782462409.git.roxy520tt@gmail.com Signed-off-by: Johannes Berg --- net/mac80211/tx.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index c13b209fad47..91b14112e24f 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -2607,6 +2607,18 @@ static u16 ieee80211_store_ack_skb(struct ieee80211_local *local, return info_id; } +static void ieee80211_remove_ack_skb(struct ieee80211_local *local, u16 info_id) +{ + struct sk_buff *ack_skb; + unsigned long flags; + + spin_lock_irqsave(&local->ack_status_lock, flags); + ack_skb = idr_remove(&local->ack_status_frames, info_id); + spin_unlock_irqrestore(&local->ack_status_lock, flags); + + kfree_skb(ack_skb); +} + /** * ieee80211_build_hdr - build 802.11 header in the given frame * @sdata: virtual interface to build the header for @@ -2982,7 +2994,8 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, if (ieee80211_skb_resize(sdata, skb, head_need, ENCRYPT_DATA)) { ieee80211_free_txskb(&local->hw, skb); skb = NULL; - return ERR_PTR(-ENOMEM); + ret = -ENOMEM; + goto free; } } @@ -3050,6 +3063,8 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, return skb; free: + if (info_id) + ieee80211_remove_ack_skb(local, info_id); kfree_skb(skb); return ERR_PTR(ret); } From 4ea6f3b2cc8b0fcdd7a50038affaa31a28634b1a Mon Sep 17 00:00:00 2001 From: Maoyi Xie Date: Sat, 27 Jun 2026 16:30:28 +0800 Subject: [PATCH 497/562] wifi: mac80211: defer link RX stats percpu free to RCU sta_remove_link() frees a removed MLO link's RX stats percpu buffer right away, but defers only the link container to RCU: sta_info_free_link(&alloc->info); kfree_rcu(alloc, rcu_head); The RX fast path reads link_sta under rcu_read_lock and writes the percpu stats. A reader that resolved link_sta before the removal keeps the pointer. The container stays alive from the kfree_rcu, so the read still works. But the percpu block it points to is already freed. This needs uses_rss. That is when pcpu_rx_stats exists. The full STA teardown frees the deflink stats only after synchronize_net(). The link removal path had no such barrier. The race is hard to win in practice, but the free should still wait for RCU. Free the link together with its data from a single RCU callback, so the percpu block is reclaimed only after readers drain. Fixes: c71420db653a ("wifi: mac80211: RCU-ify link STA pointers") Link: https://lore.kernel.org/r/20260626080158.3589711-1-maoyixie.tju@gmail.com Suggested-by: Johannes Berg Co-developed-by: Kaixuan Li Signed-off-by: Kaixuan Li Signed-off-by: Maoyi Xie Link: https://patch.msgid.link/20260627083028.3826810-1-maoyixie.tju@gmail.com Signed-off-by: Johannes Berg --- net/mac80211/sta_info.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c index 02b587ff8504..22eba0e6e54c 100644 --- a/net/mac80211/sta_info.c +++ b/net/mac80211/sta_info.c @@ -355,6 +355,15 @@ static void sta_info_free_link(struct link_sta_info *link_sta) free_percpu(link_sta->pcpu_rx_stats); } +static void sta_link_free_rcu(struct rcu_head *head) +{ + struct sta_link_alloc *alloc = + container_of(head, struct sta_link_alloc, rcu_head); + + sta_info_free_link(&alloc->info); + kfree(alloc); +} + static void sta_accumulate_removed_link_stats(struct sta_info *sta, int link_id) { struct link_sta_info *link_sta = wiphy_dereference(sta->local->hw.wiphy, @@ -439,10 +448,8 @@ static void sta_remove_link(struct sta_info *sta, unsigned int link_id, RCU_INIT_POINTER(sta->link[link_id], NULL); RCU_INIT_POINTER(sta->sta.link[link_id], NULL); - if (alloc) { - sta_info_free_link(&alloc->info); - kfree_rcu(alloc, rcu_head); - } + if (alloc) + call_rcu(&alloc->rcu_head, sta_link_free_rcu); ieee80211_sta_recalc_aggregates(&sta->sta); } From 8b78a1d7430b460c58162987e3e40d71bc8864bd Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Sat, 27 Jun 2026 17:05:10 -0700 Subject: [PATCH 498/562] wifi: p54: validate RX frame length in p54_rx_eeprom_readback() p54_rx_eeprom_readback() copies the requested EEPROM slice out of a device-supplied readback frame without checking that the skb actually holds that many bytes. Commit da1b9a55ff11 ("wifi: p54: prevent buffer-overflow in p54_rx_eeprom_readback()") closed the destination overflow by copying a fixed priv->eeprom_slice_size (and rejecting a mismatched advertised len), but the source side is still unbounded: nothing verifies the frame is long enough to supply that many bytes. A malicious USB device can send a short frame whose advertised len matches priv->eeprom_slice_size while the payload is truncated. The equality check passes and memcpy() reads past the end of the skb, leaking adjacent heap: BUG: KASAN: slab-out-of-bounds in p54_rx (drivers/net/wireless/intersil/p54/txrx.c:507) Read of size 1016 at addr ffff88800f077114 by task swapper/0/0 Call Trace: ... __asan_memcpy (mm/kasan/shadow.c:105) p54_rx (drivers/net/wireless/intersil/p54/txrx.c:507) p54u_rx_cb (drivers/net/wireless/intersil/p54/p54usb.c:163) __usb_hcd_giveback_urb (drivers/usb/core/hcd.c:1657) dummy_timer (drivers/usb/gadget/udc/dummy_hcd.c:2005) ... The buggy address belongs to the object at ffff88800f0770c0 which belongs to the cache skbuff_small_head of size 704 The buggy address is located 84 bytes inside of allocated 704-byte region [ffff88800f0770c0, ffff88800f077380) Check that the slice fits in the skb before copying. Fixes: 7cb770729ba8 ("p54: move eeprom code into common library") Reported-by: Weiming Shi Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Xiang Mei Acked-by: Christian Lamparter Link: https://patch.msgid.link/20260628000510.4152481-1-xmei5@asu.edu Signed-off-by: Johannes Berg --- drivers/net/wireless/intersil/p54/txrx.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/net/wireless/intersil/p54/txrx.c b/drivers/net/wireless/intersil/p54/txrx.c index 1294a1d6528e..9f491334c8d0 100644 --- a/drivers/net/wireless/intersil/p54/txrx.c +++ b/drivers/net/wireless/intersil/p54/txrx.c @@ -499,11 +499,19 @@ static void p54_rx_eeprom_readback(struct p54_common *priv, if (le16_to_cpu(eeprom->v2.len) != priv->eeprom_slice_size) return; + if (eeprom->v2.data + priv->eeprom_slice_size > + skb_tail_pointer(skb)) + return; + memcpy(priv->eeprom, eeprom->v2.data, priv->eeprom_slice_size); } else { if (le16_to_cpu(eeprom->v1.len) != priv->eeprom_slice_size) return; + if (eeprom->v1.data + priv->eeprom_slice_size > + skb_tail_pointer(skb)) + return; + memcpy(priv->eeprom, eeprom->v1.data, priv->eeprom_slice_size); } From 6b0d481d85f08ff2a0ef034891ddf4472f9d9018 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 1 Jul 2026 13:34:14 +0800 Subject: [PATCH 499/562] wifi: rsi: avoid reading TKIP MIC keys for non-TKIP ciphers rsi_hal_load_key() copies tx_mic_key and rx_mic_key from data[16] and data[24] whenever key data is present. Those offsets are only part of the 32-byte TKIP key layout. Shorter keys used by other ciphers, such as CCMP, do not provide those bytes, so the unconditional copies can read past the supplied key buffer. Only copy the MIC keys for TKIP, and reject malformed TKIP keys that are shorter than the expected 32-byte layout. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260701053414.34015-1-pengpeng@iscas.ac.cn [drop useless length check] Signed-off-by: Johannes Berg --- drivers/net/wireless/rsi/rsi_91x_mgmt.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/rsi/rsi_91x_mgmt.c b/drivers/net/wireless/rsi/rsi_91x_mgmt.c index 7f2c1608f2ce..2ddf4d158bfe 100644 --- a/drivers/net/wireless/rsi/rsi_91x_mgmt.c +++ b/drivers/net/wireless/rsi/rsi_91x_mgmt.c @@ -848,8 +848,10 @@ int rsi_hal_load_key(struct rsi_common *common, } else { memcpy(&set_key->key[0][0], data, key_len); } - memcpy(set_key->tx_mic_key, &data[16], 8); - memcpy(set_key->rx_mic_key, &data[24], 8); + if (cipher == WLAN_CIPHER_SUITE_TKIP) { + memcpy(set_key->tx_mic_key, &data[16], 8); + memcpy(set_key->rx_mic_key, &data[24], 8); + } } else { memset(&set_key[FRAME_DESC_SZ], 0, frame_len - FRAME_DESC_SZ); } From 140c5aea1402e280028fa457c06fbdc5666553a3 Mon Sep 17 00:00:00 2001 From: Haofeng Li Date: Wed, 1 Jul 2026 17:33:27 +0800 Subject: [PATCH 500/562] wifi: cfg80211: validate EHT MLE before MLD ID read cfg80211_gen_new_ie() copies ML probe response elements from the parent frame when the parent EHT multi-link element has an MLD ID matching the nontransmitted BSSID index. The code only checked that the extension element had more than one byte before calling ieee80211_mle_get_mld_id(). That helper assumes a BASIC MLE with enough common info and documents that callers must first use ieee80211_mle_type_ok(). Attack chain: malicious AP sends a short EHT MLE in an MBSSID beacon. cfg80211_inform_bss_frame_data() stores the copied IE buffer. cfg80211_parse_mbssid_data() builds the nontransmitted BSS IE. cfg80211_gen_new_ie() sees the EHT MLE in the parent frame. ieee80211_mle_get_mld_id() then reads past the IE boundary. Validate the MLE type and size before reading the MLD ID. This matches the contract required by the MLE helper and rejects the short element before any internal MLE fields are accessed. Cc: stable@vger.kernel.org Fixes: 61dcfa8c2a8f ("wifi: cfg80211: copy multi-link element from the multi-link probe request's frame body to the generated elements") Signed-off-by: Haofeng Li Link: https://patch.msgid.link/20260701093327.2680709-1-lihaofeng@kylinos.cn Signed-off-by: Johannes Berg --- net/wireless/scan.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/net/wireless/scan.c b/net/wireless/scan.c index 38001684014d..e1c09040a5c8 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c @@ -326,8 +326,11 @@ cfg80211_gen_new_ie(const u8 *ie, size_t ielen, /* For ML probe response, match the MLE in the frame body with * MLD id being 'bssid_index' */ - if (parent->id == WLAN_EID_EXTENSION && parent->datalen > 1 && + if (parent->id == WLAN_EID_EXTENSION && parent->data[0] == WLAN_EID_EXT_EHT_MULTI_LINK && + ieee80211_mle_type_ok(parent->data + 1, + IEEE80211_ML_CONTROL_TYPE_BASIC, + parent->datalen - 1) && bssid_index == ieee80211_mle_get_mld_id(parent->data + 1)) { if (!cfg80211_copy_elem_with_frags(parent, ie, ielen, From cbf175c43152427ec28ee90121e94baa073db369 Mon Sep 17 00:00:00 2001 From: Peddolla Harshavardhan Reddy Date: Fri, 3 Jul 2026 13:55:23 +0530 Subject: [PATCH 501/562] wifi: cfg80211: convert pmsr_free_wk to wiphy_work to fix deadlock When a netlink socket that owns a PMSR session is closed, cfg80211_release_pmsr() clears the request's nl_portid and queues pmsr_free_wk to call cfg80211_pmsr_process_abort() asynchronously. If the interface tears down concurrently, cfg80211_pmsr_wdev_down() is called under wiphy_lock and calls cancel_work_sync(&pmsr_free_wk) to wait for any running work. The work function acquires wiphy_lock via guard(wiphy) before calling process_abort. This is a deadlock: wdev_down holds wiphy_lock and blocks inside cancel_work_sync(); pmsr_free_wk blocks trying to acquire that same wiphy_lock. Neither thread can proceed. The same deadlock is reachable from cfg80211_leave_locked(), which calls cfg80211_pmsr_wdev_down() for all interface types under wiphy_lock. Fix this by converting pmsr_free_wk from a plain work_struct to a wiphy_work. The wiphy_work dispatcher holds wiphy_lock when running work items, so the explicit guard(wiphy) in the work function is no longer needed. wiphy_work_cancel() can be called safely while holding wiphy_lock - since wiphy_lock prevents the work from running concurrently, wiphy_work_cancel() never blocks, eliminating the deadlock. Remove the cancel_work_sync() for pmsr_free_wk from the NETDEV_GOING_DOWN handler. cfg80211_leave(), called unconditionally just before it, already cancels any pending work under wiphy_lock via wiphy_work_cancel() inside cfg80211_pmsr_wdev_down(). Fixes: 6dccbc9f3e1d ("wifi: cfg80211: cancel pmsr_free_wk in cfg80211_pmsr_wdev_down") Signed-off-by: Peddolla Harshavardhan Reddy Link: https://patch.msgid.link/20260703082523.2629324-1-peddolla.reddy@oss.qualcomm.com Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 2 +- net/wireless/core.c | 3 +-- net/wireless/core.h | 2 +- net/wireless/pmsr.c | 8 +++----- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 8188ad200de5..3751a1d74765 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -7265,7 +7265,7 @@ struct wireless_dev { struct list_head pmsr_list; spinlock_t pmsr_lock; - struct work_struct pmsr_free_wk; + struct wiphy_work pmsr_free_wk; unsigned long unprot_beacon_reported; diff --git a/net/wireless/core.c b/net/wireless/core.c index 2c729a7aca12..082f0ee12f1b 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -1614,7 +1614,7 @@ void cfg80211_init_wdev(struct wireless_dev *wdev) INIT_LIST_HEAD(&wdev->mgmt_registrations); INIT_LIST_HEAD(&wdev->pmsr_list); spin_lock_init(&wdev->pmsr_lock); - INIT_WORK(&wdev->pmsr_free_wk, cfg80211_pmsr_free_wk); + wiphy_work_init(&wdev->pmsr_free_wk, cfg80211_pmsr_free_wk); #ifdef CONFIG_CFG80211_WEXT wdev->wext.default_key = -1; @@ -1748,7 +1748,6 @@ static int cfg80211_netdev_notifier_call(struct notifier_block *nb, cfg80211_remove_links(wdev); /* since we just did cfg80211_leave() nothing to do there */ cancel_work_sync(&wdev->disconnect_wk); - cancel_work_sync(&wdev->pmsr_free_wk); break; case NETDEV_DOWN: wiphy_lock(&rdev->wiphy); diff --git a/net/wireless/core.h b/net/wireless/core.h index df47ed6208a5..f60c66b88677 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -586,7 +586,7 @@ cfg80211_get_6ghz_power_type(const u8 *elems, size_t elems_len, void cfg80211_release_pmsr(struct wireless_dev *wdev, u32 portid); void cfg80211_pmsr_wdev_down(struct wireless_dev *wdev); -void cfg80211_pmsr_free_wk(struct work_struct *work); +void cfg80211_pmsr_free_wk(struct wiphy *wiphy, struct wiphy_work *work); void cfg80211_remove_link(struct wireless_dev *wdev, unsigned int link_id); void cfg80211_remove_links(struct wireless_dev *wdev); diff --git a/net/wireless/pmsr.c b/net/wireless/pmsr.c index c8447448f3a5..2c8db33d9c30 100644 --- a/net/wireless/pmsr.c +++ b/net/wireless/pmsr.c @@ -807,13 +807,11 @@ static void cfg80211_pmsr_process_abort(struct wireless_dev *wdev) } } -void cfg80211_pmsr_free_wk(struct work_struct *work) +void cfg80211_pmsr_free_wk(struct wiphy *wiphy, struct wiphy_work *work) { struct wireless_dev *wdev = container_of(work, struct wireless_dev, pmsr_free_wk); - guard(wiphy)(wdev->wiphy); - cfg80211_pmsr_process_abort(wdev); } @@ -829,7 +827,7 @@ void cfg80211_pmsr_wdev_down(struct wireless_dev *wdev) } spin_unlock_bh(&wdev->pmsr_lock); - cancel_work_sync(&wdev->pmsr_free_wk); + wiphy_work_cancel(wdev->wiphy, &wdev->pmsr_free_wk); if (found) cfg80211_pmsr_process_abort(wdev); @@ -844,7 +842,7 @@ void cfg80211_release_pmsr(struct wireless_dev *wdev, u32 portid) list_for_each_entry(req, &wdev->pmsr_list, list) { if (req->nl_portid == portid) { req->nl_portid = 0; - schedule_work(&wdev->pmsr_free_wk); + wiphy_work_queue(wdev->wiphy, &wdev->pmsr_free_wk); } } spin_unlock_bh(&wdev->pmsr_lock); From ef28acb38a1c61e629e9e99e6692787d8f55e023 Mon Sep 17 00:00:00 2001 From: Corentin Labbe Date: Fri, 3 Jul 2026 13:49:32 +0000 Subject: [PATCH 502/562] wifi: ralink: RT2X00: init EEPROM properly I have an hostapd setup with a 01:00.0 Network controller: Ralink corp. RT2790 Wireless 802.11n 1T/2R PCIe The setup work fine on 6.18.26-gentoo It breaks on 6.18.33-gentoo (and still broken on 6.18.37) I found an hint in dmesg: On 6.18.26-gentoo I see: May 31 15:48:45 trash01 kernel: ieee80211 phy0: rt2x00_set_rf: Info - RF chipset 0003 detected On 6.18.33-gentoo I see: May 31 15:22:57 trash01 kernel: ieee80211 phy0: rt2x00_set_rf: Info - RF chipset 0006 detected The RF chipset seems badly detected. The problem was the EEPROM which was badly initialized. Probably the origin was in some PCI change but unfortunately I couldn't play to bisect/reboot often the board with this card to do it. Signed-off-by: Corentin Labbe Acked-by: Stanislaw Gruszka Link: https://patch.msgid.link/20260703134932.3786771-1-clabbe@baylibre.com Signed-off-by: Johannes Berg --- drivers/net/wireless/ralink/rt2x00/rt2400pci.c | 2 +- drivers/net/wireless/ralink/rt2x00/rt2500pci.c | 2 +- drivers/net/wireless/ralink/rt2x00/rt2800pci.c | 2 +- drivers/net/wireless/ralink/rt2x00/rt61pci.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/ralink/rt2x00/rt2400pci.c b/drivers/net/wireless/ralink/rt2x00/rt2400pci.c index cac191304bf5..b846fe589c2b 100644 --- a/drivers/net/wireless/ralink/rt2x00/rt2400pci.c +++ b/drivers/net/wireless/ralink/rt2x00/rt2400pci.c @@ -1429,7 +1429,7 @@ static irqreturn_t rt2400pci_interrupt(int irq, void *dev_instance) */ static int rt2400pci_validate_eeprom(struct rt2x00_dev *rt2x00dev) { - struct eeprom_93cx6 eeprom; + struct eeprom_93cx6 eeprom = {}; u32 reg; u16 word; u8 *mac; diff --git a/drivers/net/wireless/ralink/rt2x00/rt2500pci.c b/drivers/net/wireless/ralink/rt2x00/rt2500pci.c index fc35b60e422c..be9df35acc33 100644 --- a/drivers/net/wireless/ralink/rt2x00/rt2500pci.c +++ b/drivers/net/wireless/ralink/rt2x00/rt2500pci.c @@ -1555,7 +1555,7 @@ static irqreturn_t rt2500pci_interrupt(int irq, void *dev_instance) */ static int rt2500pci_validate_eeprom(struct rt2x00_dev *rt2x00dev) { - struct eeprom_93cx6 eeprom; + struct eeprom_93cx6 eeprom = {}; u32 reg; u16 word; u8 *mac; diff --git a/drivers/net/wireless/ralink/rt2x00/rt2800pci.c b/drivers/net/wireless/ralink/rt2x00/rt2800pci.c index 4fa14bb573ad..2596b9fcc7dd 100644 --- a/drivers/net/wireless/ralink/rt2x00/rt2800pci.c +++ b/drivers/net/wireless/ralink/rt2x00/rt2800pci.c @@ -108,7 +108,7 @@ static void rt2800pci_eepromregister_write(struct eeprom_93cx6 *eeprom) static int rt2800pci_read_eeprom_pci(struct rt2x00_dev *rt2x00dev) { - struct eeprom_93cx6 eeprom; + struct eeprom_93cx6 eeprom = {}; u32 reg; reg = rt2x00mmio_register_read(rt2x00dev, E2PROM_CSR); diff --git a/drivers/net/wireless/ralink/rt2x00/rt61pci.c b/drivers/net/wireless/ralink/rt2x00/rt61pci.c index 79e1fd0a1fbd..d4783658b2c5 100644 --- a/drivers/net/wireless/ralink/rt2x00/rt61pci.c +++ b/drivers/net/wireless/ralink/rt2x00/rt61pci.c @@ -2298,7 +2298,7 @@ static irqreturn_t rt61pci_interrupt(int irq, void *dev_instance) */ static int rt61pci_validate_eeprom(struct rt2x00_dev *rt2x00dev) { - struct eeprom_93cx6 eeprom; + struct eeprom_93cx6 eeprom = {}; u32 reg; u16 word; u8 *mac; From 4d7ed3b9fd7b660cb160e9d87f2963db109f8d06 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sat, 4 Jul 2026 09:11:40 +0800 Subject: [PATCH 503/562] wifi: libertas: reject short monitor TX frames In monitor mode, lbs_hard_start_xmit() casts skb->data to a radiotap TX header, skips that header, and then copies the 802.11 destination address from offset 4 in the remaining frame. The generic length check only rejects zero-length and oversized skbs, so a short monitor frame can be read past the end of the skb data. Require enough bytes for the radiotap TX header and the destination address field before using the monitor-mode header layout. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260704011140.37639-1-pengpeng@iscas.ac.cn Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/libertas/tx.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/net/wireless/marvell/libertas/tx.c b/drivers/net/wireless/marvell/libertas/tx.c index 27304a98787d..13d08022e414 100644 --- a/drivers/net/wireless/marvell/libertas/tx.c +++ b/drivers/net/wireless/marvell/libertas/tx.c @@ -117,6 +117,13 @@ netdev_tx_t lbs_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) if (priv->wdev->iftype == NL80211_IFTYPE_MONITOR) { struct tx_radiotap_hdr *rtap_hdr = (void *)skb->data; + if (skb->len < sizeof(*rtap_hdr) + 4 + ETH_ALEN) { + lbs_deb_tx("tx err: short monitor frame %u\n", skb->len); + dev->stats.tx_dropped++; + dev->stats.tx_errors++; + goto free; + } + /* set txpd fields from the radiotap header */ txpd->tx_control = cpu_to_le32(convert_radiotap_rate_to_mv(rtap_hdr->rate)); From c7ec00f7e991f1a428b01f00a9f7fd8ed1fefc60 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sat, 4 Jul 2026 09:12:30 +0800 Subject: [PATCH 504/562] wifi: rsi: bound background scan probe request copy rsi_send_bgscan_probe_req() allocates room for struct rsi_bgscan_probe plus MAX_BGSCAN_PROBE_REQ_LEN bytes, but copies the entire mac80211-generated probe request skb after the fixed header. The probe request length depends on scan IEs and is not checked against the fixed firmware buffer. Reject generated probe requests that do not fit the firmware command buffer before copying them into the skb. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260704011231.45593-1-pengpeng@iscas.ac.cn Signed-off-by: Johannes Berg --- drivers/net/wireless/rsi/rsi_91x_mgmt.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/wireless/rsi/rsi_91x_mgmt.c b/drivers/net/wireless/rsi/rsi_91x_mgmt.c index 2ddf4d158bfe..bb167f03367b 100644 --- a/drivers/net/wireless/rsi/rsi_91x_mgmt.c +++ b/drivers/net/wireless/rsi/rsi_91x_mgmt.c @@ -1913,6 +1913,12 @@ int rsi_send_bgscan_probe_req(struct rsi_common *common, return -ENOMEM; } + if (probereq_skb->len > MAX_BGSCAN_PROBE_REQ_LEN) { + dev_kfree_skb(probereq_skb); + dev_kfree_skb(skb); + return -EINVAL; + } + memcpy(&skb->data[frame_len], probereq_skb->data, probereq_skb->len); bgscan->probe_req_length = cpu_to_le16(probereq_skb->len); From 50b18c66b911375521eb90ace6abac058648d964 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 5 Jul 2026 16:35:19 +0800 Subject: [PATCH 505/562] wifi: libipw: fix key index receive bound checks libipw_rx() reads skb->data[hdrlen + 3] to extract the WEP key index in both the software-decrypt key selection path and the hardware-decrypted IV/ICV strip path. In both places the existing guard only checks skb->len >= hdrlen + 3, which proves bytes up to hdrlen + 2 but not the byte at hdrlen + 3. Require hdrlen + 4 bytes before reading that item in both paths. This is a local source-boundary check only; it does not change the key index semantics. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260705083519.23567-1-pengpeng@iscas.ac.cn Signed-off-by: Johannes Berg --- drivers/net/wireless/intel/ipw2x00/libipw_rx.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/intel/ipw2x00/libipw_rx.c b/drivers/net/wireless/intel/ipw2x00/libipw_rx.c index b7bc94f7abd8..c8841f9b9ad9 100644 --- a/drivers/net/wireless/intel/ipw2x00/libipw_rx.c +++ b/drivers/net/wireless/intel/ipw2x00/libipw_rx.c @@ -414,7 +414,7 @@ int libipw_rx(struct libipw_device *ieee, struct sk_buff *skb, ieee->host_mc_decrypt : ieee->host_decrypt; if (can_be_decrypted) { - if (skb->len >= hdrlen + 3) { + if (skb->len >= hdrlen + 4) { /* Top two-bits of byte 3 are the key index */ keyidx = skb->data[hdrlen + 3] >> 6; } @@ -660,7 +660,7 @@ int libipw_rx(struct libipw_device *ieee, struct sk_buff *skb, int trimlen = 0; /* Top two-bits of byte 3 are the key index */ - if (skb->len >= hdrlen + 3) + if (skb->len >= hdrlen + 4) keyidx = skb->data[hdrlen + 3] >> 6; /* To strip off any security data which appears before the From 5e672342d8f2598f1f1ca528cff5b0ce0a77d006 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 5 Jul 2026 16:48:24 +0800 Subject: [PATCH 506/562] wifi: rsi: validate beacon length before fixed buffer copy rsi_prepare_beacon() copies the mac80211 beacon frame after FRAME_DESC_SZ into a management skb whose usable tailroom may be smaller than MAX_MGMT_PKT_SIZE after alignment. Validate the beacon length against the actual tailroom before the copy and skb_put(). Leave ownership of the management skb with the caller on error, matching the existing rsi_send_beacon() cleanup path. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260705084824.68105-1-pengpeng@iscas.ac.cn Signed-off-by: Johannes Berg --- drivers/net/wireless/rsi/rsi_91x_hal.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/net/wireless/rsi/rsi_91x_hal.c b/drivers/net/wireless/rsi/rsi_91x_hal.c index a0c36144eb0b..501071104a9f 100644 --- a/drivers/net/wireless/rsi/rsi_91x_hal.c +++ b/drivers/net/wireless/rsi/rsi_91x_hal.c @@ -431,6 +431,7 @@ int rsi_prepare_beacon(struct rsi_common *common, struct sk_buff *skb) struct ieee80211_vif *vif; struct sk_buff *mac_bcn; u8 vap_id = 0, i; + unsigned int tailroom; u16 tim_offset = 0; for (i = 0; i < RSI_MAX_VIFS; i++) { @@ -480,6 +481,13 @@ int rsi_prepare_beacon(struct rsi_common *common, struct sk_buff *skb) if (mac_bcn->data[tim_offset + 2] == 0) bcn_frm->frame_info |= cpu_to_le16(RSI_DATA_DESC_DTIM_BEACON); + tailroom = skb_tailroom(skb); + if (tailroom < FRAME_DESC_SZ || + mac_bcn->len > tailroom - FRAME_DESC_SZ) { + dev_kfree_skb(mac_bcn); + return -EMSGSIZE; + } + memcpy(&skb->data[FRAME_DESC_SZ], mac_bcn->data, mac_bcn->len); skb_put(skb, mac_bcn->len + FRAME_DESC_SZ); From 94b9b9643624f01337815191c0def6079d487eb5 Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Wed, 10 Jun 2026 19:22:09 +0800 Subject: [PATCH 507/562] wifi: nl80211: free RNR data on MBSSID mismatch nl80211_parse_beacon() rejects EMA RNR data when there are fewer RNR entries than MBSSID entries. The rejected RNR allocation has not been attached to the beacon data yet, so free it before returning the error. Fixes: dbbb27e183b1 ("cfg80211: support RNR for EMA AP") Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260610112208.1308-2-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 53b4b3f76697..056388c04599 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -6803,8 +6803,10 @@ static int nl80211_parse_beacon(struct cfg80211_registered_device *rdev, if (IS_ERR(rnr)) return PTR_ERR(rnr); - if (rnr && rnr->cnt < bcn->mbssid_ies->cnt) + if (rnr && rnr->cnt < bcn->mbssid_ies->cnt) { + kfree(rnr); return -EINVAL; + } bcn->rnr_ies = rnr; } From 0827ba44238f6e9ecb8bf4c4d9b20c196a47c1dc Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Fri, 12 Jun 2026 00:19:45 +0800 Subject: [PATCH 508/562] wifi: mac80211: validate extension-frame layout before RX Extension frames only have the extension header at the regular 802.11 header offset. The generic RX path can still reach helpers and interface dispatch code that read regular header address fields before unsupported extension subtypes are dropped. mac80211 currently only handles S1G beacon extension frames. Drop other extension subtypes before they can reach regular-header RX processing. For S1G beacons, linearize the SKB with the management-frame path and require the fixed S1G beacon header, including optional fixed fields indicated by frame control, before generic RX dispatch. Route S1G beacons through the station/default-link RX path without regular-header station lookup. Avoid regular-header address reads in the mac80211 RX paths that process S1G extension beacons, including accept-frame, duplicate-detection, address-copy, and MLO address-translation paths. Also make ieee80211_get_bssid() length-safe before returning the S1G source-address pointer. Fixes: 09a740ce352e ("mac80211: receive and process S1G beacons") Cc: stable@vger.kernel.org Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260611161943.91069-5-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- net/mac80211/rx.c | 34 ++++++++++++++++++++++++++++++++-- net/mac80211/util.c | 3 +++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index fb9a3574afe9..d9ea19be075d 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -1526,6 +1526,9 @@ ieee80211_rx_h_check_dup(struct ieee80211_rx_data *rx) if (status->flag & RX_FLAG_DUP_VALIDATED) return RX_CONTINUE; + if (ieee80211_is_ext(hdr->frame_control)) + return RX_CONTINUE; + /* * Drop duplicate 802.11 retransmissions * (IEEE 802.11-2012: 9.3.2.10 "Duplicate detection and recovery") @@ -4510,12 +4513,16 @@ static bool ieee80211_accept_frame(struct ieee80211_rx_data *rx) struct ieee80211_hdr *hdr = (void *)skb->data; struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb); u8 *bssid = ieee80211_get_bssid(hdr, skb->len, sdata->vif.type); - bool multicast = is_multicast_ether_addr(hdr->addr1) || - ieee80211_is_s1g_beacon(hdr->frame_control); + bool multicast; static const u8 nan_network_id[ETH_ALEN] __aligned(2) = { 0x51, 0x6F, 0x9A, 0x01, 0x00, 0x00 }; + if (ieee80211_is_s1g_beacon(hdr->frame_control)) + return sdata->vif.type == NL80211_IFTYPE_STATION && bssid; + + multicast = is_multicast_ether_addr(hdr->addr1); + switch (sdata->vif.type) { case NL80211_IFTYPE_STATION: if (!bssid && !sdata->u.mgd.use_4addr) @@ -5212,6 +5219,11 @@ static bool ieee80211_prepare_and_rx_handle(struct ieee80211_rx_data *rx, hdr = (struct ieee80211_hdr *)rx->skb->data; } + if (ieee80211_is_s1g_beacon(hdr->frame_control)) { + ieee80211_invoke_rx_handlers(rx); + return true; + } + /* Store a copy of the pre-translated link addresses for SW crypto */ if (unlikely(is_unicast_ether_addr(hdr->addr1) && !ieee80211_is_data(hdr->frame_control))) @@ -5301,6 +5313,13 @@ static bool ieee80211_rx_for_interface(struct ieee80211_rx_data *rx, struct sta_info *sta; int link_id = -1; + if (ieee80211_is_s1g_beacon(hdr->frame_control)) { + if (!ieee80211_rx_data_set_sta(rx, NULL, -1)) + return false; + + return ieee80211_prepare_and_rx_handle(rx, skb, consume); + } + /* * Look up link station first, in case there's a * chance that they might have a link address that @@ -5376,6 +5395,17 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw, err = -ENOBUFS; else err = skb_linearize(skb); + } else if (ieee80211_is_s1g_beacon(fc)) { + size_t s1g_hdr_len = offsetof(struct ieee80211_ext, + u.s1g_beacon.variable) + + ieee80211_s1g_optional_len(fc); + + if (skb->len < s1g_hdr_len) + err = -ENOBUFS; + else + err = skb_linearize(skb); + } else if (ieee80211_is_ext(fc)) { + err = -EINVAL; } else { err = !pskb_may_pull(skb, ieee80211_hdrlen(fc)); } diff --git a/net/mac80211/util.c b/net/mac80211/util.c index f6d4ae4127c8..59f73dabe6e0 100644 --- a/net/mac80211/util.c +++ b/net/mac80211/util.c @@ -73,6 +73,9 @@ u8 *ieee80211_get_bssid(struct ieee80211_hdr *hdr, size_t len, if (ieee80211_is_s1g_beacon(fc)) { struct ieee80211_ext *ext = (void *) hdr; + if (len < offsetofend(struct ieee80211_ext, u.s1g_beacon.sa)) + return NULL; + return ext->u.s1g_beacon.sa; } From 9f7315bb0b2d3780155b5a7a2fedb800bde4622f Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Fri, 12 Jun 2026 00:19:46 +0800 Subject: [PATCH 509/562] wifi: cfg80211: derive S1G beacon TSF from S1G fields cfg80211_inform_bss_frame_data() parses S1G beacons with the extension frame layout, but still reads the TSF from the regular probe response layout after the S1G branch. For S1G beacons that reads bytes at the regular management-frame timestamp offset instead of the S1G timestamp. Use the 32-bit S1G beacon timestamp and the S1G Beacon Compatibility element's TSF completion field when informing an S1G BSS. Keep the regular management-frame timestamp read in the non-S1G branch. Fixes: 9eaffe5078ca ("cfg80211: convert S1G beacon to scan results") Signed-off-by: Zhao Li Tested-by: Lachlan Hodges Reviewed-by: Lachlan Hodges Link: https://patch.msgid.link/20260611161943.91069-6-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- net/wireless/scan.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/net/wireless/scan.c b/net/wireless/scan.c index e1c09040a5c8..5c97b5bd5d69 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c @@ -3314,14 +3314,15 @@ cfg80211_inform_bss_frame_data(struct wiphy *wiphy, bssid = ext->u.s1g_beacon.sa; capability = le16_to_cpu(compat->compat_info); beacon_interval = le16_to_cpu(compat->beacon_int); + tsf = le32_to_cpu(ext->u.s1g_beacon.timestamp); + tsf |= (u64)le32_to_cpu(compat->tsf_completion) << 32; } else { bssid = mgmt->bssid; beacon_interval = le16_to_cpu(mgmt->u.probe_resp.beacon_int); capability = le16_to_cpu(mgmt->u.probe_resp.capab_info); + tsf = le64_to_cpu(mgmt->u.probe_resp.timestamp); } - tsf = le64_to_cpu(mgmt->u.probe_resp.timestamp); - if (ieee80211_is_probe_resp(mgmt->frame_control)) ftype = CFG80211_BSS_FTYPE_PRESP; else if (ext) From 671aca1533d7b277bd139a6d462d8c9bc0886f10 Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Fri, 12 Jun 2026 01:35:07 +0800 Subject: [PATCH 510/562] wifi: ieee80211: validate MLE common info length ieee80211_mle_common_size() uses the first common-info octet as the common information length for all known MLE types. However, ieee80211_mle_size_ok() only validates that octet for Basic, Probe Request, and TDLS MLEs. Reconfiguration MLEs also skipped the length octet when calculating the minimum common size, and Priority Access MLEs skipped validation of the advertised common information length. Account for the Reconfiguration common-info length octet and validate the advertised common information length for all known MLE types. Keep unknown-type handling unchanged. Fixes: 0f48b8b88aa9 ("wifi: ieee80211: add definitions for multi-link element") Cc: stable@vger.kernel.org Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260611173506.36838-2-enderaoelyther@gmail.com [remove now misleading comment] Signed-off-by: Johannes Berg --- include/linux/ieee80211-eht.h | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/include/linux/ieee80211-eht.h b/include/linux/ieee80211-eht.h index 18f9c662cf4c..c109722b1969 100644 --- a/include/linux/ieee80211-eht.h +++ b/include/linux/ieee80211-eht.h @@ -857,7 +857,7 @@ static inline bool ieee80211_mle_size_ok(const u8 *data, size_t len) const struct ieee80211_multi_link_elem *mle = (const void *)data; u8 fixed = sizeof(*mle); u8 common = 0; - bool check_common_len = false; + u8 common_len; u16 control; if (!data || len < fixed) @@ -868,7 +868,6 @@ static inline bool ieee80211_mle_size_ok(const u8 *data, size_t len) switch (u16_get_bits(control, IEEE80211_ML_CONTROL_TYPE)) { case IEEE80211_ML_CONTROL_TYPE_BASIC: common += sizeof(struct ieee80211_mle_basic_common_info); - check_common_len = true; if (control & IEEE80211_MLC_BASIC_PRES_LINK_ID) common += 1; if (control & IEEE80211_MLC_BASIC_PRES_BSS_PARAM_CH_CNT) @@ -888,9 +887,9 @@ static inline bool ieee80211_mle_size_ok(const u8 *data, size_t len) common += sizeof(struct ieee80211_mle_preq_common_info); if (control & IEEE80211_MLC_PREQ_PRES_MLD_ID) common += 1; - check_common_len = true; break; case IEEE80211_ML_CONTROL_TYPE_RECONF: + common += 1; if (control & IEEE80211_MLC_RECONF_PRES_MLD_MAC_ADDR) common += ETH_ALEN; if (control & IEEE80211_MLC_RECONF_PRES_EML_CAPA) @@ -902,7 +901,6 @@ static inline bool ieee80211_mle_size_ok(const u8 *data, size_t len) break; case IEEE80211_ML_CONTROL_TYPE_TDLS: common += sizeof(struct ieee80211_mle_tdls_common_info); - check_common_len = true; break; case IEEE80211_ML_CONTROL_TYPE_PRIO_ACCESS: common = ETH_ALEN + 1; @@ -915,11 +913,9 @@ static inline bool ieee80211_mle_size_ok(const u8 *data, size_t len) if (len < fixed + common) return false; - if (!check_common_len) - return true; + common_len = mle->variable[0]; - /* if present, common length is the first octet there */ - return mle->variable[0] >= common; + return common_len >= common && common_len <= len - fixed; } /** From cf6a9d0808066225409c3ea1f174bcf6ca6db932 Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Fri, 12 Jun 2026 21:18:55 +0800 Subject: [PATCH 511/562] wifi: nl80211: validate nested MBSSID IE blobs Validate each nested NL80211_ATTR_MBSSID_ELEMS entry as a well-formed information-element stream before storing it for beacon construction. RNR parsing already validates each nested blob with validate_ie_attr() before storing it. Apply the same syntactic IE validation to MBSSID entries before counting and copying their data and length pointers. Fixes: dc1e3cb8da8b ("nl80211: MBSSID and EMA support in AP mode") Assisted-by: Codex:gpt-5.5 Assisted-by: Claude:claude-opus-4.8 Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260612131854.43575-3-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 056388c04599..26b781770d4f 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -6510,7 +6510,8 @@ static int nl80211_parse_mbssid_config(struct wiphy *wiphy, } static struct cfg80211_mbssid_elems * -nl80211_parse_mbssid_elems(struct wiphy *wiphy, struct nlattr *attrs) +nl80211_parse_mbssid_elems(struct wiphy *wiphy, struct nlattr *attrs, + struct netlink_ext_ack *extack) { struct nlattr *nl_elems; struct cfg80211_mbssid_elems *elems; @@ -6521,6 +6522,12 @@ nl80211_parse_mbssid_elems(struct wiphy *wiphy, struct nlattr *attrs) return ERR_PTR(-EINVAL); nla_for_each_nested(nl_elems, attrs, rem_elems) { + int ret; + + ret = validate_ie_attr(nl_elems, extack); + if (ret) + return ERR_PTR(ret); + if (num_elems >= 255) return ERR_PTR(-EINVAL); num_elems++; @@ -6787,7 +6794,8 @@ static int nl80211_parse_beacon(struct cfg80211_registered_device *rdev, if (attrs[NL80211_ATTR_MBSSID_ELEMS]) { struct cfg80211_mbssid_elems *mbssid = nl80211_parse_mbssid_elems(&rdev->wiphy, - attrs[NL80211_ATTR_MBSSID_ELEMS]); + attrs[NL80211_ATTR_MBSSID_ELEMS], + extack); if (IS_ERR(mbssid)) return PTR_ERR(mbssid); From 2df76ff8d0eec99b01b7474dcbecdf6fd3ff4ae2 Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Fri, 12 Jun 2026 21:18:56 +0800 Subject: [PATCH 512/562] wifi: nl80211: constrain MBSSID TX link ID range MBSSID transmitted-profile link IDs are valid only in the range 0..IEEE80211_MLD_MAX_NUM_LINKS - 1. Constrain the nl80211 policy to reject out-of-range values during attribute validation. Fixes: 37523c3c47b3 ("wifi: nl80211: add link id of transmitted profile for MLO MBSSID") Assisted-by: Codex:gpt-5.5 Assisted-by: Claude:claude-opus-4.8 Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260612131854.43575-4-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 26b781770d4f..3e05db942a18 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -630,7 +630,7 @@ nl80211_mbssid_config_policy[NL80211_MBSSID_CONFIG_ATTR_MAX + 1] = { [NL80211_MBSSID_CONFIG_ATTR_TX_IFINDEX] = { .type = NLA_U32 }, [NL80211_MBSSID_CONFIG_ATTR_EMA] = { .type = NLA_FLAG }, [NL80211_MBSSID_CONFIG_ATTR_TX_LINK_ID] = - NLA_POLICY_MAX(NLA_U8, IEEE80211_MLD_MAX_NUM_LINKS), + NLA_POLICY_RANGE(NLA_U8, 0, IEEE80211_MLD_MAX_NUM_LINKS - 1), }; static const struct nla_policy From fc1a1001dec5ababd5f71c7731be91dd8361d671 Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Fri, 12 Jun 2026 21:36:57 +0800 Subject: [PATCH 513/562] wifi: cfg80211: validate PMSR measurement type data PMSR request parsing accepts missing or duplicated measurement type entries in NL80211_PMSR_REQ_ATTR_DATA. Track whether one measurement type was already provided, reject a second one immediately, and return an error if the request data block contains no measurement type at all. Fixes: 9bb7e0f24e7e7 ("cfg80211: add peer measurement with FTM initiator API") Assisted-by: Codex:gpt-5.5 Assisted-by: Claude:claude-opus-4.8 Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260612133656.92900-2-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- net/wireless/pmsr.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/net/wireless/pmsr.c b/net/wireless/pmsr.c index 2c8db33d9c30..4962456fda30 100644 --- a/net/wireless/pmsr.c +++ b/net/wireless/pmsr.c @@ -310,6 +310,7 @@ static int pmsr_parse_peer(struct cfg80211_registered_device *rdev, { struct nlattr *tb[NL80211_PMSR_PEER_ATTR_MAX + 1]; struct nlattr *req[NL80211_PMSR_REQ_ATTR_MAX + 1]; + bool have_measurement_type = false; struct nlattr *treq; int err, rem; @@ -376,6 +377,14 @@ static int pmsr_parse_peer(struct cfg80211_registered_device *rdev, } nla_for_each_nested(treq, req[NL80211_PMSR_REQ_ATTR_DATA], rem) { + if (have_measurement_type) { + NL_SET_ERR_MSG_ATTR(info->extack, treq, + "multiple measurement types in request data"); + return -EINVAL; + } + + have_measurement_type = true; + switch (nla_type(treq)) { case NL80211_PMSR_TYPE_FTM: err = pmsr_parse_ftm(rdev, treq, out, info); @@ -385,10 +394,16 @@ static int pmsr_parse_peer(struct cfg80211_registered_device *rdev, "unsupported measurement type"); err = -EINVAL; } + if (err) + return err; } - if (err) - return err; + if (!have_measurement_type) { + NL_SET_ERR_MSG_ATTR(info->extack, + req[NL80211_PMSR_REQ_ATTR_DATA], + "missing measurement type in request data"); + return -EINVAL; + } return 0; } From 3ed1238abd9b78e138770c74737cad1d86bc764c Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Fri, 12 Jun 2026 21:37:04 +0800 Subject: [PATCH 514/562] wifi: cfg80211: validate PMSR FTM preamble range PMSR FTM request parsing accepts preamble values outside the enumerated nl80211 preamble range. Reject out-of-range values before using them in the parser capability bit test using the policy. Fixes: 9bb7e0f24e7e7 ("cfg80211: add peer measurement with FTM initiator API") Assisted-by: Codex:gpt-5.5 Assisted-by: Claude:claude-opus-4.8 Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260612133703.93274-2-enderaoelyther@gmail.com [drop unnecessary check] Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 3e05db942a18..625c99cf70a3 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -461,7 +461,9 @@ nl80211_ftm_responder_policy[NL80211_FTM_RESP_ATTR_MAX + 1] = { static const struct nla_policy nl80211_pmsr_ftm_req_attr_policy[NL80211_PMSR_FTM_REQ_ATTR_MAX + 1] = { [NL80211_PMSR_FTM_REQ_ATTR_ASAP] = { .type = NLA_FLAG }, - [NL80211_PMSR_FTM_REQ_ATTR_PREAMBLE] = { .type = NLA_U32 }, + [NL80211_PMSR_FTM_REQ_ATTR_PREAMBLE] = + NLA_POLICY_RANGE(NLA_U32, NL80211_PREAMBLE_LEGACY, + NL80211_PREAMBLE_HE), [NL80211_PMSR_FTM_REQ_ATTR_NUM_BURSTS_EXP] = NLA_POLICY_MAX(NLA_U8, 15), [NL80211_PMSR_FTM_REQ_ATTR_BURST_PERIOD] = { .type = NLA_U16 }, From 991779e787d304e7594814711133c60732312bd8 Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Fri, 12 Jun 2026 21:37:11 +0800 Subject: [PATCH 515/562] wifi: cfg80211: reject unsupported PMSR FTM location requests PMSR FTM location request flags are syntactically valid, but they must be rejected when the device capability does not advertise support for them. Return an error immediately after rejecting unsupported LCI or civic location request bits so the request cannot reach the driver. Fixes: 9bb7e0f24e7e7 ("cfg80211: add peer measurement with FTM initiator API") Assisted-by: Codex:gpt-5.5 Assisted-by: Claude:claude-opus-4.8 Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260612133710.93544-2-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- net/wireless/pmsr.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/wireless/pmsr.c b/net/wireless/pmsr.c index 4962456fda30..34ecbc6644a3 100644 --- a/net/wireless/pmsr.c +++ b/net/wireless/pmsr.c @@ -125,6 +125,7 @@ static int pmsr_parse_ftm(struct cfg80211_registered_device *rdev, NL_SET_ERR_MSG_ATTR(info->extack, tb[NL80211_PMSR_FTM_REQ_ATTR_REQUEST_LCI], "FTM: LCI request not supported"); + return -EOPNOTSUPP; } out->ftm.request_civicloc = @@ -133,6 +134,7 @@ static int pmsr_parse_ftm(struct cfg80211_registered_device *rdev, NL_SET_ERR_MSG_ATTR(info->extack, tb[NL80211_PMSR_FTM_REQ_ATTR_REQUEST_CIVICLOC], "FTM: civic location request not supported"); + return -EOPNOTSUPP; } out->ftm.trigger_based = From b44371fc4d16d95f17b005f5bdedd8d4cb6de704 Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Fri, 12 Jun 2026 21:37:18 +0800 Subject: [PATCH 516/562] wifi: cfg80211: reject empty PMSR peer lists A PMSR request with an empty peers array is not a useful request and weakens the cfg80211-to-driver contract by allowing start_pmsr() with no target peer. Reject empty peer lists before allocating the request object or calling into the driver. Fixes: 9bb7e0f24e7e7 ("cfg80211: add peer measurement with FTM initiator API") Assisted-by: Codex:gpt-5.5 Assisted-by: Claude:claude-opus-4.8 Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260612133717.93783-2-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- net/wireless/pmsr.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/wireless/pmsr.c b/net/wireless/pmsr.c index 34ecbc6644a3..34c3625f7fd5 100644 --- a/net/wireless/pmsr.c +++ b/net/wireless/pmsr.c @@ -444,6 +444,11 @@ int nl80211_pmsr_start(struct sk_buff *skb, struct genl_info *info) } } + if (!count) { + NL_SET_ERR_MSG_ATTR(info->extack, peers, "No peers specified"); + return -EINVAL; + } + req = kzalloc_flex(*req, peers, count); if (!req) return -ENOMEM; From 74c604442f3d7a1df7873a44ca5ebe453be7e544 Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Fri, 12 Jun 2026 23:24:41 +0800 Subject: [PATCH 517/562] wifi: mac80211: avoid non-S1G AID fallback for S1G assoc When assoc_data->s1g is set and no AID Response element is present, falling back to mgmt->u.assoc_resp.aid reads the non-S1G association-response layout. Keep the fallback for non-S1G only. If a successful S1G association response omits the AID Response element, abandon the association instead of proceeding with AID 0. Initialize aid to 0 for other S1G responses so the later mask and logging flow keeps a defined value without reading the non-S1G layout. Fixes: 2a8a6b7c4cb0 ("wifi: mac80211: handle station association response with S1G") Assisted-by: Codex:gpt-5.5 Assisted-by: Claude:claude-opus-4.8 Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260612152440.25955-2-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- net/mac80211/mlme.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index 9e92337bb6f9..86c90b504cfd 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -7138,7 +7138,7 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata, { struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; struct ieee80211_mgd_assoc_data *assoc_data = ifmgd->assoc_data; - u16 capab_info, status_code, aid; + u16 capab_info, status_code, aid = 0; struct ieee80211_elems_parse_params parse_params = { .bss = NULL, .link_id = -1, @@ -7217,8 +7217,10 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata, if (elems->aid_resp) aid = le16_to_cpu(elems->aid_resp->aid); - else + else if (!assoc_data->s1g) aid = le16_to_cpu(mgmt->u.assoc_resp.aid); + else if (status_code == WLAN_STATUS_SUCCESS) + goto abandon_assoc; /* * The 5 MSB of the AID field are reserved for a non-S1G STA. For From f843cf31dfc2c75843362a8f7e75180ed0cc44d6 Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Sat, 13 Jun 2026 02:50:45 +0800 Subject: [PATCH 518/562] wifi: mac80211: validate deauth frame length before reason access ieee80211_rx_mgmt_deauth() reads the deauth reason code before checking that the fixed field is actually present in the received frame. Validate the deauth frame length first and only then read the reason code. Assisted-by: Codex:gpt-5.5 Assisted-by: Claude:claude-opus-4.8 Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260612185042.66260-6-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- net/mac80211/mlme.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index 86c90b504cfd..fa773f3b0541 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -5641,13 +5641,15 @@ static void ieee80211_rx_mgmt_deauth(struct ieee80211_sub_if_data *sdata, struct ieee80211_mgmt *mgmt, size_t len) { struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; - u16 reason_code = le16_to_cpu(mgmt->u.deauth.reason_code); + u16 reason_code; lockdep_assert_wiphy(sdata->local->hw.wiphy); - if (len < 24 + 2) + if (len < offsetofend(struct ieee80211_mgmt, u.deauth.reason_code)) return; + reason_code = le16_to_cpu(mgmt->u.deauth.reason_code); + if (!ether_addr_equal(mgmt->bssid, mgmt->sa)) { ieee80211_tdls_handle_disconnect(sdata, mgmt->sa, reason_code); return; From 49aa703854380feef87c146ae370cb356232a97d Mon Sep 17 00:00:00 2001 From: P Praneesh Date: Sun, 14 Jun 2026 10:47:31 +0530 Subject: [PATCH 519/562] wifi: cfg80211: Drop unused link stats handling in nl80211_send_station() Remove the link level statistics handling from nl80211_send_station() and drop the unused link_stats parameter from its signature and callers. The removed code iterated over each MLO link and attempted to send link specific station data through NL80211_ATTR_MLO_LINKS, but this logic was never used because link_stats was always false. This logic was introduced during early work on link level station statistics with the intention of reporting information for each link. Due to message size concerns when a station has multiple links, the feature was disabled behind the link_stats flag and remained unused. The link level reporting block in nl80211_send_station() is dead code and cannot support larger messages, so remove it. This cleanup also prepares for proper link level statistics reporting in nl80211_dump_station() in a later patch, where fragmentation allows safe transmission of multi link data. Also fix label indentation: the nla_put_failure label had an erroneous leading space. Signed-off-by: P Praneesh Link: https://patch.msgid.link/20260614051739.3979947-2-praneesh.p@oss.qualcomm.com Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 50 +++++------------------------------------- 1 file changed, 6 insertions(+), 44 deletions(-) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index d738c0089990..091deaa58204 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -8033,14 +8033,10 @@ static int nl80211_send_station(struct sk_buff *msg, u32 cmd, u32 portid, u32 seq, int flags, struct cfg80211_registered_device *rdev, struct wireless_dev *wdev, - const u8 *mac_addr, struct station_info *sinfo, - bool link_stats) + const u8 *mac_addr, struct station_info *sinfo) { void *hdr; struct nlattr *sinfoattr, *bss_param; - struct link_station_info *link_sinfo; - struct nlattr *links, *link; - int link_id; hdr = nl80211hdr_put(msg, portid, seq, flags, cmd); if (!hdr) { @@ -8258,45 +8254,11 @@ static int nl80211_send_station(struct sk_buff *msg, u32 cmd, u32 portid, goto nla_put_failure; } - if (link_stats && sinfo->valid_links) { - links = nla_nest_start(msg, NL80211_ATTR_MLO_LINKS); - if (!links) - goto nla_put_failure; - - for_each_valid_link(sinfo, link_id) { - link_sinfo = sinfo->links[link_id]; - - if (WARN_ON_ONCE(!link_sinfo)) - continue; - - if (!is_valid_ether_addr(link_sinfo->addr)) - continue; - - link = nla_nest_start(msg, link_id + 1); - if (!link) - goto nla_put_failure; - - if (nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID, - link_id)) - goto nla_put_failure; - - if (nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, - link_sinfo->addr)) - goto nla_put_failure; - - if (nl80211_fill_link_station(msg, rdev, link_sinfo)) - goto nla_put_failure; - - nla_nest_end(msg, link); - } - nla_nest_end(msg, links); - } - cfg80211_sinfo_release_content(sinfo); genlmsg_end(msg, hdr); return 0; - nla_put_failure: +nla_put_failure: cfg80211_sinfo_release_content(sinfo); genlmsg_cancel(msg, hdr); return -EMSGSIZE; @@ -8549,7 +8511,7 @@ static int nl80211_dump_station(struct sk_buff *skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq, NLM_F_MULTI, rdev, wdev, mac_addr, - &sinfo, false) < 0) + &sinfo) < 0) goto out; sta_idx++; @@ -8613,7 +8575,7 @@ static int nl80211_get_station(struct sk_buff *skb, struct genl_info *info) if (nl80211_send_station(msg, NL80211_CMD_NEW_STATION, info->snd_portid, info->snd_seq, 0, - rdev, wdev, mac_addr, &sinfo, false) < 0) { + rdev, wdev, mac_addr, &sinfo) < 0) { nlmsg_free(msg); return -ENOBUFS; } @@ -21651,7 +21613,7 @@ void cfg80211_new_sta(struct wireless_dev *wdev, const u8 *mac_addr, return; if (nl80211_send_station(msg, NL80211_CMD_NEW_STATION, 0, 0, 0, - rdev, wdev, mac_addr, sinfo, false) < 0) { + rdev, wdev, mac_addr, sinfo) < 0) { nlmsg_free(msg); return; } @@ -21681,7 +21643,7 @@ void cfg80211_del_sta_sinfo(struct wireless_dev *wdev, const u8 *mac_addr, } if (nl80211_send_station(msg, NL80211_CMD_DEL_STATION, 0, 0, 0, - rdev, wdev, mac_addr, sinfo, false) < 0) { + rdev, wdev, mac_addr, sinfo) < 0) { nlmsg_free(msg); return; } From 4b0991053a3378545783859b891da1ead28784ee Mon Sep 17 00:00:00 2001 From: P Praneesh Date: Sun, 14 Jun 2026 10:47:32 +0530 Subject: [PATCH 520/562] wifi: cfg80211: Add helper to pack station-level STA_INFO Add a helper function nl80211_put_sta_info_common() to pack the station-level (aggregated) STA information into a netlink message. This prepares the code for future enhancements such as supporting fragmented link statistics in nl80211_dump_station. Signed-off-by: P Praneesh Link: https://patch.msgid.link/20260614051739.3979947-3-praneesh.p@oss.qualcomm.com Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 66 +++++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 091deaa58204..912009fb9dda 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -8029,32 +8029,15 @@ static int nl80211_fill_link_station(struct sk_buff *msg, return -EMSGSIZE; } -static int nl80211_send_station(struct sk_buff *msg, u32 cmd, u32 portid, - u32 seq, int flags, - struct cfg80211_registered_device *rdev, - struct wireless_dev *wdev, - const u8 *mac_addr, struct station_info *sinfo) +static int nl80211_put_sta_info_common(struct sk_buff *msg, + struct cfg80211_registered_device *rdev, + struct station_info *sinfo) { - void *hdr; struct nlattr *sinfoattr, *bss_param; - hdr = nl80211hdr_put(msg, portid, seq, flags, cmd); - if (!hdr) { - cfg80211_sinfo_release_content(sinfo); - return -1; - } - - if ((wdev->netdev && - nla_put_u32(msg, NL80211_ATTR_IFINDEX, wdev->netdev->ifindex)) || - nla_put_u64_64bit(msg, NL80211_ATTR_WDEV, wdev_id(wdev), - NL80211_ATTR_PAD) || - nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, mac_addr) || - nla_put_u32(msg, NL80211_ATTR_GENERATION, sinfo->generation)) - goto nla_put_failure; - - sinfoattr = nla_nest_start_noflag(msg, NL80211_ATTR_STA_INFO); + sinfoattr = nla_nest_start(msg, NL80211_ATTR_STA_INFO); if (!sinfoattr) - goto nla_put_failure; + return -EMSGSIZE; #define PUT_SINFO(attr, memb, type) do { \ BUILD_BUG_ON(sizeof(type) == sizeof(u64)); \ @@ -8145,8 +8128,7 @@ static int nl80211_send_station(struct sk_buff *msg, u32 cmd, u32 portid, PUT_SINFO_U64(T_OFFSET, t_offset); if (sinfo->filled & BIT_ULL(NL80211_STA_INFO_BSS_PARAM)) { - bss_param = nla_nest_start_noflag(msg, - NL80211_STA_INFO_BSS_PARAM); + bss_param = nla_nest_start(msg, NL80211_STA_INFO_BSS_PARAM); if (!bss_param) goto nla_put_failure; @@ -8188,8 +8170,7 @@ static int nl80211_send_station(struct sk_buff *msg, u32 cmd, u32 portid, struct nlattr *tidsattr; int tid; - tidsattr = nla_nest_start_noflag(msg, - NL80211_STA_INFO_TID_STATS); + tidsattr = nla_nest_start(msg, NL80211_STA_INFO_TID_STATS); if (!tidsattr) goto nla_put_failure; @@ -8202,7 +8183,7 @@ static int nl80211_send_station(struct sk_buff *msg, u32 cmd, u32 portid, if (!tidstats->filled) continue; - tidattr = nla_nest_start_noflag(msg, tid + 1); + tidattr = nla_nest_start(msg, tid + 1); if (!tidattr) goto nla_put_failure; @@ -8232,6 +8213,37 @@ static int nl80211_send_station(struct sk_buff *msg, u32 cmd, u32 portid, } nla_nest_end(msg, sinfoattr); + return 0; + +nla_put_failure: + nla_nest_cancel(msg, sinfoattr); + return -EMSGSIZE; +} + +static int nl80211_send_station(struct sk_buff *msg, u32 cmd, u32 portid, + u32 seq, int flags, + struct cfg80211_registered_device *rdev, + struct wireless_dev *wdev, + const u8 *mac_addr, struct station_info *sinfo) +{ + void *hdr; + + hdr = nl80211hdr_put(msg, portid, seq, flags, cmd); + if (!hdr) { + cfg80211_sinfo_release_content(sinfo); + return -1; + } + + if ((wdev->netdev && + nla_put_u32(msg, NL80211_ATTR_IFINDEX, wdev->netdev->ifindex)) || + nla_put_u64_64bit(msg, NL80211_ATTR_WDEV, wdev_id(wdev), + NL80211_ATTR_PAD) || + nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, mac_addr) || + nla_put_u32(msg, NL80211_ATTR_GENERATION, sinfo->generation)) + goto nla_put_failure; + + if (nl80211_put_sta_info_common(msg, rdev, sinfo)) + goto nla_put_failure; if (sinfo->assoc_req_ies_len && nla_put(msg, NL80211_ATTR_IE, sinfo->assoc_req_ies_len, From f0ac06cb5cc7c7fc6d71fb44246e9dc1088bd1ea Mon Sep 17 00:00:00 2001 From: P Praneesh Date: Sun, 14 Jun 2026 10:47:33 +0530 Subject: [PATCH 521/562] wifi: cfg80211: Refactor nl80211_dump_station() to prepare for per-link stats Currently, nl80211_dump_station() relies on the netlink callback's generic args array (cb->args[2]) to track the station index during dumps. It also processes the entire sinfo structure and transmits it to userspace immediately in a single pass. This approach creates a bottleneck for MLO. When an MLD station has multiple active links, the aggregated station information, combined with the individual per-link statistics, can easily exceed the maximum netlink message size limits. The current monolithic dump iteration cannot pause and resume mid-station to fragment these large per-link statistics across multiple netlink messages. Introduce a stateful context structure (struct nl80211_dump_station_ctx) allocated during the dump to track the iteration state. Store the context pointer directly at cb->args[2], following the same pattern as nl80211_dump_wiphy which stores its state pointer at cb->args[0]. Move the station index (sta_idx) tracking and the sinfo payload into this context. The per-station netlink message is built inline in the loop: common header attributes are assembled directly, then nl80211_put_sta_info_common() adds the STA_INFO payload. Furthermore, move the NL80211_CMD_GET_STATION command definition from genl_small_ops to genl_ops to natively support the .done callback. Implement nl80211_dump_station_done() to ensure the newly allocated state context and its deeply allocated sinfo payload are safely freed when the dump concludes or is aborted prematurely by userspace. Note that the previous dump path used nl80211_send_station(), which included NL80211_ATTR_IE and NL80211_ATTR_RESP_IE. These attributes are not carried forward in this implementation. As documented, association response IEs (assoc_resp_ies) are only relevant at station creation time (e.g. via cfg80211_new_sta()) to notify userspace about association details, and are not expected to be part of get_station()/dump_station() callbacks. Aligning with this expectation, these IEs are intentionally omitted here. This refactoring maintains the existing netlink batching performance while laying the stateful foundation required for per-link statistics fragmentation in subsequent patches. At out_err_release, cfg80211_sinfo_release_content() frees any dynamically allocated sub-fields inside ctx->sinfo (including per-link pointers in sinfo.links[]). Without the subsequent memset, those pointers remain non-NULL in the embedded sinfo. When the dump concludes or is aborted, nl80211_dump_station_done() calls cfg80211_sinfo_release_content() a second time on the same ctx->sinfo, which would free the already-released link memory. The memset(&ctx->sinfo, 0, sizeof(ctx->sinfo)) zeroes all pointers so the second release call hits kfree(NULL), which is a harmless no-op. Signed-off-by: P Praneesh Link: https://patch.msgid.link/20260614051739.3979947-4-praneesh.p@oss.qualcomm.com Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 129 +++++++++++++++++++++++++++-------------- 1 file changed, 85 insertions(+), 44 deletions(-) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 912009fb9dda..ccc4341a0aea 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -8464,16 +8464,19 @@ static void cfg80211_sta_set_mld_sinfo(struct station_info *sinfo) sinfo->filled &= ~BIT_ULL(NL80211_STA_INFO_CHAIN_SIGNAL_AVG); } +struct nl80211_dump_station_ctx { + int sta_idx; + u8 mac_addr[ETH_ALEN]; + struct station_info sinfo; +}; + static int nl80211_dump_station(struct sk_buff *skb, struct netlink_callback *cb) { - struct station_info sinfo; struct cfg80211_registered_device *rdev; struct wireless_dev *wdev; - u8 mac_addr[ETH_ALEN]; - int sta_idx = cb->args[2]; - bool sinfo_alloc = false; - int err, i; + struct nl80211_dump_station_ctx *ctx = (void *)cb->args[2]; + int err; err = nl80211_prepare_wdev_dump(cb, &rdev, &wdev, NULL); if (err) @@ -8481,6 +8484,15 @@ static int nl80211_dump_station(struct sk_buff *skb, /* nl80211_prepare_wdev_dump acquired it in the successful case */ __acquire(&rdev->wiphy.mtx); + if (!ctx) { + ctx = kzalloc_obj(*ctx); + if (!ctx) { + err = -ENOMEM; + goto out_err; + } + cb->args[2] = (long)ctx; + } + if (!wdev->netdev && wdev->iftype != NL80211_IFTYPE_NAN) { err = -EINVAL; goto out_err; @@ -8491,55 +8503,83 @@ static int nl80211_dump_station(struct sk_buff *skb, goto out_err; } - while (1) { - memset(&sinfo, 0, sizeof(sinfo)); + while (true) { + void *hdr; - for (i = 0; i < IEEE80211_MLD_MAX_NUM_LINKS; i++) { - sinfo.links[i] = - kzalloc_obj(*sinfo.links[0]); - if (!sinfo.links[i]) { + memset(&ctx->sinfo, 0, sizeof(ctx->sinfo)); + for (int i = 0; i < IEEE80211_MLD_MAX_NUM_LINKS; i++) { + ctx->sinfo.links[i] = + kzalloc_obj(*ctx->sinfo.links[0]); + if (!ctx->sinfo.links[i]) { err = -ENOMEM; - goto out_err; + goto out_err_release; } - sinfo_alloc = true; } - err = rdev_dump_station(rdev, wdev, sta_idx, - mac_addr, &sinfo); - if (err == -ENOENT) - break; + err = rdev_dump_station(rdev, wdev, ctx->sta_idx, + ctx->mac_addr, &ctx->sinfo); + if (err == -ENOENT) { + err = skb->len; + goto out_err_release; + } if (err) - goto out_err; + goto out_err_release; - if (sinfo.valid_links) - cfg80211_sta_set_mld_sinfo(&sinfo); + if (ctx->sinfo.valid_links) + cfg80211_sta_set_mld_sinfo(&ctx->sinfo); - /* reset the sinfo_alloc flag as nl80211_send_station() - * always releases sinfo - */ - sinfo_alloc = false; + hdr = nl80211hdr_put(skb, NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, NLM_F_MULTI, + NL80211_CMD_NEW_STATION); + if (!hdr) { + err = skb->len; + goto out_err_release; + } - if (nl80211_send_station(skb, NL80211_CMD_NEW_STATION, - NETLINK_CB(cb->skb).portid, - cb->nlh->nlmsg_seq, NLM_F_MULTI, - rdev, wdev, mac_addr, - &sinfo) < 0) - goto out; + if ((wdev->netdev && + nla_put_u32(skb, NL80211_ATTR_IFINDEX, + wdev->netdev->ifindex)) || + nla_put_u64_64bit(skb, NL80211_ATTR_WDEV, + wdev_id(wdev), NL80211_ATTR_PAD) || + nla_put(skb, NL80211_ATTR_MAC, ETH_ALEN, ctx->mac_addr) || + nla_put_u32(skb, NL80211_ATTR_GENERATION, + ctx->sinfo.generation)) { + genlmsg_cancel(skb, hdr); + err = skb->len; + goto out_err_release; + } - sta_idx++; + if (nl80211_put_sta_info_common(skb, rdev, &ctx->sinfo)) { + genlmsg_cancel(skb, hdr); + err = skb->len; + goto out_err_release; + } + + genlmsg_end(skb, hdr); + cfg80211_sinfo_release_content(&ctx->sinfo); + ctx->sta_idx++; } - out: - cb->args[2] = sta_idx; - err = skb->len; - out_err: - if (sinfo_alloc) - cfg80211_sinfo_release_content(&sinfo); +out_err_release: + cfg80211_sinfo_release_content(&ctx->sinfo); + memset(&ctx->sinfo, 0, sizeof(ctx->sinfo)); +out_err: wiphy_unlock(&rdev->wiphy); return err; } +static int nl80211_dump_station_done(struct netlink_callback *cb) +{ + struct nl80211_dump_station_ctx *ctx = (void *)cb->args[2]; + + if (ctx) { + cfg80211_sinfo_release_content(&ctx->sinfo); + kfree(ctx); + } + return 0; +} + static int nl80211_get_station(struct sk_buff *skb, struct genl_info *info) { struct cfg80211_registered_device *rdev = info->user_ptr[0]; @@ -19517,6 +19557,14 @@ static const struct genl_ops nl80211_ops[] = { /* can be retrieved by unprivileged users */ .internal_flags = IFLAGS(NL80211_FLAG_NEED_WIPHY), }, + { + .cmd = NL80211_CMD_GET_STATION, + .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, + .doit = nl80211_get_station, + .dumpit = nl80211_dump_station, + .done = nl80211_dump_station_done, + .internal_flags = IFLAGS(NL80211_FLAG_NEED_WDEV), + }, }; static const struct genl_small_ops nl80211_small_ops[] = { @@ -19616,13 +19664,6 @@ static const struct genl_small_ops nl80211_small_ops[] = { .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_MLO_VALID_LINK_ID), }, - { - .cmd = NL80211_CMD_GET_STATION, - .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, - .doit = nl80211_get_station, - .dumpit = nl80211_dump_station, - .internal_flags = IFLAGS(NL80211_FLAG_NEED_WDEV), - }, { .cmd = NL80211_CMD_SET_STATION, .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, From aef91f72994cffd6393a8372d38f5611e6c12ba2 Mon Sep 17 00:00:00 2001 From: P Praneesh Date: Sun, 14 Jun 2026 10:47:34 +0530 Subject: [PATCH 522/562] wifi: cfg80211: Fragment per-link station stats in nl80211_dump_station() In MLO scenarios, stations may have multiple links, each with distinct statistics. When userspace tools like iw or hostapd request station dumps, attempting to pack all per-link stats into a single netlink message can easily exceed the default 4KB buffer limit, especially when more than two links are active. This results in -EMSGSIZE errors and incomplete data delivery. To address this, fragment per-link station statistics across multiple netlink messages to ensure reliable delivery of complete MLO station information. Extend the stateful context with a two-phase dump mechanism: phase 0 (AGGREGATED) sends combined MLO-level statistics and phase 1 (PER_LINK) sends individual per-link statistics for each active link. The dump loop is structured to produce exactly one netlink message per iteration, with a common header (ifindex, wdev, mac, generation) built once and phase-specific payload added via a switch statement. This keeps header construction in one place and makes the EMSGSIZE bail-out uniform. Add a new request flag attribute, NL80211_ATTR_STA_DUMP_LINK_STATS (NLA_FLAG), for NL80211_CMD_GET_STATION dump. Userspace can set this flag to request per-link station statistics for MLO stations. Extract this flag during the first dump invocation by passing an attrbuf to nl80211_prepare_wdev_dump(); use __free(kfree) to avoid scattered manual kfree() calls. Cache the boolean in the dump context to avoid repeated parsing on subsequent invocations. Per-link messages carry a single NL80211_ATTR_MLO_LINKS nest with the link ID, link-specific MAC, and per-link NL80211_ATTR_STA_INFO payload. The link-specific validity (is_valid_ether_addr) and null pointer guard are checked in nl80211_put_link_station_payload() before any message construction begins. Also fix all nla_nest_start_noflag() calls in nl80211_fill_link_station() for nested attribute types (STA_INFO, BSS_PARAM, TID_STATS, per-tid) to use nla_nest_start() so the NLA_F_NESTED flag is set correctly. Propagate the actual return value from nl80211_put_sta_info_common() in the AGGREGATED phase rather than returning skb->len. Returning skb->len signals netlink to re-invoke the dump with the same sta_idx, causing an infinite loop when the aggregated payload is too large to fit; returning the real error code (-EMSGSIZE or otherwise) terminates the dump cleanly. Backward compatibility is seamlessly preserved for non-MLO stations. Signed-off-by: P Praneesh Link: https://patch.msgid.link/20260614051739.3979947-5-praneesh.p@oss.qualcomm.com Signed-off-by: Johannes Berg --- include/uapi/linux/nl80211.h | 19 ++++ net/wireless/nl80211.c | 170 ++++++++++++++++++++++++++++------- 2 files changed, 157 insertions(+), 32 deletions(-) diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index d9a8c693457f..020387d76412 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -3168,6 +3168,23 @@ enum nl80211_commands { * @NL80211_ATTR_NPCA_PRIMARY_FREQ: NPCA primary channel (u32) * @NL80211_ATTR_NPCA_PUNCT_BITMAP: NPCA puncturing bitmap (u32) * + * @NL80211_ATTR_STA_DUMP_LINK_STATS: Request flag for %NL80211_CMD_GET_STATION + * (dump mode only). When set on an MLD station, the dump produces two + * %NL80211_CMD_NEW_STATION messages per station per dump call: + * + * 1. An aggregated-stats message whose top-level %NL80211_ATTR_STA_INFO + * contains MLO-combined statistics (same content as a dump without + * this flag). + * + * 2. For each active link, a per-link message containing + * %NL80211_ATTR_MLO_LINKS with a single link entry. Each entry holds + * %NL80211_ATTR_MLO_LINK_ID, the link-specific %NL80211_ATTR_MAC, + * and %NL80211_ATTR_STA_INFO with per-link statistics (see + * &enum nl80211_sta_info). + * + * The aggregated message always precedes the per-link messages for the + * same station within a dump sequence. + * * @NUM_NL80211_ATTR: total number of nl80211_attrs available * @NL80211_ATTR_MAX: highest attribute number currently defined * @__NL80211_ATTR_AFTER_LAST: internal use @@ -3766,6 +3783,8 @@ enum nl80211_attrs { NL80211_ATTR_NPCA_PRIMARY_FREQ, NL80211_ATTR_NPCA_PUNCT_BITMAP, + NL80211_ATTR_STA_DUMP_LINK_STATS, + /* add attributes here, update the policy in nl80211.c */ __NL80211_ATTR_AFTER_LAST, diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index ccc4341a0aea..be16a40132ff 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -1093,6 +1093,7 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = { [NL80211_ATTR_NPCA_PRIMARY_FREQ] = { .type = NLA_U32 }, [NL80211_ATTR_NPCA_PUNCT_BITMAP] = NLA_POLICY_FULL_RANGE(NLA_U32, &nl80211_punct_bitmap_range), + [NL80211_ATTR_STA_DUMP_LINK_STATS] = { .type = NLA_FLAG }, }; /* policy for the key attributes */ @@ -7870,7 +7871,7 @@ static int nl80211_fill_link_station(struct sk_buff *msg, goto nla_put_failure; \ } while (0) - link_sinfoattr = nla_nest_start_noflag(msg, NL80211_ATTR_STA_INFO); + link_sinfoattr = nla_nest_start(msg, NL80211_ATTR_STA_INFO); if (!link_sinfoattr) goto nla_put_failure; @@ -7936,8 +7937,8 @@ static int nl80211_fill_link_station(struct sk_buff *msg, PUT_LINK_SINFO(BEACON_LOSS, beacon_loss_count, u32); if (link_sinfo->filled & BIT_ULL(NL80211_STA_INFO_BSS_PARAM)) { - bss_param = nla_nest_start_noflag(msg, - NL80211_STA_INFO_BSS_PARAM); + bss_param = nla_nest_start(msg, + NL80211_STA_INFO_BSS_PARAM); if (!bss_param) goto nla_put_failure; @@ -7979,8 +7980,7 @@ static int nl80211_fill_link_station(struct sk_buff *msg, struct nlattr *tidsattr; int tid; - tidsattr = nla_nest_start_noflag(msg, - NL80211_STA_INFO_TID_STATS); + tidsattr = nla_nest_start(msg, NL80211_STA_INFO_TID_STATS); if (!tidsattr) goto nla_put_failure; @@ -7993,7 +7993,7 @@ static int nl80211_fill_link_station(struct sk_buff *msg, if (!tidstats->filled) continue; - tidattr = nla_nest_start_noflag(msg, tid + 1); + tidattr = nla_nest_start(msg, tid + 1); if (!tidattr) goto nla_put_failure; @@ -8464,21 +8464,74 @@ static void cfg80211_sta_set_mld_sinfo(struct station_info *sinfo) sinfo->filled &= ~BIT_ULL(NL80211_STA_INFO_CHAIN_SIGNAL_AVG); } +enum nl80211_dump_station_phase { + NL80211_DUMP_STA_PHASE_AGGREGATED = 0, + NL80211_DUMP_STA_PHASE_PER_LINK = 1, +}; + struct nl80211_dump_station_ctx { int sta_idx; + int link_idx; + enum nl80211_dump_station_phase phase; + bool dump_link_stats; u8 mac_addr[ETH_ALEN]; struct station_info sinfo; }; +static int nl80211_put_link_station_payload(struct sk_buff *msg, + struct cfg80211_registered_device *rdev, + struct station_info *sinfo, + int link_idx) +{ + struct link_station_info *link_sinfo = sinfo->links[link_idx]; + struct nlattr *links, *link; + + if (WARN_ON_ONCE(!link_sinfo)) + return -ENOENT; + + if (!is_valid_ether_addr(link_sinfo->addr)) + return -EADDRNOTAVAIL; + + links = nla_nest_start(msg, NL80211_ATTR_MLO_LINKS); + if (!links) + return -EMSGSIZE; + + link = nla_nest_start(msg, link_idx + 1); + if (!link) + goto nla_put_failure; + + if (nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID, link_idx) || + nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, link_sinfo->addr)) + goto nla_put_failure; + + if (nl80211_fill_link_station(msg, rdev, link_sinfo)) + goto nla_put_failure; + + nla_nest_end(msg, link); + nla_nest_end(msg, links); + return 0; + +nla_put_failure: + nla_nest_cancel(msg, links); + return -EMSGSIZE; +} + static int nl80211_dump_station(struct sk_buff *skb, struct netlink_callback *cb) { struct cfg80211_registered_device *rdev; struct wireless_dev *wdev; struct nl80211_dump_station_ctx *ctx = (void *)cb->args[2]; + struct nlattr **attrbuf __free(kfree) = NULL; int err; - err = nl80211_prepare_wdev_dump(cb, &rdev, &wdev, NULL); + if (!ctx) { + attrbuf = kzalloc_objs(*attrbuf, NUM_NL80211_ATTR); + if (!attrbuf) + return -ENOMEM; + } + + err = nl80211_prepare_wdev_dump(cb, &rdev, &wdev, attrbuf); if (err) return err; /* nl80211_prepare_wdev_dump acquired it in the successful case */ @@ -8490,6 +8543,9 @@ static int nl80211_dump_station(struct sk_buff *skb, err = -ENOMEM; goto out_err; } + ctx->phase = NL80211_DUMP_STA_PHASE_AGGREGATED; + ctx->dump_link_stats = + !!attrbuf[NL80211_ATTR_STA_DUMP_LINK_STATS]; cb->args[2] = (long)ctx; } @@ -8505,34 +8561,53 @@ static int nl80211_dump_station(struct sk_buff *skb, while (true) { void *hdr; + int ret; - memset(&ctx->sinfo, 0, sizeof(ctx->sinfo)); - for (int i = 0; i < IEEE80211_MLD_MAX_NUM_LINKS; i++) { - ctx->sinfo.links[i] = - kzalloc_obj(*ctx->sinfo.links[0]); - if (!ctx->sinfo.links[i]) { - err = -ENOMEM; + /* AGGREGATED phase: fetch sinfo from driver once per station */ + if (ctx->phase == NL80211_DUMP_STA_PHASE_AGGREGATED) { + memset(&ctx->sinfo, 0, sizeof(ctx->sinfo)); + for (int i = 0; i < IEEE80211_MLD_MAX_NUM_LINKS; i++) { + ctx->sinfo.links[i] = + kzalloc_obj(*ctx->sinfo.links[0]); + if (!ctx->sinfo.links[i]) { + err = -ENOMEM; + goto out_err_release; + } + } + + err = rdev_dump_station(rdev, wdev, ctx->sta_idx, + ctx->mac_addr, &ctx->sinfo); + if (err == -ENOENT) { + err = skb->len; goto out_err_release; } - } + if (err) + goto out_err_release; - err = rdev_dump_station(rdev, wdev, ctx->sta_idx, - ctx->mac_addr, &ctx->sinfo); - if (err == -ENOENT) { - err = skb->len; - goto out_err_release; + if (ctx->sinfo.valid_links) + cfg80211_sta_set_mld_sinfo(&ctx->sinfo); + } else { + /* PER_LINK phase: advance to next valid link */ + while (ctx->link_idx < IEEE80211_MLD_MAX_NUM_LINKS && + !(ctx->sinfo.valid_links & BIT(ctx->link_idx))) + ctx->link_idx++; + + if (ctx->link_idx >= IEEE80211_MLD_MAX_NUM_LINKS) { + cfg80211_sinfo_release_content(&ctx->sinfo); + ctx->sta_idx++; + ctx->phase = NL80211_DUMP_STA_PHASE_AGGREGATED; + continue; + } } - if (err) - goto out_err_release; - - if (ctx->sinfo.valid_links) - cfg80211_sta_set_mld_sinfo(&ctx->sinfo); + /* Build common header for both phases */ hdr = nl80211hdr_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq, NLM_F_MULTI, NL80211_CMD_NEW_STATION); if (!hdr) { err = skb->len; + if (ctx->phase == NL80211_DUMP_STA_PHASE_PER_LINK) + goto out_err; goto out_err_release; } @@ -8546,18 +8621,49 @@ static int nl80211_dump_station(struct sk_buff *skb, ctx->sinfo.generation)) { genlmsg_cancel(skb, hdr); err = skb->len; + if (ctx->phase == NL80211_DUMP_STA_PHASE_PER_LINK) + goto out_err; goto out_err_release; } - if (nl80211_put_sta_info_common(skb, rdev, &ctx->sinfo)) { - genlmsg_cancel(skb, hdr); - err = skb->len; - goto out_err_release; - } + switch (ctx->phase) { + case NL80211_DUMP_STA_PHASE_AGGREGATED: + ret = nl80211_put_sta_info_common(skb, rdev, &ctx->sinfo); + if (ret) { + genlmsg_cancel(skb, hdr); + err = ret; + goto out_err_release; + } + genlmsg_end(skb, hdr); + + if (ctx->dump_link_stats && ctx->sinfo.valid_links) { + ctx->phase = NL80211_DUMP_STA_PHASE_PER_LINK; + ctx->link_idx = 0; + } else { + cfg80211_sinfo_release_content(&ctx->sinfo); + ctx->sta_idx++; + } + break; - genlmsg_end(skb, hdr); - cfg80211_sinfo_release_content(&ctx->sinfo); - ctx->sta_idx++; + case NL80211_DUMP_STA_PHASE_PER_LINK: + ret = nl80211_put_link_station_payload(skb, rdev, + &ctx->sinfo, + ctx->link_idx); + if (ret == -EMSGSIZE) { + genlmsg_cancel(skb, hdr); + err = skb->len; + goto out_err; + } + if (ret) { + /* skip invalid link, do not abort the dump */ + genlmsg_cancel(skb, hdr); + ctx->link_idx++; + continue; + } + genlmsg_end(skb, hdr); + ctx->link_idx++; + break; + } } out_err_release: From 2bfd9da88b95d89041c91c06acd1c38258edc38b Mon Sep 17 00:00:00 2001 From: P Praneesh Date: Sun, 14 Jun 2026 10:47:35 +0530 Subject: [PATCH 523/562] wifi: cfg80211: support MAC address filtering in station dump for link stats Currently, when userspace requests station information with link statistics using NL80211_CMD_GET_STATION with the NL80211_ATTR_STA_DUMP_LINK_STATS flag, the kernel uses the .doit callback (nl80211_get_station) which sends a single netlink message. For MLO stations with multiple links, the link statistics can be large and may exceed the maximum netlink message size, causing the operation to fail with -EMSGSIZE. The .dumpit callback (nl80211_dump_station) already supports fragmentation across multiple netlink messages, making it suitable for handling large link statistics. However, it currently iterates over all stations on the interface, which is inefficient when userspace only wants information about a specific station. Add support for MAC address filtering in nl80211_dump_station to allow userspace to request fragmented link statistics for a specific station. When NL80211_ATTR_MAC is present in a dump request, cache the MAC address in the dump context and use rdev_get_station() to retrieve information for only that station, instead of iterating over all stations with rdev_dump_station(). This allows userspace tools (like iw) to use NL80211_CMD_GET_STATION with NLM_F_DUMP flag to retrieve complete link statistics for a specific station across multiple netlink messages, avoiding the message size limitation. Signed-off-by: P Praneesh Link: https://patch.msgid.link/20260614051739.3979947-6-praneesh.p@oss.qualcomm.com Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index be16a40132ff..242071ad10d6 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -8474,6 +8474,8 @@ struct nl80211_dump_station_ctx { int link_idx; enum nl80211_dump_station_phase phase; bool dump_link_stats; + bool filter_mac; + u8 filter_mac_addr[ETH_ALEN]; u8 mac_addr[ETH_ALEN]; struct station_info sinfo; }; @@ -8543,10 +8545,22 @@ static int nl80211_dump_station(struct sk_buff *skb, err = -ENOMEM; goto out_err; } + cb->args[2] = (long)ctx; ctx->phase = NL80211_DUMP_STA_PHASE_AGGREGATED; ctx->dump_link_stats = !!attrbuf[NL80211_ATTR_STA_DUMP_LINK_STATS]; - cb->args[2] = (long)ctx; + if (attrbuf[NL80211_ATTR_MAC]) { + const u8 *mac = nla_data(attrbuf[NL80211_ATTR_MAC]); + + if (!is_valid_ether_addr(mac)) { + kfree(ctx); + cb->args[2] = 0; + err = -EINVAL; + goto out_err; + } + ctx->filter_mac = true; + memcpy(ctx->filter_mac_addr, mac, ETH_ALEN); + } } if (!wdev->netdev && wdev->iftype != NL80211_IFTYPE_NAN) { @@ -8554,7 +8568,12 @@ static int nl80211_dump_station(struct sk_buff *skb, goto out_err; } - if (!rdev->ops->dump_station) { + if (ctx->filter_mac) { + if (!rdev->ops->get_station) { + err = -EOPNOTSUPP; + goto out_err; + } + } else if (!rdev->ops->dump_station) { err = -EOPNOTSUPP; goto out_err; } @@ -8575,8 +8594,22 @@ static int nl80211_dump_station(struct sk_buff *skb, } } - err = rdev_dump_station(rdev, wdev, ctx->sta_idx, - ctx->mac_addr, &ctx->sinfo); + if (ctx->filter_mac) { + if (ctx->sta_idx > 0) { + err = skb->len; + goto out_err_release; + } + err = rdev_get_station(rdev, wdev, + ctx->filter_mac_addr, + &ctx->sinfo); + if (!err) + memcpy(ctx->mac_addr, + ctx->filter_mac_addr, ETH_ALEN); + } else { + err = rdev_dump_station(rdev, wdev, ctx->sta_idx, + ctx->mac_addr, + &ctx->sinfo); + } if (err == -ENOENT) { err = skb->len; goto out_err_release; From 64bb408c84325ef1309f7e71af8688345cf1d7ef Mon Sep 17 00:00:00 2001 From: Mohammad Shahid Date: Fri, 3 Jul 2026 23:03:01 +0530 Subject: [PATCH 524/562] ntfs: reparse: remove redundant NULL checks before kvfree() kvfree() safely handles NULL pointers, so the explicit NULL checks before calling kvfree() are unnecessary. This issue was reported by ifnullfree.cocci. Signed-off-by: Mohammad Shahid Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/reparse.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/fs/ntfs/reparse.c b/fs/ntfs/reparse.c index fa523dc3691e..ced4c675d91f 100644 --- a/fs/ntfs/reparse.c +++ b/fs/ntfs/reparse.c @@ -317,8 +317,7 @@ unsigned int ntfs_make_symlink(struct ntfs_inode *ni) } else ni->flags &= ~FILE_ATTR_REPARSE_POINT; - if (reparse_attr) - kvfree(reparse_attr); + kvfree(reparse_attr); return mode; } @@ -358,8 +357,7 @@ unsigned int ntfs_reparse_tag_dt_types(struct ntfs_volume *vol, unsigned long mr } } - if (reparse_attr) - kvfree(reparse_attr); + kvfree(reparse_attr); iput(vi); return dt_type; From c5d69260b58c3d78b2bcfa6da5997e61ce94a147 Mon Sep 17 00:00:00 2001 From: Mohammad Shahid Date: Sat, 4 Jul 2026 16:40:57 +0530 Subject: [PATCH 525/562] ntfs: mft: use kmemdup() instead of kmalloc() and memcpy() Use kmemdup() instead of a separate kmalloc() and memcpy() pair, simplifying the code while preserving the existing behavior. This issue was reported by memdup.cocci. Signed-off-by: Mohammad Shahid Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/mft.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index fd20d7abd6f5..f95e433885a0 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -2420,7 +2420,7 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, * record. */ - (*ni)->mrec = kmalloc(vol->mft_record_size, GFP_NOFS); + (*ni)->mrec = kmemdup(m, vol->mft_record_size, GFP_NOFS); if (!(*ni)->mrec) { folio_unlock(folio); kunmap_local(m); @@ -2429,7 +2429,6 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, goto undo_mftbmp_alloc; } - memcpy((*ni)->mrec, m, vol->mft_record_size); post_read_mst_fixup((struct ntfs_record *)(*ni)->mrec, vol->mft_record_size); ntfs_mft_mark_dirty(folio); folio_unlock(folio); From c02bbc2556708a0389a2b620fea231e5cfd37e0f Mon Sep 17 00:00:00 2001 From: Mohammad Shahid Date: Sat, 4 Jul 2026 20:16:54 +0530 Subject: [PATCH 526/562] ntfs: dir: use kmemdup() instead of kmalloc() and memcpy() Use kmemdup() instead of a separate kmalloc() and memcpy() pair, simplifying the code while preserving the existing behavior. This issue was reported by memdup.cocci. Signed-off-by: Mohammad Shahid Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/dir.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fs/ntfs/dir.c b/fs/ntfs/dir.c index 6fa9ae3377cb..2d594cbb4ebe 100644 --- a/fs/ntfs/dir.c +++ b/fs/ntfs/dir.c @@ -966,13 +966,14 @@ static int ntfs_readdir(struct file *file, struct dir_context *actor) */ private = file->private_data; kfree(private->key); - private->key = kmalloc(le16_to_cpu(next->key_length), GFP_KERNEL); + private->key = kmemdup(&next->key.file_name, + le16_to_cpu(next->key_length), + GFP_KERNEL); if (!private->key) { err = -ENOMEM; goto out; } - memcpy(private->key, &next->key.file_name, le16_to_cpu(next->key_length)); private->key_length = next->key_length; break; } From 8e9685d3c41c35dd1b37df70d854137abcb2fbac Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 6 Jul 2026 16:08:23 +0100 Subject: [PATCH 527/562] Add linux-next specific files for 20260706 Signed-off-by: Mark Brown --- Next/SHA1s | 429 +++ Next/Trees | 429 +++ Next/merge.log | 6516 +++++++++++++++++++++++++++++++++++++++++++++ localversion-next | 1 + 4 files changed, 7375 insertions(+) create mode 100644 Next/SHA1s create mode 100644 Next/Trees create mode 100644 Next/merge.log create mode 100644 localversion-next diff --git a/Next/SHA1s b/Next/SHA1s new file mode 100644 index 000000000000..4516367a3452 --- /dev/null +++ b/Next/SHA1s @@ -0,0 +1,429 @@ +Name SHA1 +---- ---- +origin 8cdeaa50eae8dad34885515f62559ee83e7e8dda +ext4-fixes 981fcc5674e67158d24d23e841523eccba19d0e7 +vfs-brauner-fixes 24dddc384fb9aec2d7eea5463ca6dac98a3b3854 +fscrypt-current dc59e4fea9d83f03bad6bddf3fa2e52491777482 +fsverity-current dc59e4fea9d83f03bad6bddf3fa2e52491777482 +btrfs-fixes 6c893b948351d42cfc3761cc746ab5b3d03ee7f3 +vfs-fixes 49c5d168a3a8f4eb27d44a2a22b7e8a856ca601f +erofs-fixes 1006b2f57f77325bfbf5bd36685efe60334fa360 +nfsd-fixes 92ea163c773cb4d0d5eaf103ed80c49d6758b96f +v9fs-fixes 028ef9c96e96197026887c0f092424679298aae8 +overlayfs-fixes 4549871118cf616eecdd2d939f78e3b9e1dddc48 +fscrypt 2d0afaac9137e95e504bd3ad3512a9044745536b +btrfs 0542d886e45d5b1ac54c37f5d303c71e8683471e +ceph 1397040896280b7a6a6213e874ee4f6146bc9cd9 +cifs 816059941cb6421d759b6abf1aac5d8b4d174617 +configfs 6363844fdbbb76afe1d44d678fe0746390204a5f +ecryptfs 95ce5ffd54cf66098f91892f98606c3bd33846fe +dlm e61113cfcaf71cbdf4c17e3d086d8fb7f92c62bf +erofs dc59e4fea9d83f03bad6bddf3fa2e52491777482 +exfat 5362e7aedd113bb8b042e47e385103fe98274764 +ext3 42093d1948d2de3991a8b20ac5d12df13b941aad +ext4 c143957520c6c9b5cd72e0de8b52b814f0c576fe +f2fs dc59e4fea9d83f03bad6bddf3fa2e52491777482 +fsverity dc59e4fea9d83f03bad6bddf3fa2e52491777482 +fuse 7d87a5a284bb34edb3f4e7e312ef403b3385a7b7 +gfs2 5f80d9113360c08111ae7471f662f3f89f23ce32 +jfs dad98c5b2a05ef744af4c884c97066a3c8cdad61 +ksmbd 8cdeaa50eae8dad34885515f62559ee83e7e8dda +nfs dc59e4fea9d83f03bad6bddf3fa2e52491777482 +nfs-anna 284ea3fb4f6715201e1d9ef3474c25e817ad70e9 +nfsd 26ddc8a26f00b67fd9b40e134f06ad269b46c1c7 +ntfs c02bbc2556708a0389a2b620fea231e5cfd37e0f +ntfs3 dc59e4fea9d83f03bad6bddf3fa2e52491777482 +orangefs e61bc5e4d87433c8759e7dc92bb640ef71a8970c +overlayfs 1f6ee9be92f8df85a8c9a5a78c20fd39c0c21a95 +ubifs 11efa98bcc0d00271e8f66c7c940c284f973f39d +v9fs aa88278693cbfaf7a2acf961379973fbb63b165c +v9fs-ericvh 028ef9c96e96197026887c0f092424679298aae8 +xfs e4281086ae6caf006b6ef0670479eb5f96880fb9 +zonefs 3a8389d42bdf4213730f4067f8bfa78bae6564ef +vfs-brauner cf6f88615485a68df77092de1f90f88708a32fa6 +vfs 4dda01b67c8662c5d0c53034974cb8280c575a5d +mm-hotfixes 79973aa4f4d1060e2e5a201175babb03312642f4 +fs-current 69c4514f08f94eb3870edce65f061d8dd7fc7fcc +kbuild-current dc59e4fea9d83f03bad6bddf3fa2e52491777482 +clang-fixes 175db11786bde9061db526bf1ac5107d915f5163 +arc-current 7aaa8047eafd0bd628065b15757d9b48c5f9c07d +arm-current 009b6c6486b94a3aff566b017256b598dc96bf18 +arm64-fixes a52d6c7160f7e2f8c56adf29146385b8f2868d3d +arm-soc-fixes 9c648f3554920721d8878807cd794fe2d7f989e8 +davinci-current dc59e4fea9d83f03bad6bddf3fa2e52491777482 +drivers-memory-fixes dc59e4fea9d83f03bad6bddf3fa2e52491777482 +sophgo-fixes 19272b37aa4f83ca52bdf9c16d5d81bdd1354494 +sophgo-soc-fixes 0af2f6be1b4281385b618cb86ad946eded089ac8 +m68k-current dc5200f6b1ada318463dd141b041ec9f044b2bf5 +powerpc-fixes 5200f5f493f79f14bbdc349e402a40dfb32f23c8 +s390-fixes 2995ccec260caa9e85b3301a4aba1e66ed80ad74 +net 9e05e91a9a847ed57926414bd7c2c5e54d6c56c6 +bpf d2c9a99135da931377240942d44f3dea104cedb8 +ipsec ea528f18231ec0f33317be57f8866913b19aba6e +netfilter d6456743424721a837e1509b912f362caaeecd97 +ipvs fe9f4ee6c61a1410afd73bf011de5ae618004796 +wireless f843cf31dfc2c75843362a8f7e75180ed0cc44d6 +ath e8d85672dd7e2523f774caafba8f858384e18df7 +iwlwifi 093305d801fae6ff9b8bb531fd78b579794c4f80 +wpan 8ce4f287524c74a118b0af1eebd4b24a8efca57a +rdma-fixes bb27fcc67c429d97f785c92c35a6c5adebb05d7f +sound-current c845febafd92b2056abc0af541c1ad85785f1353 +sound-asoc-fixes 83245e7a436c04e511378af14dd81fd188b41541 +regmap-fixes fabc5b59d3e647637dd71cdb94c96ac0fbecf632 +regulator-fixes d504c1edc4a953e3594e2f0dc0b5c28403a2e48c +spi-fixes 6b448abe08f9b53e2c6da1c1e5ab8283f0ec77e5 +pci-current 34db32102439d948e0c9aea060e48f979aae827d +driver-core.current 667d0fb32149f023b8b34a1f6f3d384556eafb5a +tty.current dc59e4fea9d83f03bad6bddf3fa2e52491777482 +usb.current 07acd41f72eec827963b239565925af2bcb3a54b +usb-serial-fixes 6bfc8d01ac4068eced509f8fc74d0cd205e4dcec +phy dc59e4fea9d83f03bad6bddf3fa2e52491777482 +staging.current dc59e4fea9d83f03bad6bddf3fa2e52491777482 +iio-fixes a9f41809bf1bd8e5c1bc4b6a1052adac58eb7ab6 +watchdog-fixes 36e05e134ee44f9fbfcebcbcdadb5f765fccd9f0 +counter-current dc59e4fea9d83f03bad6bddf3fa2e52491777482 +char-misc.current bc4a9828897871ff3e5a1f8a1d346decbf4ee95e +soundwire-fixes dc59e4fea9d83f03bad6bddf3fa2e52491777482 +thunderbolt-fixes db79679595326fd3f6bd1e6fd0cefc3ba016039a +input-current 536394ec81419b67d9f4f0028812c4372397be1b +crypto-current 6ea0ce3a19f9c37a014099e2b0a46b27fa164564 +libcrypto-fixes dc59e4fea9d83f03bad6bddf3fa2e52491777482 +vfio-fixes e242e974e812e7a47e3088860c80d9492fac314f +kselftest-fixes dc59e4fea9d83f03bad6bddf3fa2e52491777482 +dmaengine-fixes 867621ba203027338b525af6729719c544135336 +backlight-fixes dc59e4fea9d83f03bad6bddf3fa2e52491777482 +mtd-fixes 2b533e775aec580cf60074417f4ca00ac9cf3580 +mfd-fixes d5d2d7a8d8be18681a0864f58e3875f1c639e11c +v4l-dvb-fixes dc59e4fea9d83f03bad6bddf3fa2e52491777482 +reset-fixes 71827776667f4e4677a4fa806bcfb24d4b8dd9d7 +mips-fixes 8cdeaa50eae8dad34885515f62559ee83e7e8dda +at91-fixes 765aaba18413a66f6c8fe8416336ca9b3dd98a79 +omap-fixes dc59e4fea9d83f03bad6bddf3fa2e52491777482 +kvm-fixes 098e32cba334da0f3fa8cfd4e022ae7c72341400 +kvms390-fixes 4cb62e371ffadd8be14febb0f9aba8c4d69570af +kvm-arm-fixes d098bb75d14fde2f12155f1a95ec0168160867ce +hwmon-fixes fe87b8dc67f1b2c64e76a66e78468c533d3c44ca +nvdimm-fixes a8aec14230322ed8f1e8042b6d656c1631d41163 +cxl-fixes d90f236f8b9e354848bd226f581db27755ab901d +dma-mapping-fixes dc59e4fea9d83f03bad6bddf3fa2e52491777482 +drivers-x86-fixes d3666875c75eb1bc8090343fa0d6fc8fb7924356 +samsung-krzk-fixes dc59e4fea9d83f03bad6bddf3fa2e52491777482 +pinctrl-samsung-fixes dc59e4fea9d83f03bad6bddf3fa2e52491777482 +pinctrl-qcom-fixes 437a8d2aa1aa442c4a176fdf4700a9b3bb0c8794 +devicetree-fixes b39a6b2e9d5bd6a3153aed4c7440172b8f6a739e +dt-krzk-fixes dc59e4fea9d83f03bad6bddf3fa2e52491777482 +scsi-fixes 5d5221f8a4064a256b9499485a9f8c6f530f21dc +drm-fixes 8cdeaa50eae8dad34885515f62559ee83e7e8dda +drm-intel-fixes 2084503f2d087bf956198e7f6eb25b03a7049cb2 +mmc-fixes dc59e4fea9d83f03bad6bddf3fa2e52491777482 +rtc-fixes 254f49634ee16a731174d2ae34bc50bd5f45e731 +gnss-fixes 5d6919055dec134de3c40167a490f33c74c12581 +hyperv-fixes dc59e4fea9d83f03bad6bddf3fa2e52491777482 +risc-v-fixes bc7b086a45521a986a49045907f017e3e46c763e +riscv-dt-fixes 0df8aa2b9aec5cd21e8c71d9cc1227e57bea43b3 +riscv-soc-fixes 75ef233975589d9a8c88bc8822a7c725c71ff650 +fpga-fixes 19272b37aa4f83ca52bdf9c16d5d81bdd1354494 +spdx dc59e4fea9d83f03bad6bddf3fa2e52491777482 +gpio-brgl-fixes db4a79713ed8e252d5e4edf6eaaa80948b6855a2 +gpio-intel-fixes dc59e4fea9d83f03bad6bddf3fa2e52491777482 +pinctrl-intel-fixes dc59e4fea9d83f03bad6bddf3fa2e52491777482 +auxdisplay-fixes dc59e4fea9d83f03bad6bddf3fa2e52491777482 +kunit-fixes dc59e4fea9d83f03bad6bddf3fa2e52491777482 +memblock-fixes 2ebce860bdd7ae5e13002811bc9bbbf33fcfc221 +renesas-fixes 8a78d1c454b3cc7b66d52582474e8b3732833352 +perf-current 558ef39aeb9a089a6be9dda8413b0b9d42e843ea +efi-fixes d8809f6931065cbbf3554647a50a65a471ab5983 +battery-fixes 254f49634ee16a731174d2ae34bc50bd5f45e731 +iommufd-fixes dc59e4fea9d83f03bad6bddf3fa2e52491777482 +rust-fixes 608045a91d9176d66b2114d0006bc8b57dff2ca9 +w1-fixes dc59e4fea9d83f03bad6bddf3fa2e52491777482 +pmdomain-fixes dc59e4fea9d83f03bad6bddf3fa2e52491777482 +i2c-andi-fixes dc59e4fea9d83f03bad6bddf3fa2e52491777482 +i2c-rust-fixes 4eb422482ca5d924d7212ad2ca1cb7ea6f5b524d +sparc-fixes 254f49634ee16a731174d2ae34bc50bd5f45e731 +clk-fixes f29a24c94705496254dd9ce7ea8a31efb6eb4358 +thead-clk-fixes 8f0b4cce4481fb22653697cced8d0d04027cb1e8 +tenstorrent-clk-fixes 6de23f81a5e08be8fbf5e8d7e9febc72a5b5f27f +fustini-config-fixes 254f49634ee16a731174d2ae34bc50bd5f45e731 +pwrseq-fixes dc59e4fea9d83f03bad6bddf3fa2e52491777482 +thead-dt-fixes 254f49634ee16a731174d2ae34bc50bd5f45e731 +ftrace-fixes 1650a1b6cb1ae6cb99bb4fce21b30ebdf9fc238e +ring-buffer-fixes 057caace5214da3b457bbd295e1a2ad34d3685ea +trace-fixes dc59e4fea9d83f03bad6bddf3fa2e52491777482 +tracefs-fixes 07004a8c4b572171934390148ee48c4175c77eed +spacemit-fixes dc59e4fea9d83f03bad6bddf3fa2e52491777482 +tip-fixes f5ee06c5f24e2318305ee56a1425f64375a572d7 +slab-fixes 67ea9d353d0ba12bdbc9183ff568dead9e949b80 +kexec-fixes dc59e4fea9d83f03bad6bddf3fa2e52491777482 +liveupdate-fixes dc59e4fea9d83f03bad6bddf3fa2e52491777482 +drm-msm-fixes db339b6bc9f234b4883eb02946ea01d8d9faa11c +uml-fixes 254f49634ee16a731174d2ae34bc50bd5f45e731 +fwctl-fixes 4549871118cf616eecdd2d939f78e3b9e1dddc48 +devsec-tsm-fixes c3fd16c3b98ed726294feab2f94f876290bf7b61 +drm-rust-fixes dc59e4fea9d83f03bad6bddf3fa2e52491777482 +tenstorrent-dt-fixes 6de23f81a5e08be8fbf5e8d7e9febc72a5b5f27f +nfc-fixes e87faad807329d1348595dbcea3444acd7bb0ca6 +drm-misc-fixes 6bb8898f702385d363dc2c513a1efa62807f8068 +rust dc59e4fea9d83f03bad6bddf3fa2e52491777482 +rust-interop 05f7e89ab9731565d8a62e3b5d1ec206485eeb0b +rust-alloc dc59e4fea9d83f03bad6bddf3fa2e52491777482 +rust-io 86731a2a651e58953fc949573895f2fa6d456841 +rust-pin-init dc59e4fea9d83f03bad6bddf3fa2e52491777482 +rust-timekeeping ddb1444d3335129ae87d9796ab1debf41c0ee51b +rust-xarray c455f19bbe6104debd980bb15515faf716bd81b8 +rust-analyzer 5f45afb8ab04d934fc0601a202f95267ebc20059 +mm-stable dc59e4fea9d83f03bad6bddf3fa2e52491777482 +mm-nonmm-stable dc59e4fea9d83f03bad6bddf3fa2e52491777482 +mm-unstable d148260a31fddf6d59cc0ea4980bd78ebe301a91 +mm-nonmm-unstable a1e4a999fa76f1b904326dc1ba869625b6d48f32 +kbuild 078f4b01a0cffa1873e37d6f0575274df3149f51 +clang-format 8f0b4cce4481fb22653697cced8d0d04027cb1e8 +perf ca0e19074bd6afcb9c7b23aa474ca17238cdb241 +compiler-attributes 8f0b4cce4481fb22653697cced8d0d04027cb1e8 +dma-mapping dc59e4fea9d83f03bad6bddf3fa2e52491777482 +asm-generic adbbd9714f8058730f93c8df5c5bf1679456424b +alpha d58041d2c63e09a1c9083e0e9f4151e487c4e16a +arm 75ca78c7d982a0b7c658722fa9eb2ce70e180307 +arm64 36fa5ffa60344bcc59fb3f50b33af8187e6b8753 +arm-perf 5936245125f78d896fdb1bbc2ae79213e28a6579 +arm-soc 972ea78305bddb6a1db0586357914f125e913ffe +amlogic 4336e970ec6890fbd424128c352564a9c1dd514a +asahi-soc a23ce286383c90a828f6ab9f4154f4abf778cc32 +at91 6d892f8d5cd9f20cfef5e9d59b0cde53c30daec6 +bmc 40ae2e16214adcd59481a8d799af03be7b0f5e08 +broadcom f705b0c80bab155e9ffc6b796408ab16c9abf206 +cix a0cffbd8878c55b12dc4555f883adf3372cecd91 +davinci dc59e4fea9d83f03bad6bddf3fa2e52491777482 +drivers-memory 1527acf2295cf2d6e11e3e9b121d63709667e6e7 +fsl c99d8ac7b355cbbdbb3f4d8b9a9b46de05a6fee9 +imx-mxs d0c222c2e2ce577d801bdf129dc6c078f29e22df +mediatek eb3e990db356964d64f5cc258d30776c0363f9e9 +mvebu 3ea0dabfa4500104c5c6eebecfa4d23aeb847611 +omap f338cc54e10ab25633a97953e0a6b2265ba65c2c +qcom 696bef37f7f22f894ffc2059bdf9ec76b309cf8f +renesas 30d3a7db7471001c926c20333e07e54749fd3572 +reset d373605cd514837d8a6de3d00c786d4bae6dbaf8 +rockchip b143af2d0da7b01f82f8ea795a0623effab394e7 +samsung-krzk 5346a27fed4d8f0d781b0035ab066f6684cc641e +scmi edb4db149ba0ad5b017ec26513a62bce6567c5e4 +sophgo f7337210bede62fc7c6230ef58013dddf7e0a921 +sophgo-soc c8754c7deab4cbfa947fa2d656cbaf83771828ef +spacemit 51ed53630915c9cc290036fe4f430849e23bbf8e +stm32 fba4a31a7f3b6b29b01c83180f83e7ed4c398738 +sunxi 5508b1e45bba27d96e4bd6a8500b66e6311d5ab8 +tee a3067938fd192b116b6dfb325b654300a6d1b461 +tegra 75767942c9895ac6e29b931dee9a2e79f9269dd4 +tenstorrent-dt 33583baeb1ba7d328e6a9775d889036900b74cdb +fustini-config fb266bbe9aef17d547db468a1a7187b59ed33e9a +thead-dt 3a5791956edbfa84d7256224167941931cbc46e7 +ti 1b98f483adfd99bf7008dc334ae72fc608d47a77 +xilinx f608bce703fc31a2cdf67abe1de882d5bbc45142 +socfpga 0764f42a8ba90cc390070dd48dbc0afde5146f86 +clk 92010229c4b38897f1319d260162d2f96925ed17 +clk-imx b3dcc8c608fbb6352bd94932ba935f2078c9090d +clk-renesas 91fb4643954379f8493dba649d520c23f0d1f4e6 +thead-clk baf4fc7c03bd0f68c768cfe27829674bd060c6b4 +tenstorrent-clk 23c8ebc952849b3ba47d04d0ec95daf5cc136061 +csky abb81e5ce7d995baa41556b8125fa59e28ba3be8 +loongarch 262a3b4fa1792d40728c69995924e11cf761f5cf +m68k dc5200f6b1ada318463dd141b041ec9f044b2bf5 +m68knommu dc59e4fea9d83f03bad6bddf3fa2e52491777482 +microblaze 6de23f81a5e08be8fbf5e8d7e9febc72a5b5f27f +mips 8cdeaa50eae8dad34885515f62559ee83e7e8dda +openrisc aca063c9024522e4e5b9a9d1927433f6a01785a3 +parisc-hd dc59e4fea9d83f03bad6bddf3fa2e52491777482 +powerpc 42f252f5a646866a95f025863c8b201042494ba1 +risc-v 798246e5edfb3aa0b2d6dca46f41014d0b99b209 +riscv-dt 82ab962ef6c2a3571e3d590b30c023118c92b776 +riscv-soc dfceff38cea2b80c6871459c30a8cb96fecc533b +s390 eb3690e410086649d90fdd32813ebb57a8941603 +sh b0aa5e4b087b686575f1b31ce54048b4d059b7b8 +sparc 5b2a3b1a98fb47c593144c2770e012d463952b70 +uml 254f49634ee16a731174d2ae34bc50bd5f45e731 +xtensa a7c958e8721eb2724ee2e2ef13129bc67e1c8a75 +fs-next 3e912da38b64f6e67840e44b4ecbcc7297e808df +printk 080d60fffa8e0d285871cde8395438006a9b5b0c +pci bcabbbd29c53a5b07eff29f8861d045b2c709c26 +pstore 24b8f8dcb9a139a36cf48bfbe935e8dc1f33ed79 +hid 194a48576843858afcd9c00ed4f105ec2b398e6c +i2c 8cd9520d35a6c38db6567e97dd93b1f11f185dc6 +i2c-andi 04e9bf1648f846976b543e91c1838a712433772a +i2c-rust c0260c740d20751c8498865a9c5dc18b69c3d267 +i3c eeedacd0866eec6e9dad2f082a5b828b895a5ed0 +dmi 1afafbaf749d8e8ec53f8e38efdc731131902b5b +hwmon-staging a810e1aa11bf6f4fb1bfc2b94bc7541615379209 +jc_docs 2933b82083e758fe6cfff570143541d4dba672c3 +v4l-dvb 8dac27bfa2f994ecb11f01a63641527d17d48fc1 +v4l-dvb-next adc218676eef25575469234709c2d87185ca223a +pm da2e2fd83f1ef6160bfe2b863bbb904b44263dc4 +cpufreq-arm f456178290356167c7349d51432889113565d1ba +cpupower dc59e4fea9d83f03bad6bddf3fa2e52491777482 +devfreq c096be11c2a9d4acca8e75f1f8edeb744aceb521 +pmdomain dc59e4fea9d83f03bad6bddf3fa2e52491777482 +opp dc59e4fea9d83f03bad6bddf3fa2e52491777482 +thermal 968098b4ca5219b0d2e0a981aed1dacfbd5adc69 +rdma c3fd3966f7dd871e47f9bcd8fe90d6e23e4cdb1a +net-next 1704cc8640702c93e79921e2dffff3cfa31f0ce5 +bpf-next 87bfe634b1193db90e5170e1ddbad04a63ef4501 +ipsec-next 355f808d8a11fa69b19dfd8811bc87d97830f5d6 +mlx5-next ddbddbf8aee54bee038149187270c93a45478473 +netfilter-next b73bc9ca3686b78b642fb35dcc1fdf874ecb74a1 +ipvs-next 805185b7c7a1069e407b6f7b3bc98e44d415f484 +bluetooth 7ea67149af719895ddf003cb8e9d2b287ef0a223 +wireless-next 2bfd9da88b95d89041c91c06acd1c38258edc38b +ath-next 913998f903fb1432c0046c33003db38a9e8bedb1 +iwlwifi-next a6136ca2dd9773d6bcd45e8290403536c9c71054 +wpan-next a6bfdfcc6711d1d5a92e98644359dedc67c0c858 +wpan-staging a6bfdfcc6711d1d5a92e98644359dedc67c0c858 +mtd d276783e490d73536135f90f96c5875f263481ab +nand adfc275b317c02cd043b0cf28b8cfb7459b041f0 +spi-nor df415c5e1de0f1aeefacb4e6252ff98d38c04437 +crypto e264401ce4776a288524e5b87593d4d864147115 +libcrypto 1002500f54b5f60faa6a03636c7865fd4db53ad6 +drm 8cdeaa50eae8dad34885515f62559ee83e7e8dda +drm-exynos 3a8660878839faadb4f1a6dd72c3179c1df56787 +drm-misc b7fcb70162acd7f15ed20bc64a14c150db34256f +amdgpu 50be7c9b5d5ea55fd40bb411cf324cec99ec7417 +drm-intel cd0121502ce61a2efec23fb61b90ffec300be281 +drm-msm 9a967125427e03c7ebc24d7ad26e9307e8403d4e +drm-msm-lumag 8de061ca149c94871e41d91a5b2e442ecc26a572 +drm-xe 3a8bfa1f2af71ca21818253753fccc53337b7b9b +drm-rust 78900738d981c97aad929e33620340d0d9bcfc82 +drm-nova 93296e9d9528f0d87f2cf3fee494599060a0f14a +etnaviv 6bde14ba5f7ef59e103ac317df6cc5ac4291ff4a +fbdev dc59e4fea9d83f03bad6bddf3fa2e52491777482 +regmap 1c790e0326461809ac256c91402d61747f2143c9 +sound bd8f2187618980f5274a74321ea4844df8667488 +ieee1394 dc59e4fea9d83f03bad6bddf3fa2e52491777482 +sound-asoc be44d21728b6646189779923b841ad3a46d694e5 +modules 36d6b929bb0c7cc1cc742d9b5805537d3b651094 +input c8f174900926d3b58cd048ac33b4cbb3de419bfe +block 39dffa2bee1ba8ed84b6befed3678a1040764ff2 +device-mapper 55c99f2d901c783c49dddd98f7c05d98ad834ce9 +libata e64e6b5dc86758c14ed28a6e85bf5d1b78146ed5 +pcmcia b3c26ea81ccc522e77ed0b1707add61fc9206216 +mmc dc59e4fea9d83f03bad6bddf3fa2e52491777482 +mfd f658393698772d1f4f85b784814030e7af0ade64 +backlight dc59e4fea9d83f03bad6bddf3fa2e52491777482 +battery a888754e51e915731c8974c4d6d62709facb35d3 +regulator 69d7343e4df1e9954ecb275e95d156182f2daf3b +security 149b192e376d746bf7b8e1e02541c2256c3b17f0 +apparmor dda61023f976d7ab3bc7c8b46d26d6d23424f890 +integrity 35d6f5e788dae0dcc4c42d1280360f19aef9ab52 +selinux ae3d476caeba0bb7779334b1ce9d9a795ed602d2 +smack a7c44fd9f80e37763acf9cd3c87a58058d206427 +tomoyo 110ef9f94f84d425ab55d09b2d70ed12ceffdf08 +tpmdd-tpm 4ece5759c1a29fd711b7b1d1a3b81078547697fd +tpmdd-keys 51cb1aa1250c36269474b8b6ca6b6319e170f5a5 +watchdog bc54a071a02dc396af9a61a7055b646194cb23d4 +iommu dd8a3c6cd531dca5917111a94fa3074077f6ba5a +audit e46c818d8fa664f57628fae93fe24f51e34d28c3 +devicetree 0108f362e410693f056da2569f8adf2c9705393d +dt-krzk dc59e4fea9d83f03bad6bddf3fa2e52491777482 +mailbox 36cac4b5101f8ecbc851356df175b99543c84ec6 +spi a1766c1c03f8261b26eb7516a4d9941dd70c27a6 +tip aa6d9def48ea424a50c21de90ebe609c383cb05d +kexec af78cec42d1b0113993e427139f0ab2f34b047ee +liveupdate af78cec42d1b0113993e427139f0ab2f34b047ee +clockevents a9ac745bc320cbdc2ed3c851eb78f91f22ff975b +edac 23f1fdb15eeb9b370e7345d5b8d7f20b7c80313f +ftrace 030bb1d8bb0773053df6518ed635f27005fd5e43 +rcu e853c1b28580ea93fda3cd729e440a3fc16fa647 +paulmck 48f7a50c027dd2abb9e7b8a6ecc8e531d87f2c21 +kvm a204badd8432f93b7e862e7dac6db0fe3d65f370 +kvm-arm 1ee27dacbe5dc4def481794d899d67b0d4570094 +kvms390 babe08404e1993697a523e60bc0f9d096ffe1ef8 +kvm-ppc 5200f5f493f79f14bbdc349e402a40dfb32f23c8 +kvm-riscv 52738352a6f29279e15285fcb7b50241ef867e27 +kvm-x86 50406d35f5635e1cc523e61409d57e851b5f5df8 +xen-tip fcd245ea7528d50fddffc0fd1308941a9180f5b3 +percpu 8f0b4cce4481fb22653697cced8d0d04027cb1e8 +workqueues ecf5aad9a4417fece80890f27a9899db90c9c457 +sched-ext 57194a3172ba0123e8f37c4574a8e2863ab67622 +drivers-x86 ff7836fa850c2f815bc219f1e48f6ec8699f4ae7 +chrome-platform d1ceb2b2324717fa30b44d56ef0c52813e239569 +chrome-platform-firmware dd06eb60e86a1ab6d3bd4f641100f47b6f7342b6 +hsi 254f49634ee16a731174d2ae34bc50bd5f45e731 +leds-lj 7ddc04d1bd08f80ffc1e2fb97f3fc6cacab0ffc0 +ipmi 6d920a75df9a83ab096b3cde7a643b656e4fdfeb +driver-core 5dcef303b29f004a447d9c69e62963328da9c608 +usb dc59e4fea9d83f03bad6bddf3fa2e52491777482 +thunderbolt d49d71ebec8f03eee2514304279a05a4952fadf7 +usb-serial dc59e4fea9d83f03bad6bddf3fa2e52491777482 +tty e508a176d86f5ca0916ac1caf80806f9ad3d91ef +char-misc 9e32d2a9784736b3fc262f51ddda1141de753314 +coresight 98495b5a4d77dd22e106f462b76e1093a55b29a7 +fastrpc 17ec912dc87bf6b9aba3a85f6a94e99f999d6511 +fpga 43a1974da6bc7ce8f4d1dc1d03d56997428c29c3 +icc 94fe92d2f662b990da2ef9788bbe3bdcfe086731 +iio fef4337eb2888c758c7058e1723903204f012a26 +nfc ed85d4cbbfaa4e630c5aa0d607348b42620d976b +phy-next dc59e4fea9d83f03bad6bddf3fa2e52491777482 +soundwire 90af3209742db61a7f9d7d054a16165818cfc6d8 +extcon 254f49634ee16a731174d2ae34bc50bd5f45e731 +gnss 5d6919055dec134de3c40167a490f33c74c12581 +vfio 785562e31dbcd85ca583cf58c446e63aa8a5af08 +w1 169ae5e65e5aaf213b6a578f6478a9fd2e523606 +spmi 3443eec9c55d128064c83225a9111f1a1a37277a +staging dc59e4fea9d83f03bad6bddf3fa2e52491777482 +counter-next dc59e4fea9d83f03bad6bddf3fa2e52491777482 +mux 59b723cd2adbac2a34fc8e12c74ae26ae45bf230 +dmaengine 0d3e3376b289cdaff5b3b6c1581999926ff1000f +cgroup ec98784b247b2c0544673710720a0f9fb76e269d +scsi cf1af0ccca54d5547cc31bf476c6a07cf658ad29 +scsi-mkp 57a6ed0b41677ccc5e28cc0976e495c1dfa33747 +vhost 1f673033c02735bb38ee2f16f1c86f042f4de9d4 +rpmsg 8ca01dbc70b5856b8ad40f5178d6cbf13898896f +gpio-brgl 8fe6fa0f223f44b8b869319d1b9c383749aa147b +gpio-intel dc59e4fea9d83f03bad6bddf3fa2e52491777482 +pinctrl 94cb9e8f270797e489633cfa53d2d44afecb8bef +pinctrl-intel dc59e4fea9d83f03bad6bddf3fa2e52491777482 +pinctrl-renesas 5a653cedec948423fc8a0180c90b8d05c2239d9d +pinctrl-samsung dc59e4fea9d83f03bad6bddf3fa2e52491777482 +pinctrl-qcom 251b53103a2e5770658ae106c490cdd2b7512c3a +pwm 1a4920940ebfd8d907858abd8f8dd09b13752946 +ktest 932cdaf3e273a2727e77af97f79f12577174c5a0 +kselftest dc59e4fea9d83f03bad6bddf3fa2e52491777482 +kunit dc59e4fea9d83f03bad6bddf3fa2e52491777482 +kunit-next 643ec8ff7d8ed15881bd05d71de24208ce0dadcb +livepatching 0839c8963b7b28d25350bd5ea69bacde794124ab +rtc dc59e4fea9d83f03bad6bddf3fa2e52491777482 +nvdimm 86e411b6ec277dbb8ac1f1d855dc337181a62a29 +at24 dc59e4fea9d83f03bad6bddf3fa2e52491777482 +ntb dc59e4fea9d83f03bad6bddf3fa2e52491777482 +seccomp 41fa04327384148b0e2e828c9be9862c5240e9fa +slimbus c922423ce66bcee6c61d1b5aadea09b29ca81400 +nvmem a3122a19f00b75a8b98b96efb4e25e3e0bfb7365 +hyperv a4ffc59238be84dd1c26bf1c001543e832674fc6 +auxdisplay dc59e4fea9d83f03bad6bddf3fa2e52491777482 +kgdb fdbdd0ccb30af18d3b29e714ac8d5ab6163279e0 +hmm 19272b37aa4f83ca52bdf9c16d5d81bdd1354494 +cfi dc59e4fea9d83f03bad6bddf3fa2e52491777482 +mhi 32845111e8ea6ef5e837b6d25080e69e99bd4561 +memblock 717cd4552ae422269f844aa19ce0125462c9c0b0 +cxl bdc0a9797abd91556f73d1c8cdae878853521699 +zstd 65d1f5507ed2c78c64fce40e44e5574a9419eb09 +efi 48a428215782321b56956974f23593e40ce84b7a +unicode bcfee135d584714c2130031c7e28aafa91057b9a +slab ea0c7a9244594f1d71268e8636a8601217e4839d +random bb9ff576fdff48c242876f55098a3ee20a29df5d +landlock 98c522eb8d64c67a82d489dd9b616a45851553e1 +sysctl f63a9df7e3f9f842945d292a19d9938924f066f9 +execve 9bf092c97b86af63694d9902b9e14047214ba76d +bitmap 40d3c88df9d82d32eb52600a06a629520a7900fc +hte 005b25ad11717139ca5c14c9f06c2a60855c2afd +kspp c2606fb910d7cade63ee7e9c28e6a4225d0383f6 +nolibc a3b2181459a2c74c03ddbad585f884eefc8ff8ff +iommufd dc59e4fea9d83f03bad6bddf3fa2e52491777482 +turbostat 1c996a37fd244d19e5dbb715328c1676e28ef607 +pwrseq 162ea02941a936f8899f3dbe10607b1d5af1b07b +capabilities-next 071588136007482d70fd2667b827036bc60b1f8f +ipe 028ef9c96e96197026887c0f092424679298aae8 +kcsan 07a1a6562ce29e2e0c134a57882d6e52e8758492 +crc dc59e4fea9d83f03bad6bddf3fa2e52491777482 +keys-next 965e9a2cf23b066d8bdeb690dff9cd7089c5f667 +fwctl dc59e4fea9d83f03bad6bddf3fa2e52491777482 +devsec-tsm 3177779ae17db4c66c851f799505fb95c7530c03 +hisilicon 6cdd694551d6a11fb03d154fd7e15ab2f9b3f1f9 +device-id 995832b2cebe6969d1b42635db698803ee31294d +kthread fa39ec4f89f2637ed1cdbcde3656825951787668 diff --git a/Next/Trees b/Next/Trees new file mode 100644 index 000000000000..ef300c6c0ed5 --- /dev/null +++ b/Next/Trees @@ -0,0 +1,429 @@ +Trees included into this release: + +Name Url +---- --- +origin https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git#master +ext4-fixes https://git.kernel.org/pub/scm/linux/kernel/git/tytso/ext4.git#fixes +vfs-brauner-fixes https://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs.git#vfs.fixes +fscrypt-current https://git.kernel.org/pub/scm/fs/fscrypt/linux.git#for-current +fsverity-current https://git.kernel.org/pub/scm/fs/fsverity/linux.git#for-current +btrfs-fixes https://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux.git#next-fixes +vfs-fixes https://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs.git#fixes +erofs-fixes https://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs.git#fixes +nfsd-fixes https://git.kernel.org/pub/scm/linux/kernel/git/cel/linux#nfsd-fixes +v9fs-fixes https://git.kernel.org/pub/scm/linux/kernel/git/ericvh/v9fs.git#fixes/next +overlayfs-fixes https://git.kernel.org/pub/scm/linux/kernel/git/overlayfs/vfs.git#ovl-fixes +fscrypt https://git.kernel.org/pub/scm/fs/fscrypt/linux.git#for-next +btrfs https://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux.git#for-next +ceph https://github.com/ceph/ceph-client.git#master +cifs git://git.samba.org/sfrench/cifs-2.6.git#for-next +configfs https://git.kernel.org/pub/scm/linux/kernel/git/a.hindborg/linux.git#configfs-next +ecryptfs https://git.kernel.org/pub/scm/linux/kernel/git/tyhicks/ecryptfs.git#next +dlm https://git.kernel.org/pub/scm/linux/kernel/git/teigland/linux-dlm.git#next +erofs https://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs.git#dev +exfat https://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/exfat.git#dev +ext3 https://git.kernel.org/pub/scm/linux/kernel/git/jack/linux-fs.git#for_next +ext4 https://git.kernel.org/pub/scm/linux/kernel/git/tytso/ext4.git#dev +f2fs https://git.kernel.org/pub/scm/linux/kernel/git/jaegeuk/f2fs.git#dev +fsverity https://git.kernel.org/pub/scm/fs/fsverity/linux.git#for-next +fuse https://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/fuse.git#for-next +gfs2 https://git.kernel.org/pub/scm/linux/kernel/git/gfs2/linux-gfs2.git#for-next +jfs https://github.com/kleikamp/linux-shaggy.git#jfs-next +ksmbd https://github.com/smfrench/smb3-kernel.git#ksmbd-for-next +nfs git://git.linux-nfs.org/projects/trondmy/nfs-2.6.git#linux-next +nfs-anna git://git.linux-nfs.org/projects/anna/linux-nfs.git#linux-next +nfsd https://git.kernel.org/pub/scm/linux/kernel/git/cel/linux#nfsd-next +ntfs https://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/ntfs.git#ntfs-next +ntfs3 https://github.com/Paragon-Software-Group/linux-ntfs3.git#master +orangefs https://git.kernel.org/pub/scm/linux/kernel/git/hubcap/linux.git#for-next +overlayfs https://git.kernel.org/pub/scm/linux/kernel/git/overlayfs/vfs.git#overlayfs-next +ubifs https://git.kernel.org/pub/scm/linux/kernel/git/rw/ubifs.git#next +v9fs https://github.com/martinetd/linux#9p-next +v9fs-ericvh https://git.kernel.org/pub/scm/linux/kernel/git/ericvh/v9fs.git#ericvh/for-next +xfs https://git.kernel.org/pub/scm/fs/xfs/xfs-linux.git#for-next +zonefs https://git.kernel.org/pub/scm/linux/kernel/git/dlemoal/zonefs.git#for-next +vfs-brauner https://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs.git#vfs.all +vfs https://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs.git#for-next +mm-hotfixes https://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm#mm-hotfixes-unstable +kbuild-current https://git.kernel.org/pub/scm/linux/kernel/git/kbuild/linux.git#kbuild-fixes-for-next +clang-fixes https://git.kernel.org/pub/scm/linux/kernel/git/nathan/linux.git#clang-fixes-for-next +arc-current https://git.kernel.org/pub/scm/linux/kernel/git/vgupta/arc.git#for-curr +arm-current https://git.kernel.org/pub/scm/linux/kernel/git/rmk/linux.git#fixes +arm64-fixes https://git.kernel.org/pub/scm/linux/kernel/git/arm64/linux#for-next/fixes +arm-soc-fixes https://git.kernel.org/pub/scm/linux/kernel/git/soc/soc.git#arm/fixes +davinci-current https://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux.git#davinci/for-current +drivers-memory-fixes https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux-mem-ctrl.git#fixes +sophgo-fixes https://github.com/sophgo/linux.git#fixes +sophgo-soc-fixes https://github.com/sophgo/linux.git#soc-fixes +m68k-current https://git.kernel.org/pub/scm/linux/kernel/git/geert/linux-m68k.git#for-linus +powerpc-fixes https://git.kernel.org/pub/scm/linux/kernel/git/powerpc/linux.git#fixes +s390-fixes https://git.kernel.org/pub/scm/linux/kernel/git/s390/linux.git#fixes +net https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net.git#main +bpf https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf.git/#master +ipsec https://git.kernel.org/pub/scm/linux/kernel/git/klassert/ipsec.git#master +netfilter https://git.kernel.org/pub/scm/linux/kernel/git/netfilter/nf.git#main +ipvs https://git.kernel.org/pub/scm/linux/kernel/git/horms/ipvs.git#main +wireless https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless.git#for-next +ath https://git.kernel.org/pub/scm/linux/kernel/git/ath/ath.git#for-current +iwlwifi https://git.kernel.org/pub/scm/linux/kernel/git/iwlwifi/iwlwifi-next.git#fixes +wpan https://git.kernel.org/pub/scm/linux/kernel/git/wpan/wpan.git#master +rdma-fixes https://git.kernel.org/pub/scm/linux/kernel/git/rdma/rdma.git#for-rc +sound-current https://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound.git#for-linus +sound-asoc-fixes https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git#for-linus +regmap-fixes https://git.kernel.org/pub/scm/linux/kernel/git/broonie/regmap.git#for-linus +regulator-fixes https://git.kernel.org/pub/scm/linux/kernel/git/broonie/regulator.git#for-linus +spi-fixes https://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi.git#for-linus +pci-current https://git.kernel.org/pub/scm/linux/kernel/git/pci/pci.git#for-linus +driver-core.current https://git.kernel.org/pub/scm/linux/kernel/git/driver-core/driver-core.git#driver-core-linus +tty.current https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/tty.git#tty-linus +usb.current https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb.git#usb-linus +usb-serial-fixes https://git.kernel.org/pub/scm/linux/kernel/git/johan/usb-serial.git#usb-linus +phy https://git.kernel.org/pub/scm/linux/kernel/git/phy/linux-phy.git#fixes +staging.current https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging.git#staging-linus +iio-fixes https://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio.git#fixes-togreg +watchdog-fixes https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git#watchdog +counter-current https://git.kernel.org/pub/scm/linux/kernel/git/wbg/counter.git#counter-current +char-misc.current https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc.git#char-misc-linus +soundwire-fixes https://git.kernel.org/pub/scm/linux/kernel/git/vkoul/soundwire.git#fixes +thunderbolt-fixes https://git.kernel.org/pub/scm/linux/kernel/git/westeri/thunderbolt.git#fixes +input-current https://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git#for-linus +crypto-current https://git.kernel.org/pub/scm/linux/kernel/git/herbert/crypto-2.6.git#master +libcrypto-fixes https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/linux.git#libcrypto-fixes +vfio-fixes https://github.com/awilliam/linux-vfio.git#for-linus +kselftest-fixes https://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest.git#fixes +dmaengine-fixes https://git.kernel.org/pub/scm/linux/kernel/git/vkoul/dmaengine.git#fixes +backlight-fixes https://git.kernel.org/pub/scm/linux/kernel/git/lee/backlight.git#for-backlight-fixes +mtd-fixes https://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux.git#mtd/fixes +mfd-fixes https://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd.git#for-mfd-fixes +v4l-dvb-fixes git://linuxtv.org/media-ci/media-pending.git#fixes +reset-fixes https://git.kernel.org/pub/scm/linux/kernel/git/pza/linux#reset/fixes +mips-fixes https://git.kernel.org/pub/scm/linux/kernel/git/mips/linux.git#mips-fixes +at91-fixes https://git.kernel.org/pub/scm/linux/kernel/git/at91/linux.git#at91-fixes +omap-fixes https://git.kernel.org/pub/scm/linux/kernel/git/khilman/linux-omap.git#fixes +kvm-fixes git://git.kernel.org/pub/scm/virt/kvm/kvm.git#master +kvms390-fixes https://git.kernel.org/pub/scm/linux/kernel/git/kvms390/linux.git#master +kvm-arm-fixes https://git.kernel.org/pub/scm/linux/kernel/git/kvmarm/kvmarm.git#fixes +hwmon-fixes https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git#hwmon +nvdimm-fixes https://git.kernel.org/pub/scm/linux/kernel/git/nvdimm/nvdimm.git#libnvdimm-fixes +cxl-fixes https://git.kernel.org/pub/scm/linux/kernel/git/cxl/cxl.git#fixes +dma-mapping-fixes https://git.kernel.org/pub/scm/linux/kernel/git/mszyprowski/linux.git#dma-mapping-fixes +drivers-x86-fixes https://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git#fixes +samsung-krzk-fixes https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux.git#fixes +pinctrl-samsung-fixes https://git.kernel.org/pub/scm/linux/kernel/git/pinctrl/samsung.git#fixes +pinctrl-qcom-fixes https://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux.git#pinctrl-qcom/for-current +devicetree-fixes https://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git#dt/linus +dt-krzk-fixes https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux-dt.git#fixes +scsi-fixes https://git.kernel.org/pub/scm/linux/kernel/git/mkp/scsi.git#fixes +drm-fixes https://gitlab.freedesktop.org/drm/kernel.git#drm-fixes +drm-intel-fixes https://gitlab.freedesktop.org/drm/i915/kernel.git#for-linux-next-fixes +mmc-fixes https://git.kernel.org/pub/scm/linux/kernel/git/ulfh/mmc.git#fixes +rtc-fixes https://git.kernel.org/pub/scm/linux/kernel/git/abelloni/linux.git#rtc-fixes +gnss-fixes https://git.kernel.org/pub/scm/linux/kernel/git/johan/gnss.git#gnss-linus +hyperv-fixes https://git.kernel.org/pub/scm/linux/kernel/git/hyperv/linux.git#hyperv-fixes +risc-v-fixes https://git.kernel.org/pub/scm/linux/kernel/git/riscv/linux.git#fixes +riscv-dt-fixes https://git.kernel.org/pub/scm/linux/kernel/git/conor/linux.git#riscv-dt-fixes +riscv-soc-fixes https://git.kernel.org/pub/scm/linux/kernel/git/conor/linux.git#riscv-soc-fixes +fpga-fixes https://git.kernel.org/pub/scm/linux/kernel/git/fpga/linux-fpga.git#fixes +spdx https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/spdx.git#spdx-linus +gpio-brgl-fixes https://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux.git#gpio/for-current +gpio-intel-fixes https://git.kernel.org/pub/scm/linux/kernel/git/andy/linux-gpio-intel.git#fixes +pinctrl-intel-fixes https://git.kernel.org/pub/scm/linux/kernel/git/pinctrl/intel.git#fixes +auxdisplay-fixes https://git.kernel.org/pub/scm/linux/kernel/git/andy/linux-auxdisplay.git#fixes +kunit-fixes https://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest.git#kunit-fixes +memblock-fixes https://git.kernel.org/pub/scm/linux/kernel/git/rppt/memblock.git#fixes +renesas-fixes https://git.kernel.org/pub/scm/linux/kernel/git/geert/renesas-devel.git#fixes +perf-current https://git.kernel.org/pub/scm/linux/kernel/git/perf/perf-tools.git#perf-tools +efi-fixes https://git.kernel.org/pub/scm/linux/kernel/git/efi/efi.git#urgent +battery-fixes https://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply.git#fixes +iommufd-fixes https://git.kernel.org/pub/scm/linux/kernel/git/jgg/iommufd.git#for-rc +rust-fixes https://github.com/Rust-for-Linux/linux.git#rust-fixes +w1-fixes https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux-w1.git#fixes +pmdomain-fixes https://git.kernel.org/pub/scm/linux/kernel/git/ulfh/linux-pm.git#fixes +i2c-andi-fixes https://git.kernel.org/pub/scm/linux/kernel/git/andi.shyti/linux.git#i2c/i2c-fixes +i2c-rust-fixes https://github.com/ikrtn/rust-for-linux#rust-i2c-fixes +sparc-fixes https://git.kernel.org/pub/scm/linux/kernel/git/alarsson/linux-sparc.git#for-linus +clk-fixes https://git.kernel.org/pub/scm/linux/kernel/git/clk/linux.git#clk-fixes +thead-clk-fixes https://git.kernel.org/pub/scm/linux/kernel/git/fustini/linux.git#thead-clk-fixes +tenstorrent-clk-fixes https://git.kernel.org/pub/scm/linux/kernel/git/tenstorrent/linux.git#tenstorrent-clk-fixes +fustini-config-fixes https://git.kernel.org/pub/scm/linux/kernel/git/fustini/linux.git#riscv-config-fixes +pwrseq-fixes https://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux.git#pwrseq/for-current +thead-dt-fixes https://git.kernel.org/pub/scm/linux/kernel/git/fustini/linux.git#thead-dt-fixes +ftrace-fixes https://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace.git#ftrace/fixes +ring-buffer-fixes https://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace.git#ring-buffer/fixes +trace-fixes https://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace.git#trace/fixes +tracefs-fixes https://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace.git#tracefs/fixes +spacemit-fixes https://git.kernel.org/pub/scm/linux/kernel/git/spacemit/linux#fixes +tip-fixes https://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git#tip/urgent +slab-fixes https://git.kernel.org/pub/scm/linux/kernel/git/vbabka/slab.git#slab/for-next-fixes +kexec-fixes https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git#kexec-fixes +liveupdate-fixes https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git#fixes +drm-msm-fixes https://gitlab.freedesktop.org/drm/msm.git#msm-fixes +uml-fixes https://git.kernel.org/pub/scm/linux/kernel/git/uml/linux.git#fixes +fwctl-fixes https://git.kernel.org/pub/scm/linux/kernel/git/fwctl/fwctl.git#for-rc +devsec-tsm-fixes https://git.kernel.org/pub/scm/linux/kernel/git/devsec/tsm.git#fixes +drm-rust-fixes https://gitlab.freedesktop.org/drm/rust/kernel.git#for-linux-next-fixes +tenstorrent-dt-fixes https://git.kernel.org/pub/scm/linux/kernel/git/tenstorrent/linux.git#tenstorrent-dt-fixes +nfc-fixes https://codeberg.org/linux-nfc/linux.git#for-linus +drm-misc-fixes https://gitlab.freedesktop.org/drm/misc/kernel.git#for-linux-next-fixes +rust https://github.com/Rust-for-Linux/linux.git#rust-next +rust-interop https://github.com/Rust-for-Linux/linux.git#interop-next +rust-alloc https://github.com/Rust-for-Linux/linux.git#alloc-next +rust-io https://github.com/Rust-for-Linux/linux.git#io-next +rust-pin-init https://github.com/Rust-for-Linux/linux.git#pin-init-next +rust-timekeeping https://github.com/Rust-for-Linux/linux.git#timekeeping-next +rust-xarray https://github.com/Rust-for-Linux/linux.git#xarray-next +rust-analyzer https://github.com/Rust-for-Linux/linux.git#rust-analyzer-next +mm-stable https://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm#mm-stable +mm-nonmm-stable https://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm#mm-nonmm-stable +mm-unstable https://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm#mm-unstable +mm-nonmm-unstable https://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm#mm-nonmm-unstable +kbuild https://git.kernel.org/pub/scm/linux/kernel/git/kbuild/linux.git#kbuild-for-next +clang-format https://github.com/ojeda/linux.git#clang-format +perf https://git.kernel.org/pub/scm/linux/kernel/git/perf/perf-tools-next.git#perf-tools-next +compiler-attributes https://github.com/ojeda/linux.git#compiler-attributes +dma-mapping https://git.kernel.org/pub/scm/linux/kernel/git/mszyprowski/linux.git#dma-mapping-for-next +asm-generic https://git.kernel.org/pub/scm/linux/kernel/git/arnd/asm-generic#master +alpha https://git.kernel.org/pub/scm/linux/kernel/git/mattst88/alpha.git#alpha-next +arm https://git.kernel.org/pub/scm/linux/kernel/git/rmk/linux.git#for-next +arm64 https://git.kernel.org/pub/scm/linux/kernel/git/arm64/linux#for-next/core +arm-perf https://git.kernel.org/pub/scm/linux/kernel/git/will/linux.git#for-next/perf +arm-soc https://git.kernel.org/pub/scm/linux/kernel/git/soc/soc.git#for-next +amlogic https://git.kernel.org/pub/scm/linux/kernel/git/amlogic/linux.git#for-next +asahi-soc https://github.com/AsahiLinux/linux.git#asahi-soc/for-next +at91 https://git.kernel.org/pub/scm/linux/kernel/git/at91/linux.git#at91-next +bmc https://git.kernel.org/pub/scm/linux/kernel/git/bmc/linux.git#for-next +broadcom https://github.com/Broadcom/stblinux.git#next +cix https://git.kernel.org/pub/scm/linux/kernel/git/peter.chen/cix.git#for-next +davinci https://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux.git#davinci/for-next +drivers-memory https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux-mem-ctrl.git#for-next +fsl https://git.kernel.org/pub/scm/linux/kernel/git/chleroy/linux.git#soc_fsl +imx-mxs https://git.kernel.org/pub/scm/linux/kernel/git/frank.li/linux.git#for-next +mediatek https://git.kernel.org/pub/scm/linux/kernel/git/mediatek/linux.git#for-next +mvebu https://git.kernel.org/pub/scm/linux/kernel/git/gclement/mvebu.git#for-next +omap https://git.kernel.org/pub/scm/linux/kernel/git/khilman/linux-omap.git#for-next +qcom https://git.kernel.org/pub/scm/linux/kernel/git/qcom/linux.git#for-next +renesas https://git.kernel.org/pub/scm/linux/kernel/git/geert/renesas-devel.git#next +reset https://git.kernel.org/pub/scm/linux/kernel/git/pza/linux#reset/next +rockchip https://git.kernel.org/pub/scm/linux/kernel/git/mmind/linux-rockchip.git#for-next +samsung-krzk https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux.git#for-next +scmi https://git.kernel.org/pub/scm/linux/kernel/git/sudeep.holla/linux.git#for-linux-next +sophgo https://github.com/sophgo/linux.git#for-next +sophgo-soc https://github.com/sophgo/linux.git#soc-for-next +spacemit https://git.kernel.org/pub/scm/linux/kernel/git/spacemit/linux#for-next +stm32 https://git.kernel.org/pub/scm/linux/kernel/git/atorgue/stm32.git#stm32-next +sunxi https://git.kernel.org/pub/scm/linux/kernel/git/sunxi/linux.git#sunxi/for-next +tee https://git.kernel.org/pub/scm/linux/kernel/git/jenswi/linux-tee.git#next +tegra https://git.kernel.org/pub/scm/linux/kernel/git/tegra/linux.git#for-next +tenstorrent-dt https://git.kernel.org/pub/scm/linux/kernel/git/tenstorrent/linux.git#tenstorrent-dt-for-next +fustini-config https://git.kernel.org/pub/scm/linux/kernel/git/fustini/linux.git#riscv-config-for-next +thead-dt https://git.kernel.org/pub/scm/linux/kernel/git/fustini/linux.git#thead-dt-for-next +ti https://git.kernel.org/pub/scm/linux/kernel/git/ti/linux.git#ti-next +xilinx https://github.com/Xilinx/linux-xlnx.git#for-next +socfpga https://git.kernel.org/pub/scm/linux/kernel/git/dinguyen/linux.git#for-next +clk https://git.kernel.org/pub/scm/linux/kernel/git/clk/linux.git#clk-next +clk-imx https://git.kernel.org/pub/scm/linux/kernel/git/abelvesa/linux.git#for-next +clk-renesas https://git.kernel.org/pub/scm/linux/kernel/git/geert/renesas-drivers.git#renesas-clk +thead-clk https://git.kernel.org/pub/scm/linux/kernel/git/fustini/linux.git#thead-clk-for-next +tenstorrent-clk https://git.kernel.org/pub/scm/linux/kernel/git/tenstorrent/linux.git#tenstorrent-clk-for-next +csky https://github.com/c-sky/csky-linux.git#linux-next +loongarch https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson.git#loongarch-next +m68k https://git.kernel.org/pub/scm/linux/kernel/git/geert/linux-m68k.git#for-next +m68knommu https://git.kernel.org/pub/scm/linux/kernel/git/gerg/m68knommu.git#for-next +microblaze git://git.monstr.eu/linux-2.6-microblaze.git#next +mips https://git.kernel.org/pub/scm/linux/kernel/git/mips/linux.git#mips-next +openrisc https://github.com/openrisc/linux.git#for-next +parisc-hd https://git.kernel.org/pub/scm/linux/kernel/git/deller/parisc-linux.git#for-next +powerpc https://git.kernel.org/pub/scm/linux/kernel/git/powerpc/linux.git#next +risc-v https://git.kernel.org/pub/scm/linux/kernel/git/riscv/linux.git#for-next +riscv-dt https://git.kernel.org/pub/scm/linux/kernel/git/conor/linux.git#riscv-dt-for-next +riscv-soc https://git.kernel.org/pub/scm/linux/kernel/git/conor/linux.git#riscv-soc-for-next +s390 https://git.kernel.org/pub/scm/linux/kernel/git/s390/linux.git#for-next +sh https://git.kernel.org/pub/scm/linux/kernel/git/glaubitz/sh-linux.git#for-next +sparc https://git.kernel.org/pub/scm/linux/kernel/git/alarsson/linux-sparc.git#for-next +uml https://git.kernel.org/pub/scm/linux/kernel/git/uml/linux.git#next +xtensa https://github.com/jcmvbkbc/linux-xtensa.git#xtensa-for-next +printk https://git.kernel.org/pub/scm/linux/kernel/git/printk/linux.git#for-next +pci https://git.kernel.org/pub/scm/linux/kernel/git/pci/pci.git#next +pstore https://git.kernel.org/pub/scm/linux/kernel/git/kees/linux.git#for-next/pstore +hid https://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git#for-next +i2c https://git.kernel.org/pub/scm/linux/kernel/git/wsa/linux.git#i2c/for-next +i2c-andi https://git.kernel.org/pub/scm/linux/kernel/git/andi.shyti/linux.git#i2c/i2c-next +i2c-rust https://github.com/ikrtn/rust-for-linux#rust-i2c-next +i3c https://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux.git#i3c/next +dmi https://git.kernel.org/pub/scm/linux/kernel/git/jdelvare/staging.git#dmi-for-next +hwmon-staging https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git#hwmon-next +jc_docs git://git.lwn.net/linux.git#docs-next +v4l-dvb git://linuxtv.org/media-ci/media-pending.git#next +v4l-dvb-next git://linuxtv.org/mchehab/media-next.git#master +pm https://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm.git#linux-next +cpufreq-arm https://git.kernel.org/pub/scm/linux/kernel/git/vireshk/pm.git#cpufreq/arm/linux-next +cpupower https://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux.git#cpupower +devfreq https://git.kernel.org/pub/scm/linux/kernel/git/chanwoo/linux.git#devfreq-next +pmdomain https://git.kernel.org/pub/scm/linux/kernel/git/ulfh/linux-pm.git#next +opp https://git.kernel.org/pub/scm/linux/kernel/git/vireshk/pm.git#opp/linux-next +thermal https://git.kernel.org/pub/scm/linux/kernel/git/thermal/linux.git#thermal/linux-next +rdma https://git.kernel.org/pub/scm/linux/kernel/git/rdma/rdma.git#for-next +net-next https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next.git#main +bpf-next https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git#for-next +ipsec-next https://git.kernel.org/pub/scm/linux/kernel/git/klassert/ipsec-next.git#master +mlx5-next https://git.kernel.org/pub/scm/linux/kernel/git/mellanox/linux.git#mlx5-next +netfilter-next https://git.kernel.org/pub/scm/linux/kernel/git/netfilter/nf-next.git#main +ipvs-next https://git.kernel.org/pub/scm/linux/kernel/git/horms/ipvs-next.git#main +bluetooth https://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth-next.git#master +wireless-next https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next.git#for-next +ath-next https://git.kernel.org/pub/scm/linux/kernel/git/ath/ath.git#for-next +iwlwifi-next https://git.kernel.org/pub/scm/linux/kernel/git/iwlwifi/iwlwifi-next.git#next +wpan-next https://git.kernel.org/pub/scm/linux/kernel/git/wpan/wpan-next.git#master +wpan-staging https://git.kernel.org/pub/scm/linux/kernel/git/wpan/wpan-next.git#staging +mtd https://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux.git#mtd/next +nand https://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux.git#nand/next +spi-nor https://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux.git#spi-nor/next +crypto https://git.kernel.org/pub/scm/linux/kernel/git/herbert/cryptodev-2.6.git#master +libcrypto https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/linux.git#libcrypto-next +drm https://gitlab.freedesktop.org/drm/kernel.git#drm-next +drm-exynos https://git.kernel.org/pub/scm/linux/kernel/git/daeinki/drm-exynos.git#for-linux-next +drm-misc https://gitlab.freedesktop.org/drm/misc/kernel.git#for-linux-next +amdgpu https://gitlab.freedesktop.org/agd5f/linux.git#drm-next +drm-intel https://gitlab.freedesktop.org/drm/i915/kernel.git#for-linux-next +drm-msm https://gitlab.freedesktop.org/drm/msm.git#msm-next +drm-msm-lumag https://gitlab.freedesktop.org/lumag/msm.git#msm-next-lumag +drm-xe https://gitlab.freedesktop.org/drm/xe/kernel.git#drm-xe-next +drm-rust https://gitlab.freedesktop.org/drm/rust/kernel.git#for-linux-next +drm-nova https://gitlab.freedesktop.org/drm/nova.git#nova-next +etnaviv https://git.pengutronix.de/git/lst/linux#etnaviv/next +fbdev https://git.kernel.org/pub/scm/linux/kernel/git/deller/linux-fbdev.git#for-next +regmap https://git.kernel.org/pub/scm/linux/kernel/git/broonie/regmap.git#for-next +sound https://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound.git#for-next +ieee1394 https://git.kernel.org/pub/scm/linux/kernel/git/ieee1394/linux1394.git#for-next +sound-asoc https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git#for-next +modules https://git.kernel.org/pub/scm/linux/kernel/git/modules/linux.git#modules-next +input https://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git#next +block https://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux.git#for-next +device-mapper https://git.kernel.org/pub/scm/linux/kernel/git/device-mapper/linux-dm.git#for-next +libata https://git.kernel.org/pub/scm/linux/kernel/git/libata/linux#for-next +pcmcia https://git.kernel.org/pub/scm/linux/kernel/git/brodo/linux.git#pcmcia-next +mmc https://git.kernel.org/pub/scm/linux/kernel/git/ulfh/mmc.git#next +mfd https://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd.git#for-mfd-next +backlight https://git.kernel.org/pub/scm/linux/kernel/git/lee/backlight.git#for-backlight-next +battery https://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply.git#for-next +regulator https://git.kernel.org/pub/scm/linux/kernel/git/broonie/regulator.git#for-next +security https://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/lsm.git#next +apparmor https://git.kernel.org/pub/scm/linux/kernel/git/jj/linux-apparmor#apparmor-next +integrity https://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-integrity#next-integrity +selinux https://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/selinux.git#next +smack https://github.com/cschaufler/smack-next#next +tomoyo git://git.code.sf.net/p/tomoyo/tomoyo.git#master +tpmdd-tpm https://git.kernel.org/pub/scm/linux/kernel/git/jarkko/linux-tpmdd.git#for-next-tpm +tpmdd-keys https://git.kernel.org/pub/scm/linux/kernel/git/jarkko/linux-tpmdd.git#for-next-keys +watchdog https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git#watchdog-next +iommu https://git.kernel.org/pub/scm/linux/kernel/git/iommu/linux.git#next +audit https://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/audit.git#next +devicetree https://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git#for-next +dt-krzk https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux-dt.git#for-next +mailbox https://git.kernel.org/pub/scm/linux/kernel/git/jassibrar/mailbox.git#for-next +spi https://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi.git#for-next +tip https://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git#master +kexec https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git#kexec-next +liveupdate https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git#next +clockevents https://git.kernel.org/pub/scm/linux/kernel/git/daniel.lezcano/linux.git#timers/drivers/next +edac https://git.kernel.org/pub/scm/linux/kernel/git/ras/ras.git#edac-for-next +ftrace https://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace.git#for-next +rcu https://git.kernel.org/pub/scm/linux/kernel/git/rcu/linux#next +paulmck https://git.kernel.org/pub/scm/linux/kernel/git/paulmck/linux-rcu.git#non-rcu/next +kvm git://git.kernel.org/pub/scm/virt/kvm/kvm.git#next +kvm-arm https://git.kernel.org/pub/scm/linux/kernel/git/kvmarm/kvmarm.git#next +kvms390 https://git.kernel.org/pub/scm/linux/kernel/git/kvms390/linux.git#next +kvm-ppc https://git.kernel.org/pub/scm/linux/kernel/git/powerpc/linux.git#topic/ppc-kvm +kvm-riscv https://github.com/kvm-riscv/linux.git#riscv_kvm_next +kvm-x86 https://github.com/kvm-x86/linux.git#next +xen-tip https://git.kernel.org/pub/scm/linux/kernel/git/xen/tip.git#linux-next +percpu https://git.kernel.org/pub/scm/linux/kernel/git/dennis/percpu.git#for-next +workqueues https://git.kernel.org/pub/scm/linux/kernel/git/tj/wq.git#for-next +sched-ext https://git.kernel.org/pub/scm/linux/kernel/git/tj/sched_ext.git#for-next +drivers-x86 https://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git#for-next +chrome-platform https://git.kernel.org/pub/scm/linux/kernel/git/chrome-platform/linux.git#for-next +chrome-platform-firmware https://git.kernel.org/pub/scm/linux/kernel/git/chrome-platform/linux.git#for-firmware-next +hsi https://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-hsi.git#for-next +leds-lj https://git.kernel.org/pub/scm/linux/kernel/git/lee/leds.git#for-leds-next +ipmi https://github.com/cminyard/linux-ipmi.git#for-next +driver-core https://git.kernel.org/pub/scm/linux/kernel/git/driver-core/driver-core.git#driver-core-next +usb https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb.git#usb-next +thunderbolt https://git.kernel.org/pub/scm/linux/kernel/git/westeri/thunderbolt.git#next +usb-serial https://git.kernel.org/pub/scm/linux/kernel/git/johan/usb-serial.git#usb-next +tty https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/tty.git#tty-next +char-misc https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc.git#char-misc-next +coresight https://git.kernel.org/pub/scm/linux/kernel/git/coresight/linux.git#next +fastrpc https://git.kernel.org/pub/scm/linux/kernel/git/srini/fastrpc.git#for-next +fpga https://git.kernel.org/pub/scm/linux/kernel/git/fpga/linux-fpga.git#for-next +icc https://git.kernel.org/pub/scm/linux/kernel/git/djakov/icc.git#icc-next +iio https://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio.git#togreg +nfc https://codeberg.org/linux-nfc/linux.git#for-next +phy-next https://git.kernel.org/pub/scm/linux/kernel/git/phy/linux-phy.git#next +soundwire https://git.kernel.org/pub/scm/linux/kernel/git/vkoul/soundwire.git#next +extcon https://git.kernel.org/pub/scm/linux/kernel/git/chanwoo/extcon.git#extcon-next +gnss https://git.kernel.org/pub/scm/linux/kernel/git/johan/gnss.git#gnss-next +vfio https://github.com/awilliam/linux-vfio.git#next +w1 https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux-w1.git#for-next +spmi https://git.kernel.org/pub/scm/linux/kernel/git/sboyd/spmi.git#spmi-next +staging https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging.git#staging-next +counter-next https://git.kernel.org/pub/scm/linux/kernel/git/wbg/counter.git#counter-next +mux https://gitlab.com/peda-linux/mux.git#for-next +dmaengine https://git.kernel.org/pub/scm/linux/kernel/git/vkoul/dmaengine.git#next +cgroup https://git.kernel.org/pub/scm/linux/kernel/git/tj/cgroup.git#for-next +scsi https://git.kernel.org/pub/scm/linux/kernel/git/jejb/scsi.git#for-next +scsi-mkp https://git.kernel.org/pub/scm/linux/kernel/git/mkp/scsi.git#for-next +vhost https://git.kernel.org/pub/scm/linux/kernel/git/mst/vhost.git#linux-next +rpmsg https://git.kernel.org/pub/scm/linux/kernel/git/remoteproc/linux.git#for-next +gpio-brgl https://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux.git#gpio/for-next +gpio-intel https://git.kernel.org/pub/scm/linux/kernel/git/andy/linux-gpio-intel.git#for-next +pinctrl https://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-pinctrl.git#for-next +pinctrl-intel https://git.kernel.org/pub/scm/linux/kernel/git/pinctrl/intel.git#for-next +pinctrl-renesas https://git.kernel.org/pub/scm/linux/kernel/git/geert/renesas-drivers.git#renesas-pinctrl +pinctrl-samsung https://git.kernel.org/pub/scm/linux/kernel/git/pinctrl/samsung.git#for-next +pinctrl-qcom https://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux.git#pinctrl-qcom/for-next +pwm https://git.kernel.org/pub/scm/linux/kernel/git/ukleinek/linux.git#pwm/for-next +ktest https://git.kernel.org/pub/scm/linux/kernel/git/rostedt/linux-ktest.git#for-next +kselftest https://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest.git#next +kunit https://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest.git#test +kunit-next https://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest.git#kunit +livepatching https://git.kernel.org/pub/scm/linux/kernel/git/livepatching/livepatching.git#for-next +rtc https://git.kernel.org/pub/scm/linux/kernel/git/abelloni/linux.git#rtc-next +nvdimm https://git.kernel.org/pub/scm/linux/kernel/git/nvdimm/nvdimm.git#libnvdimm-for-next +at24 https://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux.git#at24/for-next +ntb https://github.com/jonmason/ntb.git#ntb-next +seccomp https://git.kernel.org/pub/scm/linux/kernel/git/kees/linux.git#for-next/seccomp +slimbus https://git.kernel.org/pub/scm/linux/kernel/git/srini/slimbus.git#for-next +nvmem https://git.kernel.org/pub/scm/linux/kernel/git/srini/nvmem.git#for-next +hyperv https://git.kernel.org/pub/scm/linux/kernel/git/hyperv/linux.git#hyperv-next +auxdisplay https://git.kernel.org/pub/scm/linux/kernel/git/andy/linux-auxdisplay.git#for-next +kgdb https://git.kernel.org/pub/scm/linux/kernel/git/danielt/linux.git#kgdb/for-next +hmm https://git.kernel.org/pub/scm/linux/kernel/git/rdma/rdma.git#hmm +cfi https://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux.git#cfi/next +mhi https://git.kernel.org/pub/scm/linux/kernel/git/mani/mhi.git#mhi-next +memblock https://git.kernel.org/pub/scm/linux/kernel/git/rppt/memblock.git#for-next +cxl https://git.kernel.org/pub/scm/linux/kernel/git/cxl/cxl.git#next +zstd https://github.com/terrelln/linux.git#zstd-next +efi https://git.kernel.org/pub/scm/linux/kernel/git/efi/efi.git#next +unicode https://git.kernel.org/pub/scm/linux/kernel/git/krisman/unicode.git#for-next +slab https://git.kernel.org/pub/scm/linux/kernel/git/vbabka/slab.git#slab/for-next +random https://git.kernel.org/pub/scm/linux/kernel/git/crng/random.git#master +landlock https://git.kernel.org/pub/scm/linux/kernel/git/mic/linux.git#next +sysctl https://git.kernel.org/pub/scm/linux/kernel/git/sysctl/sysctl.git#sysctl-next +execve https://git.kernel.org/pub/scm/linux/kernel/git/kees/linux.git#for-next/execve +bitmap https://github.com/norov/linux.git#bitmap-for-next +hte https://git.kernel.org/pub/scm/linux/kernel/git/pateldipen1984/linux.git#for-next +kspp https://git.kernel.org/pub/scm/linux/kernel/git/kees/linux.git#for-next/kspp +nolibc https://git.kernel.org/pub/scm/linux/kernel/git/nolibc/linux-nolibc.git#for-next +iommufd https://git.kernel.org/pub/scm/linux/kernel/git/jgg/iommufd.git#for-next +turbostat https://git.kernel.org/pub/scm/linux/kernel/git/lenb/linux.git#next +pwrseq https://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux.git#pwrseq/for-next +capabilities-next https://git.kernel.org/pub/scm/linux/kernel/git/sergeh/linux.git#caps-next +ipe https://git.kernel.org/pub/scm/linux/kernel/git/wufan/ipe.git#next +kcsan https://git.kernel.org/pub/scm/linux/kernel/git/melver/linux.git#next +crc https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/linux.git#crc-next +keys-next https://git.kernel.org/pub/scm/linux/kernel/git/dhowells/linux-fs.git#keys-next +fwctl https://git.kernel.org/pub/scm/linux/kernel/git/fwctl/fwctl.git#for-next +devsec-tsm https://git.kernel.org/pub/scm/linux/kernel/git/devsec/tsm.git#next +hisilicon https://github.com/hisilicon/linux-hisi.git#for-next +device-id https://git.kernel.org/pub/scm/linux/kernel/git/ukleinek/linux.git#device-id-rework +kthread https://git.kernel.org/pub/scm/linux/kernel/git/frederic/linux-dynticks.git#for-next diff --git a/Next/merge.log b/Next/merge.log new file mode 100644 index 000000000000..7f96094532f4 --- /dev/null +++ b/Next/merge.log @@ -0,0 +1,6516 @@ +$ date -R +Mon, 06 Jul 2026 12:50:32 +0100 +$ git checkout master +Already on 'master' +$ git reset --hard stable +HEAD is now at d2c9a99135da9 Merge tag 'device-id-rework' of git://git.kernel.org/pub/scm/linux/kernel/git/ukleinek/linux +Merging origin/master (8cdeaa50eae8d Linux 7.2-rc2) +$ git merge -m Merge branch 'master' of https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git origin/master +Updating d2c9a99135da9..8cdeaa50eae8d +Fast-forward (no commit created; -m option ignored) + .../devicetree/bindings/spi/snps,dw-apb-ssi.yaml | 4 +- + Documentation/filesystems/smb/ksmbd.rst | 4 +- + Documentation/sound/codecs/tas675x.rst | 11 +- + MAINTAINERS | 3 +- + Makefile | 2 +- + arch/mips/configs/cu1000-neo_defconfig | 2 +- + arch/mips/configs/cu1830-neo_defconfig | 2 +- + arch/mips/configs/gcw0_defconfig | 2 +- + arch/mips/dec/platform.c | 6 +- + arch/mips/include/asm/irq_work.h | 9 + + arch/mips/include/asm/smp.h | 2 + + arch/mips/loongson64/smp.c | 10 ++ + arch/mips/mm/init.c | 5 +- + arch/mips/vdso/elf.S | 3 + + arch/riscv/Kconfig | 3 +- + arch/riscv/kernel/asm-offsets.c | 4 +- + arch/riscv/kernel/entry.S | 8 +- + arch/riscv/kernel/probes/rethook_trampoline.S | 3 + + arch/riscv/kernel/smpboot.c | 5 +- + arch/riscv/kernel/vdso/note.S | 3 + + arch/x86/kernel/cpu/resctrl/monitor.c | 5 + + arch/x86/kernel/uprobes.c | 26 ++- + arch/x86/xen/mmu_pv.c | 15 +- + drivers/accel/amdxdna/aie2_ctx.c | 68 ++++++-- + drivers/accel/amdxdna/amdxdna_ctx.h | 1 + + drivers/accel/amdxdna/amdxdna_iommu.c | 43 +++-- + drivers/accel/amdxdna/amdxdna_pci_drv.c | 38 ++--- + drivers/accel/amdxdna/amdxdna_pci_drv.h | 1 + + drivers/acpi/acpi_tad.c | 2 +- + drivers/acpi/acpica/acutils.h | 2 - + drivers/acpi/acpica/utnonansi.c | 16 -- + drivers/acpi/riscv/rimt.c | 7 +- + drivers/dma-buf/dma-fence-unwrap.c | 3 + + drivers/dma-buf/dma-fence.c | 6 +- + drivers/gpio/gpio-f7188x.c | 6 +- + drivers/gpio/gpio-htc-egpio.c | 6 +- + drivers/gpio/gpio-mt7621.c | 27 +-- + drivers/gpio/gpio-mvebu.c | 5 +- + drivers/gpio/gpio-shared-proxy.c | 76 ++++----- + drivers/gpio/gpio-timberdale.c | 2 +- + drivers/gpio/gpiolib-shared.c | 9 +- + drivers/gpio/gpiolib-shared.h | 28 +-- + drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c | 13 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c | 2 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 2 - + drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c | 12 ++ + drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c | 8 + + drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c | 1 + + drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c | 2 + + drivers/gpu/drm/amd/amdgpu/amdgpu_hmm.c | 3 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h | 2 + + drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c | 46 +++-- + drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.h | 2 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 4 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c | 8 + + drivers/gpu/drm/amd/amdgpu/amdgpu_vce.c | 17 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c | 58 ++++--- + drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h | 9 +- + drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c | 13 +- + drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c | 78 ++++++--- + drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c | 69 +++++--- + drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c | 24 ++- + drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c | 3 - + drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c | 10 +- + drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c | 11 +- + drivers/gpu/drm/amd/amdgpu/gmc_v11_0.c | 4 + + drivers/gpu/drm/amd/amdgpu/imu_v11_0.c | 2 + + drivers/gpu/drm/amd/amdgpu/jpeg_v4_0_3.c | 2 +- + drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c | 2 +- + drivers/gpu/drm/amd/amdgpu/mes_v11_0.c | 6 + + drivers/gpu/drm/amd/amdgpu/mes_v12_0.c | 2 + + drivers/gpu/drm/amd/amdgpu/mes_v12_1.c | 2 + + drivers/gpu/drm/amd/amdgpu/psp_v15_0.c | 2 + + drivers/gpu/drm/amd/amdgpu/sdma_v4_4_2.c | 4 +- + drivers/gpu/drm/amd/amdgpu/sdma_v5_0.c | 4 +- + drivers/gpu/drm/amd/amdgpu/sdma_v5_2.c | 4 +- + drivers/gpu/drm/amd/amdgpu/sdma_v6_0.c | 4 +- + drivers/gpu/drm/amd/amdgpu/sdma_v7_0.c | 4 +- + drivers/gpu/drm/amd/amdgpu/sdma_v7_1.c | 4 +- + drivers/gpu/drm/amd/amdgpu/soc21.c | 56 ++++++ + drivers/gpu/drm/amd/amdgpu/soc24.c | 28 +++ + drivers/gpu/drm/amd/amdgpu/vcn_v4_0.c | 15 +- + drivers/gpu/drm/amd/amdkfd/kfd_chardev.c | 12 +- + drivers/gpu/drm/amd/amdkfd/kfd_crat.c | 2 + + drivers/gpu/drm/amd/amdkfd/kfd_device.c | 10 ++ + drivers/gpu/drm/amd/amdkfd/kfd_migrate.c | 2 +- + drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.h | 1 + + drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v10.c | 4 +- + drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v11.c | 4 +- + drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c | 4 +- + drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c | 4 +- + drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c | 25 ++- + drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_vi.c | 4 +- + drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 10 +- + drivers/gpu/drm/amd/display/dc/core/dc.c | 16 +- + .../gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.h | 62 ++++--- + .../drm/amd/display/dc/dce/dce_stream_encoder.c | 15 +- + .../drm/amd/display/dc/dce/dce_stream_encoder.h | 3 +- + .../gpu/drm/amd/display/dc/link/link_detection.c | 5 +- + .../gpu/drm/amd/display/modules/hdcp/hdcp_log.c | 30 ++-- + drivers/gpu/drm/amd/include/mes_v11_api_def.h | 2 + + drivers/gpu/drm/amd/pm/amdgpu_pm.c | 26 ++- + .../gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c | 11 +- + .../gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_7_ppt.c | 15 +- + drivers/gpu/drm/arm/display/komeda/komeda_dev.c | 6 +- + drivers/gpu/drm/arm/display/komeda/komeda_drv.c | 14 +- + drivers/gpu/drm/arm/malidp_drv.c | 22 ++- + drivers/gpu/drm/display/drm_dp_mst_topology.c | 4 +- + drivers/gpu/drm/i915/display/intel_bios.c | 36 +++- + drivers/gpu/drm/i915/display/intel_hdcp.c | 12 +- + drivers/gpu/drm/i915/display/intel_vrr.c | 4 + + drivers/gpu/drm/i915/i915_active.c | 7 +- + drivers/gpu/drm/imagination/pvr_context.c | 18 +- + drivers/gpu/drm/imagination/pvr_drv.c | 19 ++- + drivers/gpu/drm/imagination/pvr_queue.c | 6 +- + drivers/gpu/drm/imagination/pvr_queue.h | 2 +- + drivers/gpu/drm/imagination/pvr_vm.c | 6 +- + drivers/gpu/drm/panthor/panthor_device.c | 4 + + drivers/gpu/drm/panthor/panthor_device.h | 17 +- + drivers/gpu/drm/panthor/panthor_fw.c | 6 +- + drivers/gpu/drm/panthor/panthor_gpu.c | 3 +- + drivers/gpu/drm/panthor/panthor_mmu.c | 9 +- + drivers/gpu/drm/panthor/panthor_pwr.c | 10 +- + drivers/gpu/drm/panthor/panthor_sched.c | 108 ++++++------ + drivers/gpu/drm/virtio/virtgpu_vq.c | 3 +- + drivers/gpu/drm/xe/display/xe_display_bo.c | 3 +- + drivers/gpu/drm/xe/display/xe_fb_pin.c | 3 +- + drivers/gpu/drm/xe/tests/xe_rtp_test.c | 103 +++++------ + drivers/gpu/drm/xe/xe_drm_client.c | 12 +- + drivers/gpu/drm/xe/xe_gt_debugfs.c | 4 +- + drivers/gpu/drm/xe/xe_guc_relay.c | 13 +- + drivers/gpu/drm/xe/xe_guc_submit.c | 9 +- + drivers/gpu/drm/xe/xe_hw_engine.c | 20 +-- + drivers/gpu/drm/xe/xe_hw_engine_types.h | 8 + + drivers/gpu/drm/xe/xe_oa.c | 7 + + drivers/gpu/drm/xe/xe_oa_types.h | 3 + + drivers/gpu/drm/xe/xe_pt.c | 67 ++++++-- + drivers/gpu/drm/xe/xe_reg_whitelist.c | 102 +++++++++-- + drivers/gpu/drm/xe/xe_reg_whitelist.h | 4 + + drivers/gpu/drm/xe/xe_rtp.c | 31 ++-- + drivers/gpu/drm/xe/xe_rtp.h | 24 ++- + drivers/gpu/drm/xe/xe_rtp_types.h | 10 ++ + drivers/gpu/drm/xe/xe_svm.c | 6 +- + drivers/gpu/drm/xe/xe_svm.h | 15 +- + drivers/gpu/drm/xe/xe_tuning.c | 45 +++-- + drivers/gpu/drm/xe/xe_userptr.c | 2 +- + drivers/gpu/drm/xe/xe_wa.c | 89 +++++----- + drivers/irqchip/irq-gic-v3-its.c | 6 +- + drivers/irqchip/irq-riscv-imsic-early.c | 15 +- + drivers/irqchip/irq-ts4800.c | 10 ++ + drivers/pinctrl/meson/pinctrl-meson.c | 2 +- + drivers/s390/char/monwriter.c | 3 + + drivers/s390/crypto/pkey_api.c | 11 +- + drivers/spi/spi-dw-core.c | 2 +- + drivers/spi/spi-dw-dma.c | 3 +- + drivers/spi/spi-rzv2h-rspi.c | 4 +- + drivers/spi/spi-sh-msiof.c | 10 +- + drivers/spi/spi.c | 3 + + drivers/xen/gntalloc.c | 19 ++- + drivers/xen/gntdev.c | 8 +- + drivers/xen/pvcalls-front.c | 88 ++++++++-- + drivers/xen/xen-front-pgdir-shbuf.c | 12 +- + drivers/xen/xenbus/xenbus_xs.c | 6 + + fs/afs/callback.c | 17 +- + fs/afs/cell.c | 27 ++- + fs/afs/cmservice.c | 7 +- + fs/afs/dir.c | 40 +++-- + fs/afs/dynroot.c | 2 +- + fs/afs/fs_operation.c | 2 +- + fs/afs/inode.c | 15 +- + fs/afs/internal.h | 3 +- + fs/afs/super.c | 5 +- + fs/afs/symlink.c | 4 +- + fs/afs/vl_list.c | 24 ++- + fs/afs/volume.c | 2 +- + fs/bpf_fs_kfuncs.c | 23 ++- + fs/cachefiles/namei.c | 4 +- + fs/exec.c | 2 +- + fs/exfat/iomap.c | 5 +- + fs/fat/dir.c | 44 ++++- + fs/fhandle.c | 2 +- + fs/freevxfs/vxfs_bmap.c | 3 +- + fs/fuse/file.c | 14 +- + fs/iomap/bio.c | 15 +- + fs/iomap/buffered-io.c | 16 +- + fs/iomap/direct-io.c | 7 +- + fs/iomap/ioend.c | 8 +- + fs/minix/minix.h | 2 +- + fs/namei.c | 4 + + fs/netfs/buffered_read.c | 2 +- + fs/netfs/buffered_write.c | 2 +- + fs/netfs/direct_write.c | 18 +- + fs/netfs/internal.h | 12 ++ + fs/netfs/locking.c | 95 +++++++++++ + fs/netfs/read_retry.c | 7 +- + fs/netfs/write_collect.c | 10 ++ + fs/netfs/write_issue.c | 55 +++--- + fs/netfs/write_retry.c | 7 +- + fs/ntfs/aops.c | 6 +- + fs/ntfs3/inode.c | 5 +- + fs/orangefs/dir.c | 7 +- + fs/overlayfs/copy_up.c | 12 +- + fs/overlayfs/inode.c | 4 +- + fs/proc/generic.c | 9 +- + fs/resctrl/monitor.c | 37 ++-- + fs/smb/client/cifsfs.h | 4 +- + fs/smb/client/file.c | 1 + + fs/smb/client/smb2pdu.c | 8 +- + fs/smb/server/ksmbd_work.h | 7 + + fs/smb/server/oplock.c | 109 ++++++++++-- + fs/smb/server/server.c | 14 ++ + fs/smb/server/smb2misc.c | 18 +- + fs/smb/server/smb2pdu.c | 17 +- + fs/smb/server/smbacl.c | 15 +- + fs/smb/server/vfs.c | 14 +- + fs/smb/server/vfs_cache.c | 79 ++++++--- + fs/xfs/libxfs/xfs_dquot_buf.c | 14 +- + fs/xfs/scrub/agheader_repair.c | 2 +- + fs/xfs/xfs_aops.c | 3 +- + fs/xfs/xfs_buf.c | 190 +++++++++++---------- + fs/xfs/xfs_buf.h | 2 +- + fs/xfs/xfs_buf_item.c | 3 +- + fs/xfs/xfs_inode.c | 3 +- + fs/xfs/xfs_log_recover.c | 6 +- + fs/xfs/xfs_qm.c | 5 +- + fs/xfs/xfs_super.c | 3 + + include/acpi/platform/aclinuxex.h | 1 + + include/drm/display/drm_dp_helper.h | 1 + + include/drm/drm_fixed.h | 3 +- + include/drm/drm_ras.h | 2 + + include/linux/iomap.h | 2 + + include/linux/netfs.h | 13 +- + include/xen/interface/xen-mca.h | 4 +- + include/xen/interface/xen.h | 8 +- + kernel/events/core.c | 17 +- + kernel/futex/requeue.c | 6 - + lib/iov_iter.c | 20 ++- + lib/raid/raid6/riscv/recov_rvv.c | 1 + + lib/raid/raid6/riscv/rvv.c | 1 + + lib/scatterlist.c | 1 + + lib/tests/kunit_iov_iter.c | 5 +- + scripts/sorttable.c | 11 +- + sound/hda/codecs/realtek/alc269.c | 1 + + sound/soc/amd/yc/acp6x-mach.c | 7 + + sound/soc/codecs/lpass-va-macro.c | 7 +- + sound/soc/codecs/tas675x.c | 14 +- + sound/soc/renesas/rcar/adg.c | 29 +++- + sound/soc/renesas/rcar/src.c | 2 + + sound/soc/sof/sof-client-probes-ipc3.c | 23 ++- + sound/soc/sof/sof-client-probes-ipc4.c | 11 +- + sound/usb/usx2y/us144mkii.c | 17 +- + sound/usb/usx2y/us144mkii_capture.c | 2 +- + tools/testing/selftests/filesystems/.gitignore | 1 + + tools/testing/selftests/filesystems/Makefile | 4 + + .../selftests/filesystems/idmapped_tmpfile.c | 168 ++++++++++++++++++ + tools/testing/selftests/x86/test_shadow_stack.c | 86 ++++++++++ + 256 files changed, 2699 insertions(+), 1261 deletions(-) + create mode 100644 arch/mips/include/asm/irq_work.h + create mode 100644 tools/testing/selftests/filesystems/idmapped_tmpfile.c +Merging ext4-fixes/fixes (981fcc5674e67 jbd2: fix deadlock in jbd2_journal_cancel_revoke()) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/tytso/ext4.git ext4-fixes/fixes +Already up to date. +Merging vfs-brauner-fixes/vfs.fixes (24dddc384fb9a Merge patch series "iomap: consolidate bio submission") +$ git merge -m Merge branch 'vfs.fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs.git vfs-brauner-fixes/vfs.fixes +Already up to date. +Merging fscrypt-current/for-current (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'for-current' of https://git.kernel.org/pub/scm/fs/fscrypt/linux.git fscrypt-current/for-current +Already up to date. +Merging fsverity-current/for-current (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'for-current' of https://git.kernel.org/pub/scm/fs/fsverity/linux.git fsverity-current/for-current +Already up to date. +Merging btrfs-fixes/next-fixes (6c893b948351d Merge branch 'misc-7.2' into next-fixes) +$ git merge -m Merge branch 'next-fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux.git btrfs-fixes/next-fixes +Merge made by the 'ort' strategy. + fs/btrfs/file-item.c | 26 +++++++++++++++++++++++++- + fs/btrfs/free-space-cache.c | 3 +++ + fs/btrfs/ioctl.c | 18 ++++++++++++------ + fs/btrfs/lzo.c | 15 ++++++++++++--- + fs/btrfs/print-tree.c | 8 ++++---- + fs/btrfs/props.c | 16 +++++++++++++--- + fs/btrfs/relocation.c | 15 +++++++-------- + fs/btrfs/tree-checker.c | 6 ++++++ + 8 files changed, 82 insertions(+), 25 deletions(-) +Merging vfs-fixes/fixes (49c5d168a3a8f udf: fix nls leak on udf_fill_super() failure) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs.git vfs-fixes/fixes +Auto-merging fs/udf/super.c +Merge made by the 'ort' strategy. +Merging erofs-fixes/fixes (1006b2f57f773 erofs: use more informative s_id for file-backed mounts) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs.git erofs-fixes/fixes +Merge made by the 'ort' strategy. + fs/erofs/super.c | 17 ++++------------- + 1 file changed, 4 insertions(+), 13 deletions(-) +Merging nfsd-fixes/nfsd-fixes (92ea163c773cb NFSD: Prevent post-shutdown use-after-free in NFSD_CMD_UNLOCK_FILESYSTEM) +$ git merge -m Merge branch 'nfsd-fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/cel/linux nfsd-fixes/nfsd-fixes +Merge made by the 'ort' strategy. + fs/nfsd/nfsctl.c | 7 ++++--- + 1 file changed, 4 insertions(+), 3 deletions(-) +Merging v9fs-fixes/fixes/next (028ef9c96e961 Linux 7.0) +$ git merge -m Merge branch 'fixes/next' of https://git.kernel.org/pub/scm/linux/kernel/git/ericvh/v9fs.git v9fs-fixes/fixes/next +Already up to date. +Merging overlayfs-fixes/ovl-fixes (4549871118cf6 Linux 7.1-rc7) +$ git merge -m Merge branch 'ovl-fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/overlayfs/vfs.git overlayfs-fixes/ovl-fixes +Already up to date. +Merging fscrypt/for-next (2d0afaac9137e fscrypt: Remove workaround for bug in gcc 7 and earlier) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/fs/fscrypt/linux.git fscrypt/for-next +Merge made by the 'ort' strategy. + fs/crypto/crypto.c | 40 ++++++++------------------------------ + fs/crypto/fscrypt_private.h | 5 +---- + fs/crypto/keyring.c | 21 +++++++++----------- + fs/crypto/keysetup.c | 26 ++++++++++--------------- + fs/crypto/policy.c | 17 ++-------------- + include/uapi/linux/fscrypt.h | 1 - + tools/include/uapi/linux/fscrypt.h | 1 - + 7 files changed, 30 insertions(+), 81 deletions(-) +Merging btrfs/for-next (0542d886e45d5 Merge branch 'for-next-current-v7.1-20260630' into for-next-20260630) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux.git btrfs/for-next +Merge made by the 'ort' strategy. +Merging ceph/master (1397040896280 selftests: ceph: wire up Ceph reset kselftests and documentation) +$ git merge -m Merge branch 'master' of https://github.com/ceph/ceph-client.git ceph/master +Auto-merging MAINTAINERS +Auto-merging tools/testing/selftests/Makefile +Merge made by the 'ort' strategy. + MAINTAINERS | 1 + + tools/testing/selftests/Makefile | 1 + + tools/testing/selftests/filesystems/ceph/Makefile | 7 + + tools/testing/selftests/filesystems/ceph/README | 84 +++ + .../filesystems/ceph/reset_corner_cases.sh | 646 +++++++++++++++++++ + .../selftests/filesystems/ceph/reset_stress.sh | 694 +++++++++++++++++++++ + .../selftests/filesystems/ceph/run_validation.sh | 350 +++++++++++ + tools/testing/selftests/filesystems/ceph/settings | 1 + + .../filesystems/ceph/validate_consistency.py | 297 +++++++++ + 9 files changed, 2081 insertions(+) + create mode 100644 tools/testing/selftests/filesystems/ceph/Makefile + create mode 100644 tools/testing/selftests/filesystems/ceph/README + create mode 100755 tools/testing/selftests/filesystems/ceph/reset_corner_cases.sh + create mode 100755 tools/testing/selftests/filesystems/ceph/reset_stress.sh + create mode 100755 tools/testing/selftests/filesystems/ceph/run_validation.sh + create mode 100644 tools/testing/selftests/filesystems/ceph/settings + create mode 100755 tools/testing/selftests/filesystems/ceph/validate_consistency.py +Merging cifs/for-next (816059941cb64 smb: client: fix busy dentry warning on unmount after DIO) +$ git merge -m Merge branch 'for-next' of git://git.samba.org/sfrench/cifs-2.6.git cifs/for-next +Merge made by the 'ort' strategy. + fs/smb/client/cifs_fs_sb.h | 1 + + fs/smb/client/cifsfs.c | 12 ++++++++++++ + fs/smb/client/connect.c | 1 + + fs/smb/client/file.c | 5 +++++ + fs/smb/client/inode.c | 4 +--- + fs/smb/client/reparse.c | 17 ++++++++++++++++- + 6 files changed, 36 insertions(+), 4 deletions(-) +Merging configfs/configfs-next (6363844fdbbb7 samples: configfs: Constify struct configfs_item_operations and configfs_group_operations) +$ git merge -m Merge branch 'configfs-next' of https://git.kernel.org/pub/scm/linux/kernel/git/a.hindborg/linux.git configfs/configfs-next +Already up to date. +Merging ecryptfs/next (95ce5ffd54cf6 ecryptfs: use kasprintf in ecryptfs_crypto_api_algify_cipher_name) +$ git merge -m Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/tyhicks/ecryptfs.git ecryptfs/next +Already up to date. +Merging dlm/next (e61113cfcaf71 dlm: init per node debugfs before add to node hash) +$ git merge -m Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/teigland/linux-dlm.git dlm/next +Already up to date. +Merging erofs/dev (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'dev' of https://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs.git erofs/dev +Already up to date. +Merging exfat/dev (5362e7aedd113 exfat: fix mmap data loss when extending valid_size over a shared mapping) +$ git merge -m Merge branch 'dev' of https://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/exfat.git exfat/dev +Auto-merging fs/exfat/iomap.c +Merge made by the 'ort' strategy. + fs/exfat/exfat_fs.h | 2 +- + fs/exfat/file.c | 219 +++++++++++++++++++++++++++++++++++++++++++--------- + fs/exfat/iomap.c | 22 +++++- + 3 files changed, 205 insertions(+), 38 deletions(-) +Merging ext3/for_next (b0961edc5667d Pull fsnotify watchdog fix.) +$ git merge -m Merge branch 'for_next' of https://git.kernel.org/pub/scm/linux/kernel/git/jack/linux-fs.git ext3/for_next +Merge made by the 'ort' strategy. + fs/notify/fanotify/fanotify.c | 1 + + fs/udf/balloc.c | 12 ++++++++++-- + fs/udf/inode.c | 25 ++++++++++++++++++++++--- + fs/udf/super.c | 23 ++++++++++++++--------- + fs/udf/truncate.c | 2 +- + fs/udf/udfdecl.h | 3 ++- + 6 files changed, 50 insertions(+), 16 deletions(-) +$ git reset --hard HEAD^ +HEAD is now at 6b845f61b85ac Merge branch 'dev' of https://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/exfat.git +Merging next-20260703 version of ext3 +$ git merge -m next-20260703/ext3 42093d1948d2de3991a8b20ac5d12df13b941aad +Merge made by the 'ort' strategy. +Merging ext4/dev (c143957520c6c ext4: validate donor file superblock early in EXT4_IOC_MOVE_EXT) +$ git merge -m Merge branch 'dev' of https://git.kernel.org/pub/scm/linux/kernel/git/tytso/ext4.git ext4/dev +Already up to date. +Merging f2fs/dev (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'dev' of https://git.kernel.org/pub/scm/linux/kernel/git/jaegeuk/f2fs.git f2fs/dev +Already up to date. +Merging fsverity/for-next (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/fs/fsverity/linux.git fsverity/for-next +Already up to date. +Merging fuse/for-next (7d87a5a284bb3 fuse-uring: clear ent->fuse_req in commit_fetch error path) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/fuse.git fuse/for-next +Already up to date. +Merging gfs2/for-next (5f80d9113360c Merge tag 'gfs2-for-7.2' of git://git.kernel.org/pub/scm/linux/kernel/git/gfs2/linux-gfs2) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/gfs2/linux-gfs2.git gfs2/for-next +Already up to date. +Merging jfs/jfs-next (dad98c5b2a05e jfs: avoid -Wtautological-constant-out-of-range-compare warning again) +$ git merge -m Merge branch 'jfs-next' of https://github.com/kleikamp/linux-shaggy.git jfs/jfs-next +Already up to date. +Merging ksmbd/ksmbd-for-next (8cdeaa50eae8d Linux 7.2-rc2) +$ git merge -m Merge branch 'ksmbd-for-next' of https://github.com/smfrench/smb3-kernel.git ksmbd/ksmbd-for-next +Already up to date. +$ git am -3 ../patches/0001-ntfs3-Fix-up-merge-with-Linus.patch +Applying: ntfs3: Fix up merge with Linus +Using index info to reconstruct a base tree... +M fs/ntfs3/file.c +Falling back to patching base and 3-way merge... +No changes -- Patch already applied. +Merging nfs/linux-next (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'linux-next' of git://git.linux-nfs.org/projects/trondmy/nfs-2.6.git nfs/linux-next +Already up to date. +Merging nfs-anna/linux-next (284ea3fb4f671 NFS: Use common error handling code in nfs_alloc_server()) +$ git merge -m Merge branch 'linux-next' of git://git.linux-nfs.org/projects/anna/linux-nfs.git nfs-anna/linux-next +Already up to date. +Merging nfsd/nfsd-next (26ddc8a26f00b NFSD: Annotate caller preconditions for the state-table walkers) +$ git merge -m Merge branch 'nfsd-next' of https://git.kernel.org/pub/scm/linux/kernel/git/cel/linux nfsd/nfsd-next +Auto-merging fs/nfsd/nfsctl.c +Merge made by the 'ort' strategy. + fs/lockd/svc.c | 4 +- + fs/lockd/svc4proc.c | 4 +- + fs/lockd/svclock.c | 42 +--- + fs/lockd/svcproc.c | 4 +- + fs/lockd/svcsubs.c | 105 +++++++--- + fs/nfs/callback.c | 4 +- + fs/nfs_common/nfslocalio.c | 16 +- + fs/nfsd/filecache.c | 143 +++++++------- + fs/nfsd/flexfilelayoutxdr.c | 20 +- + fs/nfsd/localio.c | 8 +- + fs/nfsd/lockd.c | 6 +- + fs/nfsd/netns.h | 29 ++- + fs/nfsd/nfs2acl.c | 21 +- + fs/nfsd/nfs3acl.c | 17 +- + fs/nfsd/nfs4callback.c | 113 +++++++++-- + fs/nfsd/nfs4layouts.c | 39 ++-- + fs/nfsd/nfs4proc.c | 84 ++++++-- + fs/nfsd/nfs4recover.c | 48 +++-- + fs/nfsd/nfs4state.c | 330 ++++++++++++++++++++++--------- + fs/nfsd/nfs4xdr.c | 27 ++- + fs/nfsd/nfscache.c | 4 +- + fs/nfsd/nfsctl.c | 79 +++++--- + fs/nfsd/nfsfh.c | 12 +- + fs/nfsd/nfsproc.c | 7 + + fs/nfsd/nfssvc.c | 61 +++--- + fs/nfsd/state.h | 4 +- + fs/nfsd/trace.h | 18 +- + fs/nfsd/vfs.c | 48 +++-- + include/linux/lockd/bind.h | 12 +- + include/linux/sunrpc/bc_xprt.h | 5 + + include/linux/sunrpc/svc.h | 1 + + include/linux/sunrpc/svc_rdma_pcl.h | 4 +- + net/sunrpc/auth_gss/auth_gss.c | 6 +- + net/sunrpc/auth_gss/gss_krb5_unseal.c | 3 + + net/sunrpc/auth_gss/gss_krb5_wrap.c | 13 +- + net/sunrpc/auth_gss/gss_rpc_upcall.c | 6 - + net/sunrpc/auth_gss/gss_rpc_upcall.h | 1 - + net/sunrpc/auth_gss/gss_rpc_xdr.c | 15 +- + net/sunrpc/auth_gss/svcauth_gss.c | 8 +- + net/sunrpc/backchannel_rqst.c | 38 +++- + net/sunrpc/cache.c | 7 +- + net/sunrpc/sunrpc_syms.c | 1 + + net/sunrpc/svc.c | 81 +++++++- + net/sunrpc/svcauth_unix.c | 4 +- + net/sunrpc/xdr.c | 2 +- + net/sunrpc/xprtrdma/ib_client.c | 42 +++- + net/sunrpc/xprtrdma/svc_rdma_pcl.c | 63 +++++- + net/sunrpc/xprtrdma/svc_rdma_recvfrom.c | 24 ++- + net/sunrpc/xprtrdma/svc_rdma_rw.c | 52 +++-- + net/sunrpc/xprtrdma/svc_rdma_transport.c | 69 +++++-- + 50 files changed, 1244 insertions(+), 510 deletions(-) +$ git am -3 ../patches/0001-Revert-smb-client-implement-fileattr_get-to-support-.patch +Applying: Revert "smb: client: implement fileattr_get to support FS_IOC_GETFLAGS" +Using index info to reconstruct a base tree... +M fs/smb/client/cifsfs.c +M fs/smb/client/cifsfs.h +M fs/smb/client/inode.c +Falling back to patching base and 3-way merge... +Auto-merging fs/smb/client/inode.c +Auto-merging fs/smb/client/cifsfs.h +Auto-merging fs/smb/client/cifsfs.c +No changes -- Patch already applied. +Merging ntfs/ntfs-next (c02bbc2556708 ntfs: dir: use kmemdup() instead of kmalloc() and memcpy()) +$ git merge -m Merge branch 'ntfs-next' of https://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/ntfs.git ntfs/ntfs-next +Auto-merging fs/ntfs/aops.c +Merge made by the 'ort' strategy. + fs/ntfs/aops.c | 17 ++++++++++++++- + fs/ntfs/attrib.c | 36 ++++++++++++++++++++----------- + fs/ntfs/attrlist.c | 9 ++++++++ + fs/ntfs/dir.c | 20 ++++++++++++------ + fs/ntfs/index.c | 5 ++++- + fs/ntfs/inode.c | 9 ++++++++ + fs/ntfs/mft.c | 11 ++++++---- + fs/ntfs/namei.c | 62 +++++++++++++++++++++++++++--------------------------- + fs/ntfs/reparse.c | 6 ++---- + 9 files changed, 116 insertions(+), 59 deletions(-) +Merging ntfs3/master (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'master' of https://github.com/Paragon-Software-Group/linux-ntfs3.git ntfs3/master +Already up to date. +Merging orangefs/for-next (e61bc5e4d8743 bufmap: manage as folios, V2.) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/hubcap/linux.git orangefs/for-next +Already up to date. +Merging overlayfs/overlayfs-next (1f6ee9be92f8d ovl: make fsync after metadata copy-up opt-in mount option) +$ git merge -m Merge branch 'overlayfs-next' of https://git.kernel.org/pub/scm/linux/kernel/git/overlayfs/vfs.git overlayfs/overlayfs-next +Already up to date. +Merging ubifs/next (11efa98bcc0d0 ubi: ubi.h: fix kernel-doc warnings) +$ git merge -m Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/rw/ubifs.git ubifs/next +Auto-merging drivers/mtd/ubi/ubi.h +Merge made by the 'ort' strategy. + drivers/mtd/ubi/ubi.h | 9 ++++++--- + 1 file changed, 6 insertions(+), 3 deletions(-) +Merging v9fs/9p-next (aa88278693cbf 9p: Add missing read barrier in virtio zero-copy path) +$ git merge -m Merge branch '9p-next' of https://github.com/martinetd/linux v9fs/9p-next +Already up to date. +Merging v9fs-ericvh/ericvh/for-next (028ef9c96e961 Linux 7.0) +$ git merge -m Merge branch 'ericvh/for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/ericvh/v9fs.git v9fs-ericvh/ericvh/for-next +Already up to date. +Merging xfs/for-next (e4281086ae6ca xfs: simplify __xfs_buf_ioend) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/fs/xfs/xfs-linux.git xfs/for-next +Already up to date. +Merging zonefs/for-next (3a8389d42bdf4 zonefs: handle integer overflow in zonefs_fname_to_fno) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/dlemoal/zonefs.git zonefs/for-next +Already up to date. +Merging vfs-brauner/vfs.all (cf6f88615485a Merge branch 'vfs-7.3.errno' into vfs.all) +$ git merge -m Merge branch 'vfs.all' of https://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs.git vfs-brauner/vfs.all +Auto-merging MAINTAINERS +Auto-merging fs/btrfs/ioctl.c +Auto-merging fs/erofs/super.c +Auto-merging fs/ntfs/namei.c +Auto-merging fs/smb/client/cifsfs.h +Auto-merging fs/smb/client/inode.c +Auto-merging fs/smb/server/smb2pdu.c +Auto-merging fs/smb/server/vfs.c +Auto-merging fs/xfs/xfs_buf.c +Auto-merging kernel/fork.c +Merge made by the 'ort' strategy. + Documentation/filesystems/locking.rst | 2 +- + Documentation/filesystems/overlayfs.rst | 16 + + Documentation/filesystems/porting.rst | 8 + + Documentation/filesystems/vfs.rst | 2 +- + MAINTAINERS | 5 - + block/bdev.c | 113 +++- + drivers/base/devtmpfs.c | 2 +- + drivers/block/rnbd/rnbd-srv.c | 4 +- + drivers/char/misc_minor_kunit.c | 25 +- + drivers/crypto/ccp/sev-dev.c | 12 +- + drivers/target/target_core_alua.c | 11 +- + drivers/target/target_core_pr.c | 4 +- + fs/9p/vfs_inode.c | 5 +- + fs/9p/vfs_inode_dotl.c | 4 +- + fs/Kconfig | 1 - + fs/Makefile | 1 - + fs/affs/affs.h | 2 +- + fs/affs/namei.c | 4 +- + fs/afs/dir.c | 6 +- + fs/autofs/root.c | 2 +- + fs/bad_inode.c | 2 +- + fs/bfs/dir.c | 2 +- + fs/bpf_fs_kfuncs.c | 37 ++ + fs/btrfs/dev-replace.c | 65 ++- + fs/btrfs/inode.c | 4 +- + fs/btrfs/ioctl.c | 4 +- + fs/btrfs/volumes.c | 116 +++- + fs/btrfs/volumes.h | 6 +- + fs/ceph/dir.c | 3 +- + fs/coda/dir.c | 9 +- + fs/coredump.c | 11 +- + fs/cramfs/inode.c | 2 +- + fs/ecryptfs/inode.c | 2 +- + fs/efivarfs/inode.c | 2 +- + fs/efs/Kconfig | 16 - + fs/efs/Makefile | 8 - + fs/efs/dir.c | 105 ---- + fs/efs/efs.h | 144 ----- + fs/efs/file.c | 42 -- + fs/efs/inode.c | 315 ---------- + fs/efs/namei.c | 120 ---- + fs/efs/super.c | 368 ------------ + fs/efs/symlink.c | 50 -- + fs/erofs/super.c | 35 +- + fs/exfat/namei.c | 2 +- + fs/ext2/namei.c | 4 +- + fs/ext4/extents-test.c | 9 +- + fs/ext4/mballoc-test.c | 9 +- + fs/ext4/namei.c | 4 +- + fs/ext4/super.c | 12 +- + fs/f2fs/namei.c | 4 +- + fs/f2fs/super.c | 6 +- + fs/fat/namei_msdos.c | 5 +- + fs/fat/namei_vfat.c | 2 +- + fs/fs_struct.c | 103 +++- + fs/fuse/dir.c | 10 +- + fs/gfs2/inode.c | 7 +- + fs/hfs/dir.c | 4 +- + fs/hfsplus/dir.c | 4 +- + fs/hostfs/hostfs_kern.c | 2 +- + fs/hpfs/namei.c | 6 +- + fs/hugetlbfs/inode.c | 4 +- + fs/inode.c | 23 +- + fs/internal.h | 1 + + fs/iomap/buffered-io.c | 6 +- + fs/iomap/direct-io.c | 293 +++++++++- + fs/iomap/ioend.c | 21 +- + fs/jffs2/dir.c | 6 +- + fs/jfs/namei.c | 4 +- + fs/kernel_read_file.c | 9 +- + fs/minix/namei.c | 4 +- + fs/namei.c | 30 +- + fs/namespace.c | 25 +- + fs/nfs/blocklayout/dev.c | 15 +- + fs/nfs/dir.c | 6 +- + fs/nfs/internal.h | 2 +- + fs/nilfs2/namei.c | 4 +- + fs/ntfs/namei.c | 4 +- + fs/ntfs3/namei.c | 4 +- + fs/nullfs.c | 14 +- + fs/ocfs2/dlmfs/dlmfs.c | 5 +- + fs/ocfs2/namei.c | 5 +- + fs/ocfs2/super.c | 1 - + fs/omfs/dir.c | 4 +- + fs/orangefs/namei.c | 5 +- + fs/overlayfs/dir.c | 18 +- + fs/overlayfs/inode.c | 26 +- + fs/overlayfs/overlayfs.h | 1 + + fs/overlayfs/super.c | 2 +- + fs/overlayfs/xattrs.c | 1 + + fs/posix_acl.c | 2 - + fs/proc/array.c | 4 +- + fs/proc/base.c | 8 +- + fs/proc_namespace.c | 4 +- + fs/ramfs/inode.c | 4 +- + fs/romfs/super.c | 2 +- + fs/smb/client/cifsfs.h | 2 +- + fs/smb/client/dir.c | 2 +- + fs/smb/client/inode.c | 7 + + fs/smb/server/mgmt/share_config.c | 4 +- + fs/smb/server/smb2pdu.c | 4 +- + fs/smb/server/vfs.c | 9 +- + fs/super.c | 639 ++++++++++++++------- + fs/ubifs/dir.c | 4 +- + fs/udf/namei.c | 4 +- + fs/ufs/namei.c | 5 +- + fs/vboxsf/dir.c | 4 +- + fs/xfs/xfs_buf.c | 2 +- + fs/xfs/xfs_iops.c | 7 +- + fs/xfs/xfs_super.c | 10 +- + include/linux/blk_types.h | 2 +- + include/linux/blkdev.h | 12 +- + include/linux/efs_vh.h | 54 -- + include/linux/fs.h | 4 +- + include/linux/fs/super.h | 8 + + include/linux/fs/super_types.h | 4 +- + include/linux/fs_struct.h | 34 ++ + include/linux/init_task.h | 1 + + include/linux/net.h | 1 + + include/linux/sched.h | 1 + + include/linux/sched/task.h | 1 + + include/linux/types.h | 2 + + include/uapi/asm-generic/errno.h | 2 +- + include/uapi/linux/efs_fs_sb.h | 63 -- + init/init_task.c | 1 + + init/initramfs.c | 14 +- + init/initramfs_test.c | 220 +++---- + init/main.c | 10 +- + ipc/mqueue.c | 7 +- + kernel/exit.c | 2 +- + kernel/fork.c | 53 +- + kernel/kcmp.c | 2 +- + kernel/umh.c | 6 +- + mm/shmem.c | 2 +- + net/socket.c | 25 + + net/unix/af_unix.c | 17 +- + tools/testing/selftests/bpf/bpf_experimental.h | 3 + + .../testing/selftests/bpf/prog_tests/sock_xattr.c | 67 +++ + .../testing/selftests/bpf/progs/sock_read_xattr.c | 54 ++ + tools/testing/selftests/filesystems/.gitignore | 1 + + tools/testing/selftests/filesystems/Makefile | 2 +- + .../selftests/filesystems/overlayfs/.gitignore | 1 + + .../selftests/filesystems/overlayfs/Makefile | 2 + + .../filesystems/overlayfs/idmapped_mounts.c | 501 ++++++++++++++++ + .../filesystems/overlayfs/set_layers_via_fds.c | 16 +- + tools/testing/selftests/filesystems/ustat_test.c | 135 +++++ + tools/testing/selftests/proc/proc-pidns.c | 1 + + 147 files changed, 2511 insertions(+), 1980 deletions(-) + delete mode 100644 fs/efs/Kconfig + delete mode 100644 fs/efs/Makefile + delete mode 100644 fs/efs/dir.c + delete mode 100644 fs/efs/efs.h + delete mode 100644 fs/efs/file.c + delete mode 100644 fs/efs/inode.c + delete mode 100644 fs/efs/namei.c + delete mode 100644 fs/efs/super.c + delete mode 100644 fs/efs/symlink.c + delete mode 100644 include/linux/efs_vh.h + delete mode 100644 include/uapi/linux/efs_fs_sb.h + create mode 100644 tools/testing/selftests/bpf/prog_tests/sock_xattr.c + create mode 100644 tools/testing/selftests/bpf/progs/sock_read_xattr.c + create mode 100644 tools/testing/selftests/filesystems/overlayfs/idmapped_mounts.c + create mode 100644 tools/testing/selftests/filesystems/ustat_test.c +$ git am -3 ../patches/0001-ksmbd-Fix-removal-of-type-parameter-from-vfs_path_pa.patch +Applying: ksmbd: Fix removal of type parameter from vfs_path_parent_lookup() +Using index info to reconstruct a base tree... +M fs/smb/server/vfs.c +Falling back to patching base and 3-way merge... +Auto-merging fs/smb/server/vfs.c +No changes -- Patch already applied. +Merging vfs/for-next (4dda01b67c866 Merge branches 'work.dcache', 'work.dcache-d_add' and 'work.configfs' into for-next) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs.git vfs/for-next +Merge made by the 'ort' strategy. +Merging mm-hotfixes/mm-hotfixes-unstable (79973aa4f4d10 userfaultfd: wait on source PMD during UFFDIO_MOVE) +$ git merge -m Merge branch 'mm-hotfixes-unstable' of https://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm mm-hotfixes/mm-hotfixes-unstable +Auto-merging .mailmap +Auto-merging MAINTAINERS +Merge made by the 'ort' strategy. + .mailmap | 5 + + Documentation/ABI/testing/sysfs-kernel-mm-damon | 174 ++++++++++++------------ + MAINTAINERS | 6 +- + arch/m68k/include/asm/page_mm.h | 2 + + arch/x86/kernel/sys_x86_64.c | 14 +- + drivers/dax/bus.c | 7 +- + drivers/virtio/virtio_balloon.c | 5 +- + fs/fat/fat.h | 4 +- + fs/fat/fat_test.c | 4 +- + fs/proc/page.c | 2 +- + include/linux/damon.h | 13 +- + include/linux/fs.h | 5 + + include/linux/page_ext.h | 19 ++- + include/linux/page_reporting.h | 4 +- + include/trace/events/memory-failure.h | 6 +- + kernel/cgroup/cpuset.c | 2 +- + lib/test_hmm.c | 2 +- + mm/compaction.c | 7 +- + mm/damon/core.c | 15 +- + mm/damon/lru_sort.c | 2 - + mm/damon/modules-common.c | 2 - + mm/damon/modules-common.h | 2 - + mm/damon/ops-common.c | 3 +- + mm/damon/ops-common.h | 2 - + mm/damon/paddr.c | 2 - + mm/damon/reclaim.c | 2 - + mm/damon/sysfs-common.c | 2 - + mm/damon/sysfs-common.h | 2 - + mm/damon/sysfs-schemes.c | 19 +-- + mm/damon/sysfs.c | 2 - + mm/damon/tests/core-kunit.h | 4 - + mm/damon/tests/sysfs-kunit.h | 2 - + mm/damon/tests/vaddr-kunit.h | 4 - + mm/damon/vaddr.c | 2 - + mm/filemap.c | 2 +- + mm/huge_memory.c | 14 +- + mm/hugetlb.c | 16 ++- + mm/hugetlb_cma.c | 2 +- + mm/kmemleak.c | 7 +- + mm/madvise.c | 3 +- + mm/memremap.c | 1 + + mm/migrate.c | 6 +- + mm/mincore.c | 3 +- + mm/mm_init.c | 15 +- + mm/page_reporting.c | 24 ++-- + mm/page_vma_mapped.c | 33 +++-- + mm/shrinker.c | 13 +- + mm/shrinker_debug.c | 10 +- + mm/userfaultfd.c | 5 +- + samples/damon/mtier.c | 3 + + tools/include/linux/overflow.h | 1 + + tools/testing/selftests/mm/hmm-tests.c | 1 + + tools/testing/selftests/mm/ksft_process_madv.sh | 2 +- + tools/testing/selftests/mm/pagemap_ioctl.c | 12 +- + tools/virtio/asm/percpu_types.h | 7 + + tools/virtio/linux/completion.h | 9 ++ + tools/virtio/linux/device.h | 1 + + tools/virtio/linux/dma-mapping.h | 1 + + tools/virtio/linux/mod_devicetable.h | 14 ++ + tools/virtio/linux/virtio_features.h | 79 +++++++++++ + 60 files changed, 400 insertions(+), 232 deletions(-) + create mode 100644 tools/virtio/asm/percpu_types.h + create mode 100644 tools/virtio/linux/completion.h + create mode 100644 tools/virtio/linux/mod_devicetable.h + create mode 100644 tools/virtio/linux/virtio_features.h +Merging fs-current (69c4514f08f94 Merge branch 'nfsd-fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/cel/linux) +$ git merge -m Merge branch 'fs-current' of linux-next fs-current +Merge made by the 'ort' strategy. + fs/btrfs/file-item.c | 26 +++++++++++++++++++++++++- + fs/btrfs/free-space-cache.c | 3 +++ + fs/btrfs/ioctl.c | 18 ++++++++++++------ + fs/btrfs/lzo.c | 15 ++++++++++++--- + fs/btrfs/print-tree.c | 8 ++++---- + fs/btrfs/props.c | 16 +++++++++++++--- + fs/btrfs/relocation.c | 15 +++++++-------- + fs/btrfs/tree-checker.c | 6 ++++++ + fs/erofs/super.c | 17 ++++------------- + fs/nfsd/nfsctl.c | 7 ++++--- + 10 files changed, 90 insertions(+), 41 deletions(-) +Merging kbuild-current/kbuild-fixes-for-next (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'kbuild-fixes-for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/kbuild/linux.git kbuild-current/kbuild-fixes-for-next +Already up to date. +Merging clang-fixes/clang-fixes-for-next (175db11786bde Disable -Wattribute-alias for clang-23 and newer) +$ git merge -m Merge branch 'clang-fixes-for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/nathan/linux.git clang-fixes/clang-fixes-for-next +Already up to date. +Merging arc-current/for-curr (7aaa8047eafd0 Linux 7.0-rc6) +$ git merge -m Merge branch 'for-curr' of https://git.kernel.org/pub/scm/linux/kernel/git/vgupta/arc.git arc-current/for-curr +Already up to date. +Merging arm-current/fixes (009b6c6486b94 ARM: 9476/1: mm: fix kexec and hibernation with CONFIG_CPU_TTBR0_PAN) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/rmk/linux.git arm-current/fixes +Already up to date. +Merging arm64-fixes/for-next/fixes (a52d6c7160f7e selftests/arm64: fix spelling errors in comments) +$ git merge -m Merge branch 'for-next/fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/arm64/linux arm64-fixes/for-next/fixes +Merge made by the 'ort' strategy. + Documentation/arch/arm64/cpu-hotplug.rst | 28 +++++++++------- + arch/arm64/include/asm/tlbbatch.h | 10 ++---- + arch/arm64/include/asm/tlbflush.h | 49 +++++----------------------- + arch/arm64/kernel/acpi.c | 2 ++ + arch/arm64/kernel/fpsimd.c | 10 ++++-- + arch/arm64/kernel/process.c | 35 -------------------- + arch/arm64/kernel/smp.c | 28 ++++++++-------- + arch/arm64/mm/mmu.c | 11 +++++-- + arch/arm64/tools/sysreg | 2 +- + tools/testing/selftests/arm64/gcs/libc-gcs.c | 2 +- + tools/testing/selftests/arm64/pauth/pac.c | 2 +- + 11 files changed, 61 insertions(+), 118 deletions(-) +Merging arm-soc-fixes/arm/fixes (9c648f3554920 Merge tag 'v7.1-rockchip-arm32fixe' of https://git.kernel.org/pub/scm/linux/kernel/git/mmind/linux-rockchip into arm/fixes) +$ git merge -m Merge branch 'arm/fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/soc/soc.git arm-soc-fixes/arm/fixes +Already up to date. +Merging davinci-current/davinci/for-current (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'davinci/for-current' of https://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux.git davinci-current/davinci/for-current +Already up to date. +Merging drivers-memory-fixes/fixes (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux-mem-ctrl.git drivers-memory-fixes/fixes +Already up to date. +Merging sophgo-fixes/fixes (19272b37aa4f8 Linux 6.16-rc1) +$ git merge -m Merge branch 'fixes' of https://github.com/sophgo/linux.git sophgo-fixes/fixes +Already up to date. +Merging sophgo-soc-fixes/soc-fixes (0af2f6be1b428 Linux 6.15-rc1) +$ git merge -m Merge branch 'soc-fixes' of https://github.com/sophgo/linux.git sophgo-soc-fixes/soc-fixes +Already up to date. +Merging m68k-current/for-linus (dc5200f6b1ada m68k: Correct CONFIG_MVME16x macro name in #endif comment) +$ git merge -m Merge branch 'for-linus' of https://git.kernel.org/pub/scm/linux/kernel/git/geert/linux-m68k.git m68k-current/for-linus +Already up to date. +Merging powerpc-fixes/fixes (5200f5f493f79 Linux 7.1-rc4) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/powerpc/linux.git powerpc-fixes/fixes +Already up to date. +Merging s390-fixes/fixes (2995ccec260ca s390/monwriter: Reject buffer reuse with different data length) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/s390/linux.git s390-fixes/fixes +Already up to date. +Merging net/main (9e05e91a9a847 amt: fix size calculation in amt_get_size()) +$ git merge -m Merge branch 'main' of https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net.git net/main +Merge made by the 'ort' strategy. + drivers/net/amt.c | 6 +- + .../net/ethernet/marvell/octeontx2/nic/otx2_pf.c | 21 +++--- + drivers/net/ethernet/mellanox/mlx5/core/en.h | 12 ++++ + .../ethernet/mellanox/mlx5/core/en/hv_vhca_stats.c | 37 ++++++---- + drivers/net/ethernet/mellanox/mlx5/core/en_main.c | 14 ++-- + drivers/net/ethernet/mellanox/mlx5/core/en_stats.c | 9 +-- + drivers/net/ethernet/mellanox/mlx5/core/en_tc.c | 3 + + .../net/ethernet/mellanox/mlx5/core/ipoib/ipoib.c | 3 +- + .../net/ethernet/mellanox/mlx5/core/lag/mpesw.c | 7 +- + .../ethernet/mellanox/mlx5/core/lag/shared_fdb.c | 2 +- + .../net/ethernet/mellanox/mlx5/core/lib/hv_vhca.c | 8 ++- + .../net/ethernet/mellanox/mlx5/core/lib/hv_vhca.h | 6 +- + .../ethernet/microchip/lan966x/lan966x_vcap_impl.c | 5 +- + .../ethernet/microchip/sparx5/sparx5_vcap_impl.c | 5 +- + drivers/net/ethernet/microchip/vcap/vcap_api.c | 72 +++++++++++--------- + drivers/net/ethernet/microchip/vcap/vcap_api.h | 3 +- + .../net/ethernet/microchip/vcap/vcap_api_debugfs.c | 8 +-- + .../microchip/vcap/vcap_api_debugfs_kunit.c | 3 +- + .../net/ethernet/microchip/vcap/vcap_api_kunit.c | 3 +- + .../net/ethernet/microchip/vcap/vcap_api_private.h | 3 + + drivers/net/ethernet/qlogic/qede/qede_fp.c | 5 ++ + .../net/ethernet/qualcomm/rmnet/rmnet_handlers.c | 5 +- + drivers/net/ethernet/qualcomm/rmnet/rmnet_map.h | 1 + + .../net/ethernet/qualcomm/rmnet/rmnet_map_data.c | 78 ++++++++++++---------- + drivers/net/usb/net1080.c | 2 +- + include/net/gue.h | 2 +- + include/net/tc_act/tc_pedit.h | 18 +++-- + net/ipv6/netfilter/ip6t_ah.c | 5 ++ + net/ipv6/netfilter/ip6t_hbh.c | 1 + + net/ipv6/netfilter/ip6t_rt.c | 3 +- + net/llc/af_llc.c | 1 + + net/mac802154/iface.c | 2 +- + net/netfilter/ipvs/ip_vs_conn.c | 4 +- + net/netfilter/ipvs/ip_vs_core.c | 6 +- + net/netfilter/nf_nat_sip.c | 11 +++ + net/netfilter/nf_tables_api.c | 3 + + net/netfilter/nfnetlink_cthelper.c | 2 + + net/netfilter/nft_set_rbtree.c | 8 ++- + net/netfilter/xt_connmark.c | 14 +++- + net/netfilter/xt_rateest.c | 2 +- + net/netfilter/xt_u32.c | 12 +++- + net/sched/act_api.c | 13 ++-- + net/sched/act_pedit.c | 13 +++- + net/sched/cls_api.c | 22 ++++-- + net/sched/sch_teql.c | 27 ++++---- + net/smc/smc_cdc.c | 15 +++-- + tools/testing/selftests/net/lib.sh | 25 ++++++- + 47 files changed, 352 insertions(+), 178 deletions(-) +Merging bpf/master (d2c9a99135da9 Merge tag 'device-id-rework' of git://git.kernel.org/pub/scm/linux/kernel/git/ukleinek/linux) +$ git merge -m Merge branch 'master' of https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf.git/ bpf/master +Already up to date. +Merging ipsec/master (ea528f18231ec xfrm: reject optional IPTFS templates in outbound policies) +$ git merge -m Merge branch 'master' of https://git.kernel.org/pub/scm/linux/kernel/git/klassert/ipsec.git ipsec/master +Merge made by the 'ort' strategy. + include/net/xfrm.h | 2 ++ + net/core/dev.c | 10 ++++++++-- + net/xfrm/xfrm_device.c | 13 +++++++++++-- + net/xfrm/xfrm_nat_keepalive.c | 15 +++++++++------ + net/xfrm/xfrm_state.c | 5 +++-- + net/xfrm/xfrm_user.c | 41 ++++++++++++++++++++++++++++++----------- + 6 files changed, 63 insertions(+), 23 deletions(-) +Merging netfilter/main (d645674342472 Merge branch 'net-mlx5e-fix-crashes-in-dynamic-per-channel-stats-and-hv-vhca-agent') +$ git merge -m Merge branch 'main' of https://git.kernel.org/pub/scm/linux/kernel/git/netfilter/nf.git netfilter/main +Already up to date. +Merging ipvs/main (fe9f4ee6c61a1 Merge branch 'net-avoid-nested-up-notifier-events') +$ git merge -m Merge branch 'main' of https://git.kernel.org/pub/scm/linux/kernel/git/horms/ipvs.git ipvs/main +Already up to date. +Merging wireless/for-next (f843cf31dfc2c wifi: mac80211: validate deauth frame length before reason access) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless.git wireless/for-next +Merge made by the 'ort' strategy. + drivers/net/wireless/intel/ipw2x00/ipw2100.c | 8 ++--- + drivers/net/wireless/intel/ipw2x00/libipw_rx.c | 4 +-- + drivers/net/wireless/intersil/p54/txrx.c | 8 +++++ + drivers/net/wireless/marvell/libertas/firmware.c | 1 + + drivers/net/wireless/marvell/libertas/tx.c | 7 +++++ + drivers/net/wireless/marvell/libertas_tf/main.c | 2 +- + drivers/net/wireless/marvell/mwifiex/cfg80211.c | 2 +- + drivers/net/wireless/marvell/mwifiex/join.c | 1 - + drivers/net/wireless/ralink/rt2x00/rt2400pci.c | 2 +- + drivers/net/wireless/ralink/rt2x00/rt2500pci.c | 2 +- + drivers/net/wireless/ralink/rt2x00/rt2800pci.c | 2 +- + drivers/net/wireless/ralink/rt2x00/rt2x00dev.c | 12 ++++++-- + drivers/net/wireless/ralink/rt2x00/rt61pci.c | 2 +- + drivers/net/wireless/rsi/rsi_91x_hal.c | 8 +++++ + drivers/net/wireless/rsi/rsi_91x_mgmt.c | 12 ++++++-- + drivers/net/wireless/virtual/mac80211_hwsim_main.c | 16 ++++++++-- + include/linux/ieee80211-eht.h | 12 +++----- + include/net/cfg80211.h | 2 +- + net/mac80211/cfg.c | 11 +++---- + net/mac80211/mlme.c | 12 +++++--- + net/mac80211/nan.c | 35 ++++++++++++---------- + net/mac80211/rx.c | 34 +++++++++++++++++++-- + net/mac80211/sta_info.c | 15 +++++++--- + net/mac80211/tx.c | 17 ++++++++++- + net/mac80211/util.c | 3 ++ + net/wireless/core.c | 4 +-- + net/wireless/core.h | 2 +- + net/wireless/nl80211.c | 22 ++++++++++---- + net/wireless/pmsr.c | 34 ++++++++++++++++----- + net/wireless/scan.c | 16 ++++++---- + 30 files changed, 228 insertions(+), 80 deletions(-) +Merging ath/for-current (e8d85672dd7e2 wifi: ath11k: fix NULL pointer dereference in ath11k_hal_srng_access_begin) +$ git merge -m Merge branch 'for-current' of https://git.kernel.org/pub/scm/linux/kernel/git/ath/ath.git ath/for-current +Merge made by the 'ort' strategy. + drivers/net/wireless/ath/ath11k/qmi.c | 11 ++++++++--- + drivers/net/wireless/ath/ath6kl/txrx.c | 2 +- + drivers/net/wireless/ath/ath9k/hif_usb.c | 7 +------ + 3 files changed, 10 insertions(+), 10 deletions(-) +Merging iwlwifi/fixes (093305d801fae wifi: iwlwifi: pcie: simplify the resume flow if fast resume is not used) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/iwlwifi/iwlwifi-next.git iwlwifi/fixes +Already up to date. +Merging wpan/master (8ce4f287524c7 net: libwx: fix firmware mailbox abnormal return) +$ git merge -m Merge branch 'master' of https://git.kernel.org/pub/scm/linux/kernel/git/wpan/wpan.git wpan/master +Already up to date. +Merging rdma-fixes/for-rc (bb27fcc67c429 RDMA/siw: publish QP after initialization) +$ git merge -m Merge branch 'for-rc' of https://git.kernel.org/pub/scm/linux/kernel/git/rdma/rdma.git rdma-fixes/for-rc +Merge made by the 'ort' strategy. + drivers/infiniband/core/cma.c | 2 +- + drivers/infiniband/core/mad.c | 30 ++++++++++++++++++++++ + drivers/infiniband/core/verbs.c | 6 ++--- + drivers/infiniband/hw/erdma/erdma_qp.c | 2 +- + drivers/infiniband/hw/hns/hns_roce_hem.c | 2 +- + drivers/infiniband/hw/irdma/verbs.c | 22 ++++++++-------- + drivers/infiniband/hw/mana/wr.c | 2 +- + drivers/infiniband/hw/mlx5/wr.c | 16 +++++++----- + drivers/infiniband/sw/siw/siw_verbs.c | 44 +++++++++++++++++--------------- + 9 files changed, 82 insertions(+), 44 deletions(-) +Merging sound-current/for-linus (c845febafd92b selftests/alsa: Fix format specifier and function mismatch in mixer-test) +$ git merge -m Merge branch 'for-linus' of https://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound.git sound-current/for-linus +Merge made by the 'ort' strategy. + sound/hda/codecs/realtek/alc269.c | 2 ++ + sound/usb/caiaq/device.c | 17 ++++++++++++++--- + sound/usb/caiaq/input.c | 6 ++++++ + sound/usb/mixer.c | 2 +- + tools/testing/selftests/alsa/mixer-test.c | 5 +++-- + 5 files changed, 26 insertions(+), 6 deletions(-) +Merging sound-asoc-fixes/for-linus (83245e7a436c0 ASoC: rsnd: src: Add missing scu_supply clock to suspend/resume) +$ git merge -m Merge branch 'for-linus' of https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git sound-asoc-fixes/for-linus +Already up to date. +Merging regmap-fixes/for-linus (fabc5b59d3e64 Merge remote-tracking branch 'regmap/for-7.1' into regmap-linus) +$ git merge -m Merge branch 'for-linus' of https://git.kernel.org/pub/scm/linux/kernel/git/broonie/regmap.git regmap-fixes/for-linus +Merge made by the 'ort' strategy. +Merging regulator-fixes/for-linus (d504c1edc4a95 Merge remote-tracking branch 'regulator/for-7.1' into regulator-linus) +$ git merge -m Merge branch 'for-linus' of https://git.kernel.org/pub/scm/linux/kernel/git/broonie/regulator.git regulator-fixes/for-linus +Merge made by the 'ort' strategy. +Merging spi-fixes/for-linus (6b448abe08f9b Merge remote-tracking branch 'spi/for-7.1' into spi-linus) +$ git merge -m Merge branch 'for-linus' of https://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi.git spi-fixes/for-linus +Merge made by the 'ort' strategy. +Merging pci-current/for-linus (34db32102439d MAINTAINERS: Drop Karthikeyan Mitran from Mobiveil PCIe entry) +$ git merge -m Merge branch 'for-linus' of https://git.kernel.org/pub/scm/linux/kernel/git/pci/pci.git pci-current/for-linus +Auto-merging MAINTAINERS +Merge made by the 'ort' strategy. + MAINTAINERS | 1 - + 1 file changed, 1 deletion(-) +Merging driver-core.current/driver-core-linus (667d0fb32149f driver core: add missing kernel-doc for union members) +$ git merge -m Merge branch 'driver-core-linus' of https://git.kernel.org/pub/scm/linux/kernel/git/driver-core/driver-core.git driver-core.current/driver-core-linus +Merge made by the 'ort' strategy. + include/linux/device.h | 2 ++ + 1 file changed, 2 insertions(+) +Merging tty.current/tty-linus (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'tty-linus' of https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/tty.git tty.current/tty-linus +Already up to date. +Merging usb.current/usb-linus (07acd41f72eec Merge tag 'usb-serial-7.2-rc2' of ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/johan/usb-serial into usb-linus) +$ git merge -m Merge branch 'usb-linus' of https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb.git usb.current/usb-linus +Auto-merging MAINTAINERS +Merge made by the 'ort' strategy. + .../ABI/testing/sysfs-bus-pci-drivers-xhci_hcd | 2 +- + MAINTAINERS | 1 + + drivers/usb/atm/ueagle-atm.c | 36 +++++++++++-- + drivers/usb/cdns3/cdnsp-mem.c | 2 + + drivers/usb/class/cdc-acm.c | 3 ++ + drivers/usb/common/ulpi.c | 23 ++++---- + drivers/usb/core/quirks.c | 6 +++ + drivers/usb/dwc3/dwc3-meson-g12a.c | 14 +++-- + drivers/usb/gadget/composite.c | 5 +- + drivers/usb/gadget/function/f_fs.c | 9 ++-- + drivers/usb/host/xhci-dbgcap.c | 60 ++++++++++++++++++++- + drivers/usb/host/xhci-dbgcap.h | 3 ++ + drivers/usb/misc/idmouse.c | 45 ++++++++-------- + drivers/usb/misc/iowarrior.c | 61 ++++++++++------------ + drivers/usb/misc/ldusb.c | 38 +++++++------- + drivers/usb/misc/legousbtower.c | 37 ++++++------- + drivers/usb/misc/usbio.c | 4 ++ + drivers/usb/mtu3/mtu3_gadget.c | 1 + + drivers/usb/serial/digi_acceleport.c | 43 +++++++++++---- + drivers/usb/serial/keyspan_pda.c | 2 +- + drivers/usb/serial/option.c | 16 ++++++ + drivers/usb/storage/usb.c | 2 +- + drivers/usb/typec/tcpm/tcpm.c | 5 ++ + drivers/usb/typec/ucsi/displayport.c | 4 +- + drivers/usb/typec/ucsi/ucsi_ccg.c | 2 +- + 25 files changed, 282 insertions(+), 142 deletions(-) +Merging usb-serial-fixes/usb-linus (6bfc8d01ac406 USB: serial: keyspan_pda: fix information leak) +$ git merge -m Merge branch 'usb-linus' of https://git.kernel.org/pub/scm/linux/kernel/git/johan/usb-serial.git usb-serial-fixes/usb-linus +Already up to date. +Merging phy/fixes (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/phy/linux-phy.git phy/fixes +Already up to date. +Merging staging.current/staging-linus (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'staging-linus' of https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging.git staging.current/staging-linus +Already up to date. +Merging iio-fixes/fixes-togreg (a9f41809bf1bd iio: adc: nxp-sar-adc: Fix the delay calculation in nxp_sar_adc_wait_for()) +$ git merge -m Merge branch 'fixes-togreg' of https://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio.git iio-fixes/fixes-togreg +Auto-merging drivers/iio/adc/lpc32xx_adc.c +Auto-merging drivers/iio/adc/nxp-sar-adc.c +Auto-merging drivers/iio/adc/spear_adc.c +Auto-merging drivers/iio/adc/ti-ads124s08.c +Auto-merging drivers/iio/dac/mcp47feb02.c +Auto-merging drivers/iio/orientation/hid-sensor-rotation.c +Merge made by the 'ort' strategy. + drivers/hid/hid-sensor-hub.c | 77 ++++++++++++++++++++-- + drivers/iio/accel/bmc150-accel-core.c | 2 + + drivers/iio/accel/kxsd9.c | 5 +- + drivers/iio/adc/Kconfig | 3 + + drivers/iio/adc/lpc32xx_adc.c | 4 +- + drivers/iio/adc/nxp-sar-adc.c | 4 +- + drivers/iio/adc/spear_adc.c | 3 +- + drivers/iio/adc/ti-ads1119.c | 6 +- + drivers/iio/adc/ti-ads124s08.c | 3 +- + drivers/iio/common/st_sensors/st_sensors_core.c | 23 +++++-- + drivers/iio/dac/mcp47feb02.c | 37 ++++++----- + drivers/iio/imu/adis_trigger.c | 2 +- + drivers/iio/imu/bmi160/bmi160_core.c | 3 +- + drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c | 9 +-- + drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h | 1 + + drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c | 21 +++++- + drivers/iio/light/Kconfig | 3 + + drivers/iio/light/gp2ap002.c | 2 +- + drivers/iio/light/tsl2591.c | 6 +- + drivers/iio/orientation/hid-sensor-rotation.c | 40 ++++++++++- + drivers/iio/pressure/mpl115.c | 4 +- + drivers/iio/temperature/Makefile | 2 +- + include/linux/hid-sensor-hub.h | 25 +++++++ + 23 files changed, 231 insertions(+), 54 deletions(-) +Merging watchdog-fixes/watchdog (36e05e134ee44 watchdog: ni903x_wdt: Check ACPI_COMPANION() against NULL) +$ git merge -m Merge branch 'watchdog' of https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git watchdog-fixes/watchdog +Merge made by the 'ort' strategy. + drivers/watchdog/ni903x_wdt.c | 7 ++++++- + drivers/watchdog/s32g_wdt.c | 3 +-- + 2 files changed, 7 insertions(+), 3 deletions(-) +Merging counter-current/counter-current (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'counter-current' of https://git.kernel.org/pub/scm/linux/kernel/git/wbg/counter.git counter-current/counter-current +Already up to date. +Merging char-misc.current/char-misc-linus (bc4a982889787 rust_binder: clear freeze listener on node removal) +$ git merge -m Merge branch 'char-misc-linus' of https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc.git char-misc.current/char-misc-linus +Merge made by the 'ort' strategy. + drivers/android/binder.c | 31 ++++++++++---- + drivers/android/binder/allocation.rs | 5 ++- + drivers/android/binder/error.rs | 13 +++--- + drivers/android/binder/freeze.rs | 11 ++++- + drivers/android/binder/node.rs | 10 +++-- + drivers/android/binder/process.rs | 18 +++++++- + drivers/android/binder/rust_binder_events.c | 7 +++- + drivers/android/binder/stats.rs | 4 +- + drivers/android/binder/thread.rs | 65 ++++++++++++++++++++--------- + drivers/android/binder/transaction.rs | 15 ++++--- + 10 files changed, 124 insertions(+), 55 deletions(-) +Merging soundwire-fixes/fixes (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/vkoul/soundwire.git soundwire-fixes/fixes +Already up to date. +Merging thunderbolt-fixes/fixes (db79679595326 thunderbolt: Bound the DROM dual link port number before indexing sw->ports) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/westeri/thunderbolt.git thunderbolt-fixes/fixes +Merge made by the 'ort' strategy. + drivers/thunderbolt/eeprom.c | 9 ++++++++- + drivers/thunderbolt/stream.c | 2 +- + drivers/thunderbolt/tb.c | 2 +- + 3 files changed, 10 insertions(+), 3 deletions(-) +Merging input-current/for-linus (536394ec81419 Input: maple_keyb - set driver data before registering input device) +$ git merge -m Merge branch 'for-linus' of https://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git input-current/for-linus +Merge made by the 'ort' strategy. + drivers/input/joystick/maplecontrol.c | 3 ++- + drivers/input/keyboard/maple_keyb.c | 4 ++-- + drivers/input/mouse/maplemouse.c | 9 ++++++--- + 3 files changed, 10 insertions(+), 6 deletions(-) +Merging crypto-current/master (6ea0ce3a19f9c crypto: tegra - fix refcount leak in tegra_se_host1x_submit()) +$ git merge -m Merge branch 'master' of https://git.kernel.org/pub/scm/linux/kernel/git/herbert/crypto-2.6.git crypto-current/master +Already up to date. +Merging libcrypto-fixes/libcrypto-fixes (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'libcrypto-fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/linux.git libcrypto-fixes/libcrypto-fixes +Already up to date. +Merging vfio-fixes/for-linus (e242e974e812e vfio: selftests: Add luuid to libvfio.mk's list of libraries, not to the Makefile) +$ git merge -m Merge branch 'for-linus' of https://github.com/awilliam/linux-vfio.git vfio-fixes/for-linus +Already up to date. +Merging kselftest-fixes/fixes (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest.git kselftest-fixes/fixes +Already up to date. +Merging dmaengine-fixes/fixes (867621ba20302 dmaengine: qcom: bam_dma: Fix command element mask field for BAM v1.6.0+) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/vkoul/dmaengine.git dmaengine-fixes/fixes +Merge made by the 'ort' strategy. + drivers/dma/idxd/cdev.c | 4 +++- + drivers/dma/idxd/init.c | 36 +++++------------------------------- + drivers/dma/sun6i-dma.c | 11 ++++------- + drivers/dma/switchtec_dma.c | 2 +- + include/linux/dma/qcom_bam_dma.h | 21 ++++++++++++++++----- + 5 files changed, 29 insertions(+), 45 deletions(-) +Merging backlight-fixes/for-backlight-fixes (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'for-backlight-fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/lee/backlight.git backlight-fixes/for-backlight-fixes +Already up to date. +Merging mtd-fixes/mtd/fixes (2b533e775aec5 Revert "mtd: maps: remove uclinux map driver") +$ git merge -m Merge branch 'mtd/fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux.git mtd-fixes/mtd/fixes +Merge made by the 'ort' strategy. + drivers/mtd/devices/mchp23k256.c | 2 +- + drivers/mtd/maps/Kconfig | 6 ++ + drivers/mtd/maps/Makefile | 1 + + drivers/mtd/maps/uclinux.c | 118 +++++++++++++++++++++++++++++ + drivers/mtd/mtd_virt_concat.c | 8 +- + drivers/mtd/mtdcore.c | 23 +++++- + drivers/mtd/mtdpart.c | 8 ++ + drivers/mtd/mtdswap.c | 5 +- + drivers/mtd/nand/ecc-mtk.c | 24 ++++-- + drivers/mtd/nand/onenand/onenand_samsung.c | 7 +- + drivers/mtd/nand/raw/Kconfig | 1 + + drivers/mtd/nand/raw/fsl_ifc_nand.c | 9 ++- + drivers/mtd/nand/raw/ingenic/ingenic_ecc.c | 7 +- + drivers/mtd/nand/raw/lpc32xx_mlc.c | 12 ++- + drivers/mtd/nand/raw/lpc32xx_slc.c | 12 ++- + drivers/mtd/nand/raw/ndfc.c | 2 +- + drivers/mtd/nand/spi/core.c | 2 +- + 17 files changed, 224 insertions(+), 23 deletions(-) + create mode 100644 drivers/mtd/maps/uclinux.c +Merging mfd-fixes/for-mfd-fixes (d5d2d7a8d8be1 MAINTAINERS: Add a mailing list entry to MFD) +$ git merge -m Merge branch 'for-mfd-fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd.git mfd-fixes/for-mfd-fixes +Already up to date. +Merging v4l-dvb-fixes/fixes (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'fixes' of git://linuxtv.org/media-ci/media-pending.git v4l-dvb-fixes/fixes +Already up to date. +Merging reset-fixes/reset/fixes (71827776667f4 reset: imx7: Correct polarity of MIPI CSI resets on i.MX8MQ) +$ git merge -m Merge branch 'reset/fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/pza/linux reset-fixes/reset/fixes +Merge made by the 'ort' strategy. + drivers/reset/reset-imx7.c | 6 ++++++ + drivers/reset/reset-sunxi.c | 4 +++- + drivers/reset/spacemit/reset-spacemit-k3.c | 2 +- + include/dt-bindings/reset/altr,rst-mgr-s10.h | 2 +- + 4 files changed, 11 insertions(+), 3 deletions(-) +Merging mips-fixes/mips-fixes (8cdeaa50eae8d Linux 7.2-rc2) +$ git merge -m Merge branch 'mips-fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/mips/linux.git mips-fixes/mips-fixes +Already up to date. +Merging at91-fixes/at91-fixes (765aaba18413a ARM: dts: microchip: sam9x7: fix GMAC clock configuration) +$ git merge -m Merge branch 'at91-fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/at91/linux.git at91-fixes/at91-fixes +Already up to date. +Merging omap-fixes/fixes (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/khilman/linux-omap.git omap-fixes/fixes +Already up to date. +Merging kvm-fixes/master (098e32cba334d x86/apic: KVM: Use cpu_physical_id() to get APIC ID of running vCPU for AVIC) +$ git merge -m Merge branch 'master' of git://git.kernel.org/pub/scm/virt/kvm/kvm.git kvm-fixes/master +Already up to date. +Merging kvms390-fixes/master (4cb62e371ffad KVM: s390: pci: Fix GISC refcount leak on AIF enable failure) +$ git merge -m Merge branch 'master' of https://git.kernel.org/pub/scm/linux/kernel/git/kvms390/linux.git kvms390-fixes/master +Merge made by the 'ort' strategy. + arch/s390/kvm/pci.c | 1 + + 1 file changed, 1 insertion(+) +Merging kvm-arm-fixes/fixes (d098bb75d14fd KVM: arm64: account pKVM reclaim against the VM mm) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/kvmarm/kvmarm.git kvm-arm-fixes/fixes +Auto-merging MAINTAINERS +Auto-merging arch/arm64/include/asm/kvm_nested.h +Auto-merging arch/arm64/kvm/at.c +Auto-merging arch/arm64/kvm/hyp/include/hyp/switch.h +Auto-merging arch/arm64/kvm/nested.c +Merge made by the 'ort' strategy. + MAINTAINERS | 1 + + arch/arm64/include/asm/kvm_nested.h | 8 ++ + arch/arm64/kvm/at.c | 8 -- + arch/arm64/kvm/emulate-nested.c | 41 +++++--- + arch/arm64/kvm/hyp/include/hyp/switch.h | 11 ++- + arch/arm64/kvm/hyp/nvhe/pkvm.c | 3 +- + arch/arm64/kvm/hyp/nvhe/sys_regs.c | 3 +- + arch/arm64/kvm/inject_fault.c | 18 +--- + arch/arm64/kvm/nested.c | 164 ++++++++++++++++---------------- + arch/arm64/kvm/pkvm.c | 2 +- + arch/arm64/kvm/vgic/vgic.c | 20 ++-- + 11 files changed, 151 insertions(+), 128 deletions(-) +Merging hwmon-fixes/hwmon (fe87b8dc67f1b hwmon: (aspeed-g6-pwm-tach) Guard fan RPM calculation against divide-by-zero) +$ git merge -m Merge branch 'hwmon' of https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git hwmon-fixes/hwmon +Already up to date. +Merging nvdimm-fixes/libnvdimm-fixes (a8aec14230322 nvdimm/bus: Fix potential use after free in asynchronous initialization) +$ git merge -m Merge branch 'libnvdimm-fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/nvdimm/nvdimm.git nvdimm-fixes/libnvdimm-fixes +Already up to date. +Merging cxl-fixes/fixes (d90f236f8b9e3 cxl/test: Update mock dev array before calling platform_device_add()) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/cxl/cxl.git cxl-fixes/fixes +Already up to date. +Merging dma-mapping-fixes/dma-mapping-fixes (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'dma-mapping-fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/mszyprowski/linux.git dma-mapping-fixes/dma-mapping-fixes +Already up to date. +Merging drivers-x86-fixes/fixes (d3666875c75eb platform/x86: bitland-mifs-wmi: Fix NULL pointer dereference during suspend/resume) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git drivers-x86-fixes/fixes +Merge made by the 'ort' strategy. + drivers/platform/x86/amd/pmc/pmc.c | 10 +++++----- + drivers/platform/x86/bitland-mifs-wmi.c | 8 ++++++++ + 2 files changed, 13 insertions(+), 5 deletions(-) +Merging samsung-krzk-fixes/fixes (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux.git samsung-krzk-fixes/fixes +Already up to date. +Merging pinctrl-samsung-fixes/fixes (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/pinctrl/samsung.git pinctrl-samsung-fixes/fixes +Already up to date. +Merging pinctrl-qcom-fixes/pinctrl-qcom/for-current (437a8d2aa1aa4 pinctrl: qcom: sc8280xp: Add missing wakeup entries for GPIO143/151) +$ git merge -m Merge branch 'pinctrl-qcom/for-current' of https://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux.git pinctrl-qcom-fixes/pinctrl-qcom/for-current +Merge made by the 'ort' strategy. + drivers/pinctrl/qcom/pinctrl-msm.c | 8 ++++---- + drivers/pinctrl/qcom/pinctrl-sc8280xp.c | 21 +++++++++++---------- + 2 files changed, 15 insertions(+), 14 deletions(-) +Merging devicetree-fixes/dt/linus (b39a6b2e9d5bd dt-bindings: mfd: khadas,mcu: Drop type reference from "fan-supply") +$ git merge -m Merge branch 'dt/linus' of https://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git devicetree-fixes/dt/linus +Already up to date. +Merging dt-krzk-fixes/fixes (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux-dt.git dt-krzk-fixes/fixes +Already up to date. +Merging scsi-fixes/fixes (5d5221f8a4064 scsi: devinfo: Broaden Promise VTrak E310/E610 identification) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/mkp/scsi.git scsi-fixes/fixes +Already up to date. +Merging drm-fixes/drm-fixes (8cdeaa50eae8d Linux 7.2-rc2) +$ git merge -m Merge branch 'drm-fixes' of https://gitlab.freedesktop.org/drm/kernel.git drm-fixes/drm-fixes +Already up to date. +Merging drm-intel-fixes/for-linux-next-fixes (2084503f2d087 drm/i915/bios: range check LFP Data Block panel_type2) +$ git merge -m Merge branch 'for-linux-next-fixes' of https://gitlab.freedesktop.org/drm/i915/kernel.git drm-intel-fixes/for-linux-next-fixes +Already up to date. +Merging mmc-fixes/fixes (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/ulfh/mmc.git mmc-fixes/fixes +Already up to date. +Merging rtc-fixes/rtc-fixes (254f49634ee16 Linux 7.1-rc1) +$ git merge -m Merge branch 'rtc-fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/abelloni/linux.git rtc-fixes/rtc-fixes +Already up to date. +Merging gnss-fixes/gnss-linus (5d6919055dec1 Linux 7.1-rc3) +$ git merge -m Merge branch 'gnss-linus' of https://git.kernel.org/pub/scm/linux/kernel/git/johan/gnss.git gnss-fixes/gnss-linus +Already up to date. +Merging hyperv-fixes/hyperv-fixes (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'hyperv-fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/hyperv/linux.git hyperv-fixes/hyperv-fixes +Already up to date. +Merging risc-v-fixes/fixes (bc7b086a45521 riscv: probes: save original sp in rethook trampoline) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/riscv/linux.git risc-v-fixes/fixes +Already up to date. +Merging riscv-dt-fixes/riscv-dt-fixes (0df8aa2b9aec5 riscv: dts: microchip: fix icicle i2c pinctrl configuration) +$ git merge -m Merge branch 'riscv-dt-fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/conor/linux.git riscv-dt-fixes/riscv-dt-fixes +Already up to date. +Merging riscv-soc-fixes/riscv-soc-fixes (75ef233975589 soc: microchip: mpfs-sys-controller: fix resource leak on probe error) +$ git merge -m Merge branch 'riscv-soc-fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/conor/linux.git riscv-soc-fixes/riscv-soc-fixes +Already up to date. +Merging fpga-fixes/fixes (19272b37aa4f8 Linux 6.16-rc1) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/fpga/linux-fpga.git fpga-fixes/fixes +Already up to date. +Merging spdx/spdx-linus (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'spdx-linus' of https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/spdx.git spdx/spdx-linus +Already up to date. +Merging gpio-brgl-fixes/gpio/for-current (db4a79713ed8e gpios: palmas: add .get_direction() op) +$ git merge -m Merge branch 'gpio/for-current' of https://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux.git gpio-brgl-fixes/gpio/for-current +Merge made by the 'ort' strategy. + drivers/gpio/gpio-palmas.c | 19 +++++++++++++++++++ + 1 file changed, 19 insertions(+) +Merging gpio-intel-fixes/fixes (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/andy/linux-gpio-intel.git gpio-intel-fixes/fixes +Already up to date. +Merging pinctrl-intel-fixes/fixes (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/pinctrl/intel.git pinctrl-intel-fixes/fixes +Already up to date. +Merging auxdisplay-fixes/fixes (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/andy/linux-auxdisplay.git auxdisplay-fixes/fixes +Already up to date. +Merging kunit-fixes/kunit-fixes (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'kunit-fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest.git kunit-fixes/kunit-fixes +Already up to date. +Merging memblock-fixes/fixes (2ebce860bdd7a mm/mm_init: handle alloc_percpu failure in free_area_init_core_hotplug) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/rppt/memblock.git memblock-fixes/fixes +Auto-merging mm/mm_init.c +Merge made by the 'ort' strategy. + include/linux/memory_hotplug.h | 2 +- + mm/memory_hotplug.c | 3 ++- + mm/mm_init.c | 19 +++++++++++++------ + 3 files changed, 16 insertions(+), 8 deletions(-) +Merging renesas-fixes/fixes (8a78d1c454b3c Merge branch 'renesas-pinctrl-fixes-for-v7.1' into renesas-fixes) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/geert/renesas-devel.git renesas-fixes/fixes +Merge made by the 'ort' strategy. +Merging perf-current/perf-tools (558ef39aeb9a0 Merge tag 'dmaengine-7.2-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/vkoul/dmaengine) +$ git merge -m Merge branch 'perf-tools' of https://git.kernel.org/pub/scm/linux/kernel/git/perf/perf-tools.git perf-current/perf-tools +Already up to date. +Merging efi-fixes/urgent (d8809f6931065 efi: sysfb_efi: Extend quirk to cover IdeaPad Duet 3 10IGL5-LTE) +$ git merge -m Merge branch 'urgent' of https://git.kernel.org/pub/scm/linux/kernel/git/efi/efi.git efi-fixes/urgent +Already up to date. +Merging battery-fixes/fixes (254f49634ee16 Linux 7.1-rc1) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply.git battery-fixes/fixes +Already up to date. +Merging iommufd-fixes/for-rc (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'for-rc' of https://git.kernel.org/pub/scm/linux/kernel/git/jgg/iommufd.git iommufd-fixes/for-rc +Already up to date. +Merging rust-fixes/rust-fixes (608045a91d917 rust: allow `suspicious_runtime_symbol_definitions` lint for Rust >= 1.98) +$ git merge -m Merge branch 'rust-fixes' of https://github.com/Rust-for-Linux/linux.git rust-fixes/rust-fixes +Merge made by the 'ort' strategy. + init/Kconfig | 3 +++ + rust/bindings/lib.rs | 4 ++++ + rust/uapi/lib.rs | 4 ++++ + 3 files changed, 11 insertions(+) +Merging w1-fixes/fixes (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux-w1.git w1-fixes/fixes +Already up to date. +Merging pmdomain-fixes/fixes (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/ulfh/linux-pm.git pmdomain-fixes/fixes +Already up to date. +Merging i2c-andi-fixes/i2c/i2c-fixes (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'i2c/i2c-fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/andi.shyti/linux.git i2c-andi-fixes/i2c/i2c-fixes +Already up to date. +Merging i2c-rust-fixes/rust-i2c-fixes (4eb422482ca5d rust: i2c: fix I2cAdapter refcounts double increment) +$ git merge -m Merge branch 'rust-i2c-fixes' of https://github.com/ikrtn/rust-for-linux i2c-rust-fixes/rust-i2c-fixes +Already up to date. +Merging sparc-fixes/for-linus (254f49634ee16 Linux 7.1-rc1) +$ git merge -m Merge branch 'for-linus' of https://git.kernel.org/pub/scm/linux/kernel/git/alarsson/linux-sparc.git sparc-fixes/for-linus +Already up to date. +Merging clk-fixes/clk-fixes (f29a24c947054 Merge tag 'qcom-clk-fixes-for-7.1' of https://git.kernel.org/pub/scm/linux/kernel/git/qcom/linux into clk-fixes) +$ git merge -m Merge branch 'clk-fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/clk/linux.git clk-fixes/clk-fixes +Already up to date. +Merging thead-clk-fixes/thead-clk-fixes (8f0b4cce4481f Linux 6.19-rc1) +$ git merge -m Merge branch 'thead-clk-fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/fustini/linux.git thead-clk-fixes/thead-clk-fixes +Already up to date. +Merging tenstorrent-clk-fixes/tenstorrent-clk-fixes (6de23f81a5e08 Linux 7.0-rc1) +$ git merge -m Merge branch 'tenstorrent-clk-fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/tenstorrent/linux.git tenstorrent-clk-fixes/tenstorrent-clk-fixes +Already up to date. +Merging fustini-config-fixes/riscv-config-fixes (254f49634ee16 Linux 7.1-rc1) +$ git merge -m Merge branch 'riscv-config-fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/fustini/linux.git fustini-config-fixes/riscv-config-fixes +Already up to date. +Merging pwrseq-fixes/pwrseq/for-current (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'pwrseq/for-current' of https://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux.git pwrseq-fixes/pwrseq/for-current +Already up to date. +Merging thead-dt-fixes/thead-dt-fixes (254f49634ee16 Linux 7.1-rc1) +$ git merge -m Merge branch 'thead-dt-fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/fustini/linux.git thead-dt-fixes/thead-dt-fixes +Already up to date. +Merging ftrace-fixes/ftrace/fixes (1650a1b6cb1ae fgraph: Check ftrace_pids_enabled on registration for early filtering) +$ git merge -m Merge branch 'ftrace/fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace.git ftrace-fixes/ftrace/fixes +Already up to date. +Merging ring-buffer-fixes/ring-buffer/fixes (057caace5214d tracing: Create output file from cmd_check_undefined) +$ git merge -m Merge branch 'ring-buffer/fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace.git ring-buffer-fixes/ring-buffer/fixes +Already up to date. +Merging trace-fixes/trace/fixes (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'trace/fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace.git trace-fixes/trace/fixes +Already up to date. +Merging tracefs-fixes/tracefs/fixes (07004a8c4b572 eventfs: Hold eventfs_mutex and SRCU when remount walks events) +$ git merge -m Merge branch 'tracefs/fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace.git tracefs-fixes/tracefs/fixes +Already up to date. +Merging spacemit-fixes/fixes (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/spacemit/linux spacemit-fixes/fixes +Already up to date. +Merging tip-fixes/tip/urgent (f5ee06c5f24e2 Merge branch into tip/master: 'x86/urgent') +$ git merge -m Merge branch 'tip/urgent' of https://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git tip-fixes/tip/urgent +Merge made by the 'ort' strategy. +Merging slab-fixes/slab/for-next-fixes (67ea9d353d0ba mm/slub: hold cpus_read_lock around flush_rcu_sheaves_on_cache()) +$ git merge -m Merge branch 'slab/for-next-fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/vbabka/slab.git slab-fixes/slab/for-next-fixes +Already up to date. +Merging kexec-fixes/kexec-fixes (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'kexec-fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git kexec-fixes/kexec-fixes +Already up to date. +Merging liveupdate-fixes/fixes (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git liveupdate-fixes/fixes +Already up to date. +Merging drm-msm-fixes/msm-fixes (db339b6bc9f23 dt-bindings: display/msm: Fix typo in clock-names property) +$ git merge -m Merge branch 'msm-fixes' of https://gitlab.freedesktop.org/drm/msm.git drm-msm-fixes/msm-fixes +Already up to date. +Merging uml-fixes/fixes (254f49634ee16 Linux 7.1-rc1) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/uml/linux.git uml-fixes/fixes +Already up to date. +Merging fwctl-fixes/for-rc (4549871118cf6 Linux 7.1-rc7) +$ git merge -m Merge branch 'for-rc' of https://git.kernel.org/pub/scm/linux/kernel/git/fwctl/fwctl.git fwctl-fixes/for-rc +Already up to date. +Merging devsec-tsm-fixes/fixes (c3fd16c3b98ed virt: tdx-guest: Fix handling of host controlled 'quote' buffer length) +$ git merge -m Merge branch 'fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/devsec/tsm.git devsec-tsm-fixes/fixes +Already up to date. +Merging drm-rust-fixes/for-linux-next-fixes (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'for-linux-next-fixes' of https://gitlab.freedesktop.org/drm/rust/kernel.git drm-rust-fixes/for-linux-next-fixes +Already up to date. +Merging tenstorrent-dt-fixes/tenstorrent-dt-fixes (6de23f81a5e08 Linux 7.0-rc1) +$ git merge -m Merge branch 'tenstorrent-dt-fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/tenstorrent/linux.git tenstorrent-dt-fixes/tenstorrent-dt-fixes +Already up to date. +Merging nfc-fixes/for-linus (e87faad807329 nfc: nci: fix out-of-bounds write in nci_target_auto_activated()) +$ git merge -m Merge branch 'for-linus' of https://codeberg.org/linux-nfc/linux.git nfc-fixes/for-linus +Merge made by the 'ort' strategy. + net/nfc/digital_technology.c | 2 ++ + net/nfc/llcp_commands.c | 18 ++++++++++++++++-- + net/nfc/llcp_core.c | 29 +++++++++++++++++++++-------- + net/nfc/llcp_sock.c | 14 +++++++++++--- + net/nfc/nci/data.c | 10 +++++----- + net/nfc/nci/ntf.c | 32 ++++++++++++++++++++++++++++---- + net/nfc/nci/rsp.c | 28 ++++++++++++++++++++++++++-- + 7 files changed, 109 insertions(+), 24 deletions(-) +Merging drm-misc-fixes/for-linux-next-fixes (6bb8898f70238 drm/bridge: analogix_dp: Fix PE/VS value shift mismatch during link training) +$ git merge -m Merge branch 'for-linux-next-fixes' of https://gitlab.freedesktop.org/drm/misc/kernel.git drm-misc-fixes/for-linux-next-fixes +Merge made by the 'ort' strategy. + drivers/accel/amdxdna/amdxdna_gem.c | 1 + + drivers/gpu/drm/bridge/analogix/analogix_dp_core.c | 4 ++++ + 2 files changed, 5 insertions(+) +Merging rust/rust-next (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'rust-next' of https://github.com/Rust-for-Linux/linux.git rust/rust-next +Already up to date. +Merging rust-interop/interop-next (05f7e89ab9731 Linux 6.19) +$ git merge -m Merge branch 'interop-next' of https://github.com/Rust-for-Linux/linux.git rust-interop/interop-next +Already up to date. +Merging rust-alloc/alloc-next (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'alloc-next' of https://github.com/Rust-for-Linux/linux.git rust-alloc/alloc-next +Already up to date. +Merging rust-io/io-next (86731a2a651e5 Linux 6.16-rc3) +$ git merge -m Merge branch 'io-next' of https://github.com/Rust-for-Linux/linux.git rust-io/io-next +Already up to date. +Merging rust-pin-init/pin-init-next (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'pin-init-next' of https://github.com/Rust-for-Linux/linux.git rust-pin-init/pin-init-next +Already up to date. +Merging rust-timekeeping/timekeeping-next (ddb1444d33351 hrtimer: add usage examples to documentation) +$ git merge -m Merge branch 'timekeeping-next' of https://github.com/Rust-for-Linux/linux.git rust-timekeeping/timekeeping-next +Already up to date. +Merging rust-xarray/xarray-next (c455f19bbe610 rust: xarray: add __rust_helper to helpers) +$ git merge -m Merge branch 'xarray-next' of https://github.com/Rust-for-Linux/linux.git rust-xarray/xarray-next +Already up to date. +Merging rust-analyzer/rust-analyzer-next (5f45afb8ab04d scripts: generate_rust_analyzer.py: pass cfg to macros crate) +$ git merge -m Merge branch 'rust-analyzer-next' of https://github.com/Rust-for-Linux/linux.git rust-analyzer/rust-analyzer-next +Auto-merging scripts/generate_rust_analyzer.py +Merge made by the 'ort' strategy. + scripts/generate_rust_analyzer.py | 1 + + 1 file changed, 1 insertion(+) +Merging mm-stable/mm-stable (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'mm-stable' of https://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm mm-stable/mm-stable +Already up to date. +Merging mm-nonmm-stable/mm-nonmm-stable (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'mm-nonmm-stable' of https://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm mm-nonmm-stable/mm-nonmm-stable +Already up to date. +Merging mm-unstable/mm-unstable (d148260a31fdd mm: fix CONFIG_STACK_GROWSUP typo in tools/testing/vma/include/dup.h) + a9f756ecd57fc ("mm/mm_init: handle alloc_percpu failure in free_area_init_core_hotplug") +$ git merge -m Merge branch 'mm-unstable' of https://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm mm-unstable/mm-unstable +Auto-merging MAINTAINERS +Auto-merging arch/riscv/Kconfig +Auto-merging mm/mm_init.c +Merge made by the 'ort' strategy. + CREDITS | 4 + + Documentation/ABI/testing/sysfs-kernel-mm-damon | 40 ++ + Documentation/admin-guide/kernel-parameters.txt | 7 +- + Documentation/admin-guide/mm/damon/usage.rst | 8 +- + Documentation/admin-guide/sysctl/vm.rst | 80 +++ + Documentation/core-api/maple_tree.rst | 21 +- + Documentation/core-api/mm-api.rst | 1 + + Documentation/driver-api/basics.rst | 3 + + Documentation/mm/damon/design.rst | 18 +- + Documentation/mm/page_owner.rst | 77 ++- + MAINTAINERS | 21 +- + arch/arm64/Kconfig | 2 +- + arch/arm64/include/asm/pgtable.h | 4 +- + arch/csky/abiv1/inc/abi/cacheflush.h | 7 +- + arch/loongarch/Kconfig | 2 +- + arch/powerpc/include/asm/book3s/64/pgtable.h | 2 +- + arch/powerpc/mm/book3s64/radix_pgtable.c | 7 +- + arch/powerpc/mm/hugetlbpage.c | 13 +- + arch/powerpc/platforms/Kconfig.cputype | 2 +- + arch/riscv/Kconfig | 2 +- + arch/riscv/include/asm/pgtable.h | 8 +- + arch/s390/Kconfig | 2 +- + arch/s390/include/asm/pgtable.h | 2 +- + arch/sparc/include/asm/pgtable_64.h | 4 - + arch/x86/Kconfig | 2 +- + arch/x86/include/asm/pgtable.h | 2 +- + drivers/base/arch_numa.c | 4 - + fs/proc/task_mmu.c | 3 +- + include/asm-generic/fixmap.h | 4 +- + include/asm-generic/getorder.h | 4 +- + include/asm-generic/memory_model.h | 6 +- + include/asm-generic/mmu.h | 2 +- + include/asm-generic/pgtable-nop4d.h | 4 +- + include/asm-generic/pgtable-nopmd.h | 4 +- + include/asm-generic/pgtable-nopud.h | 4 +- + include/linux/cpuset.h | 4 +- + include/linux/damon.h | 15 +- + include/linux/gfp_types.h | 5 +- + include/linux/huge_mm.h | 2 +- + include/linux/hugetlb.h | 51 +- + include/linux/hugetlb_cgroup.h | 2 +- + include/linux/leafops.h | 28 +- + include/linux/lockdep.h | 3 + + include/linux/lockdep_types.h | 3 +- + include/linux/maple_tree.h | 10 +- + include/linux/migrate.h | 6 +- + include/linux/migrate_mode.h | 1 + + include/linux/mm.h | 46 +- + include/linux/mm_inline.h | 4 +- + include/linux/mm_types.h | 1 + + include/linux/mmzone.h | 43 +- + include/linux/oom.h | 2 +- + include/linux/page-flags.h | 14 - + include/linux/page_owner.h | 7 +- + include/linux/percpu-refcount.h | 5 +- + include/linux/pfn.h | 2 +- + include/linux/pgtable.h | 39 +- + include/linux/radix-tree.h | 5 +- + include/linux/sched.h | 1 + + include/linux/swap.h | 9 +- + include/linux/swapops.h | 6 +- + include/linux/vmalloc.h | 4 +- + include/linux/vmpressure.h | 48 +- + include/linux/writeback.h | 2 +- + include/trace/events/damon.h | 8 +- + include/trace/events/migrate.h | 11 +- + include/trace/events/pagemap.h | 8 + + include/uapi/linux/mempolicy.h | 2 +- + kernel/cgroup/cpuset.c | 2 +- + kernel/locking/lockdep.c | 58 ++- + lib/Kconfig.debug | 28 -- + lib/Makefile | 1 - + lib/maple_tree.c | 353 +++++++++----- + lib/percpu-refcount.c | 2 +- + lib/test_hmm.c | 2 +- + mm/Kconfig | 2 +- + mm/Kconfig.debug | 28 ++ + mm/Makefile | 1 + + {lib => mm}/alloc_tag.c | 0 + mm/cma.c | 3 +- + mm/damon/core.c | 278 ++++++----- + mm/damon/paddr.c | 9 +- + mm/damon/stat.c | 2 +- + mm/damon/sysfs-schemes.c | 28 +- + mm/damon/sysfs.c | 125 +++-- + mm/damon/tests/core-kunit.h | 144 +++++- + mm/damon/vaddr.c | 12 +- + mm/debug_vm_pgtable.c | 12 +- + mm/filemap.c | 43 +- + mm/hmm.c | 4 +- + mm/huge_memory.c | 17 +- + mm/hugetlb.c | 539 +++++++++++---------- + mm/hugetlb_cma.c | 140 ++++-- + mm/hugetlb_cma.h | 8 +- + mm/hugetlb_vmemmap.c | 137 ++---- + mm/hugetlb_vmemmap.h | 5 - + mm/internal.h | 29 +- + mm/kasan/hw_tags.c | 2 +- + mm/kmemleak.c | 91 +++- + mm/ksm.c | 17 +- + mm/madvise.c | 6 +- + mm/memcontrol-v1.c | 300 ++++++++++++ + mm/memcontrol-v1.h | 6 - + mm/memcontrol.c | 12 +- + mm/memory-failure.c | 108 ++++- + mm/memory.c | 11 +- + mm/mempolicy.c | 21 +- + mm/migrate.c | 68 ++- + mm/migrate_device.c | 34 +- + mm/mincore.c | 11 +- + mm/mlock.c | 2 +- + mm/mm_init.c | 86 ++-- + mm/mmzone.c | 5 +- + mm/mprotect.c | 21 +- + mm/page_alloc.c | 81 ++-- + mm/page_owner.c | 311 +++++++++--- + mm/percpu-vm.c | 48 +- + mm/percpu.c | 20 +- + mm/rmap.c | 9 +- + mm/shmem.c | 13 +- + mm/show_mem.c | 9 +- + mm/slab.h | 2 +- + mm/sparse-vmemmap.c | 26 +- + mm/sparse.c | 50 +- + mm/swap.c | 21 +- + mm/swap.h | 4 +- + mm/swap_state.c | 2 - + mm/swapfile.c | 16 +- + mm/userfaultfd.c | 10 +- + mm/vmalloc.c | 31 +- + mm/vmpressure.c | 301 +----------- + mm/vmscan.c | 18 +- + mm/vmstat.c | 2 +- + mm/zsmalloc.c | 230 +++++++-- + mm/zswap.c | 2 +- + samples/damon/Kconfig | 2 +- + samples/damon/mtier.c | 14 +- + samples/damon/prcl.c | 11 +- + samples/damon/wsse.c | 11 +- + scripts/gdb/linux/page_owner.py | 4 +- + tools/mm/.gitignore | 1 + + tools/mm/Makefile | 4 +- + tools/mm/page_owner_filter.c | 310 ++++++++++++ + tools/mm/page_owner_sort.c | 87 +++- + tools/testing/radix-tree/maple.c | 4 +- + tools/testing/selftests/damon/Makefile | 1 + + tools/testing/selftests/damon/_damon_sysfs.py | 23 +- + .../selftests/damon/damos_apply_interval.py | 2 +- + tools/testing/selftests/damon/damos_quota_goal.py | 2 +- + .../testing/selftests/damon/damos_tried_regions.py | 4 +- + .../selftests/damon/drgn_dump_damon_status.py | 3 +- + tools/testing/selftests/damon/sysfs.py | 35 +- + tools/testing/selftests/damon/sysfs.sh | 77 ++- + tools/testing/selftests/damon/sysfs_refresh.py | 75 +++ + ..._update_schemes_tried_regions_wss_estimation.py | 2 +- + tools/testing/selftests/mm/Makefile | 4 + + tools/testing/selftests/mm/hwpoison-panic.sh | 255 ++++++++++ + tools/testing/selftests/mm/pkey-helpers.h | 4 +- + tools/testing/selftests/mm/pkey-powerpc.h | 2 +- + tools/testing/selftests/mm/pkey_sighandler_tests.c | 107 ++-- + tools/testing/selftests/mm/pkey_util.c | 90 ++++ + tools/testing/selftests/mm/protection_keys.c | 99 +--- + tools/testing/selftests/mm/uffd-common.c | 9 +- + tools/testing/vma/include/dup.h | 2 +- + 164 files changed, 4022 insertions(+), 2014 deletions(-) + rename {lib => mm}/alloc_tag.c (100%) + create mode 100644 tools/mm/page_owner_filter.c + create mode 100755 tools/testing/selftests/damon/sysfs_refresh.py + create mode 100755 tools/testing/selftests/mm/hwpoison-panic.sh +$ git am -3 ../patches/0001-mm-Fix-up-build-for-uninitialised-pte.patch +Applying: mm: Fix up build for uninitialised pte +Using index info to reconstruct a base tree... +M mm/khugepaged.c +Falling back to patching base and 3-way merge... +Auto-merging mm/khugepaged.c +No changes -- Patch already applied. +Merging mm-nonmm-unstable/mm-nonmm-unstable (a1e4a999fa76f pps: don't allow PPS_KC_BIND on removed devices) +$ git merge -m Merge branch 'mm-nonmm-unstable' of https://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm mm-nonmm-unstable/mm-nonmm-unstable +Auto-merging arch/riscv/include/asm/pgtable.h +Auto-merging arch/riscv/kernel/entry.S +Auto-merging drivers/pps/clients/pps-gpio.c +Auto-merging lib/Kconfig.debug +Auto-merging mm/sparse-vmemmap.c +Merge made by the 'ort' strategy. + Documentation/accounting/delay-accounting.rst | 43 ++++ + arch/riscv/include/asm/cacheflush.h | 15 +- + arch/riscv/include/asm/pgtable.h | 13 +- + arch/riscv/kernel/entry.S | 11 +- + arch/riscv/mm/init.c | 6 + + drivers/media/v4l2-core/v4l2-vp9.c | 30 +-- + drivers/pps/clients/pps-gpio.c | 39 ++-- + drivers/pps/kc.c | 10 + + drivers/pps/pps.c | 10 +- + fs/fat/misc.c | 4 + + fs/ocfs2/cluster/heartbeat.c | 43 ++-- + fs/ocfs2/cluster/nodemanager.c | 19 +- + fs/ocfs2/cluster/nodemanager.h | 2 + + fs/ocfs2/dir.c | 20 +- + fs/ocfs2/dlm/dlmmaster.c | 6 + + fs/ocfs2/dlm/dlmrecovery.c | 9 + + fs/ocfs2/namei.c | 17 +- + fs/ramfs/file-nommu.c | 3 + + include/linux/nmi.h | 4 +- + include/linux/pps_kernel.h | 1 + + ipc/ipc_sysctl.c | 2 +- + kernel/panic.c | 7 +- + kernel/params.c | 4 +- + kernel/resource.c | 2 +- + kernel/signal.c | 51 ++--- + kernel/watchdog.c | 16 +- + lib/Kconfig | 6 - + lib/Kconfig.debug | 26 +++ + lib/idr.c | 55 +++-- + lib/math/tests/Makefile | 1 + + lib/math/tests/polynomial_kunit.c | 270 ++++++++++++++++++++++++ + lib/random32.c | 184 +---------------- + lib/string.c | 3 +- + lib/test_ida.c | 14 ++ + lib/tests/Makefile | 1 + + lib/tests/random32_kunit.c | 182 ++++++++++++++++ + lib/xz/xz_dec_bcj.c | 2 +- + lib/xz/xz_dec_lzma2.c | 63 +++--- + lib/xz/xz_dec_stream.c | 4 +- + lib/xz/xz_lzma2.h | 2 +- + mm/sparse-vmemmap.c | 8 + + tools/accounting/delaytop.c | 286 ++++++++++++++++++++++---- + tools/accounting/getdelays.c | 5 + + tools/include/linux/compiler.h | 2 +- + 44 files changed, 1099 insertions(+), 402 deletions(-) + create mode 100644 lib/math/tests/polynomial_kunit.c + create mode 100644 lib/tests/random32_kunit.c +Merging kbuild/kbuild-for-next (078f4b01a0cff kbuild: remove srctree path from CHECK output) +$ git merge -m Merge branch 'kbuild-for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/kbuild/linux.git kbuild/kbuild-for-next +Auto-merging Makefile +Merge made by the 'ort' strategy. + Makefile | 6 ++++ + arch/arm64/include/asm/module.lds.h | 13 ------- + scripts/Makefile.build | 4 +-- + scripts/kconfig/menu.c | 22 +++++++++++- + scripts/kconfig/tests/warn_dead_default/Kconfig | 40 ++++++++++++++++++++++ + .../kconfig/tests/warn_dead_default/__init__.py | 8 +++++ + .../tests/warn_dead_default/expected_stderr | 4 +++ + 7 files changed, 81 insertions(+), 16 deletions(-) + create mode 100644 scripts/kconfig/tests/warn_dead_default/Kconfig + create mode 100644 scripts/kconfig/tests/warn_dead_default/__init__.py + create mode 100644 scripts/kconfig/tests/warn_dead_default/expected_stderr +Merging clang-format/clang-format (8f0b4cce4481f Linux 6.19-rc1) +$ git merge -m Merge branch 'clang-format' of https://github.com/ojeda/linux.git clang-format/clang-format +Already up to date. +Merging perf/perf-tools-next (ca0e19074bd6a perf test: Add Arm CoreSight callchain test) +$ git merge -m Merge branch 'perf-tools-next' of https://git.kernel.org/pub/scm/linux/kernel/git/perf/perf-tools-next.git perf/perf-tools-next +Merge made by the 'ort' strategy. + tools/perf/Documentation/perf-test.txt | 6 +- + tools/perf/Makefile.perf | 9 +- + tools/perf/arch/arm/util/cs-etm.c | 10 +- + tools/perf/arch/arm64/util/arm-spe.c | 8 +- + tools/perf/arch/arm64/util/hisi-ptt.c | 2 +- + tools/perf/arch/x86/tests/hybrid.c | 22 +- + tools/perf/arch/x86/tests/topdown.c | 4 +- + tools/perf/arch/x86/util/auxtrace.c | 2 +- + tools/perf/arch/x86/util/intel-bts.c | 6 +- + tools/perf/arch/x86/util/intel-pt.c | 9 +- + tools/perf/arch/x86/util/iostat.c | 14 +- + tools/perf/bench/evlist-open-close.c | 29 +- + tools/perf/builtin-annotate.c | 7 +- + tools/perf/builtin-ftrace.c | 14 +- + tools/perf/builtin-inject.c | 9 +- + tools/perf/builtin-kvm.c | 123 +- + tools/perf/builtin-kwork.c | 8 +- + tools/perf/builtin-lock.c | 4 +- + tools/perf/builtin-record.c | 95 +- + tools/perf/builtin-report.c | 6 +- + tools/perf/builtin-sched.c | 46 +- + tools/perf/builtin-script.c | 16 +- + tools/perf/builtin-stat.c | 103 +- + tools/perf/builtin-timechart.c | 212 +- + tools/perf/builtin-top.c | 104 +- + tools/perf/builtin-trace.c | 65 +- + .../perf/pmu-events/arch/x86/arrowlake/cache.json | 30 +- + .../arch/x86/arrowlake/floating-point.json | 45 + + .../perf/pmu-events/arch/x86/arrowlake/memory.json | 18 + + .../pmu-events/arch/x86/arrowlake/pipeline.json | 129 +- + .../pmu-events/arch/x86/emeraldrapids/cache.json | 9 + + .../x86/graniterapids/uncore-interconnect.json | 10 + + .../arch/x86/graniterapids/uncore-memory.json | 2 +- + .../perf/pmu-events/arch/x86/lunarlake/cache.json | 2 +- + .../pmu-events/arch/x86/lunarlake/pipeline.json | 27 +- + .../arch/x86/lunarlake/uncore-memory.json | 208 +- + tools/perf/pmu-events/arch/x86/mapfile.csv | 12 +- + .../pmu-events/arch/x86/pantherlake/counter.json | 5 + + .../pmu-events/arch/x86/pantherlake/pipeline.json | 29 +- + .../arch/x86/pantherlake/uncore-interconnect.json | 10 + + .../arch/x86/pantherlake/uncore-memory.json | 221 +- + tools/perf/python/perf.pyi | 672 ++++++ + tools/perf/python/perf_live.py | 59 + + tools/perf/scripts/python/arm-cs-trace-disasm.py | 9 +- + tools/perf/scripts/python/powerpc-hcalls.py | 137 +- + tools/perf/tests/backward-ring-buffer.c | 26 +- + tools/perf/tests/builtin-test.c | 182 +- + tools/perf/tests/code-reading.c | 14 +- + tools/perf/tests/event-times.c | 6 +- + tools/perf/tests/event_update.c | 4 +- + tools/perf/tests/evsel-roundtrip-name.c | 8 +- + tools/perf/tests/evsel-tp-sched.c | 4 +- + tools/perf/tests/expand-cgroup.c | 15 +- + tools/perf/tests/hists_cumulate.c | 2 +- + tools/perf/tests/hists_filter.c | 2 +- + tools/perf/tests/hists_link.c | 2 +- + tools/perf/tests/hists_output.c | 2 +- + tools/perf/tests/hwmon_pmu.c | 7 +- + tools/perf/tests/keep-tracking.c | 10 +- + tools/perf/tests/mmap-basic.c | 24 +- + tools/perf/tests/openat-syscall-all-cpus.c | 6 +- + tools/perf/tests/openat-syscall-tp-fields.c | 26 +- + tools/perf/tests/openat-syscall.c | 6 +- + tools/perf/tests/parse-events.c | 150 +- + tools/perf/tests/parse-metric.c | 11 +- + tools/perf/tests/parse-no-sample-id-all.c | 2 +- + tools/perf/tests/perf-record.c | 38 +- + tools/perf/tests/perf-time-to-tsc.c | 12 +- + tools/perf/tests/pfm.c | 12 +- + tools/perf/tests/pmu-events.c | 21 +- + tools/perf/tests/pmu.c | 4 +- + tools/perf/tests/sample-parsing.c | 45 +- + tools/perf/tests/shell/coresight/callchain.sh | 172 ++ + .../shell/coresight/test_arm_coresight_disasm.sh | 4 +- + tools/perf/tests/shell/inject_aslr.sh | 10 +- + tools/perf/tests/shell/jitdump-python.sh | 69 +- + tools/perf/tests/shell/kvm.sh | 71 +- + .../perf/tests/shell/lib/perf_metric_validation.py | 11 +- + tools/perf/tests/shell/lib/perf_record.sh | 58 + + tools/perf/tests/shell/lib/setup_python.sh | 13 + + tools/perf/tests/shell/lock_contention.sh | 32 +- + tools/perf/tests/shell/pipe_test.sh | 4 +- + tools/perf/tests/shell/record.sh | 173 +- + tools/perf/tests/shell/record_lbr.sh | 50 +- + tools/perf/tests/shell/record_offcpu.sh | 14 +- + tools/perf/tests/shell/stat_all_metrics.sh | 77 +- + tools/perf/tests/shell/stat_bpf_counters.sh | 28 +- + tools/perf/tests/shell/stat_metrics_values.sh | 9 +- + tools/perf/tests/shell/test_brstack.sh | 99 +- + tools/perf/tests/shell/timechart.sh | 24 +- + tools/perf/tests/shell/trace_record_replay.sh | 38 +- + tools/perf/tests/sw-clock.c | 20 +- + tools/perf/tests/switch-tracking.c | 11 +- + tools/perf/tests/task-exit.c | 20 +- + tools/perf/tests/tests.h | 1 + + tools/perf/tests/time-utils-test.c | 14 +- + tools/perf/tests/tool_pmu.c | 7 +- + tools/perf/tests/topology.c | 4 +- + tools/perf/tests/uncore-event-sorting.c | 6 +- + tools/perf/tests/workloads/Build | 2 + + tools/perf/tests/workloads/callchain.c | 33 + + tools/perf/tests/workloads/noploop.c | 17 +- + tools/perf/tests/workloads/thloop.c | 16 +- + tools/perf/ui/browsers/annotate.c | 2 +- + tools/perf/ui/browsers/hists.c | 22 +- + tools/perf/util/Build | 1 - + tools/perf/util/amd-sample-raw.c | 2 +- + tools/perf/util/annotate-data.c | 2 +- + tools/perf/util/annotate.c | 10 +- + tools/perf/util/aslr.c | 1 + + tools/perf/util/auxtrace.c | 14 +- + tools/perf/util/block-info.c | 4 +- + tools/perf/util/bpf_counter.c | 2 +- + tools/perf/util/bpf_counter_cgroup.c | 14 +- + tools/perf/util/bpf_ftrace.c | 9 +- + tools/perf/util/bpf_lock_contention.c | 12 +- + tools/perf/util/bpf_off_cpu.c | 44 +- + tools/perf/util/bpf_trace_augment.c | 8 +- + tools/perf/util/cgroup.c | 26 +- + tools/perf/util/cs-etm.c | 382 +-- + tools/perf/util/data-convert-bt.c | 2 +- + tools/perf/util/data.c | 27 +- + tools/perf/util/data.h | 4 +- + tools/perf/util/evlist.c | 494 ++-- + tools/perf/util/evlist.h | 273 ++- + tools/perf/util/evsel.c | 39 +- + tools/perf/util/evsel.h | 42 +- + tools/perf/util/expr.c | 2 +- + tools/perf/util/header.c | 69 +- + tools/perf/util/header.h | 2 +- + tools/perf/util/intel-pt.c | 8 +- + tools/perf/util/intel-tpebs.c | 7 +- + tools/perf/util/iostat.c | 2 +- + tools/perf/util/iostat.h | 2 +- + tools/perf/util/kvm-stat-arch/book3s_hv_exits.h | 4 +- + tools/perf/util/kvm-stat-arch/kvm-stat-powerpc.c | 31 +- + tools/perf/util/kvm-stat-arch/kvm-stat-x86.c | 35 +- + tools/perf/util/kvm-stat.c | 41 + + tools/perf/util/kvm-stat.h | 18 +- + tools/perf/util/map.h | 9 +- + tools/perf/util/metricgroup.c | 38 +- + tools/perf/util/metricgroup.h | 4 +- + tools/perf/util/parse-events.c | 40 +- + tools/perf/util/parse-events.h | 17 +- + tools/perf/util/parse-events.y | 2 +- + tools/perf/util/perf_api_probe.c | 20 +- + tools/perf/util/pfm.c | 4 +- + tools/perf/util/pmu.c | 6 +- + tools/perf/util/pmus.c | 2 + + tools/perf/util/print-events.c | 2 +- + tools/perf/util/probe-file.c | 2 + + tools/perf/util/python.c | 2503 +++++++++++++++++--- + tools/perf/util/record.c | 11 +- + tools/perf/util/s390-sample-raw.c | 22 +- + tools/perf/util/sample-raw.c | 4 +- + tools/perf/util/sample.c | 19 +- + tools/perf/util/session.c | 69 +- + tools/perf/util/session.h | 2 + + tools/perf/util/setup.py | 5 + + tools/perf/util/sideband_evlist.c | 40 +- + tools/perf/util/sort.c | 2 +- + tools/perf/util/stat-display.c | 6 +- + tools/perf/util/stat-shadow.c | 24 +- + tools/perf/util/stat.c | 22 +- + tools/perf/util/stream.c | 4 +- + tools/perf/util/symbol-elf.c | 4 + + tools/perf/util/symbol.c | 4 +- + tools/perf/util/symbol.h | 12 + + tools/perf/util/synthetic-events.c | 11 +- + tools/perf/util/time-utils.c | 12 +- + tools/perf/util/top.c | 4 +- + 171 files changed, 6753 insertions(+), 2145 deletions(-) + create mode 100644 tools/perf/pmu-events/arch/x86/pantherlake/uncore-interconnect.json + create mode 100644 tools/perf/python/perf.pyi + create mode 100755 tools/perf/python/perf_live.py + create mode 100755 tools/perf/tests/shell/coresight/callchain.sh + create mode 100644 tools/perf/tests/shell/lib/perf_record.sh + create mode 100644 tools/perf/tests/workloads/callchain.c +Merging compiler-attributes/compiler-attributes (8f0b4cce4481f Linux 6.19-rc1) +$ git merge -m Merge branch 'compiler-attributes' of https://github.com/ojeda/linux.git compiler-attributes/compiler-attributes +Already up to date. +Merging dma-mapping/dma-mapping-for-next (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'dma-mapping-for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/mszyprowski/linux.git dma-mapping/dma-mapping-for-next +Already up to date. +Merging asm-generic/master (adbbd9714f805 scripts: headers_install.sh: Remove config leak ignore machinery) +$ git merge -m Merge branch 'master' of https://git.kernel.org/pub/scm/linux/kernel/git/arnd/asm-generic asm-generic/master +Already up to date. +Merging alpha/alpha-next (d58041d2c63e0 MAINTAINERS: Add Magnus Lindholm as maintainer for alpha port) +$ git merge -m Merge branch 'alpha-next' of https://git.kernel.org/pub/scm/linux/kernel/git/mattst88/alpha.git alpha/alpha-next +Already up to date. +Merging arm/for-next (75ca78c7d982a Merge branches 'fixes' and 'misc' into for-next) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/rmk/linux.git arm/for-next +Merge made by the 'ort' strategy. + arch/arm/probes/kprobes/test-core.c | 14 +++----------- + 1 file changed, 3 insertions(+), 11 deletions(-) +Merging arm64/for-next/core (36fa5ffa60344 arm64: mm: Defer read-only remap of data/bss linear alias) +$ git merge -m Merge branch 'for-next/core' of https://git.kernel.org/pub/scm/linux/kernel/git/arm64/linux arm64/for-next/core +Already up to date. +Merging arm-perf/for-next/perf (5936245125f78 perf/arm-cmn: Fix DVM node events) +$ git merge -m Merge branch 'for-next/perf' of https://git.kernel.org/pub/scm/linux/kernel/git/will/linux.git arm-perf/for-next/perf +Already up to date. +Merging arm-soc/for-next (972ea78305bdd soc: document merges) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/soc/soc.git arm-soc/for-next +Merge made by the 'ort' strategy. + arch/arm/arm-soc-for-next-contents.txt | 200 +++++++++++++++++++++++++++++++++ + 1 file changed, 200 insertions(+) + create mode 100644 arch/arm/arm-soc-for-next-contents.txt +Merging amlogic/for-next (4336e970ec689 Merge branch 'v7.3/arm64-dt' into for-next) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/amlogic/linux.git amlogic/for-next +Merge made by the 'ort' strategy. + .../soc/amlogic/amlogic,meson-gx-clk-measure.yaml | 2 + + arch/arm64/boot/dts/amlogic/amlogic-a9.dtsi | 140 +++++++++++ + arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi | 5 + + arch/arm64/boot/dts/amlogic/meson-a1.dtsi | 5 + + drivers/soc/amlogic/meson-clk-measure.c | 272 +++++++++++++++++++++ + 5 files changed, 424 insertions(+) +Merging asahi-soc/asahi-soc/for-next (a23ce286383c9 Merge branch 'apple-soc/drivers-7.3' into asahi-soc/for-next) +$ git merge -m Merge branch 'asahi-soc/for-next' of https://github.com/AsahiLinux/linux.git asahi-soc/asahi-soc/for-next +Merge made by the 'ort' strategy. + arch/arm64/boot/dts/apple/t8122.dtsi | 30 +++++++++++++++--------------- + drivers/soc/apple/sart.c | 9 +++++++-- + 2 files changed, 22 insertions(+), 17 deletions(-) +Merging at91/at91-next (6d892f8d5cd9f Merge branch 'clk-microchip-fixes' into at91-next) +$ git merge -m Merge branch 'at91-next' of https://git.kernel.org/pub/scm/linux/kernel/git/at91/linux.git at91/at91-next +Merge made by the 'ort' strategy. +Merging bmc/for-next (40ae2e16214ad Merge branch 'nuvoton/arm/dt' into for-next) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/bmc/linux.git bmc/for-next +Merge made by the 'ort' strategy. + arch/arm/boot/dts/nuvoton/nuvoton-common-npcm7xx.dtsi | 9 +++------ + 1 file changed, 3 insertions(+), 6 deletions(-) +Merging broadcom/next (f705b0c80bab1 arm: dts: bcm2711: Fix typo in gpio-line-names) +$ git merge -m Merge branch 'next' of https://github.com/Broadcom/stblinux.git broadcom/next +Merge made by the 'ort' strategy. +Merging cix/for-next (a0cffbd8878c5 Merge remote-tracking branch 'cix/dt' into for-next) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/peter.chen/cix.git cix/for-next +Merge made by the 'ort' strategy. +Merging davinci/davinci/for-next (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'davinci/for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux.git davinci/davinci/for-next +Already up to date. +Merging drivers-memory/for-next (1527acf2295cf memory: stm32_omm: initialize ret in stm32_omm_set_amcr) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux-mem-ctrl.git drivers-memory/for-next +Auto-merging drivers/memory/stm32_omm.c +Merge made by the 'ort' strategy. + drivers/memory/stm32_omm.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) +Merging fsl/soc_fsl (c99d8ac7b355c bus: fsl-mc: wait for the MC firmware to complete its boot) +$ git merge -m Merge branch 'soc_fsl' of https://git.kernel.org/pub/scm/linux/kernel/git/chleroy/linux.git fsl/soc_fsl +Auto-merging drivers/irqchip/Kconfig +Auto-merging drivers/irqchip/Makefile +Auto-merging drivers/irqchip/irq-gic-its-msi-parent.c +Auto-merging drivers/soc/fsl/qe/qe.c +Auto-merging include/linux/fsl/mc.h +Merge made by the 'ort' strategy. +Merging imx-mxs/for-next (d0c222c2e2ce5 Merge branches 'imx/dt', 'imx/dt64' and 'imx/soc' into for-next) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/frank.li/linux.git imx-mxs/for-next +Merge made by the 'ort' strategy. + Documentation/devicetree/bindings/arm/fsl.yaml | 12 + + .../bindings/soc/imx/fsl,imx93-media-blk-ctrl.yaml | 39 + + arch/arm/boot/dts/nxp/imx/imx7d-pico.dtsi | 23 + + arch/arm/mach-imx/avic.c | 2 + + arch/arm/mach-imx/common.h | 7 - + arch/arm/mach-imx/src.c | 3 + + arch/arm/mach-imx/system.c | 8 - + arch/arm64/boot/dts/freescale/Makefile | 16 + + .../boot/dts/freescale/imx8mm-var-som-symphony.dts | 12 + + arch/arm64/boot/dts/freescale/imx8mp-ab2.dts | 4 + + .../boot/dts/freescale/imx8mp-evk-flexcan2.dtso | 15 + + arch/arm64/boot/dts/freescale/imx8mp-evk.dts | 23 +- + arch/arm64/boot/dts/freescale/imx8mp-frdm.dts | 2 + + ...imx8mp-tqma8mpqs-mb-smarc-2-lvds-g133han01.dtso | 81 ++ + ...8mp-tqma8mpqs-mb-smarc-2-lvds0-tm070jvhg33.dtso | 43 + + ...8mp-tqma8mpqs-mb-smarc-2-lvds1-tm070jvhg33.dtso | 43 + + .../dts/freescale/imx8mp-tqma8mpqs-mb-smarc-2.dts | 380 +++++++ + .../arm64/boot/dts/freescale/imx8mp-tqma8mpqs.dtsi | 1178 ++++++++++++++++++++ + .../boot/dts/freescale/imx8mp-var-som-symphony.dts | 389 ++++++- + arch/arm64/boot/dts/freescale/imx8mp-var-som.dtsi | 12 +- + arch/arm64/boot/dts/freescale/imx8mq-evk.dts | 22 + + .../freescale/imx93-11x11-evk-dy1212w-4856.dtso | 81 ++ + .../boot/dts/freescale/imx93-kontron-osm-s.dtsi | 4 +- + arch/arm64/boot/dts/freescale/imx93.dtsi | 39 +- + arch/arm64/boot/dts/freescale/imx94.dtsi | 5 +- + arch/arm64/boot/dts/freescale/imx943.dtsi | 5 +- + .../boot/dts/freescale/imx95-toradex-smarc.dtsi | 1 + + drivers/firmware/imx/sm-misc.c | 3 + + 28 files changed, 2426 insertions(+), 26 deletions(-) + create mode 100644 arch/arm64/boot/dts/freescale/imx8mp-evk-flexcan2.dtso + create mode 100644 arch/arm64/boot/dts/freescale/imx8mp-tqma8mpqs-mb-smarc-2-lvds-g133han01.dtso + create mode 100644 arch/arm64/boot/dts/freescale/imx8mp-tqma8mpqs-mb-smarc-2-lvds0-tm070jvhg33.dtso + create mode 100644 arch/arm64/boot/dts/freescale/imx8mp-tqma8mpqs-mb-smarc-2-lvds1-tm070jvhg33.dtso + create mode 100644 arch/arm64/boot/dts/freescale/imx8mp-tqma8mpqs-mb-smarc-2.dts + create mode 100644 arch/arm64/boot/dts/freescale/imx8mp-tqma8mpqs.dtsi + create mode 100644 arch/arm64/boot/dts/freescale/imx93-11x11-evk-dy1212w-4856.dtso +Merging mediatek/for-next (eb3e990db3569 arm64: dts: mediatek: mt8188-geralt: Add supply for SPI NOR flash) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/mediatek/linux.git mediatek/for-next +Merge made by the 'ort' strategy. + arch/arm64/boot/dts/mediatek/mt8183-kukui.dtsi | 2 + + arch/arm64/boot/dts/mediatek/mt8186.dtsi | 110 ++++++++++++------------ + arch/arm64/boot/dts/mediatek/mt8188-geralt.dtsi | 1 + + 3 files changed, 58 insertions(+), 55 deletions(-) +Merging mvebu/for-next (3ea0dabfa4500 Merge branch 'mvebu/dt64' into mvebu/for-next) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/gclement/mvebu.git mvebu/for-next +Merge made by the 'ort' strategy. + arch/arm64/boot/dts/marvell/armada-37xx.dtsi | 1 + + 1 file changed, 1 insertion(+) +Merging omap/for-next (f338cc54e10ab Merge branch 'omap-for-v7.3/drivers' into tmp/omap-next-20260629.131449) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/khilman/linux-omap.git omap/for-next +Merge made by the 'ort' strategy. + arch/arm/boot/dts/ti/davinci/da850-lcdk.dts | 2 +- + arch/arm/boot/dts/ti/omap/Makefile | 4 + + .../dts/ti/omap/am335x-icev2-prueth-overlay.dtso | 156 +++++++++++++++++++++ + arch/arm/boot/dts/ti/omap/am33xx-l4.dtsi | 13 +- + arch/arm/boot/dts/ti/omap/am3517-evm.dts | 2 +- + arch/arm/boot/dts/ti/omap/am4372.dtsi | 11 ++ + arch/arm/boot/dts/ti/omap/am437x-idk-evm.dts | 103 +++++++++++++- + arch/arm/boot/dts/ti/omap/am437x-l4.dtsi | 2 +- + arch/arm/boot/dts/ti/omap/am57-pruss.dtsi | 11 ++ + arch/arm/boot/dts/ti/omap/am571x-idk.dts | 8 +- + arch/arm/boot/dts/ti/omap/am5729-beagleboneai.dts | 2 +- + arch/arm/boot/dts/ti/omap/am572x-idk.dts | 10 +- + arch/arm/boot/dts/ti/omap/am574x-idk.dts | 10 +- + arch/arm/boot/dts/ti/omap/am57xx-idk-common.dtsi | 61 ++++++++ + arch/arm/boot/dts/ti/omap/dm814x.dtsi | 2 +- + arch/arm/boot/dts/ti/omap/dm816x.dtsi | 2 +- + arch/arm/boot/dts/ti/omap/dra7-l4.dtsi | 2 +- + arch/arm/boot/dts/ti/omap/omap2430.dtsi | 2 +- + arch/arm/boot/dts/ti/omap/omap3.dtsi | 2 +- + arch/arm/boot/dts/ti/omap/omap4-l4.dtsi | 2 +- + arch/arm/boot/dts/ti/omap/omap4-panda-es.dts | 2 +- + arch/arm/boot/dts/ti/omap/omap4-var-som-om44.dtsi | 14 ++ + arch/arm/boot/dts/ti/omap/omap5-l4.dtsi | 6 +- + arch/arm/configs/multi_v7_defconfig | 2 + + arch/arm/configs/omap2plus_defconfig | 11 ++ + arch/arm/mach-omap2/omap_hwmod.c | 26 ++-- + arch/arm/mach-omap2/sleep44xx.S | 5 +- + 27 files changed, 438 insertions(+), 35 deletions(-) + create mode 100644 arch/arm/boot/dts/ti/omap/am335x-icev2-prueth-overlay.dtso +Merging qcom/for-next (696bef37f7f22 Merge branches 'arm32-for-7.3', 'arm64-defconfig-for-7.3', 'arm64-fixes-for-7.2', 'arm64-for-7.3', 'clk-fixes-for-7.2' and 'drivers-for-7.3' into for-next) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/qcom/linux.git qcom/for-next +Auto-merging drivers/clk/qcom/dispcc-eliza.c +Merge made by the 'ort' strategy. + Documentation/devicetree/bindings/arm/qcom.yaml | 500 ++++--- + .../devicetree/bindings/soc/qcom/qcom,smd-rpm.yaml | 1 + + Documentation/devicetree/bindings/sram/sram.yaml | 1 + + arch/arm/boot/dts/qcom/qcom-msm8974pro-htc-m8.dts | 94 +- + arch/arm/boot/dts/qcom/qcom-sdx55-t55.dts | 2 - + .../boot/dts/qcom/qcom-sdx55-telit-fn980-tlb.dts | 2 - + arch/arm/boot/dts/qcom/qcom-sdx55.dtsi | 3 + + arch/arm64/boot/dts/qcom/Makefile | 3 +- + arch/arm64/boot/dts/qcom/glymur-crd.dtsi | 23 + + arch/arm64/boot/dts/qcom/glymur.dtsi | 509 ++++++- + arch/arm64/boot/dts/qcom/hamoa-iot-evk.dts | 16 + + arch/arm64/boot/dts/qcom/hamoa.dtsi | 101 +- + arch/arm64/boot/dts/qcom/ipq5018.dtsi | 10 + + arch/arm64/boot/dts/qcom/ipq5332.dtsi | 10 + + arch/arm64/boot/dts/qcom/ipq6018.dtsi | 10 + + arch/arm64/boot/dts/qcom/ipq9574.dtsi | 10 + + arch/arm64/boot/dts/qcom/kaanapali.dtsi | 48 + + arch/arm64/boot/dts/qcom/kodiak.dtsi | 15 + + arch/arm64/boot/dts/qcom/lemans-auto.dtsi | 104 -- + arch/arm64/boot/dts/qcom/lemans-evk.dts | 176 ++- + arch/arm64/boot/dts/qcom/lemans.dtsi | 14 + + arch/arm64/boot/dts/qcom/milos.dtsi | 234 ++- + arch/arm64/boot/dts/qcom/monaco-arduino-monza.dts | 70 + + arch/arm64/boot/dts/qcom/monaco.dtsi | 30 + + arch/arm64/boot/dts/qcom/pm8010-kaanapali.dtsi | 20 + + arch/arm64/boot/dts/qcom/pm8010.dtsi | 20 + + arch/arm64/boot/dts/qcom/purwa.dtsi | 2 + + arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts | 3 +- + .../boot/dts/qcom/qcm6490-particle-tachyon.dts | 2 - + arch/arm64/boot/dts/qcom/qcm6490-shift-otter.dts | 3 +- + .../boot/dts/qcom/qcs6490-radxa-dragon-q6a.dts | 4 + + arch/arm64/boot/dts/qcom/qcs6490-rb3gen2.dts | 3 +- + .../dts/qcom/qcs6490-thundercomm-minipc-g1iot.dts | 4 + + .../boot/dts/qcom/qcs6490-thundercomm-rubikpi3.dts | 4 + + arch/arm64/boot/dts/qcom/qcs8550-rb5gen2.dts | 1573 ++++++++++++++++++++ + arch/arm64/boot/dts/qcom/sa8775p-ride-r3.dts | 17 - + arch/arm64/boot/dts/qcom/sa8775p-ride.dts | 17 - + .../boot/dts/qcom/sc7280-herobrine-lte-sku.dtsi | 2 + + .../boot/dts/qcom/sc8280xp-huawei-gaokun3.dts | 2 +- + arch/arm64/boot/dts/qcom/sc8280xp.dtsi | 4 +- + arch/arm64/boot/dts/qcom/sdm845-lg-common.dtsi | 3 - + arch/arm64/boot/dts/qcom/sdm845-mtp.dts | 2 - + .../arm64/boot/dts/qcom/sdm845-oneplus-common.dtsi | 2 - + .../boot/dts/qcom/sdm845-samsung-starqltechn.dts | 2 - + arch/arm64/boot/dts/qcom/sdm845-shift-axolotl.dts | 2 - + .../dts/qcom/sdm845-xiaomi-beryllium-common.dtsi | 2 - + arch/arm64/boot/dts/qcom/sdm845-xiaomi-polaris.dts | 2 - + arch/arm64/boot/dts/qcom/sdm845.dtsi | 3 + + .../dts/qcom/sdm850-huawei-matebook-e-2019.dts | 2 - + .../boot/dts/qcom/sdm850-lenovo-yoga-c630.dts | 2 - + arch/arm64/boot/dts/qcom/sdm850-samsung-w737.dts | 3 +- + arch/arm64/boot/dts/qcom/sm6115-fxtec-pro1x.dts | 2 +- + arch/arm64/boot/dts/qcom/sm6350.dtsi | 3 + + arch/arm64/boot/dts/qcom/sm7225-fairphone-fp4.dts | 3 +- + arch/arm64/boot/dts/qcom/sm7325-motorola-dubai.dts | 3 - + .../boot/dts/qcom/sm7325-nothing-spacewar.dts | 2 - + arch/arm64/boot/dts/qcom/sm8350-hdk.dts | 5 +- + .../dts/qcom/sm8350-microsoft-surface-duo2.dts | 2 - + arch/arm64/boot/dts/qcom/sm8350-mtp.dts | 2 - + .../boot/dts/qcom/sm8350-sony-xperia-sagami.dtsi | 3 +- + arch/arm64/boot/dts/qcom/sm8350.dtsi | 3 + + arch/arm64/boot/dts/qcom/sm8550-hdk.dts | 4 +- + arch/arm64/boot/dts/qcom/sm8550-qrd.dts | 4 +- + arch/arm64/boot/dts/qcom/sm8550.dtsi | 26 +- + arch/arm64/boot/dts/qcom/sm8650-hdk.dts | 3 +- + arch/arm64/boot/dts/qcom/sm8650-qrd.dts | 3 +- + arch/arm64/boot/dts/qcom/sm8650.dtsi | 7 +- + arch/arm64/boot/dts/qcom/sm8750.dtsi | 28 + + arch/arm64/boot/dts/qcom/talos.dtsi | 61 +- + arch/arm64/boot/dts/qcom/x1-crd.dtsi | 16 + + arch/arm64/configs/defconfig | 1 + + drivers/clk/qcom/dispcc-eliza.c | 2 +- + drivers/soc/qcom/Kconfig | 12 +- + drivers/soc/qcom/cmd-db.c | 2 +- + drivers/soc/qcom/socinfo.c | 1 + + drivers/soc/qcom/ubwc_config.c | 369 ++--- + include/dt-bindings/arm/qcom,ids.h | 1 + + include/linux/soc/qcom/ubwc.h | 83 +- + 78 files changed, 3444 insertions(+), 898 deletions(-) + delete mode 100644 arch/arm64/boot/dts/qcom/lemans-auto.dtsi + create mode 100644 arch/arm64/boot/dts/qcom/qcs8550-rb5gen2.dts + delete mode 100644 arch/arm64/boot/dts/qcom/sa8775p-ride-r3.dts + delete mode 100644 arch/arm64/boot/dts/qcom/sa8775p-ride.dts +Merging renesas/next (30d3a7db74710 Merge branch 'renesas-dts-for-v7.3' into renesas-next) +$ git merge -m Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/geert/renesas-devel.git renesas/next +Merge made by the 'ort' strategy. + arch/arm/boot/dts/renesas/r8a7740.dtsi | 12 +- + .../arm/boot/dts/renesas/r9a06g032-rzn1d400-eb.dts | 25 + + arch/arm/boot/dts/renesas/r9a06g032.dtsi | 84 ++++ + arch/arm/mach-shmobile/regulator-quirk-rcar-gen2.c | 10 +- + arch/arm/mach-shmobile/setup-rcar-gen2.c | 9 +- + arch/arm64/boot/dts/renesas/r8a774a1.dtsi | 2 +- + arch/arm64/boot/dts/renesas/r8a774b1.dtsi | 2 +- + arch/arm64/boot/dts/renesas/r8a774e1.dtsi | 2 +- + arch/arm64/boot/dts/renesas/r8a77960.dtsi | 2 +- + arch/arm64/boot/dts/renesas/r8a77961.dtsi | 2 +- + .../arm64/boot/dts/renesas/r8a77965-salvator-x.dts | 4 + + .../boot/dts/renesas/r8a77965-salvator-xs.dts | 4 + + arch/arm64/boot/dts/renesas/r8a77965-ulcb.dts | 4 + + arch/arm64/boot/dts/renesas/r8a77965.dtsi | 2 +- + arch/arm64/boot/dts/renesas/r8a77970.dtsi | 2 +- + arch/arm64/boot/dts/renesas/r8a77980.dtsi | 2 +- + arch/arm64/boot/dts/renesas/r8a77995.dtsi | 2 +- + arch/arm64/boot/dts/renesas/r8a779g0.dtsi | 17 + + arch/arm64/boot/dts/renesas/r8a78000.dtsi | 32 ++ + arch/arm64/boot/dts/renesas/r9a08g046.dtsi | 69 +++ + arch/arm64/boot/dts/renesas/r9a08g046l48-smarc.dts | 78 +++ + arch/arm64/boot/dts/renesas/r9a09g047.dtsi | 523 ++++++++++++++++++++- + arch/arm64/boot/dts/renesas/r9a09g047e57-smarc.dts | 114 ++++- + arch/arm64/boot/dts/renesas/r9a09g056.dtsi | 20 +- + arch/arm64/boot/dts/renesas/r9a09g057.dtsi | 20 +- + .../boot/dts/renesas/r9a09g077m44-rzt2h-evk.dts | 144 ++++-- + .../boot/dts/renesas/r9a09g087m44-rzn2h-evk.dts | 144 ++++-- + arch/arm64/boot/dts/renesas/rzg3e-smarc-som.dtsi | 44 ++ + .../boot/dts/renesas/rzt2h-n2h-evk-common.dtsi | 14 + + 29 files changed, 1270 insertions(+), 119 deletions(-) +Merging reset/reset/next (d373605cd5148 Merge tag 'reset-fixes-for-v7.0-2' into reset/next) +$ git merge -m Merge branch 'reset/next' of https://git.kernel.org/pub/scm/linux/kernel/git/pza/linux reset/reset/next +Already up to date. +Merging rockchip/for-next (b143af2d0da7b Merge branch 'v7.3-armsoc/dts64' into for-next) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/mmind/linux-rockchip.git rockchip/for-next +Merge made by the 'ort' strategy. + .../devicetree/bindings/arm/rockchip.yaml | 20 + + .../devicetree/bindings/vendor-prefixes.yaml | 2 + + arch/arm64/boot/dts/rockchip/Makefile | 4 + + arch/arm64/boot/dts/rockchip/px30-cobra.dtsi | 2 +- + arch/arm64/boot/dts/rockchip/px30-pp1516.dtsi | 2 +- + arch/arm64/boot/dts/rockchip/px30-ringneck.dtsi | 2 +- + .../boot/dts/rockchip/rk3528-hinlink-h28k.dts | 318 ++++++ + .../boot/dts/rockchip/rk3568-9tripod-x3568-v4.dts | 60 +- + arch/arm64/boot/dts/rockchip/rk3576.dtsi | 28 + + arch/arm64/boot/dts/rockchip/rk3588-base.dtsi | 4 +- + arch/arm64/boot/dts/rockchip/rk3588-extra.dtsi | 4 +- + arch/arm64/boot/dts/rockchip/rk3588-nanopc-t6.dtsi | 14 +- + .../boot/dts/rockchip/rk3588-vicharak-axon.dts | 917 +++++++++++++++ + .../boot/dts/rockchip/rk3588-vicharak-vaaman2.dts | 544 +++++++++ + .../boot/dts/rockchip/rk3588-youyeetoo-yy3588.dts | 1190 ++++++++++++++++++++ + drivers/clk/rockchip/clk-rk3588.c | 8 +- + 16 files changed, 3095 insertions(+), 24 deletions(-) + create mode 100644 arch/arm64/boot/dts/rockchip/rk3528-hinlink-h28k.dts + create mode 100644 arch/arm64/boot/dts/rockchip/rk3588-vicharak-axon.dts + create mode 100644 arch/arm64/boot/dts/rockchip/rk3588-vicharak-vaaman2.dts + create mode 100644 arch/arm64/boot/dts/rockchip/rk3588-youyeetoo-yy3588.dts +Merging samsung-krzk/for-next (5346a27fed4d8 Merge branches 'next/drivers', 'next/dt', 'next/dt64' and 'next/soc' into for-next) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux.git samsung-krzk/for-next +Merge made by the 'ort' strategy. + .../soc/samsung/samsung,exynos-sysreg.yaml | 1 + + arch/arm/boot/dts/samsung/exynos5250-manta.dts | 41 +++++++++++++++++++++- + arch/arm/mach-s3c/map-base.h | 2 +- + arch/arm64/boot/dts/exynos/exynosautov920.dtsi | 6 ++++ + drivers/soc/samsung/exynos-pmu.c | 4 +-- + include/linux/serial_s3c.h | 4 +-- + 6 files changed, 52 insertions(+), 6 deletions(-) +Merging scmi/for-linux-next (edb4db149ba0a Merge branch 'for-next/ffa/fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/sudeep.holla/linux) +$ git merge -m Merge branch 'for-linux-next' of https://git.kernel.org/pub/scm/linux/kernel/git/sudeep.holla/linux.git scmi/for-linux-next +Merge made by the 'ort' strategy. + drivers/firmware/arm_ffa/driver.c | 29 +++++++++++++++++++++-------- + 1 file changed, 21 insertions(+), 8 deletions(-) +Merging sophgo/for-next (f7337210bede6 Merge branch 'dt/riscv' into for-next) +$ git merge -m Merge branch 'for-next' of https://github.com/sophgo/linux.git sophgo/for-next +Merge made by the 'ort' strategy. +Merging sophgo-soc/soc-for-next (c8754c7deab4c soc: sophgo: cv1800: rtcsys: New driver (handling RTC only)) +$ git merge -m Merge branch 'soc-for-next' of https://github.com/sophgo/linux.git sophgo-soc/soc-for-next +Already up to date. +Merging spacemit/for-next (51ed53630915c clk: spacemit: k3: fix USB2 bus clock) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/spacemit/linux spacemit/for-next +Merge made by the 'ort' strategy. + drivers/clk/spacemit/ccu-k3.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) +Merging stm32/stm32-next (fba4a31a7f3b6 arm64: dts: st: Fix SAI addresses on stm32mp251) +$ git merge -m Merge branch 'stm32-next' of https://git.kernel.org/pub/scm/linux/kernel/git/atorgue/stm32.git stm32/stm32-next +Already up to date. +Merging sunxi/sunxi/for-next (5508b1e45bba2 Merge branches 'sunxi/config-for-7.3' and 'sunxi/dt-for-7.3' into sunxi/for-next) +$ git merge -m Merge branch 'sunxi/for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/sunxi/linux.git sunxi/sunxi/for-next +Auto-merging Documentation/devicetree/bindings/vendor-prefixes.yaml +Auto-merging arch/arm64/configs/defconfig +Merge made by the 'ort' strategy. + Documentation/devicetree/bindings/arm/sunxi.yaml | 6 + + .../input/allwinner,sun4i-a10-lradc-keys.yaml | 1 + + .../devicetree/bindings/vendor-prefixes.yaml | 2 + + arch/arm64/boot/dts/allwinner/Makefile | 1 + + arch/arm64/boot/dts/allwinner/sun50i-a100.dtsi | 10 ++ + .../allwinner/sun50i-a133-helperboard-core.dtsi | 197 +++++++++++++++++++++ + .../boot/dts/allwinner/sun50i-a133-helperboard.dts | 148 ++++++++++++++++ + arch/arm64/configs/defconfig | 1 + + 8 files changed, 366 insertions(+) + create mode 100644 arch/arm64/boot/dts/allwinner/sun50i-a133-helperboard-core.dtsi + create mode 100644 arch/arm64/boot/dts/allwinner/sun50i-a133-helperboard.dts +Merging tee/next (a3067938fd192 Merge branches 'qcomtee_for_v7.3', 'optee_fix_for_v7.2' and 'jw-korg' into next) +$ git merge -m Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/jenswi/linux-tee.git tee/next +Auto-merging .mailmap +Auto-merging MAINTAINERS +Merge made by the 'ort' strategy. + .mailmap | 1 + + MAINTAINERS | 6 +++--- + drivers/tee/optee/ffa_abi.c | 3 +++ + drivers/tee/qcomtee/call.c | 7 ++++++- + 4 files changed, 13 insertions(+), 4 deletions(-) +Merging tegra/for-next (75767942c9895 Merge branch for-7.2/arm64/dt into for-next) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/tegra/linux.git tegra/for-next +Auto-merging drivers/gpu/drm/tegra/sor.c +Auto-merging drivers/soc/tegra/fuse/tegra-apbmisc.c +Merge made by the 'ort' strategy. + arch/arm64/boot/dts/nvidia/tegra234.dtsi | 24 ++++++++++++------------ + arch/arm64/boot/dts/nvidia/tegra264.dtsi | 4 ++-- + drivers/gpu/drm/tegra/sor.c | 6 ++---- + drivers/soc/tegra/fuse/tegra-apbmisc.c | 2 +- + include/soc/tegra/pmc.h | 5 ++++- + 5 files changed, 21 insertions(+), 20 deletions(-) +Merging tenstorrent-dt/tenstorrent-dt-for-next (33583baeb1ba7 dt-bindings: iommu: riscv: Add bindings for Tenstorrent RISC-V IOMMU) +$ git merge -m Merge branch 'tenstorrent-dt-for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/tenstorrent/linux.git tenstorrent-dt/tenstorrent-dt-for-next +Already up to date. +Merging fustini-config/riscv-config-for-next (fb266bbe9aef1 riscv: defconfig: thead: enable PCA953X GPIO driver) +$ git merge -m Merge branch 'riscv-config-for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/fustini/linux.git fustini-config/riscv-config-for-next +Auto-merging arch/riscv/configs/defconfig +Merge made by the 'ort' strategy. + arch/riscv/configs/defconfig | 2 ++ + 1 file changed, 2 insertions(+) +Merging thead-dt/thead-dt-for-next (3a5791956edbf riscv: dts: thead: Enable wifi on the BeagleV-Ahead) +$ git merge -m Merge branch 'thead-dt-for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/fustini/linux.git thead-dt/thead-dt-for-next +Already up to date. +Merging ti/ti-next (1b98f483adfd9 Merge branches 'ti-drivers-soc-next' and 'ti-k3-maintainer-next' into ti-next) +$ git merge -m Merge branch 'ti-next' of https://git.kernel.org/pub/scm/linux/kernel/git/ti/linux.git ti/ti-next +Auto-merging MAINTAINERS +Merge made by the 'ort' strategy. + MAINTAINERS | 1 + + drivers/firmware/ti_sci.c | 4 ++++ + 2 files changed, 5 insertions(+) +Merging xilinx/for-next (f608bce703fc3 arm64: versal-net: Switch Versal NET to firmware clock interface) +$ git merge -m Merge branch 'for-next' of https://github.com/Xilinx/linux-xlnx.git xilinx/for-next +Merge made by the 'ort' strategy. + .../devicetree/bindings/clock/xlnx,versal-clk.yaml | 89 ++---- + .../devicetree/bindings/clock/xlnx,zynqmp-clk.yaml | 68 ++++ + .../firmware/xilinx/xlnx,zynqmp-firmware.yaml | 15 +- + arch/arm/boot/dts/xilinx/zynq-ebaz4205.dts | 1 - + arch/arm/boot/dts/xilinx/zynq-zc702.dts | 2 - + arch/arm/boot/dts/xilinx/zynq-zc706.dts | 2 - + arch/arm64/boot/dts/xilinx/versal-net-clk.dtsi | 351 +++++++++++++++------ + arch/arm64/boot/dts/xilinx/xlnx-versal-clk.h | 123 ++++++++ + arch/arm64/boot/dts/xilinx/xlnx-versal-net-clk.h | 74 +++++ + arch/arm64/boot/dts/xilinx/xlnx-versal-net-power.h | 38 +++ + .../arm64/boot/dts/xilinx/xlnx-versal-net-resets.h | 53 ++++ + arch/arm64/boot/dts/xilinx/xlnx-versal-power.h | 55 ++++ + arch/arm64/boot/dts/xilinx/xlnx-versal-resets.h | 106 +++++++ + .../boot/dts/xilinx/zynqmp-sck-kv-g-revA.dtso | 1 - + .../boot/dts/xilinx/zynqmp-sck-kv-g-revB.dtso | 1 - + .../boot/dts/xilinx/zynqmp-zc1751-xm015-dc1.dts | 4 - + .../boot/dts/xilinx/zynqmp-zc1751-xm019-dc5.dts | 2 - + arch/arm64/boot/dts/xilinx/zynqmp-zcu100-revC.dts | 1 - + arch/arm64/boot/dts/xilinx/zynqmp-zcu102-revA.dts | 2 - + arch/arm64/boot/dts/xilinx/zynqmp-zcu104-revA.dts | 1 - + arch/arm64/boot/dts/xilinx/zynqmp-zcu104-revC.dts | 1 - + arch/arm64/boot/dts/xilinx/zynqmp-zcu106-revA.dts | 2 - + arch/arm64/boot/dts/xilinx/zynqmp-zcu111-revA.dts | 1 - + 23 files changed, 798 insertions(+), 195 deletions(-) + create mode 100644 Documentation/devicetree/bindings/clock/xlnx,zynqmp-clk.yaml + create mode 100644 arch/arm64/boot/dts/xilinx/xlnx-versal-clk.h + create mode 100644 arch/arm64/boot/dts/xilinx/xlnx-versal-net-clk.h + create mode 100644 arch/arm64/boot/dts/xilinx/xlnx-versal-net-power.h + create mode 100644 arch/arm64/boot/dts/xilinx/xlnx-versal-net-resets.h + create mode 100644 arch/arm64/boot/dts/xilinx/xlnx-versal-power.h + create mode 100644 arch/arm64/boot/dts/xilinx/xlnx-versal-resets.h +Merging socfpga/for-next (0764f42a8ba90 arm64: dts: socfpga: agilex5: update channel interrupts for gmac1 and gmac2) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/dinguyen/linux.git socfpga/for-next +Merge made by the 'ort' strategy. + Documentation/devicetree/bindings/arm/altera.yaml | 6 + + .../bindings/net/altr,socfpga-stmmac.yaml | 46 +++++- + arch/arm64/boot/dts/intel/Makefile | 1 + + arch/arm64/boot/dts/intel/keembay-soc.dtsi | 2 +- + arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi | 112 ++++++++++++++- + arch/arm64/boot/dts/intel/socfpga_agilex72.dtsi | 156 +++++++++++++++++++++ + .../boot/dts/intel/socfpga_agilex72_socdk.dts | 27 ++++ + 7 files changed, 341 insertions(+), 9 deletions(-) + create mode 100644 arch/arm64/boot/dts/intel/socfpga_agilex72.dtsi + create mode 100644 arch/arm64/boot/dts/intel/socfpga_agilex72_socdk.dts +Merging clk/clk-next (92010229c4b38 Merge branches 'clk-microchip' and 'clk-qcom' into clk-next) +$ git merge -m Merge branch 'clk-next' of https://git.kernel.org/pub/scm/linux/kernel/git/clk/linux.git clk/clk-next +Already up to date. +Merging clk-imx/for-next (b3dcc8c608fbb clk: imx: Add audio PLL debugfs for K-divider control) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/abelvesa/linux.git clk-imx/for-next +Merge made by the 'ort' strategy. + drivers/clk/imx/clk-imx8mm.c | 6 +++ + drivers/clk/imx/clk-pll14xx.c | 119 +++++++++++++++++++++++++++++++++++++++++- + drivers/clk/imx/clk.h | 1 + + 3 files changed, 125 insertions(+), 1 deletion(-) +Merging clk-renesas/renesas-clk (91fb464395437 dt-bindings: clock: renesas,versaclock7: Update maintainer) +$ git merge -m Merge branch 'renesas-clk' of https://git.kernel.org/pub/scm/linux/kernel/git/geert/renesas-drivers.git clk-renesas/renesas-clk +Auto-merging MAINTAINERS +Auto-merging drivers/clk/renesas/renesas-cpg-mssr.c +Auto-merging drivers/clk/renesas/rzv2h-cpg.c +Merge made by the 'ort' strategy. + .../bindings/clock/renesas,rzv2h-cpg.yaml | 6 + + .../bindings/clock/renesas,versaclock7.yaml | 2 +- + MAINTAINERS | 6 +- + drivers/clk/renesas/Kconfig | 6 + + drivers/clk/renesas/Makefile | 1 + + drivers/clk/renesas/r9a08g046-cpg.c | 130 +++++++ + drivers/clk/renesas/r9a09g047-cpg.c | 123 ++++++- + drivers/clk/renesas/r9a09g077-cpg.c | 375 ++++++++++++++++++++- + drivers/clk/renesas/renesas-cpg-mssr.c | 20 +- + drivers/clk/renesas/rzv2h-cpg-lib.c | 217 ++++++++++++ + drivers/clk/renesas/rzv2h-cpg.c | 203 ----------- + .../dt-bindings/clock/renesas,r9a09g077-cpg-mssr.h | 2 + + .../dt-bindings/clock/renesas,r9a09g087-cpg-mssr.h | 2 + + include/linux/clk/renesas.h | 34 +- + 14 files changed, 904 insertions(+), 223 deletions(-) + create mode 100644 drivers/clk/renesas/rzv2h-cpg-lib.c +Merging thead-clk/thead-clk-for-next (baf4fc7c03bd0 clk: thead: th1520-ap: Support CPU frequency scaling) +$ git merge -m Merge branch 'thead-clk-for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/fustini/linux.git thead-clk/thead-clk-for-next +Already up to date. +Merging tenstorrent-clk/tenstorrent-clk-for-next (23c8ebc952849 clk: tenstorrent: Add Atlantis clock controller driver) +$ git merge -m Merge branch 'tenstorrent-clk-for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/tenstorrent/linux.git tenstorrent-clk/tenstorrent-clk-for-next +Already up to date. +Merging csky/linux-next (abb81e5ce7d99 csky: Fix a4/a5 restoration in syscall trace path) +$ git merge -m Merge branch 'linux-next' of https://github.com/c-sky/csky-linux.git csky/linux-next +Merge made by the 'ort' strategy. + arch/csky/kernel/entry.S | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) +Merging loongarch/loongarch-next (262a3b4fa1792 selftests/bpf: Test jited inline of bpf_get_smp_processor_id() for LoongArch) +$ git merge -m Merge branch 'loongarch-next' of https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson.git loongarch/loongarch-next +Already up to date. +Merging m68k/for-next (dc5200f6b1ada m68k: Correct CONFIG_MVME16x macro name in #endif comment) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/geert/linux-m68k.git m68k/for-next +Already up to date. +Merging m68knommu/for-next (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/gerg/m68knommu.git m68knommu/for-next +Already up to date. +Merging microblaze/next (6de23f81a5e08 Linux 7.0-rc1) +$ git merge -m Merge branch 'next' of git://git.monstr.eu/linux-2.6-microblaze.git microblaze/next +Already up to date. +Merging mips/mips-next (8cdeaa50eae8d Linux 7.2-rc2) +$ git merge -m Merge branch 'mips-next' of https://git.kernel.org/pub/scm/linux/kernel/git/mips/linux.git mips/mips-next +Already up to date. +Merging openrisc/for-next (aca063c902452 openrisc: Fix jump_label smp syncing) +$ git merge -m Merge branch 'for-next' of https://github.com/openrisc/linux.git openrisc/for-next +Already up to date. +Merging parisc-hd/for-next (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/deller/parisc-linux.git parisc-hd/for-next +Already up to date. +Merging powerpc/next (42f252f5a6468 powerpc/fadump: define MIN_RMA in bytes rather than MB) +$ git merge -m Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/powerpc/linux.git powerpc/next +Already up to date. +Merging risc-v/for-next (798246e5edfb3 riscv: acpi: Enable ARCH_HAS_ACPI_TABLE_UPGRADE) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/riscv/linux.git risc-v/for-next +Auto-merging arch/riscv/Kconfig +CONFLICT (content): Merge conflict in arch/riscv/Kconfig +Resolved 'arch/riscv/Kconfig' using previous resolution. +Automatic merge failed; fix conflicts and then commit the result. +$ git commit --no-edit -v -a +[master b2890843757c0] Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/riscv/linux.git +$ git diff -M --stat --summary HEAD^.. + arch/riscv/Kconfig | 1 + + arch/riscv/include/asm/acpi.h | 2 ++ + arch/riscv/kernel/acpi.c | 5 +++++ + arch/riscv/kernel/efi.c | 5 +++++ + arch/riscv/kernel/reset.c | 7 +++++++ + arch/riscv/kernel/setup.c | 2 ++ + drivers/acpi/tables.c | 1 + + 7 files changed, 23 insertions(+) +Merging riscv-dt/riscv-dt-for-next (82ab962ef6c2a Merge branch 'k230-basic' into riscv-dt-for-next) +$ git merge -m Merge branch 'riscv-dt-for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/conor/linux.git riscv-dt/riscv-dt-for-next +Auto-merging Documentation/devicetree/bindings/interrupt-controller/sifive,plic-1.0.0.yaml +Auto-merging Documentation/devicetree/bindings/timer/sifive,clint.yaml +Merge made by the 'ort' strategy. + .../interrupt-controller/sifive,plic-1.0.0.yaml | 1 + + .../devicetree/bindings/riscv/canaan.yaml | 8 +- + .../devicetree/bindings/timer/sifive,clint.yaml | 1 + + arch/riscv/boot/dts/canaan/Makefile | 2 + + arch/riscv/boot/dts/canaan/k230-canmv.dts | 343 +++++++++++++++++++++ + arch/riscv/boot/dts/canaan/k230-evb.dts | 39 +++ + arch/riscv/boot/dts/canaan/k230-pinctrl.h | 18 ++ + arch/riscv/boot/dts/canaan/k230.dtsi | 167 ++++++++++ + 8 files changed, 578 insertions(+), 1 deletion(-) + create mode 100644 arch/riscv/boot/dts/canaan/k230-canmv.dts + create mode 100644 arch/riscv/boot/dts/canaan/k230-evb.dts + create mode 100644 arch/riscv/boot/dts/canaan/k230-pinctrl.h + create mode 100644 arch/riscv/boot/dts/canaan/k230.dtsi +Merging riscv-soc/riscv-soc-for-next (dfceff38cea2b Merge branches 'riscv-soc-drivers-for-next' and 'cache-for-next' into riscv-soc-for-next) +$ git merge -m Merge branch 'riscv-soc-for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/conor/linux.git riscv-soc/riscv-soc-for-next +Merge made by the 'ort' strategy. +Merging s390/for-next (eb3690e410086 Merge branch 'fixes' into for-next) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/s390/linux.git s390/for-next +Merge made by the 'ort' strategy. +Merging sh/for-next (b0aa5e4b087b6 sh: Fix fallout from ZERO_PAGE consolidation) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/glaubitz/sh-linux.git sh/for-next +Already up to date. +Merging sparc/for-next (5b2a3b1a98fb4 sparc: Remove remaining defconfig references to the pktcdvd driver) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/alarsson/linux-sparc.git sparc/for-next +Already up to date. +Merging uml/next (254f49634ee16 Linux 7.1-rc1) +$ git merge -m Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/uml/linux.git uml/next +Already up to date. +Merging xtensa/xtensa-for-next (a7c958e8721eb xtensa: correct CONFIG_XTENSA_CALIBRATE_CCOUNT macro name in comment) +$ git merge -m Merge branch 'xtensa-for-next' of https://github.com/jcmvbkbc/linux-xtensa.git xtensa/xtensa-for-next +Merge made by the 'ort' strategy. + arch/xtensa/include/asm/platform.h | 2 +- + arch/xtensa/platforms/iss/console.c | 5 +++-- + 2 files changed, 4 insertions(+), 3 deletions(-) +Merging fs-next (3e912da38b64f Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs.git) +$ git merge -m Merge branch 'fs-next' of linux-next fs-next +Auto-merging MAINTAINERS +Auto-merging fs/ocfs2/namei.c +Auto-merging include/linux/fs.h +Auto-merging include/linux/sched.h +Auto-merging mm/shmem.c +Merge made by the 'ort' strategy. + Documentation/filesystems/locking.rst | 2 +- + Documentation/filesystems/overlayfs.rst | 16 + + Documentation/filesystems/porting.rst | 8 + + Documentation/filesystems/vfs.rst | 2 +- + MAINTAINERS | 6 +- + block/bdev.c | 113 +++- + drivers/base/devtmpfs.c | 2 +- + drivers/block/rnbd/rnbd-srv.c | 4 +- + drivers/char/misc_minor_kunit.c | 25 +- + drivers/crypto/ccp/sev-dev.c | 12 +- + drivers/mtd/ubi/ubi.h | 9 +- + drivers/target/target_core_alua.c | 11 +- + drivers/target/target_core_pr.c | 4 +- + fs/9p/vfs_inode.c | 5 +- + fs/9p/vfs_inode_dotl.c | 4 +- + fs/Kconfig | 1 - + fs/Makefile | 1 - + fs/affs/affs.h | 2 +- + fs/affs/namei.c | 4 +- + fs/afs/dir.c | 6 +- + fs/autofs/root.c | 2 +- + fs/bad_inode.c | 2 +- + fs/bfs/dir.c | 2 +- + fs/bpf_fs_kfuncs.c | 37 ++ + fs/btrfs/dev-replace.c | 65 +- + fs/btrfs/inode.c | 4 +- + fs/btrfs/ioctl.c | 4 +- + fs/btrfs/volumes.c | 116 +++- + fs/btrfs/volumes.h | 6 +- + fs/ceph/dir.c | 3 +- + fs/coda/dir.c | 9 +- + fs/coredump.c | 11 +- + fs/cramfs/inode.c | 2 +- + fs/crypto/crypto.c | 40 +- + fs/crypto/fscrypt_private.h | 5 +- + fs/crypto/keyring.c | 21 +- + fs/crypto/keysetup.c | 26 +- + fs/crypto/policy.c | 17 +- + fs/ecryptfs/inode.c | 2 +- + fs/efivarfs/inode.c | 2 +- + fs/efs/Kconfig | 16 - + fs/efs/Makefile | 8 - + fs/efs/dir.c | 105 ---- + fs/efs/efs.h | 144 ----- + fs/efs/file.c | 42 -- + fs/efs/inode.c | 315 ---------- + fs/efs/namei.c | 120 ---- + fs/efs/super.c | 368 ----------- + fs/efs/symlink.c | 50 -- + fs/erofs/super.c | 35 +- + fs/exfat/exfat_fs.h | 2 +- + fs/exfat/file.c | 219 +++++-- + fs/exfat/iomap.c | 22 +- + fs/exfat/namei.c | 2 +- + fs/ext2/namei.c | 4 +- + fs/ext4/extents-test.c | 9 +- + fs/ext4/mballoc-test.c | 9 +- + fs/ext4/namei.c | 4 +- + fs/ext4/super.c | 12 +- + fs/f2fs/namei.c | 4 +- + fs/f2fs/super.c | 6 +- + fs/fat/namei_msdos.c | 5 +- + fs/fat/namei_vfat.c | 2 +- + fs/fs_struct.c | 103 ++- + fs/fuse/dir.c | 10 +- + fs/gfs2/inode.c | 7 +- + fs/hfs/dir.c | 4 +- + fs/hfsplus/dir.c | 4 +- + fs/hostfs/hostfs_kern.c | 2 +- + fs/hpfs/namei.c | 6 +- + fs/hugetlbfs/inode.c | 4 +- + fs/inode.c | 23 +- + fs/internal.h | 1 + + fs/iomap/buffered-io.c | 6 +- + fs/iomap/direct-io.c | 293 ++++++++- + fs/iomap/ioend.c | 21 +- + fs/jffs2/dir.c | 6 +- + fs/jfs/namei.c | 4 +- + fs/kernel_read_file.c | 9 +- + fs/lockd/svc.c | 4 +- + fs/lockd/svc4proc.c | 4 +- + fs/lockd/svclock.c | 42 +- + fs/lockd/svcproc.c | 4 +- + fs/lockd/svcsubs.c | 105 +++- + fs/minix/namei.c | 4 +- + fs/namei.c | 30 +- + fs/namespace.c | 25 +- + fs/nfs/blocklayout/dev.c | 15 +- + fs/nfs/callback.c | 4 +- + fs/nfs/dir.c | 6 +- + fs/nfs/internal.h | 2 +- + fs/nfs_common/nfslocalio.c | 16 +- + fs/nfsd/filecache.c | 143 +++-- + fs/nfsd/flexfilelayoutxdr.c | 20 +- + fs/nfsd/localio.c | 8 +- + fs/nfsd/lockd.c | 6 +- + fs/nfsd/netns.h | 29 +- + fs/nfsd/nfs2acl.c | 21 +- + fs/nfsd/nfs3acl.c | 17 +- + fs/nfsd/nfs4callback.c | 113 +++- + fs/nfsd/nfs4layouts.c | 39 +- + fs/nfsd/nfs4proc.c | 84 ++- + fs/nfsd/nfs4recover.c | 48 +- + fs/nfsd/nfs4state.c | 330 +++++++--- + fs/nfsd/nfs4xdr.c | 27 +- + fs/nfsd/nfscache.c | 4 +- + fs/nfsd/nfsctl.c | 79 ++- + fs/nfsd/nfsfh.c | 12 +- + fs/nfsd/nfsproc.c | 7 + + fs/nfsd/nfssvc.c | 61 +- + fs/nfsd/state.h | 4 +- + fs/nfsd/trace.h | 18 +- + fs/nfsd/vfs.c | 48 +- + fs/nilfs2/namei.c | 4 +- + fs/ntfs/aops.c | 17 +- + fs/ntfs/attrib.c | 36 +- + fs/ntfs/attrlist.c | 9 + + fs/ntfs/dir.c | 20 +- + fs/ntfs/index.c | 5 +- + fs/ntfs/inode.c | 9 + + fs/ntfs/mft.c | 11 +- + fs/ntfs/namei.c | 66 +- + fs/ntfs/reparse.c | 6 +- + fs/ntfs3/namei.c | 4 +- + fs/nullfs.c | 14 +- + fs/ocfs2/dlmfs/dlmfs.c | 5 +- + fs/ocfs2/namei.c | 5 +- + fs/ocfs2/super.c | 1 - + fs/omfs/dir.c | 4 +- + fs/orangefs/namei.c | 5 +- + fs/overlayfs/dir.c | 18 +- + fs/overlayfs/inode.c | 26 +- + fs/overlayfs/overlayfs.h | 1 + + fs/overlayfs/super.c | 2 +- + fs/overlayfs/xattrs.c | 1 + + fs/posix_acl.c | 2 - + fs/proc/array.c | 4 +- + fs/proc/base.c | 8 +- + fs/proc_namespace.c | 4 +- + fs/ramfs/inode.c | 4 +- + fs/romfs/super.c | 2 +- + fs/smb/client/cifs_fs_sb.h | 1 + + fs/smb/client/cifsfs.c | 12 + + fs/smb/client/cifsfs.h | 2 +- + fs/smb/client/connect.c | 1 + + fs/smb/client/dir.c | 2 +- + fs/smb/client/file.c | 5 + + fs/smb/client/inode.c | 11 +- + fs/smb/client/reparse.c | 17 +- + fs/smb/server/mgmt/share_config.c | 4 +- + fs/smb/server/smb2pdu.c | 4 +- + fs/smb/server/vfs.c | 9 +- + fs/super.c | 639 +++++++++++++------ + fs/ubifs/dir.c | 4 +- + fs/udf/namei.c | 4 +- + fs/ufs/namei.c | 5 +- + fs/vboxsf/dir.c | 4 +- + fs/xfs/xfs_buf.c | 2 +- + fs/xfs/xfs_iops.c | 7 +- + fs/xfs/xfs_super.c | 10 +- + include/linux/blk_types.h | 2 +- + include/linux/blkdev.h | 12 +- + include/linux/efs_vh.h | 54 -- + include/linux/fs.h | 4 +- + include/linux/fs/super.h | 8 + + include/linux/fs/super_types.h | 4 +- + include/linux/fs_struct.h | 34 + + include/linux/init_task.h | 1 + + include/linux/lockd/bind.h | 12 +- + include/linux/net.h | 1 + + include/linux/sched.h | 1 + + include/linux/sched/task.h | 1 + + include/linux/sunrpc/bc_xprt.h | 5 + + include/linux/sunrpc/svc.h | 1 + + include/linux/sunrpc/svc_rdma_pcl.h | 4 +- + include/linux/types.h | 2 + + include/uapi/asm-generic/errno.h | 2 +- + include/uapi/linux/efs_fs_sb.h | 63 -- + include/uapi/linux/fscrypt.h | 1 - + init/init_task.c | 1 + + init/initramfs.c | 14 +- + init/initramfs_test.c | 220 ++++--- + init/main.c | 10 +- + ipc/mqueue.c | 7 +- + kernel/exit.c | 2 +- + kernel/fork.c | 53 +- + kernel/kcmp.c | 2 +- + kernel/umh.c | 6 +- + mm/shmem.c | 2 +- + net/socket.c | 25 + + net/sunrpc/auth_gss/auth_gss.c | 6 +- + net/sunrpc/auth_gss/gss_krb5_unseal.c | 3 + + net/sunrpc/auth_gss/gss_krb5_wrap.c | 13 +- + net/sunrpc/auth_gss/gss_rpc_upcall.c | 6 - + net/sunrpc/auth_gss/gss_rpc_upcall.h | 1 - + net/sunrpc/auth_gss/gss_rpc_xdr.c | 15 +- + net/sunrpc/auth_gss/svcauth_gss.c | 8 +- + net/sunrpc/backchannel_rqst.c | 38 +- + net/sunrpc/cache.c | 7 +- + net/sunrpc/sunrpc_syms.c | 1 + + net/sunrpc/svc.c | 81 ++- + net/sunrpc/svcauth_unix.c | 4 +- + net/sunrpc/xdr.c | 2 +- + net/sunrpc/xprtrdma/ib_client.c | 42 +- + net/sunrpc/xprtrdma/svc_rdma_pcl.c | 63 +- + net/sunrpc/xprtrdma/svc_rdma_recvfrom.c | 24 +- + net/sunrpc/xprtrdma/svc_rdma_rw.c | 52 +- + net/sunrpc/xprtrdma/svc_rdma_transport.c | 69 +- + net/unix/af_unix.c | 17 +- + tools/include/uapi/linux/fscrypt.h | 1 - + tools/testing/selftests/Makefile | 1 + + tools/testing/selftests/bpf/bpf_experimental.h | 3 + + .../testing/selftests/bpf/prog_tests/sock_xattr.c | 67 ++ + .../testing/selftests/bpf/progs/sock_read_xattr.c | 54 ++ + tools/testing/selftests/filesystems/.gitignore | 1 + + tools/testing/selftests/filesystems/Makefile | 2 +- + tools/testing/selftests/filesystems/ceph/Makefile | 7 + + tools/testing/selftests/filesystems/ceph/README | 84 +++ + .../filesystems/ceph/reset_corner_cases.sh | 646 +++++++++++++++++++ + .../selftests/filesystems/ceph/reset_stress.sh | 694 +++++++++++++++++++++ + .../selftests/filesystems/ceph/run_validation.sh | 350 +++++++++++ + tools/testing/selftests/filesystems/ceph/settings | 1 + + .../filesystems/ceph/validate_consistency.py | 297 +++++++++ + .../selftests/filesystems/overlayfs/.gitignore | 1 + + .../selftests/filesystems/overlayfs/Makefile | 2 + + .../filesystems/overlayfs/idmapped_mounts.c | 501 +++++++++++++++ + .../filesystems/overlayfs/set_layers_via_fds.c | 16 +- + tools/testing/selftests/filesystems/ustat_test.c | 135 ++++ + tools/testing/selftests/proc/proc-pidns.c | 1 + + 229 files changed, 6229 insertions(+), 2675 deletions(-) + delete mode 100644 fs/efs/Kconfig + delete mode 100644 fs/efs/Makefile + delete mode 100644 fs/efs/dir.c + delete mode 100644 fs/efs/efs.h + delete mode 100644 fs/efs/file.c + delete mode 100644 fs/efs/inode.c + delete mode 100644 fs/efs/namei.c + delete mode 100644 fs/efs/super.c + delete mode 100644 fs/efs/symlink.c + delete mode 100644 include/linux/efs_vh.h + delete mode 100644 include/uapi/linux/efs_fs_sb.h + create mode 100644 tools/testing/selftests/bpf/prog_tests/sock_xattr.c + create mode 100644 tools/testing/selftests/bpf/progs/sock_read_xattr.c + create mode 100644 tools/testing/selftests/filesystems/ceph/Makefile + create mode 100644 tools/testing/selftests/filesystems/ceph/README + create mode 100755 tools/testing/selftests/filesystems/ceph/reset_corner_cases.sh + create mode 100755 tools/testing/selftests/filesystems/ceph/reset_stress.sh + create mode 100755 tools/testing/selftests/filesystems/ceph/run_validation.sh + create mode 100644 tools/testing/selftests/filesystems/ceph/settings + create mode 100755 tools/testing/selftests/filesystems/ceph/validate_consistency.py + create mode 100644 tools/testing/selftests/filesystems/overlayfs/idmapped_mounts.c + create mode 100644 tools/testing/selftests/filesystems/ustat_test.c +Merging printk/for-next (080d60fffa8e0 Merge branch 'for-7.3' into for-next) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/printk/linux.git printk/for-next +Merge made by the 'ort' strategy. + lib/tests/test_ratelimit.c | 29 +++++++++++++++++++---------- + lib/vsprintf.c | 4 ++++ + 2 files changed, 23 insertions(+), 10 deletions(-) +Merging pci/next (bcabbbd29c53a Merge branch 'pci/controller/rzg3s-host') +$ git merge -m Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/pci/pci.git pci/next +Auto-merging MAINTAINERS +Auto-merging drivers/pci/controller/dwc/pci-meson.c +Merge made by the 'ort' strategy. + .../devicetree/bindings/pci/fsl,imx6q-pcie.yaml | 25 +++ + .../devicetree/bindings/pci/qcom,hawi-pcie.yaml | 196 +++++++++++++++++ + .../devicetree/bindings/pci/qcom,pcie-ipq9574.yaml | 15 ++ + .../bindings/pci/renesas,r9a08g045-pcie.yaml | 34 ++- + MAINTAINERS | 1 + + drivers/pci/controller/dwc/Kconfig | 2 + + drivers/pci/controller/dwc/pci-imx6.c | 25 ++- + drivers/pci/controller/dwc/pci-meson.c | 2 +- + drivers/pci/controller/dwc/pcie-qcom.c | 38 ++++ + drivers/pci/controller/pci-host-common.c | 29 +-- + drivers/pci/controller/pcie-rzg3s-host.c | 233 +++++++++++++++++++-- + 11 files changed, 547 insertions(+), 53 deletions(-) + create mode 100644 Documentation/devicetree/bindings/pci/qcom,hawi-pcie.yaml +Merging pstore/for-next/pstore (24b8f8dcb9a13 pstore/ftrace: Factor KASLR offset in the core kernel instruction addresses) +$ git merge -m Merge branch 'for-next/pstore' of https://git.kernel.org/pub/scm/linux/kernel/git/kees/linux.git pstore/for-next/pstore +Already up to date. +Merging hid/for-next (194a485768438 Merge branch 'for-7.2/upstream-fixes' into for-next) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git hid/for-next +Merge made by the 'ort' strategy. + drivers/hid/Kconfig | 9 + + drivers/hid/bpf/hid_bpf_dispatch.c | 5 +- + drivers/hid/hid-appleir.c | 45 ++- + drivers/hid/hid-core.c | 14 + + drivers/hid/hid-ids.h | 2 + + drivers/hid/hid-letsketch.c | 36 ++- + drivers/hid/hid-lg-g15.c | 16 ++ + drivers/hid/hid-logitech-dj.c | 9 +- + drivers/hid/hid-multitouch.c | 32 ++- + drivers/hid/hid-nintendo.c | 1 - + drivers/hid/hid-picolcd_core.c | 3 +- + drivers/hid/hid-roccat-kone.c | 65 ++++- + drivers/hid/hid-sony.c | 245 ++++++++-------- + drivers/hid/hid-steelseries.c | 313 ++++++++++++++++++++- + drivers/hid/wacom_wac.c | 8 +- + tools/testing/selftests/hid/Makefile | 2 +- + tools/testing/selftests/hid/hid_bpf.c | 36 ++- + tools/testing/selftests/hid/progs/hid.c | 15 + + .../testing/selftests/hid/tests/test_multitouch.py | 114 ++++++++ + 19 files changed, 799 insertions(+), 171 deletions(-) +Merging i2c/i2c/for-next (8cd9520d35a6c Linux 7.1) +$ git merge -m Merge branch 'i2c/for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/wsa/linux.git i2c/i2c/for-next +Already up to date. +$ git am -3 ../patches/0001-i2c-Fix-up-the-rest-of-the-merge.patch +Applying: i2c: Fix up the rest of the merge +Using index info to reconstruct a base tree... +M drivers/i2c/i2c-core-base.c +Falling back to patching base and 3-way merge... +Auto-merging drivers/i2c/i2c-core-base.c +No changes -- Patch already applied. +Merging i2c-andi/i2c/i2c-next (04e9bf1648f84 i2c: nomadik: Use generic definitions for bus frequencies) +$ git merge -m Merge branch 'i2c/i2c-next' of https://git.kernel.org/pub/scm/linux/kernel/git/andi.shyti/linux.git i2c-andi/i2c/i2c-next +Auto-merging drivers/i2c/busses/i2c-amd-asf-plat.c +Merge made by the 'ort' strategy. + .../bindings/i2c/altr,softip-i2c-v1.0.yaml | 62 ++++ + .../devicetree/bindings/i2c/i2c-altera.txt | 39 --- + .../devicetree/bindings/i2c/i2c-axxia.txt | 30 -- + .../devicetree/bindings/i2c/lsi,api2c.yaml | 52 ++++ + .../bindings/i2c/qcom,sa8255p-geni-i2c.yaml | 64 ++++ + drivers/i2c/busses/Kconfig | 2 +- + drivers/i2c/busses/i2c-amd-asf-plat.c | 4 + + drivers/i2c/busses/i2c-davinci.c | 4 +- + drivers/i2c/busses/i2c-k1.c | 183 +++++++++++- + drivers/i2c/busses/i2c-microchip-corei2c.c | 4 +- + drivers/i2c/busses/i2c-nomadik.c | 4 +- + drivers/i2c/busses/i2c-octeon-core.h | 2 +- + drivers/i2c/busses/i2c-pnx.c | 3 +- + drivers/i2c/busses/i2c-qcom-geni.c | 328 ++++++++++----------- + drivers/i2c/i2c-core-acpi.c | 6 +- + 15 files changed, 522 insertions(+), 265 deletions(-) + create mode 100644 Documentation/devicetree/bindings/i2c/altr,softip-i2c-v1.0.yaml + delete mode 100644 Documentation/devicetree/bindings/i2c/i2c-altera.txt + delete mode 100644 Documentation/devicetree/bindings/i2c/i2c-axxia.txt + create mode 100644 Documentation/devicetree/bindings/i2c/lsi,api2c.yaml + create mode 100644 Documentation/devicetree/bindings/i2c/qcom,sa8255p-geni-i2c.yaml +Merging i2c-rust/rust-i2c-next (c0260c740d207 i2c: rust: mark I2cAdapter methods as inline) +$ git merge -m Merge branch 'rust-i2c-next' of https://github.com/ikrtn/rust-for-linux i2c-rust/rust-i2c-next +Auto-merging rust/kernel/i2c.rs +Merge made by the 'ort' strategy. + rust/kernel/i2c.rs | 3 +++ + 1 file changed, 3 insertions(+) +Merging i3c/i3c/next (eeedacd0866ee i3c: dw: avoid shift-out-of-bounds when DAA assigns no devices) +$ git merge -m Merge branch 'i3c/next' of https://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux.git i3c/i3c/next +Merge made by the 'ort' strategy. + drivers/i3c/master/adi-i3c-master.c | 15 +++++++-------- + drivers/i3c/master/dw-i3c-master.c | 10 +++++++++- + drivers/i3c/master/svc-i3c-master.c | 18 +++++++++++++++--- + 3 files changed, 31 insertions(+), 12 deletions(-) +Merging dmi/dmi-for-next (1afafbaf749d8 firmware/dmi: Include product_family info to modalias) +$ git merge -m Merge branch 'dmi-for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/jdelvare/staging.git dmi/dmi-for-next +Already up to date. +Merging hwmon-staging/hwmon-next (a810e1aa11bf6 hwmon: (cros_ec) Implement custom kelvin to celsius conversions) +$ git merge -m Merge branch 'hwmon-next' of https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git hwmon-staging/hwmon-next +Auto-merging MAINTAINERS +Auto-merging drivers/hwmon/cros_ec_hwmon.c +Merge made by the 'ort' strategy. + .../devicetree/bindings/hwmon/adi,adm1275.yaml | 43 ++- + .../bindings/hwmon/amphenol,chipcap2.yaml | 6 + + .../bindings/hwmon/pmbus/silergy,sq24860.yaml | 74 ++++ + .../bindings/hwmon/pmbus/ti,lm25066.yaml | 20 + + .../devicetree/bindings/hwmon/ti,ina2xx.yaml | 3 + + Documentation/hwmon/adm1275.rst | 24 ++ + Documentation/hwmon/chipcap2.rst | 2 + + Documentation/hwmon/coretemp.rst | 4 +- + Documentation/hwmon/index.rst | 2 + + Documentation/hwmon/sq24860.rst | 96 +++++ + Documentation/hwmon/tvs-mpfs.rst | 53 +++ + MAINTAINERS | 1 + + drivers/hwmon/Kconfig | 13 + + drivers/hwmon/Makefile | 1 + + drivers/hwmon/chipcap2.c | 25 +- + drivers/hwmon/coretemp.c | 2 +- + drivers/hwmon/cros_ec_hwmon.c | 12 +- + drivers/hwmon/ina2xx.c | 20 + + drivers/hwmon/pmbus/Kconfig | 33 +- + drivers/hwmon/pmbus/Makefile | 1 + + drivers/hwmon/pmbus/adm1275.c | 142 ++++++- + drivers/hwmon/pmbus/lm25066.c | 37 ++ + drivers/hwmon/pmbus/sq24860.c | 430 +++++++++++++++++++++ + drivers/hwmon/pmbus/xdpe1a2g7b.c | 11 + + drivers/hwmon/tvs-mpfs.c | 390 +++++++++++++++++++ + drivers/hwmon/xgene-hwmon.c | 4 - + 26 files changed, 1418 insertions(+), 31 deletions(-) + create mode 100644 Documentation/devicetree/bindings/hwmon/pmbus/silergy,sq24860.yaml + create mode 100644 Documentation/hwmon/sq24860.rst + create mode 100644 Documentation/hwmon/tvs-mpfs.rst + create mode 100644 drivers/hwmon/pmbus/sq24860.c + create mode 100644 drivers/hwmon/tvs-mpfs.c +Merging jc_docs/docs-next (2933b82083e75 Documentation: locking.rst: update deprecated function) +$ git merge -m Merge branch 'docs-next' of git://git.lwn.net/linux.git jc_docs/docs-next +Auto-merging Documentation/admin-guide/kernel-parameters.txt +Merge made by the 'ort' strategy. + Documentation/admin-guide/kernel-parameters.txt | 6 +- + Documentation/admin-guide/media/bttv.rst | 2 +- + Documentation/admin-guide/mm/pagemap.rst | 8 +- + Documentation/conf.py | 4 +- + Documentation/core-api/SMP.rst | 11 + + Documentation/core-api/index.rst | 1 + + Documentation/dev-tools/container.rst | 6 +- + Documentation/kernel-hacking/locking.rst | 8 +- + Documentation/mm/hmm.rst | 4 +- + Documentation/mm/process_addrs.rst | 2 +- + .../translations/pt_BR/process/4.Coding.rst | 440 +++++++++++++++++++++ + .../translations/pt_BR/process/5.Posting.rst | 376 ++++++++++++++++++ + .../pt_BR/process/development-process.rst | 2 + + .../pt_BR/process/maintainer-netdev.rst | 23 +- + tools/lib/python/kdoc/kdoc_output.py | 2 +- + 15 files changed, 867 insertions(+), 28 deletions(-) + create mode 100644 Documentation/core-api/SMP.rst + create mode 100644 Documentation/translations/pt_BR/process/4.Coding.rst + create mode 100644 Documentation/translations/pt_BR/process/5.Posting.rst +Merging v4l-dvb/next (8dac27bfa2f99 media: dt-bindings: rc: Sync keymap list with latest list) +$ git merge -m Merge branch 'next' of git://linuxtv.org/media-ci/media-pending.git v4l-dvb/next +Merge made by the 'ort' strategy. + Documentation/devicetree/bindings/media/rc.yaml | 4 +-- + drivers/media/cec/core/cec-adap.c | 2 +- + drivers/media/cec/core/cec-core.c | 2 -- + drivers/media/cec/core/cec-pin.c | 8 +++--- + drivers/media/cec/platform/meson/ao-cec-g12a.c | 1 + + drivers/media/cec/platform/stm32/stm32-cec.c | 19 +++++++++----- + .../extron-da-hd-4k-plus/extron-da-hd-4k-plus.c | 3 ++- + drivers/media/i2c/ov9282.c | 1 - + .../media/platform/renesas/rzg2l-cru/rzg2l-cru.h | 2 -- + drivers/media/radio/radio-cadet.c | 4 +-- + drivers/media/radio/radio-gemtek.c | 4 +-- + drivers/media/rc/fintek-cir.c | 4 +-- + drivers/media/rc/ite-cir.c | 29 ++++++++++++++-------- + drivers/media/rc/keymaps/Makefile | 2 +- + .../{rc-videomate-m1f.c => rc-videomate-k100.c} | 2 +- + drivers/media/rc/nuvoton-cir.c | 6 ++--- + drivers/media/rc/sunxi-cir.c | 9 ++++--- + drivers/media/rc/winbond-cir.c | 4 +-- + include/media/media-entity.h | 1 + + include/media/rc-map.h | 2 -- + 20 files changed, 61 insertions(+), 48 deletions(-) + rename drivers/media/rc/keymaps/{rc-videomate-m1f.c => rc-videomate-k100.c} (97%) +Merging v4l-dvb-next/master (adc218676eef2 Linux 6.12) +$ git merge -m Merge branch 'master' of git://linuxtv.org/mchehab/media-next.git v4l-dvb-next/master +Already up to date. +Merging pm/linux-next (da2e2fd83f1ef Merge branch 'pnp' into linux-next) +$ git merge -m Merge branch 'linux-next' of https://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm.git pm/linux-next +Merge made by the 'ort' strategy. + drivers/pnp/system.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) +Merging cpufreq-arm/cpufreq/arm/linux-next (f456178290356 rust: rcpufreq_dt: use vertical import style) +$ git merge -m Merge branch 'cpufreq/arm/linux-next' of https://git.kernel.org/pub/scm/linux/kernel/git/vireshk/pm.git cpufreq-arm/cpufreq/arm/linux-next +Merge made by the 'ort' strategy. + drivers/cpufreq/apple-soc-cpufreq.c | 36 ++++++++++++++---------------------- + drivers/cpufreq/cpufreq-dt-platdev.c | 1 + + drivers/cpufreq/qcom-cpufreq-nvmem.c | 8 ++++++++ + drivers/cpufreq/rcpufreq_dt.rs | 13 ++++++++++--- + 4 files changed, 33 insertions(+), 25 deletions(-) +Merging cpupower/cpupower (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'cpupower' of https://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux.git cpupower/cpupower +Already up to date. +Merging devfreq/devfreq-next (c096be11c2a9d PM / devfreq: Fix governor_store() failing when device has no current governor) +$ git merge -m Merge branch 'devfreq-next' of https://git.kernel.org/pub/scm/linux/kernel/git/chanwoo/linux.git devfreq/devfreq-next +Merge made by the 'ort' strategy. + drivers/devfreq/devfreq.c | 50 +++++++----------------------------- + drivers/devfreq/event/rockchip-dfi.c | 4 ++- + 2 files changed, 12 insertions(+), 42 deletions(-) +Merging pmdomain/next (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/ulfh/linux-pm.git pmdomain/next +Already up to date. +Merging opp/opp/linux-next (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'opp/linux-next' of https://git.kernel.org/pub/scm/linux/kernel/git/vireshk/pm.git opp/opp/linux-next +Already up to date. +Merging thermal/thermal/linux-next (968098b4ca521 thermal/drivers/qcom/tsens: Disable wakeup interrupt setup on automotive targets) +$ git merge -m Merge branch 'thermal/linux-next' of https://git.kernel.org/pub/scm/linux/kernel/git/thermal/linux.git thermal/thermal/linux-next +Already up to date. +Merging rdma/for-next (c3fd3966f7dd8 RDMA/mlx5: Remove kernel-doc warning in umr.c) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/rdma/rdma.git rdma/for-next +Merge made by the 'ort' strategy. + Documentation/infiniband/user_mad.rst | 2 +- + drivers/infiniband/core/iwpm_msg.c | 2 +- + drivers/infiniband/core/nldev.c | 29 ++++++++++++++++++++++++--- + drivers/infiniband/core/uverbs_std_types_mr.c | 6 ++++-- + drivers/infiniband/hw/mlx5/umr.c | 2 +- + include/uapi/rdma/rdma_netlink.h | 5 +++++ + 6 files changed, 38 insertions(+), 8 deletions(-) +Merging net-next/main (1704cc8640702 selftests/tc-testing: Add tests that force multiq and taprio to enqueue to child's gso_skb) +$ git merge -m Merge branch 'main' of https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next.git net-next/main +Auto-merging MAINTAINERS +CONFLICT (content): Merge conflict in MAINTAINERS +Auto-merging drivers/net/amt.c +Auto-merging include/linux/net.h +Auto-merging net/netfilter/nfnetlink_cthelper.c +Auto-merging net/netfilter/nft_set_rbtree.c +Auto-merging tools/testing/selftests/net/lib.sh +CONFLICT (content): Merge conflict in tools/testing/selftests/net/lib.sh +Resolved 'MAINTAINERS' using previous resolution. +Recorded preimage for 'tools/testing/selftests/net/lib.sh' +Automatic merge failed; fix conflicts and then commit the result. +$ git commit --no-edit -v -a +Recorded resolution for 'tools/testing/selftests/net/lib.sh'. +[master cf4d44c65ca6b] Merge branch 'main' of https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next.git +$ git diff -M --stat --summary HEAD^.. + Documentation/netlink/specs/rt-addr.yaml | 4 + + MAINTAINERS | 5 - + arch/powerpc/configs/ppc64_defconfig | 1 - + arch/powerpc/mm/mem.c | 6 - + drivers/net/amt.c | 43 +- + drivers/net/bonding/bond_3ad.c | 24 +- + drivers/net/bonding/bond_netlink.c | 109 +- + drivers/net/bonding/bond_options.c | 8 +- + drivers/net/dsa/b53/b53_priv.h | 3 +- + drivers/net/dsa/microchip/ksz8.c | 2 +- + drivers/net/dsa/qca/qca8k-leds.c | 3 + + drivers/net/dsa/realtek/rtl8366rb.c | 268 +- + drivers/net/dsa/realtek/rtl83xx.c | 26 +- + drivers/net/ethernet/allwinner/sun4i-emac.c | 2 +- + drivers/net/ethernet/apm/xgene/xgene_enet_main.c | 2 +- + .../net/ethernet/freescale/dpaa2/dpaa2-switch.c | 931 ++++- + .../net/ethernet/freescale/dpaa2/dpaa2-switch.h | 42 +- + drivers/net/ethernet/freescale/dpaa2/dpsw-cmd.h | 18 +- + drivers/net/ethernet/freescale/dpaa2/dpsw.c | 60 + + drivers/net/ethernet/freescale/dpaa2/dpsw.h | 30 + + drivers/net/ethernet/hisilicon/hns3/hnae3.h | 14 + + .../hisilicon/hns3/hns3_common/hclge_comm_cmd.h | 3 + + drivers/net/ethernet/hisilicon/hns3/hns3_ethtool.c | 12 + + .../net/ethernet/hisilicon/hns3/hns3pf/hclge_cmd.h | 9 + + .../ethernet/hisilicon/hns3/hns3pf/hclge_main.c | 172 + + .../ethernet/hisilicon/hns3/hns3pf/hclge_main.h | 7 + + drivers/net/ethernet/ibm/Kconfig | 9 - + drivers/net/ethernet/ibm/Makefile | 1 - + drivers/net/ethernet/ibm/ehea/Makefile | 7 - + drivers/net/ethernet/ibm/ehea/ehea.h | 477 --- + drivers/net/ethernet/ibm/ehea/ehea_ethtool.c | 277 -- + drivers/net/ethernet/ibm/ehea/ehea_hw.h | 253 -- + drivers/net/ethernet/ibm/ehea/ehea_main.c | 3581 -------------------- + drivers/net/ethernet/ibm/ehea/ehea_phyp.c | 612 ---- + drivers/net/ethernet/ibm/ehea/ehea_phyp.h | 433 --- + drivers/net/ethernet/ibm/ehea/ehea_qmr.c | 999 ------ + drivers/net/ethernet/ibm/ehea/ehea_qmr.h | 390 --- + drivers/net/ethernet/marvell/mvneta.c | 18 + + drivers/net/ethernet/marvell/mvneta_bm.c | 72 + + drivers/net/ethernet/marvell/octeontx2/af/mbox.h | 2 + + drivers/net/ethernet/marvell/octeontx2/af/npc.h | 4 +- + drivers/net/ethernet/marvell/octeontx2/af/rvu.h | 2 +- + .../net/ethernet/marvell/octeontx2/af/rvu_nix.c | 9 +- + .../net/ethernet/marvell/octeontx2/af/rvu_npc.c | 43 +- + drivers/net/ethernet/marvell/octeontx2/nic/cn20k.c | 1 + + .../ethernet/marvell/octeontx2/nic/otx2_common.c | 2 +- + .../net/ethernet/mellanox/mlxsw/spectrum_acl_erp.c | 2 +- + .../ethernet/mellanox/mlxsw/spectrum_acl_tcam.h | 2 +- + .../net/ethernet/oki-semi/pch_gbe/pch_gbe_main.c | 2 +- + .../net/ethernet/pensando/ionic/ionic_rx_filter.c | 7 +- + drivers/net/ethernet/sgi/ioc3-eth.c | 9 +- + drivers/net/macsec.c | 78 +- + drivers/net/macvlan.c | 109 +- + drivers/net/netconsole.c | 2 +- + drivers/net/phy/mdio_device.c | 2 +- + drivers/net/phy/phylink.c | 15 +- + drivers/net/usb/rtl8150.c | 4 +- + include/linux/net.h | 23 + + include/linux/netfilter.h | 7 + + include/linux/netfilter/nf_conntrack_h323.h | 2 - + include/linux/netfilter/nf_conntrack_pptp.h | 2 - + include/linux/netfilter/nf_conntrack_sane.h | 2 - + include/linux/netfilter/nf_conntrack_tftp.h | 2 - + include/net/bond_3ad.h | 4 +- + include/net/bonding.h | 8 +- + include/net/fib_rules.h | 4 +- + include/net/ip_fib.h | 3 +- + include/net/ip_vs.h | 2 +- + include/net/netfilter/nf_conntrack_helper.h | 10 +- + include/net/netns/ipv4.h | 1 + + include/net/udp.h | 2 +- + include/uapi/linux/if_addr.h | 1 + + net/batman-adv/bat_iv_ogm.c | 12 +- + net/batman-adv/bat_v_elp.c | 9 +- + net/batman-adv/bat_v_ogm.c | 33 +- + net/batman-adv/bridge_loop_avoidance.c | 9 +- + net/batman-adv/hard-interface.c | 165 +- + net/batman-adv/hard-interface.h | 10 +- + net/batman-adv/main.c | 9 - + net/batman-adv/main.h | 3 - + net/batman-adv/mesh-interface.c | 13 +- + net/batman-adv/netlink.c | 4 +- + net/batman-adv/originator.c | 4 - + net/batman-adv/tp_meter.c | 215 +- + net/batman-adv/tvlv.c | 86 +- + net/batman-adv/types.h | 28 +- + net/bridge/netfilter/ebtables.c | 12 +- + net/core/fib_rules.c | 82 +- + net/core/netpoll.c | 2 +- + net/ipv4/fib_frontend.c | 66 +- + net/ipv4/fib_rules.c | 20 +- + net/ipv4/fib_trie.c | 3 +- + net/ipv4/igmp.c | 2 + + net/ipv4/netfilter/nf_nat_snmp_basic_main.c | 2 +- + net/ipv4/raw.c | 41 +- + net/ipv4/udp.c | 39 +- + net/ipv6/addrconf.c | 1 + + net/ipv6/fib6_rules.c | 17 +- + net/ipv6/mcast.c | 1 + + net/ipv6/udp.c | 19 +- + net/netfilter/ipvs/ip_vs_nfct.c | 2 +- + net/netfilter/nf_conntrack_amanda.c | 6 +- + net/netfilter/nf_conntrack_broadcast.c | 2 - + net/netfilter/nf_conntrack_ftp.c | 32 +- + net/netfilter/nf_conntrack_h323_main.c | 12 +- + net/netfilter/nf_conntrack_helper.c | 77 +- + net/netfilter/nf_conntrack_irc.c | 27 +- + net/netfilter/nf_conntrack_netbios_ns.c | 2 - + net/netfilter/nf_conntrack_ovs.c | 6 +- + net/netfilter/nf_conntrack_pptp.c | 2 +- + net/netfilter/nf_conntrack_sane.c | 34 +- + net/netfilter/nf_conntrack_sip.c | 45 +- + net/netfilter/nf_conntrack_snmp.c | 4 +- + net/netfilter/nf_conntrack_tftp.c | 33 +- + net/netfilter/nf_nat_core.c | 6 - + net/netfilter/nf_nat_proto.c | 8 + + net/netfilter/nfnetlink_cthelper.c | 21 +- + net/netfilter/nfnetlink_cttimeout.c | 2 +- + net/netfilter/nfnetlink_hook.c | 37 +- + net/netfilter/nft_ct.c | 35 + + net/netfilter/nft_set_rbtree.c | 3 +- + net/netfilter/x_tables.c | 30 +- + net/netfilter/xt_TCPOPTSTRIP.c | 8 +- + net/netfilter/xt_dscp.c | 12 + + net/netfilter/xt_recent.c | 2 +- + net/netfilter/xt_tcpmss.c | 13 + + net/sched/act_ct.c | 4 +- + net/vmw_vsock/virtio_transport_common.c | 49 +- + tools/net/ynl/pyynl/__init__.py | 9 + + tools/net/ynl/pyynl/cli.py | 56 +- + tools/net/ynl/pyynl/lib/__init__.py | 3 +- + tools/net/ynl/pyynl/lib/nlspec.py | 22 +- + tools/net/ynl/pyynl/lib/specdir.py | 51 + + tools/net/ynl/pyynl/lib/ynl.py | 19 +- + tools/net/ynl/tests/ethtool.py | 7 +- + tools/testing/selftests/bpf/prog_tests/test_xsk.c | 4 +- + tools/testing/selftests/drivers/net/hw/toeplitz.py | 22 + + tools/testing/selftests/net/getsockopt_iter.c | 97 + + tools/testing/selftests/net/lib.sh | 15 +- + tools/testing/selftests/net/rtnetlink.py | 101 +- + .../tc-testing/tc-tests/infra/qdiscs.json | 164 + + 141 files changed, 2951 insertions(+), 8341 deletions(-) + delete mode 100644 drivers/net/ethernet/ibm/ehea/Makefile + delete mode 100644 drivers/net/ethernet/ibm/ehea/ehea.h + delete mode 100644 drivers/net/ethernet/ibm/ehea/ehea_ethtool.c + delete mode 100644 drivers/net/ethernet/ibm/ehea/ehea_hw.h + delete mode 100644 drivers/net/ethernet/ibm/ehea/ehea_main.c + delete mode 100644 drivers/net/ethernet/ibm/ehea/ehea_phyp.c + delete mode 100644 drivers/net/ethernet/ibm/ehea/ehea_phyp.h + delete mode 100644 drivers/net/ethernet/ibm/ehea/ehea_qmr.c + delete mode 100644 drivers/net/ethernet/ibm/ehea/ehea_qmr.h + create mode 100644 tools/net/ynl/pyynl/lib/specdir.py +Merging bpf-next/for-next (87bfe634b1193 Merge git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf 7.2-rc2) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git bpf-next/for-next +Merge made by the 'ort' strategy. + Documentation/bpf/kfuncs.rst | 23 +- + include/linux/bpf.h | 4 +- + include/linux/bpf_verifier.h | 5 + + include/uapi/linux/bpf.h | 28 ++- + kernel/bpf/bpf_lsm.c | 1 - + kernel/bpf/fixups.c | 2 +- + kernel/bpf/verifier.c | 53 ++++- + kernel/trace/bpf_trace.c | 55 +++++ + net/core/filter.c | 12 +- + tools/bpf/bpftool/Makefile | 2 +- + tools/bpf/bpftool/btf.c | 13 +- + tools/bpf/bpftool/btf_dumper.c | 14 +- + tools/bpf/bpftool/link.c | 133 +++++++++++ + tools/bpf/bpftool/map.c | 12 +- + tools/bpf/bpftool/struct_ops.c | 4 + + tools/include/linux/btf_ids.h | 78 ++++++- + tools/include/uapi/linux/bpf.h | 28 ++- + tools/lib/bpf/btf.c | 10 +- + tools/lib/bpf/btf_dump.c | 4 +- + tools/lib/bpf/btf_relocate.c | 8 +- + tools/lib/bpf/elf.c | 2 +- + tools/lib/bpf/gen_loader.c | 16 +- + tools/lib/bpf/libbpf.c | 88 ++++---- + tools/lib/bpf/nlattr.c | 2 +- + tools/lib/bpf/relo_core.c | 22 +- + tools/lib/bpf/usdt.c | 33 +-- + tools/testing/selftests/bpf/network_helpers.c | 4 +- + tools/testing/selftests/bpf/network_helpers.h | 5 + + .../selftests/bpf/prog_tests/fill_link_info.c | 242 +++++++++++++++++++++ + tools/testing/selftests/bpf/prog_tests/mptcp.c | 13 +- + .../selftests/bpf/prog_tests/resolve_btfids.c | 169 +++++++++----- + .../testing/selftests/bpf/prog_tests/tc_redirect.c | 68 ++++++ + .../selftests/bpf/prog_tests/test_map_uninit.c | 68 ++++++ + .../testing/selftests/bpf/progs/bpf_tracing_net.h | 3 + + tools/testing/selftests/bpf/progs/btf_data.c | 10 + + tools/testing/selftests/bpf/progs/map_kptr.c | 12 + + tools/testing/selftests/bpf/progs/mptcpify.c | 2 +- + .../selftests/bpf/progs/sockmap_verdict_prog.c | 14 +- + .../selftests/bpf/progs/test_fill_link_info.c | 6 + + tools/testing/selftests/bpf/progs/test_tc_peer.c | 22 ++ + .../selftests/bpf/progs/verifier_spill_fill.c | 26 +++ + .../testing/selftests/bpf/progs/verifier_var_off.c | 110 ++++++++++ + 42 files changed, 1206 insertions(+), 220 deletions(-) + create mode 100644 tools/testing/selftests/bpf/prog_tests/test_map_uninit.c +$ git am -3 ../patches/0001-perf-Fixup-for-btf_vlan-API-change.patch +Applying: perf: Fixup for btf_vlan() API change +Using index info to reconstruct a base tree... +M tools/perf/builtin-trace.c +M tools/perf/util/btf.c +Falling back to patching base and 3-way merge... +Auto-merging tools/perf/builtin-trace.c +No changes -- Patch already applied. +Merging ipsec-next/master (355f808d8a11f Merge branch 'xfrm: XFRM_MSG_MIGRATE_STATE new netlink message') +$ git merge -m Merge branch 'master' of https://git.kernel.org/pub/scm/linux/kernel/git/klassert/ipsec-next.git ipsec-next/master +Already up to date. +Merging mlx5-next/mlx5-next (ddbddbf8aee54 net/mlx5: Add sd_group_size bits for SD management) +$ git merge -m Merge branch 'mlx5-next' of https://git.kernel.org/pub/scm/linux/kernel/git/mellanox/linux.git mlx5-next/mlx5-next +Already up to date. +Merging netfilter-next/main (b73bc9ca3686b Merge tag 'nf-next-26-07-02' of https://git.kernel.org/pub/scm/linux/kernel/git/netfilter/nf-next) +$ git merge -m Merge branch 'main' of https://git.kernel.org/pub/scm/linux/kernel/git/netfilter/nf-next.git netfilter-next/main +Already up to date. +Merging ipvs-next/main (805185b7c7a10 Merge tag 'net-7.2-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net) +$ git merge -m Merge branch 'main' of https://git.kernel.org/pub/scm/linux/kernel/git/horms/ipvs-next.git ipvs-next/main +Already up to date. +Merging bluetooth/master (7ea67149af719 Bluetooth: L2CAP: fix tx ident leak for commands without a response) +$ git merge -m Merge branch 'master' of https://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth-next.git bluetooth/master +Merge made by the 'ort' strategy. + drivers/bluetooth/Makefile | 2 + + drivers/bluetooth/bpa10x.c | 8 +- + drivers/bluetooth/btintel_pcie.c | 102 +++++++++++----------- + drivers/bluetooth/btnxpuart.c | 6 ++ + drivers/bluetooth/btqca.c | 25 +----- + drivers/bluetooth/btusb.c | 92 ++++++++++++-------- + drivers/bluetooth/hci_ldisc.c | 14 ++- + include/net/bluetooth/hci.h | 5 +- + include/net/bluetooth/hci_core.h | 1 + + include/net/bluetooth/hci_sync.h | 4 +- + include/net/bluetooth/l2cap.h | 10 ++- + net/bluetooth/6lowpan.c | 84 +++++------------- + net/bluetooth/Makefile | 2 + + net/bluetooth/af_bluetooth.c | 24 ++---- + net/bluetooth/bnep/Makefile | 2 + + net/bluetooth/bnep/core.c | 27 ++++-- + net/bluetooth/hci_conn.c | 21 +---- + net/bluetooth/hci_core.c | 3 + + net/bluetooth/hci_debugfs.c | 12 +-- + net/bluetooth/hci_event.c | 5 +- + net/bluetooth/hci_sync.c | 181 ++++++++++++++++++++++++++++----------- + net/bluetooth/hidp/Makefile | 2 + + net/bluetooth/iso.c | 44 ++++++---- + net/bluetooth/l2cap_core.c | 140 +++++++++++++++++++++++------- + net/bluetooth/l2cap_sock.c | 108 +++++++++++------------ + net/bluetooth/mgmt.c | 5 ++ + net/bluetooth/msft.c | 2 +- + net/bluetooth/rfcomm/Makefile | 2 + + net/bluetooth/rfcomm/sock.c | 1 + + net/bluetooth/sco.c | 15 +++- + net/bluetooth/smp.c | 27 ++---- + 31 files changed, 566 insertions(+), 410 deletions(-) +Merging wireless-next/for-next (2bfd9da88b95d wifi: cfg80211: support MAC address filtering in station dump for link stats) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next.git wireless-next/for-next +Auto-merging MAINTAINERS +Auto-merging drivers/net/wireless/marvell/mwifiex/cfg80211.c +Auto-merging include/net/cfg80211.h +Auto-merging net/mac80211/cfg.c +Auto-merging net/wireless/nl80211.c +Merge made by the 'ort' strategy. + MAINTAINERS | 7 + + drivers/mmc/core/quirks.h | 3 + + drivers/net/wireless/Kconfig | 1 + + drivers/net/wireless/Makefile | 1 + + drivers/net/wireless/ath/ath12k/mac.c | 6 - + drivers/net/wireless/ath/wil6210/cfg80211.c | 10 +- + drivers/net/wireless/broadcom/b43/debugfs.c | 12 +- + drivers/net/wireless/broadcom/b43legacy/debugfs.c | 12 +- + drivers/net/wireless/intersil/p54/Kconfig | 6 +- + drivers/net/wireless/intersil/p54/fwio.c | 4 +- + drivers/net/wireless/intersil/p54/p54usb.c | 2 +- + drivers/net/wireless/marvell/libertas/debugfs.c | 39 +- + drivers/net/wireless/marvell/mwifiex/cfg80211.c | 8 +- + drivers/net/wireless/marvell/mwifiex/debugfs.c | 62 +- + drivers/net/wireless/nxp/Kconfig | 17 + + drivers/net/wireless/nxp/Makefile | 3 + + drivers/net/wireless/nxp/nxpwifi/11ac.c | 280 ++ + drivers/net/wireless/nxp/nxpwifi/11ac.h | 33 + + drivers/net/wireless/nxp/nxpwifi/11ax.c | 594 ++++ + drivers/net/wireless/nxp/nxpwifi/11ax.h | 73 + + drivers/net/wireless/nxp/nxpwifi/11h.c | 339 ++ + drivers/net/wireless/nxp/nxpwifi/11n.c | 837 +++++ + drivers/net/wireless/nxp/nxpwifi/11n.h | 158 + + drivers/net/wireless/nxp/nxpwifi/11n_aggr.c | 251 ++ + drivers/net/wireless/nxp/nxpwifi/11n_aggr.h | 21 + + drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c | 826 +++++ + drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.h | 71 + + drivers/net/wireless/nxp/nxpwifi/Kconfig | 22 + + drivers/net/wireless/nxp/nxpwifi/Makefile | 39 + + drivers/net/wireless/nxp/nxpwifi/cfg.h | 1019 ++++++ + drivers/net/wireless/nxp/nxpwifi/cfg80211.c | 3931 +++++++++++++++++++++ + drivers/net/wireless/nxp/nxpwifi/cfg80211.h | 18 + + drivers/net/wireless/nxp/nxpwifi/cfp.c | 458 +++ + drivers/net/wireless/nxp/nxpwifi/cmdevt.c | 1310 +++++++ + drivers/net/wireless/nxp/nxpwifi/cmdevt.h | 122 + + drivers/net/wireless/nxp/nxpwifi/debugfs.c | 1094 ++++++ + drivers/net/wireless/nxp/nxpwifi/ethtool.c | 58 + + drivers/net/wireless/nxp/nxpwifi/fw.h | 2459 +++++++++++++ + drivers/net/wireless/nxp/nxpwifi/ie.c | 480 +++ + drivers/net/wireless/nxp/nxpwifi/init.c | 607 ++++ + drivers/net/wireless/nxp/nxpwifi/join.c | 787 +++++ + drivers/net/wireless/nxp/nxpwifi/main.c | 1673 +++++++++ + drivers/net/wireless/nxp/nxpwifi/main.h | 1427 ++++++++ + drivers/net/wireless/nxp/nxpwifi/scan.c | 2695 ++++++++++++++ + drivers/net/wireless/nxp/nxpwifi/sdio.c | 2327 ++++++++++++ + drivers/net/wireless/nxp/nxpwifi/sdio.h | 340 ++ + drivers/net/wireless/nxp/nxpwifi/sta_cfg.c | 1165 ++++++ + drivers/net/wireless/nxp/nxpwifi/sta_cmd.c | 3387 ++++++++++++++++++ + drivers/net/wireless/nxp/nxpwifi/sta_event.c | 862 +++++ + drivers/net/wireless/nxp/nxpwifi/sta_rx.c | 242 ++ + drivers/net/wireless/nxp/nxpwifi/sta_tx.c | 190 + + drivers/net/wireless/nxp/nxpwifi/txrx.c | 352 ++ + drivers/net/wireless/nxp/nxpwifi/uap_cmd.c | 1256 +++++++ + drivers/net/wireless/nxp/nxpwifi/uap_event.c | 488 +++ + drivers/net/wireless/nxp/nxpwifi/uap_txrx.c | 478 +++ + drivers/net/wireless/nxp/nxpwifi/util.c | 1381 ++++++++ + drivers/net/wireless/nxp/nxpwifi/util.h | 155 + + drivers/net/wireless/nxp/nxpwifi/wmm.c | 1313 +++++++ + drivers/net/wireless/nxp/nxpwifi/wmm.h | 77 + + drivers/net/wireless/realtek/rtw89/core.c | 3 - + drivers/net/wireless/ti/wlcore/main.c | 14 +- + include/linux/ieee80211.h | 1 + + include/linux/mmc/sdio_ids.h | 1 + + include/net/cfg80211.h | 41 +- + include/net/ieee80211_radiotap.h | 190 + + include/uapi/linux/nl80211.h | 46 +- + net/mac80211/cfg.c | 6 +- + net/mac80211/status.c | 2 +- + net/wireless/nl80211.c | 476 ++- + net/wireless/rdev-ops.h | 10 +- + net/wireless/trace.h | 2 +- + net/wireless/wext-core.c | 6 +- + 72 files changed, 36397 insertions(+), 289 deletions(-) + create mode 100644 drivers/net/wireless/nxp/Kconfig + create mode 100644 drivers/net/wireless/nxp/Makefile + create mode 100644 drivers/net/wireless/nxp/nxpwifi/11ac.c + create mode 100644 drivers/net/wireless/nxp/nxpwifi/11ac.h + create mode 100644 drivers/net/wireless/nxp/nxpwifi/11ax.c + create mode 100644 drivers/net/wireless/nxp/nxpwifi/11ax.h + create mode 100644 drivers/net/wireless/nxp/nxpwifi/11h.c + create mode 100644 drivers/net/wireless/nxp/nxpwifi/11n.c + create mode 100644 drivers/net/wireless/nxp/nxpwifi/11n.h + create mode 100644 drivers/net/wireless/nxp/nxpwifi/11n_aggr.c + create mode 100644 drivers/net/wireless/nxp/nxpwifi/11n_aggr.h + create mode 100644 drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c + create mode 100644 drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.h + create mode 100644 drivers/net/wireless/nxp/nxpwifi/Kconfig + create mode 100644 drivers/net/wireless/nxp/nxpwifi/Makefile + create mode 100644 drivers/net/wireless/nxp/nxpwifi/cfg.h + create mode 100644 drivers/net/wireless/nxp/nxpwifi/cfg80211.c + create mode 100644 drivers/net/wireless/nxp/nxpwifi/cfg80211.h + create mode 100644 drivers/net/wireless/nxp/nxpwifi/cfp.c + create mode 100644 drivers/net/wireless/nxp/nxpwifi/cmdevt.c + create mode 100644 drivers/net/wireless/nxp/nxpwifi/cmdevt.h + create mode 100644 drivers/net/wireless/nxp/nxpwifi/debugfs.c + create mode 100644 drivers/net/wireless/nxp/nxpwifi/ethtool.c + create mode 100644 drivers/net/wireless/nxp/nxpwifi/fw.h + create mode 100644 drivers/net/wireless/nxp/nxpwifi/ie.c + create mode 100644 drivers/net/wireless/nxp/nxpwifi/init.c + create mode 100644 drivers/net/wireless/nxp/nxpwifi/join.c + create mode 100644 drivers/net/wireless/nxp/nxpwifi/main.c + create mode 100644 drivers/net/wireless/nxp/nxpwifi/main.h + create mode 100644 drivers/net/wireless/nxp/nxpwifi/scan.c + create mode 100644 drivers/net/wireless/nxp/nxpwifi/sdio.c + create mode 100644 drivers/net/wireless/nxp/nxpwifi/sdio.h + create mode 100644 drivers/net/wireless/nxp/nxpwifi/sta_cfg.c + create mode 100644 drivers/net/wireless/nxp/nxpwifi/sta_cmd.c + create mode 100644 drivers/net/wireless/nxp/nxpwifi/sta_event.c + create mode 100644 drivers/net/wireless/nxp/nxpwifi/sta_rx.c + create mode 100644 drivers/net/wireless/nxp/nxpwifi/sta_tx.c + create mode 100644 drivers/net/wireless/nxp/nxpwifi/txrx.c + create mode 100644 drivers/net/wireless/nxp/nxpwifi/uap_cmd.c + create mode 100644 drivers/net/wireless/nxp/nxpwifi/uap_event.c + create mode 100644 drivers/net/wireless/nxp/nxpwifi/uap_txrx.c + create mode 100644 drivers/net/wireless/nxp/nxpwifi/util.c + create mode 100644 drivers/net/wireless/nxp/nxpwifi/util.h + create mode 100644 drivers/net/wireless/nxp/nxpwifi/wmm.c + create mode 100644 drivers/net/wireless/nxp/nxpwifi/wmm.h +Merging ath-next/for-next (913998f903fb1 wifi: ath12k: correct monitor destination ring size) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/ath/ath.git ath-next/for-next +Auto-merging drivers/net/wireless/ath/ath12k/mac.c +Merge made by the 'ort' strategy. + drivers/net/wireless/ath/ath12k/ahb.c | 1 - + drivers/net/wireless/ath/ath12k/ahb.h | 2 +- + drivers/net/wireless/ath/ath12k/core.c | 7 +- + drivers/net/wireless/ath/ath12k/debugfs.c | 14 +- + drivers/net/wireless/ath/ath12k/dp.c | 14 +- + drivers/net/wireless/ath/ath12k/dp.h | 3 +- + drivers/net/wireless/ath/ath12k/dp_mon.c | 70 +------ + drivers/net/wireless/ath/ath12k/dp_mon.h | 4 +- + drivers/net/wireless/ath/ath12k/hal.c | 42 ++-- + drivers/net/wireless/ath/ath12k/hal.h | 13 +- + drivers/net/wireless/ath/ath12k/mac.c | 9 +- + drivers/net/wireless/ath/ath12k/pci.c | 12 +- + drivers/net/wireless/ath/ath12k/qmi.c | 214 +++++++++++---------- + drivers/net/wireless/ath/ath12k/qmi.h | 26 --- + drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c | 66 +++---- + .../net/wireless/ath/ath12k/wifi7/hal_qcc2072.c | 4 +- + .../net/wireless/ath/ath12k/wifi7/hal_qcn9274.c | 13 +- + .../net/wireless/ath/ath12k/wifi7/hal_wcn7850.c | 13 +- + drivers/net/wireless/ath/ath12k/wifi7/hw.c | 61 ++++-- + drivers/net/wireless/ath/ath12k/wmi.c | 47 +++-- + drivers/net/wireless/ath/ath12k/wmi.h | 7 + + kernel/irq/manage.c | 1 + + 22 files changed, 350 insertions(+), 293 deletions(-) +Merging iwlwifi-next/next (a6136ca2dd977 wifi: iwlwifi: bump maximum core version for BZ/SC/DR to 106) +$ git merge -m Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/iwlwifi/iwlwifi-next.git iwlwifi-next/next +Already up to date. +Merging wpan-next/master (a6bfdfcc6711d ieee802154: allow legacy LLSEC ADD/DEL ops to pass strict validation) +$ git merge -m Merge branch 'master' of https://git.kernel.org/pub/scm/linux/kernel/git/wpan/wpan-next.git wpan-next/master +Already up to date. +Merging wpan-staging/staging (a6bfdfcc6711d ieee802154: allow legacy LLSEC ADD/DEL ops to pass strict validation) +$ git merge -m Merge branch 'staging' of https://git.kernel.org/pub/scm/linux/kernel/git/wpan/wpan-next.git wpan-staging/staging +Already up to date. +Merging mtd/mtd/next (d276783e490d7 mtd: maps: correct CONFIG_MTD_COMPLEX_MAPPINGS macro name in comment) +$ git merge -m Merge branch 'mtd/next' of https://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux.git mtd/mtd/next +Merge made by the 'ort' strategy. + drivers/mtd/chips/cfi_cmdset_0001.c | 6 ++++++ + drivers/mtd/maps/map_funcs.c | 2 +- + 2 files changed, 7 insertions(+), 1 deletion(-) +Merging nand/nand/next (adfc275b317c0 mtd: parsers: redboot: reject unterminated FIS names) +$ git merge -m Merge branch 'nand/next' of https://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux.git nand/nand/next +Auto-merging drivers/mtd/nand/spi/core.c +Merge made by the 'ort' strategy. + drivers/mtd/nand/raw/atmel/nand-controller.c | 3 +- + drivers/mtd/nand/raw/pl35x-nand-controller.c | 20 ++-- + drivers/mtd/nand/spi/Makefile | 2 +- + drivers/mtd/nand/spi/core.c | 1 + + drivers/mtd/nand/spi/fmsh.c | 95 +++++++++++++++++++ + drivers/mtd/nand/spi/heyangtek.c | 132 +++++++++++++++++++++++++++ + drivers/mtd/parsers/redboot.c | 9 +- + include/linux/mtd/spinand.h | 1 + + 8 files changed, 252 insertions(+), 11 deletions(-) + create mode 100644 drivers/mtd/nand/spi/heyangtek.c +Merging spi-nor/spi-nor/next (df415c5e1de0f mtd: spi-nor: spansion: add die erase support in s28hx-t) +$ git merge -m Merge branch 'spi-nor/next' of https://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux.git spi-nor/spi-nor/next +Already up to date. +Merging crypto/master (e264401ce4776 crypto: keembay - Fix AEAD unregister count in error path) +$ git merge -m Merge branch 'master' of https://git.kernel.org/pub/scm/linux/kernel/git/herbert/cryptodev-2.6.git crypto/master +Auto-merging MAINTAINERS +Auto-merging arch/arm/configs/multi_v7_defconfig +Auto-merging arch/arm64/configs/defconfig +Auto-merging drivers/char/hw_random/atmel-rng.c +Auto-merging drivers/char/hw_random/xilinx-trng.c +Auto-merging drivers/crypto/atmel-tdes.c +Auto-merging drivers/crypto/ccp/sev-dev.c +Merge made by the 'ort' strategy. + .../ABI/testing/sysfs-firmware-sev-vulnerabilities | 19 ++ + Documentation/admin-guide/sysctl/crypto.rst | 36 +++ + Documentation/crypto/userspace-if.rst | 13 +- + MAINTAINERS | 2 +- + arch/arm/configs/multi_v7_defconfig | 2 +- + arch/arm/configs/qcom_defconfig | 2 +- + arch/arm64/configs/defconfig | 2 +- + crypto/af_alg.c | 72 +++++- + crypto/algif_aead.c | 11 + + crypto/algif_hash.c | 24 ++ + crypto/algif_rng.c | 9 + + crypto/algif_skcipher.c | 20 ++ + crypto/api.c | 2 +- + crypto/crypto_user.c | 9 +- + crypto/ecc.c | 98 +++++---- + crypto/hctr2.c | 3 +- + crypto/lrw.c | 2 +- + crypto/lskcipher.c | 3 +- + crypto/xts.c | 3 +- + drivers/char/hw_random/Kconfig | 11 + + drivers/char/hw_random/Makefile | 1 + + drivers/char/hw_random/atmel-rng.c | 2 +- + drivers/char/hw_random/core.c | 4 +- + drivers/char/hw_random/omap-rng.c | 30 ++- + drivers/{crypto => char/hw_random}/qcom-rng.c | 156 +++---------- + drivers/char/hw_random/xilinx-trng.c | 32 ++- + drivers/crypto/Kconfig | 12 - + drivers/crypto/Makefile | 1 - + drivers/crypto/allwinner/Kconfig | 16 -- + drivers/crypto/allwinner/sun8i-ce/Makefile | 1 - + drivers/crypto/allwinner/sun8i-ce/sun8i-ce-core.c | 63 ------ + drivers/crypto/allwinner/sun8i-ce/sun8i-ce-prng.c | 159 -------------- + drivers/crypto/allwinner/sun8i-ce/sun8i-ce.h | 29 --- + drivers/crypto/allwinner/sun8i-ss/Makefile | 1 - + drivers/crypto/allwinner/sun8i-ss/sun8i-ss-core.c | 45 ---- + drivers/crypto/allwinner/sun8i-ss/sun8i-ss-prng.c | 177 --------------- + drivers/crypto/allwinner/sun8i-ss/sun8i-ss.h | 23 -- + drivers/crypto/amcc/crypto4xx_core.c | 11 +- + drivers/crypto/atmel-ecc.c | 42 ++-- + drivers/crypto/atmel-i2c.c | 5 +- + drivers/crypto/atmel-sha204a.c | 6 +- + drivers/crypto/atmel-tdes.c | 5 +- + drivers/crypto/caam/Kconfig | 9 - + drivers/crypto/caam/Makefile | 1 - + drivers/crypto/caam/caamprng.c | 241 --------------------- + drivers/crypto/caam/intern.h | 15 -- + drivers/crypto/caam/jr.c | 2 - + drivers/crypto/cavium/nitrox/nitrox_hal.c | 3 +- + drivers/crypto/ccp/ccp-crypto-sha.c | 2 +- + drivers/crypto/ccp/sev-dev.c | 177 +++++++++++++++ + drivers/crypto/ccp/sev-dev.h | 3 + + drivers/crypto/hisilicon/qm.c | 5 +- + .../crypto/intel/keembay/keembay-ocs-aes-core.c | 4 +- + drivers/crypto/intel/qat/qat_common/adf_aer.c | 2 + + drivers/crypto/intel/qat/qat_common/adf_cfg.c | 7 +- + .../crypto/intel/qat/qat_common/adf_cfg_services.c | 2 +- + .../crypto/intel/qat/qat_common/adf_mstate_mgr.c | 3 +- + .../intel/qat/qat_common/adf_transport_debug.c | 3 +- + drivers/crypto/intel/qat/qat_common/qat_algs.c | 1 + + .../crypto/intel/qat/qat_common/qat_compression.c | 3 +- + drivers/crypto/marvell/octeontx/otx_cptpf_ucode.c | 4 +- + .../crypto/marvell/octeontx2/otx2_cptpf_ucode.c | 4 +- + drivers/crypto/mxs-dcp.c | 2 +- + drivers/crypto/qce/aead.c | 56 +---- + drivers/crypto/qce/common.c | 55 +---- + drivers/crypto/qce/common.h | 16 +- + drivers/crypto/qce/regs-v5.h | 4 - + drivers/crypto/qce/sha.c | 61 +----- + drivers/crypto/qce/sha.h | 1 - + drivers/crypto/qce/skcipher.c | 97 +-------- + drivers/crypto/rockchip/rk3288_crypto_ahash.c | 7 +- + drivers/crypto/sa2ul.c | 6 +- + drivers/gpu/drm/ci/arm64.config | 2 +- + include/crypto/if_alg.h | 8 + + include/linux/psp-sev.h | 51 +++++ + include/linux/rhashtable.h | 1 - + lib/rhashtable.c | 2 + + 77 files changed, 686 insertions(+), 1338 deletions(-) + create mode 100644 Documentation/ABI/testing/sysfs-firmware-sev-vulnerabilities + rename drivers/{crypto => char/hw_random}/qcom-rng.c (53%) + delete mode 100644 drivers/crypto/allwinner/sun8i-ce/sun8i-ce-prng.c + delete mode 100644 drivers/crypto/allwinner/sun8i-ss/sun8i-ss-prng.c + delete mode 100644 drivers/crypto/caam/caamprng.c +Merging libcrypto/libcrypto-next (1002500f54b5f lib/crypto: md5: Remove support for md5_mod_init_arch()) +$ git merge -m Merge branch 'libcrypto-next' of https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/linux.git libcrypto/libcrypto-next +Merge made by the 'ort' strategy. + lib/crypto/md5.c | 14 -------------- + 1 file changed, 14 deletions(-) +Merging drm/drm-next (8cdeaa50eae8d Linux 7.2-rc2) +$ git merge -m Merge branch 'drm-next' of https://gitlab.freedesktop.org/drm/kernel.git drm/drm-next +Already up to date. +$ git am -3 ../patches/0001-drm-Fix-up-lut3d-mismerge.patch +Applying: drm: Fix up lut3d mismerge +Using index info to reconstruct a base tree... +M drivers/gpu/drm/drm_atomic.c +M include/drm/drm_colorop.h +Falling back to patching base and 3-way merge... +No changes -- Patch already applied. +Merging drm-exynos/for-linux-next (3a8660878839f Linux 6.18-rc1) +$ git merge -m Merge branch 'for-linux-next' of https://git.kernel.org/pub/scm/linux/kernel/git/daeinki/drm-exynos.git drm-exynos/for-linux-next +Already up to date. +Merging drm-misc/for-linux-next (b7fcb70162acd drm/ssd130x: fix column and row end address in partial updates in ssd133x) +$ git merge -m Merge branch 'for-linux-next' of https://gitlab.freedesktop.org/drm/misc/kernel.git drm-misc/for-linux-next +Auto-merging .mailmap +Auto-merging Documentation/devicetree/bindings/vendor-prefixes.yaml +Auto-merging MAINTAINERS +Auto-merging drivers/accel/amdxdna/amdxdna_gem.c +Auto-merging drivers/accel/ethosu/ethosu_drv.c +Auto-merging drivers/dma-buf/dma-fence-unwrap.c +Auto-merging drivers/dma-buf/dma-fence.c +Auto-merging drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +Auto-merging drivers/gpu/drm/bridge/analogix/analogix_dp_core.c +Auto-merging drivers/gpu/drm/bridge/inno-hdmi.c +Auto-merging drivers/gpu/drm/bridge/ssd2825.c +Auto-merging drivers/gpu/drm/bridge/tc358762.c +Auto-merging drivers/gpu/drm/i915/i915_active.c +Auto-merging drivers/gpu/drm/imagination/pvr_drv.c +Auto-merging drivers/gpu/drm/panel/panel-himax-hx83121a.c +Auto-merging drivers/gpu/drm/panthor/panthor_device.h +Auto-merging drivers/gpu/drm/panthor/panthor_mmu.c +Auto-merging drivers/gpu/drm/rockchip/rockchip_vop2_reg.c +Auto-merging drivers/gpu/drm/virtio/virtgpu_vq.c +Auto-merging drivers/gpu/drm/xe/xe_svm.c +Auto-merging drivers/gpu/drm/xe/xe_userptr.c +Merge made by the 'ort' strategy. + .mailmap | 1 + + Documentation/admin-guide/cgroup-v2.rst | 6 + + .../bindings/display/panel/anbernic,td4310.yaml | 66 ++ + .../bindings/display/panel/chipone,icna3512.yaml | 79 ++ + .../bindings/display/panel/himax,hx83121a.yaml | 3 + + .../bindings/display/panel/ilitek,ili7807s.yaml | 71 ++ + .../bindings/display/panel/ilitek,ili9488.yaml | 63 ++ + .../bindings/display/panel/renesas,r63419.yaml | 98 ++ + .../bindings/display/panel/samsung,atna33xc20.yaml | 2 + + .../display/rockchip/rockchip,analogix-dp.yaml | 47 +- + .../devicetree/bindings/gpu/img,powervr-rogue.yaml | 3 +- + .../devicetree/bindings/gpu/img,powervr-sgx.yaml | 3 +- + .../devicetree/bindings/vendor-prefixes.yaml | 6 + + Documentation/gpu/automated_testing.rst | 3 +- + Documentation/gpu/drm-kms-helpers.rst | 6 + + Documentation/gpu/drm-kms.rst | 12 + + Documentation/gpu/todo.rst | 50 +- + MAINTAINERS | 20 +- + drivers/accel/amdxdna/amdxdna_gem.c | 6 + + drivers/accel/ethosu/Makefile | 2 +- + drivers/accel/ethosu/ethosu_device.h | 35 +- + drivers/accel/ethosu/ethosu_drv.c | 23 +- + drivers/accel/ethosu/ethosu_drv.h | 61 +- + drivers/accel/ethosu/ethosu_job.c | 61 +- + drivers/accel/ethosu/ethosu_job.h | 2 + + drivers/accel/ethosu/ethosu_perfmon.c | 301 +++++++ + drivers/accel/ivpu/ivpu_drv.c | 28 +- + drivers/accel/ivpu/ivpu_drv.h | 5 +- + drivers/accel/ivpu/ivpu_hw.c | 4 + + drivers/accel/ivpu/ivpu_ipc.c | 25 +- + drivers/accel/ivpu/ivpu_ipc.h | 3 +- + drivers/accel/ivpu/ivpu_job.c | 59 +- + drivers/accel/ivpu/ivpu_job.h | 7 +- + drivers/accel/rocket/rocket_job.c | 32 +- + drivers/dma-buf/dma-fence-unwrap.c | 8 +- + drivers/dma-buf/dma-fence.c | 22 +- + drivers/dma-buf/dma-heap.c | 3 +- + drivers/dma-buf/st-dma-fence-chain.c | 4 +- + drivers/dma-buf/st-dma-fence-unwrap.c | 42 +- + drivers/dma-buf/st-dma-fence.c | 16 +- + drivers/dma-buf/st-dma-resv.c | 10 +- + drivers/firmware/Kconfig | 28 +- + drivers/gpu/buddy.c | 105 ++- + drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 115 ++- + drivers/gpu/drm/bridge/adv7511/adv7511_drv.c | 2 +- + drivers/gpu/drm/bridge/analogix/analogix_dp_core.c | 19 +- + drivers/gpu/drm/bridge/analogix/analogix_dp_core.h | 6 +- + drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c | 32 +- + drivers/gpu/drm/bridge/analogix/anx7625.c | 2 +- + drivers/gpu/drm/bridge/cadence/cdns-dsi-core.c | 9 +- + .../gpu/drm/bridge/cadence/cdns-mhdp8546-core.c | 8 +- + drivers/gpu/drm/bridge/chipone-icn6211.c | 2 +- + drivers/gpu/drm/bridge/display-connector.c | 38 +- + drivers/gpu/drm/bridge/fsl-ldb.c | 2 +- + drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pvi.c | 2 +- + drivers/gpu/drm/bridge/imx/imx8qm-ldb.c | 2 +- + drivers/gpu/drm/bridge/imx/imx8qxp-ldb.c | 2 +- + .../gpu/drm/bridge/imx/imx8qxp-pixel-combiner.c | 2 +- + drivers/gpu/drm/bridge/imx/imx8qxp-pixel-link.c | 2 +- + drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c | 2 +- + drivers/gpu/drm/bridge/inno-hdmi.c | 2 +- + drivers/gpu/drm/bridge/ite-it6263.c | 2 +- + drivers/gpu/drm/bridge/ite-it6505.c | 2 +- + drivers/gpu/drm/bridge/ite-it66121.c | 2 +- + drivers/gpu/drm/bridge/lontium-lt9211.c | 2 +- + drivers/gpu/drm/bridge/lontium-lt9611.c | 2 +- + drivers/gpu/drm/bridge/lvds-codec.c | 2 +- + drivers/gpu/drm/bridge/nwl-dsi.c | 2 +- + drivers/gpu/drm/bridge/of-display-mode-bridge.c | 2 +- + drivers/gpu/drm/bridge/panel.c | 2 +- + drivers/gpu/drm/bridge/parade-ps8640.c | 2 +- + drivers/gpu/drm/bridge/samsung-dsim.c | 2 +- + drivers/gpu/drm/bridge/sii902x.c | 6 +- + drivers/gpu/drm/bridge/ssd2825.c | 2 +- + drivers/gpu/drm/bridge/synopsys/dw-dp.c | 8 +- + drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c | 3 +- + drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 2 +- + drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c | 2 +- + drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi2.c | 2 +- + drivers/gpu/drm/bridge/tc358762.c | 2 +- + drivers/gpu/drm/bridge/tc358767.c | 4 +- + drivers/gpu/drm/bridge/tc358768.c | 2 +- + drivers/gpu/drm/bridge/tc358775.c | 2 +- + drivers/gpu/drm/bridge/ti-dlpc3433.c | 2 +- + drivers/gpu/drm/bridge/ti-sn65dsi83.c | 3 +- + drivers/gpu/drm/bridge/ti-sn65dsi86.c | 2 +- + drivers/gpu/drm/bridge/ti-tdp158.c | 2 +- + drivers/gpu/drm/bridge/ti-tfp410.c | 2 +- + drivers/gpu/drm/clients/drm_log.c | 10 +- + drivers/gpu/drm/display/drm_bridge_connector.c | 41 +- + drivers/gpu/drm/display/drm_hdmi_state_helper.c | 68 +- + drivers/gpu/drm/drm_atomic.c | 80 ++ + drivers/gpu/drm/drm_atomic_helper.c | 86 ++ + drivers/gpu/drm/drm_atomic_state_helper.c | 154 +++- + drivers/gpu/drm/drm_atomic_uapi.c | 4 + + drivers/gpu/drm/drm_bridge.c | 124 ++- + drivers/gpu/drm/drm_buddy.c | 30 +- + drivers/gpu/drm/drm_colorop.c | 41 +- + drivers/gpu/drm/drm_connector.c | 192 +++- + drivers/gpu/drm/drm_displayid_internal.h | 24 + + drivers/gpu/drm/drm_draw_internal.h | 7 - + drivers/gpu/drm/drm_drv.c | 4 +- + drivers/gpu/drm/drm_dumb_buffers.c | 1 - + drivers/gpu/drm/drm_edid.c | 80 +- + drivers/gpu/drm/drm_fb_helper.c | 11 +- + drivers/gpu/drm/drm_gem.c | 3 +- + drivers/gpu/drm/drm_gem_shmem_helper.c | 22 +- + drivers/gpu/drm/drm_gpuvm.c | 44 - + drivers/gpu/drm/drm_mode_config.c | 189 +++- + drivers/gpu/drm/drm_panic.c | 6 +- + drivers/gpu/drm/drm_print.c | 4 +- + drivers/gpu/drm/drm_writeback.c | 6 + + drivers/gpu/drm/gma500/psb_drv.c | 56 +- + drivers/gpu/drm/hisilicon/hibmc/Kconfig | 4 +- + drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_de.c | 114 +-- + drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c | 78 +- + drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h | 5 + + drivers/gpu/drm/hyperv/hyperv_drm.h | 16 +- + drivers/gpu/drm/hyperv/hyperv_drm_drv.c | 92 +- + drivers/gpu/drm/hyperv/hyperv_drm_modeset.c | 110 +-- + drivers/gpu/drm/hyperv/hyperv_drm_proto.c | 70 +- + drivers/gpu/drm/i915/display/intel_crtc.c | 2 +- + drivers/gpu/drm/i915/display/intel_dp.c | 43 +- + drivers/gpu/drm/i915/display/intel_plane.c | 2 +- + drivers/gpu/drm/i915/i915_active.c | 2 +- + drivers/gpu/drm/imagination/pvr_drv.c | 2 +- + drivers/gpu/drm/imx/ipuv3/parallel-display.c | 2 +- + drivers/gpu/drm/ingenic/ingenic-drm-drv.c | 2 +- + drivers/gpu/drm/lima/lima_device.c | 12 +- + drivers/gpu/drm/loongson/lsdc_drv.c | 4 +- + drivers/gpu/drm/mediatek/mtk_dp.c | 2 +- + drivers/gpu/drm/mediatek/mtk_dpi.c | 2 +- + drivers/gpu/drm/mediatek/mtk_dsi.c | 2 +- + drivers/gpu/drm/mediatek/mtk_hdmi.c | 2 +- + drivers/gpu/drm/mediatek/mtk_hdmi_v2.c | 2 +- + drivers/gpu/drm/meson/meson_encoder_cvbs.c | 2 +- + drivers/gpu/drm/meson/meson_encoder_dsi.c | 2 +- + drivers/gpu/drm/meson/meson_encoder_hdmi.c | 2 +- + drivers/gpu/drm/mgag200/mgag200_drv.c | 24 +- + drivers/gpu/drm/msm/dp/dp_drm.c | 4 +- + drivers/gpu/drm/msm/dsi/dsi_host.c | 25 +- + drivers/gpu/drm/msm/hdmi/hdmi_bridge.c | 2 +- + drivers/gpu/drm/msm/msm_drv.c | 2 - + drivers/gpu/drm/mxsfb/lcdif_drv.c | 2 +- + drivers/gpu/drm/mxsfb/lcdif_kms.c | 18 +- + drivers/gpu/drm/mxsfb/lcdif_regs.h | 1 + + drivers/gpu/drm/nouveau/nouveau_drm.c | 35 +- + drivers/gpu/drm/nouveau/nouveau_gem.c | 11 +- + drivers/gpu/drm/nouveau/nvkm/engine/disp/gv100.c | 4 +- + drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h | 2 + + .../gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c | 8 +- + drivers/gpu/drm/nouveau/nvkm/subdev/mmu/vmm.c | 31 +- + drivers/gpu/drm/omapdrm/dss/hdmi4.c | 2 +- + drivers/gpu/drm/omapdrm/dss/hdmi5.c | 2 +- + drivers/gpu/drm/panel/Kconfig | 56 ++ + drivers/gpu/drm/panel/Makefile | 5 + + drivers/gpu/drm/panel/panel-anbernic-td4310.c | 257 ++++++ + drivers/gpu/drm/panel/panel-chipone-icna35xx.c | 422 +++++++++ + drivers/gpu/drm/panel/panel-edp.c | 19 + + drivers/gpu/drm/panel/panel-himax-hx83121a.c | 39 +- + drivers/gpu/drm/panel/panel-ilitek-ili7807s.c | 285 ++++++ + drivers/gpu/drm/panel/panel-ilitek-ili9488.c | 289 ++++++ + drivers/gpu/drm/panel/panel-novatek-nt36672a.c | 522 ++++------- + drivers/gpu/drm/panel/panel-renesas-r63419.c | 350 ++++++++ + drivers/gpu/drm/panel/panel-visionox-vtdr6130.c | 42 +- + drivers/gpu/drm/panthor/panthor_device.h | 3 + + drivers/gpu/drm/panthor/panthor_drv.c | 14 +- + drivers/gpu/drm/panthor/panthor_gem.c | 18 + + drivers/gpu/drm/panthor/panthor_gem.h | 2 + + drivers/gpu/drm/panthor/panthor_mmu.c | 259 ++++-- + drivers/gpu/drm/qxl/qxl_drv.c | 15 +- + drivers/gpu/drm/renesas/rcar-du/rcar_du_drv.c | 20 +- + drivers/gpu/drm/renesas/rcar-du/rcar_lvds.c | 2 +- + drivers/gpu/drm/renesas/rcar-du/rcar_mipi_dsi.c | 2 +- + drivers/gpu/drm/renesas/rz-du/rzg2l_mipi_dsi.c | 2 +- + drivers/gpu/drm/rockchip/analogix_dp-rockchip.c | 31 +- + drivers/gpu/drm/rockchip/cdn-dp-core.c | 2 +- + drivers/gpu/drm/rockchip/dw_dp-rockchip.c | 15 +- + drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 227 +++-- + drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c | 111 ++- + drivers/gpu/drm/rockchip/rk3066_hdmi.c | 2 +- + drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 4 + + drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 176 +++- + drivers/gpu/drm/rockchip/rockchip_drm_vop2.h | 2 +- + drivers/gpu/drm/rockchip/rockchip_lvds.c | 2 +- + drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 46 +- + drivers/gpu/drm/scheduler/sched_entity.c | 11 +- + drivers/gpu/drm/scheduler/sched_main.c | 9 - + drivers/gpu/drm/scheduler/tests/tests_basic.c | 359 ++++++++ + drivers/gpu/drm/scheduler/tests/tests_scheduler.c | 2 +- + drivers/gpu/drm/solomon/ssd130x.c | 360 +++----- + drivers/gpu/drm/stm/lvds.c | 2 +- + drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c | 2 +- + drivers/gpu/drm/sysfb/Kconfig | 7 +- + drivers/gpu/drm/sysfb/simpledrm.c | 109 ++- + drivers/gpu/drm/tegra/drm.c | 2 + + drivers/gpu/drm/tegra/dsi.c | 126 ++- + drivers/gpu/drm/tegra/dsi.h | 10 + + drivers/gpu/drm/tests/Makefile | 3 +- + drivers/gpu/drm/tests/drm_bridge_test.c | 973 +++++++++++++++++++- + drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c | 347 ++++++- + drivers/gpu/drm/tests/drm_kunit_edid.c | 995 +++++++++++++++++++++ + drivers/gpu/drm/tests/drm_kunit_edid.h | 985 +------------------- + drivers/gpu/drm/tidss/tidss_crtc.c | 17 +- + drivers/gpu/drm/tidss/tidss_encoder.c | 2 +- + drivers/gpu/drm/tidss/tidss_oldi.c | 2 +- + drivers/gpu/drm/tidss/tidss_plane.c | 2 +- + drivers/gpu/drm/tiny/gm12u320.c | 5 +- + drivers/gpu/drm/ttm/tests/ttm_bo_validate_test.c | 2 +- + drivers/gpu/drm/ttm/ttm_bo.c | 2 +- + drivers/gpu/drm/ttm/ttm_pool.c | 37 +- + drivers/gpu/drm/v3d/Kconfig | 1 + + drivers/gpu/drm/v3d/v3d_drv.c | 20 +- + drivers/gpu/drm/v3d/v3d_drv.h | 36 +- + drivers/gpu/drm/v3d/v3d_sched.c | 66 +- + drivers/gpu/drm/v3d/v3d_submit.c | 770 ++++++++-------- + drivers/gpu/drm/vc4/vc4_dsi.c | 2 +- + drivers/gpu/drm/vc4/vc4_hdmi.c | 2 +- + drivers/gpu/drm/verisilicon/vs_bridge.c | 4 +- + drivers/gpu/drm/virtio/virtgpu_drv.c | 101 ++- + drivers/gpu/drm/virtio/virtgpu_drv.h | 26 +- + drivers/gpu/drm/virtio/virtgpu_kms.c | 71 +- + drivers/gpu/drm/virtio/virtgpu_object.c | 90 +- + drivers/gpu/drm/virtio/virtgpu_prime.c | 45 +- + drivers/gpu/drm/virtio/virtgpu_vq.c | 50 +- + drivers/gpu/drm/virtio/virtgpu_vram.c | 5 +- + drivers/gpu/drm/xe/xe_bo.c | 2 +- + drivers/gpu/drm/xe/xe_device.c | 4 +- + drivers/gpu/drm/xe/xe_sched_job.c | 2 +- + drivers/gpu/drm/xe/xe_svm.c | 2 +- + drivers/gpu/drm/xe/xe_userptr.c | 2 +- + drivers/gpu/drm/xe/xe_vm.c | 4 +- + drivers/gpu/drm/xlnx/zynqmp_dp.c | 2 +- + include/drm/bridge/analogix_dp.h | 13 +- + include/drm/bridge/dw_dp.h | 1 + + include/drm/display/drm_dp.h | 13 +- + include/drm/display/drm_hdmi_state_helper.h | 4 +- + include/drm/drm_atomic.h | 5 +- + include/drm/drm_atomic_helper.h | 7 + + include/drm/drm_atomic_state_helper.h | 18 +- + include/drm/drm_bridge.h | 37 +- + include/drm/drm_colorop.h | 2 + + include/drm/drm_connector.h | 118 +++ + include/drm/drm_crtc.h | 16 + + include/drm/drm_debugfs.h | 2 +- + include/drm/drm_drv.h | 6 - + include/drm/drm_gem.h | 3 - + include/drm/drm_gem_shmem_helper.h | 4 + + include/drm/drm_gpuvm.h | 4 +- + include/drm/drm_managed.h | 2 +- + include/drm/drm_mipi_dsi.h | 2 + + include/drm/drm_mode_config.h | 1 + + include/drm/drm_plane.h | 16 + + include/drm/drm_print.h | 2 +- + include/drm/gpu_scheduler.h | 3 +- + include/linux/dma-fence-unwrap.h | 6 +- + include/linux/dma-fence.h | 4 +- + include/linux/font.h | 3 + + include/linux/gpu_buddy.h | 15 + + include/linux/sysfb.h | 4 +- + include/uapi/drm/drm_mode.h | 1 + + include/uapi/drm/ethosu_accel.h | 60 +- + include/uapi/drm/panthor_drm.h | 26 +- + kernel/cgroup/dmem.c | 15 + + lib/fonts/fonts.c | 31 + + 265 files changed, 9889 insertions(+), 3258 deletions(-) + create mode 100644 Documentation/devicetree/bindings/display/panel/anbernic,td4310.yaml + create mode 100644 Documentation/devicetree/bindings/display/panel/chipone,icna3512.yaml + create mode 100644 Documentation/devicetree/bindings/display/panel/ilitek,ili7807s.yaml + create mode 100644 Documentation/devicetree/bindings/display/panel/ilitek,ili9488.yaml + create mode 100644 Documentation/devicetree/bindings/display/panel/renesas,r63419.yaml + create mode 100644 drivers/accel/ethosu/ethosu_perfmon.c + create mode 100644 drivers/gpu/drm/panel/panel-anbernic-td4310.c + create mode 100644 drivers/gpu/drm/panel/panel-chipone-icna35xx.c + create mode 100644 drivers/gpu/drm/panel/panel-ilitek-ili7807s.c + create mode 100644 drivers/gpu/drm/panel/panel-ilitek-ili9488.c + create mode 100644 drivers/gpu/drm/panel/panel-renesas-r63419.c + create mode 100644 drivers/gpu/drm/tests/drm_kunit_edid.c +$ git am -3 ../patches/0001-drm-bridge-microchip-lvds-Rename-drm_atomic_state-to.patch +Applying: drm/bridge: microchip-lvds: Rename drm_atomic_state to drm_atomic_commit +Using index info to reconstruct a base tree... +M drivers/gpu/drm/bridge/microchip-lvds.c +Falling back to patching base and 3-way merge... +No changes -- Patch already applied. +Merging amdgpu/drm-next (50be7c9b5d5ea drm/amdgpu: Do not fiddle with the idle workers too much) +$ git merge -m Merge branch 'drm-next' of https://gitlab.freedesktop.org/agd5f/linux.git amdgpu/drm-next +Auto-merging drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c +Auto-merging drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h +Auto-merging drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c +Auto-merging drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c +Auto-merging drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c +CONFLICT (content): Merge conflict in drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c +Auto-merging drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c +Auto-merging drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +CONFLICT (content): Merge conflict in drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +Auto-merging drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c +Auto-merging drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +Auto-merging drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c +Auto-merging drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c +Auto-merging drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h +Auto-merging drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c +Auto-merging drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.h +Auto-merging drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +Auto-merging drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c +Auto-merging drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c +Auto-merging drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c +Auto-merging drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c +Auto-merging drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c +Auto-merging drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c +Auto-merging drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c +Auto-merging drivers/gpu/drm/amd/amdgpu/jpeg_v4_0_3.c +Auto-merging drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c +Auto-merging drivers/gpu/drm/amd/amdgpu/mes_v11_0.c +Auto-merging drivers/gpu/drm/amd/amdgpu/mes_v12_0.c +Auto-merging drivers/gpu/drm/amd/amdgpu/mes_v12_1.c +Auto-merging drivers/gpu/drm/amd/amdgpu/sdma_v4_4_2.c +Auto-merging drivers/gpu/drm/amd/amdgpu/sdma_v6_0.c +Auto-merging drivers/gpu/drm/amd/amdgpu/sdma_v7_0.c +Auto-merging drivers/gpu/drm/amd/amdgpu/sdma_v7_1.c +Auto-merging drivers/gpu/drm/amd/amdgpu/soc21.c +Auto-merging drivers/gpu/drm/amd/amdgpu/soc24.c +Auto-merging drivers/gpu/drm/amd/amdgpu/vcn_v4_0.c +Auto-merging drivers/gpu/drm/amd/amdkfd/kfd_chardev.c +Auto-merging drivers/gpu/drm/amd/amdkfd/kfd_device.c +Auto-merging drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.h +Auto-merging drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c +Auto-merging drivers/gpu/drm/amd/amdkfd/kfd_process.c +Auto-merging drivers/gpu/drm/amd/amdkfd/kfd_process_queue_manager.c +Auto-merging drivers/gpu/drm/amd/amdkfd/kfd_smi_events.c +CONFLICT (content): Merge conflict in drivers/gpu/drm/amd/amdkfd/kfd_smi_events.c +Auto-merging drivers/gpu/drm/amd/amdkfd/kfd_svm.c +Auto-merging drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +CONFLICT (content): Merge conflict in drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +Auto-merging drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +Auto-merging drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c +Auto-merging drivers/gpu/drm/amd/display/dc/core/dc.c +Auto-merging drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.h +Auto-merging drivers/gpu/drm/amd/display/dc/link/link_detection.c +Auto-merging drivers/gpu/drm/amd/include/mes_v11_api_def.h +Auto-merging drivers/gpu/drm/amd/pm/amdgpu_pm.c +Auto-merging drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c +Auto-merging drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_7_ppt.c +Auto-merging drivers/gpu/drm/drm_connector.c +Auto-merging drivers/gpu/drm/drm_displayid_internal.h +Auto-merging drivers/gpu/drm/drm_edid.c +Auto-merging include/drm/drm_connector.h +Resolved 'drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c' using previous resolution. +Resolved 'drivers/gpu/drm/amd/amdgpu/amdgpu_device.c' using previous resolution. +Resolved 'drivers/gpu/drm/amd/amdkfd/kfd_smi_events.c' using previous resolution. +Resolved 'drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c' using previous resolution. +Automatic merge failed; fix conflicts and then commit the result. +$ git commit --no-edit -v -a +[master 38de4fb7f9a1b] Merge branch 'drm-next' of https://gitlab.freedesktop.org/agd5f/linux.git +$ git diff -M --stat --summary HEAD^.. + drivers/gpu/drm/amd/amdgpu/Makefile | 2 +- + drivers/gpu/drm/amd/amdgpu/amdgpu.h | 52 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_aca.c | 985 --- + drivers/gpu/drm/amd/amdgpu/amdgpu_aca.h | 232 - + drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.c | 68 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c | 14 + + drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h | 28 +- + .../gpu/drm/amd/amdgpu/amdgpu_amdkfd_gc_9_4_3.c | 63 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c | 20 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_atombios.c | 2 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_atpx_handler.c | 33 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c | 10 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.c | 184 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.h | 22 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_cper.c | 116 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_cper.h | 8 - + drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c | 67 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c | 215 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.h | 14 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c | 7 + + drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c | 1 + + drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 188 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c | 312 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.h | 6 + + drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c | 27 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c | 53 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c | 284 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h | 24 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c | 9 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.h | 1 - + drivers/gpu/drm/amd/amdgpu/amdgpu_ip.c | 151 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_ip.h | 8 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_job.c | 13 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_jpeg.c | 9 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_jpeg.h | 3 + + drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c | 38 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_mca.c | 486 -- + drivers/gpu/drm/amd/amdgpu/amdgpu_mca.h | 107 - + drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c | 70 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_mes.h | 15 + + drivers/gpu/drm/amd/amdgpu/amdgpu_mmhub.h | 23 - + drivers/gpu/drm/amd/amdgpu/amdgpu_object.h | 40 - + drivers/gpu/drm/amd/amdgpu/amdgpu_preempt_mgr.c | 14 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_psp_ta.c | 5 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c | 860 +-- + drivers/gpu/drm/amd/amdgpu/amdgpu_ras.h | 40 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c | 368 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.h | 3 - + drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c | 188 + + drivers/gpu/drm/amd/amdgpu/amdgpu_ring.h | 8 + + drivers/gpu/drm/amd/amdgpu/amdgpu_rlc.c | 39 + + drivers/gpu/drm/amd/amdgpu/amdgpu_rlc.h | 10 + + drivers/gpu/drm/amd/amdgpu/amdgpu_sa.h | 77 + + drivers/gpu/drm/amd/amdgpu/amdgpu_sched.c | 8 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_sdma.c | 19 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_sdma.h | 28 - + drivers/gpu/drm/amd/amdgpu/amdgpu_trace.h | 150 + + drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c | 2 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.h | 1 + + drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c | 263 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_umc.h | 26 - + drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 165 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h | 16 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c | 10 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_vcn.c | 76 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_vcn.h | 3 + + drivers/gpu/drm/amd/amdgpu/amdgpu_xcp.c | 42 +- + drivers/gpu/drm/amd/amdgpu/amdgpu_xgmi.c | 299 +- + drivers/gpu/drm/amd/amdgpu/aqua_vanjaram.c | 61 +- + drivers/gpu/drm/amd/amdgpu/atom.c | 66 +- + drivers/gpu/drm/amd/amdgpu/atom.h | 3 +- + drivers/gpu/drm/amd/amdgpu/cik.c | 7 - + drivers/gpu/drm/amd/amdgpu/dce_v10_0.c | 66 - + drivers/gpu/drm/amd/amdgpu/dce_v6_0.c | 57 - + drivers/gpu/drm/amd/amdgpu/dce_v8_0.c | 57 - + drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c | 2 + + drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c | 334 +- + drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c | 251 +- + drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c | 4 + + drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c | 232 +- + drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c | 2 + + drivers/gpu/drm/amd/amdgpu/gfx_v9_4_3.c | 1093 +-- + drivers/gpu/drm/amd/amdgpu/gmc_v8_0.c | 125 - + drivers/gpu/drm/amd/amdgpu/gmc_v9_0.c | 19 +- + drivers/gpu/drm/amd/amdgpu/jpeg_v4_0_3.c | 88 +- + drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c | 92 +- + drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_2.c | 3 - + drivers/gpu/drm/amd/amdgpu/mes_userqueue.c | 147 +- + drivers/gpu/drm/amd/amdgpu/mes_userqueue.h | 9 + + drivers/gpu/drm/amd/amdgpu/mes_v11_0.c | 240 +- + drivers/gpu/drm/amd/amdgpu/mes_v12_0.c | 267 +- + drivers/gpu/drm/amd/amdgpu/mes_v12_1.c | 25 +- + drivers/gpu/drm/amd/amdgpu/mmhub_v1_8.c | 231 +- + drivers/gpu/drm/amd/amdgpu/nv.c | 6 - + drivers/gpu/drm/amd/amdgpu/sdma_v3_0.c | 62 - + drivers/gpu/drm/amd/amdgpu/sdma_v4_4_2.c | 196 +- + drivers/gpu/drm/amd/amdgpu/sdma_v6_0.c | 18 - + drivers/gpu/drm/amd/amdgpu/sdma_v7_0.c | 18 - + drivers/gpu/drm/amd/amdgpu/sdma_v7_1.c | 18 - + drivers/gpu/drm/amd/amdgpu/si.c | 7 - + drivers/gpu/drm/amd/amdgpu/soc15.c | 9 - + drivers/gpu/drm/amd/amdgpu/soc15_common.h | 61 +- + drivers/gpu/drm/amd/amdgpu/soc21.c | 12 - + drivers/gpu/drm/amd/amdgpu/soc24.c | 11 - + drivers/gpu/drm/amd/amdgpu/soc_v1_0.c | 14 +- + drivers/gpu/drm/amd/amdgpu/tonga_ih.c | 40 - + drivers/gpu/drm/amd/amdgpu/umc_v12_0.c | 585 +- + drivers/gpu/drm/amd/amdgpu/umc_v12_0.h | 28 - + drivers/gpu/drm/amd/amdgpu/uvd_v6_0.c | 45 - + drivers/gpu/drm/amd/amdgpu/vce_v3_0.c | 69 - + drivers/gpu/drm/amd/amdgpu/vcn_v2_5.c | 12 +- + drivers/gpu/drm/amd/amdgpu/vcn_v4_0.c | 45 +- + drivers/gpu/drm/amd/amdgpu/vcn_v4_0_3.c | 87 +- + drivers/gpu/drm/amd/amdgpu/vcn_v4_0_5.c | 6 +- + drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c | 90 +- + drivers/gpu/drm/amd/amdgpu/vcn_v5_0_2.c | 3 - + drivers/gpu/drm/amd/amdgpu/vi.c | 22 - + drivers/gpu/drm/amd/amdkfd/kfd_chardev.c | 12 +- + drivers/gpu/drm/amd/amdkfd/kfd_device.c | 24 + + .../gpu/drm/amd/amdkfd/kfd_device_queue_manager.c | 223 +- + .../gpu/drm/amd/amdkfd/kfd_device_queue_manager.h | 2 + + drivers/gpu/drm/amd/amdkfd/kfd_events.c | 224 +- + drivers/gpu/drm/amd/amdkfd/kfd_events.h | 4 - + drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.c | 16 + + drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.h | 2 + + drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c | 14 - + drivers/gpu/drm/amd/amdkfd/kfd_priv.h | 25 +- + drivers/gpu/drm/amd/amdkfd/kfd_process.c | 76 +- + .../gpu/drm/amd/amdkfd/kfd_process_queue_manager.c | 8 +- + drivers/gpu/drm/amd/amdkfd/kfd_smi_events.c | 4 +- + drivers/gpu/drm/amd/amdkfd/kfd_svm.c | 8 +- + drivers/gpu/drm/amd/amdkfd/kfd_topology.c | 11 +- + drivers/gpu/drm/amd/amdxcp/amdgpu_xcp_drv.c | 18 +- + drivers/gpu/drm/amd/display/amdgpu_dm/Makefile | 6 +- + drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 7222 +------------------- + drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 79 +- + .../drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c | 323 + + .../amdgpu_dm_audio.h} | 47 +- + .../amd/display/amdgpu_dm/amdgpu_dm_backlight.c | 726 ++ + .../amd/display/amdgpu_dm/amdgpu_dm_backlight.h | 75 + + .../drm/amd/display/amdgpu_dm/amdgpu_dm_color.c | 183 +- + .../drm/amd/display/amdgpu_dm/amdgpu_dm_color.h | 8 +- + .../drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.c | 1 + + .../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 3601 ++++++++++ + .../amd/display/amdgpu_dm/amdgpu_dm_connector.h | 162 + + .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c | 102 +- + .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.h | 6 + + .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c | 22 +- + .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.h | 6 + + .../drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c | 78 +- + .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c | 943 +++ + .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h | 68 + + .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c | 115 +- + .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h | 12 + + .../drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 66 +- + .../drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h | 20 + + .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 1548 ++++- + .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h | 27 + + .../display/amdgpu_dm/amdgpu_dm_kunit_helpers.h | 1 + + .../amd/display/amdgpu_dm/amdgpu_dm_mst_types.c | 189 +- + .../amd/display/amdgpu_dm/amdgpu_dm_mst_types.h | 25 + + .../drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c | 133 +- + .../drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h | 51 + + .../drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c | 327 +- + .../drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h | 61 + + .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.c | 51 +- + .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.h | 5 + + .../drm/amd/display/amdgpu_dm/amdgpu_dm_quirks.c | 2 + + .../drm/amd/display/amdgpu_dm/amdgpu_dm_replay.c | 10 +- + .../drm/amd/display/amdgpu_dm/amdgpu_dm_services.c | 7 + + .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c | 16 +- + .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.h | 13 + + .../drm/amd/display/amdgpu_dm/tests/.kunitconfig | 6 + + .../gpu/drm/amd/display/amdgpu_dm/tests/Makefile | 18 + + .../display/amdgpu_dm/tests/amdgpu_dm_audio_test.c | 490 ++ + .../amdgpu_dm/tests/amdgpu_dm_backlight_test.c | 1242 ++++ + .../display/amdgpu_dm/tests/amdgpu_dm_color_test.c | 64 +- + .../amdgpu_dm/tests/amdgpu_dm_colorop_test.c | 140 +- + .../amdgpu_dm/tests/amdgpu_dm_connector_test.c | 2158 ++++++ + .../display/amdgpu_dm/tests/amdgpu_dm_crc_test.c | 122 + + .../display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c | 523 ++ + .../display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c | 582 ++ + .../display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c | 297 +- + .../amdgpu_dm/tests/amdgpu_dm_helpers_test.c | 634 ++ + .../display/amdgpu_dm/tests/amdgpu_dm_irq_test.c | 909 +++ + .../display/amdgpu_dm/tests/amdgpu_dm_ism_test.c | 39 +- + .../amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c | 142 + + .../amdgpu_dm/tests/amdgpu_dm_kunit_test_helpers.h | 32 + + .../amdgpu_dm/tests/amdgpu_dm_mst_types_test.c | 1092 +++ + .../display/amdgpu_dm/tests/amdgpu_dm_plane_test.c | 1228 ++++ + .../amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c | 2454 +++++++ + .../display/amdgpu_dm/tests/amdgpu_dm_psr_test.c | 518 +- + .../amdgpu_dm/tests/amdgpu_dm_quirks_test.c | 103 + + .../amdgpu_dm/tests/amdgpu_dm_replay_test.c | 432 +- + .../amdgpu_dm/tests/amdgpu_dm_services_test.c | 313 + + .../amd/display/amdgpu_dm/tests/amdgpu_dm_test.c | 949 +++ + .../display/amdgpu_dm/tests/amdgpu_dm_wb_test.c | 392 ++ + .../amd/display/dc/clk_mgr/dcn401/dcn401_clk_mgr.c | 57 +- + .../amd/display/dc/clk_mgr/dcn401/dcn401_clk_mgr.h | 9 + + drivers/gpu/drm/amd/display/dc/core/dc.c | 293 +- + .../gpu/drm/amd/display/dc/core/dc_hw_sequencer.c | 174 +- + drivers/gpu/drm/amd/display/dc/core/dc_resource.c | 11 +- + drivers/gpu/drm/amd/display/dc/core/dc_surface.c | 44 +- + drivers/gpu/drm/amd/display/dc/dc.h | 238 +- + drivers/gpu/drm/amd/display/dc/dc_dmub_srv.c | 41 +- + drivers/gpu/drm/amd/display/dc/dc_dmub_srv.h | 43 +- + drivers/gpu/drm/amd/display/dc/dc_dp_types.h | 1 + + drivers/gpu/drm/amd/display/dc/dc_helper.c | 226 - + drivers/gpu/drm/amd/display/dc/dc_stream.h | 29 + + drivers/gpu/drm/amd/display/dc/dc_types.h | 67 +- + .../gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c | 59 +- + .../gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.h | 1 + + drivers/gpu/drm/amd/display/dc/dce/dce_aux.c | 9 +- + .../drm/amd/display/dc/dce110/dce110_compressor.c | 2 - + .../drm/amd/display/dc/dce110/dce110_mem_input_v.c | 2 - + .../drm/amd/display/dc/dce112/dce112_compressor.c | 8 +- + .../gpu/drm/amd/display/dc/dcn30/dcn30_cm_common.c | 184 + + .../display/dc/dio/dcn31/dcn31_dio_link_encoder.c | 4 + + .../display/dc/dio/dcn31/dcn31_dio_link_encoder.h | 2 + + drivers/gpu/drm/amd/display/dc/dm_services.h | 4 - + .../gpu/drm/amd/display/dc/dml/calcs/dcn_calcs.c | 2 +- + .../amd/display/dc/dml/dcn31/display_mode_vba_31.c | 2 +- + .../display/dc/dml/dcn314/display_mode_vba_314.c | 2 +- + .../display/dc/dml2_0/dml21/dml21_wrapper_fpu.c | 8 +- + .../dml21/inc/bounding_boxes/dcn42b_soc_bb.h | 40 +- + .../dml2_0/dml21/inc/dml_top_soc_parameter_types.h | 13 + + .../dml21/src/dml2_core/dml2_core_dcn4_calcs.c | 41 +- + .../dml2_0/dml21/src/dml2_core/dml2_core_factory.c | 2 - + .../dml21/src/dml2_core/dml2_core_shared_types.h | 2 - + .../dml21/src/inc/dml2_internal_shared_types.h | 1 - + .../drm/amd/display/dc/dpp/dcn10/dcn10_dpp_cm.c | 5 - + .../drm/amd/display/dc/dwb/dcn30/dcn30_cm_common.h | 4 + + .../amd/display/dc/gpio/dce80/hw_translate_dce80.c | 5 +- + .../amd/display/dc/gpio/dcn10/hw_translate_dcn10.c | 484 +- + .../amd/display/dc/gpio/dcn20/hw_translate_dcn20.c | 434 +- + .../amd/display/dc/gpio/dcn21/hw_translate_dcn21.c | 419 +- + .../amd/display/dc/gpio/dcn30/hw_translate_dcn30.c | 434 +- + .../display/dc/gpio/dcn315/hw_translate_dcn315.c | 420 +- + .../amd/display/dc/gpio/dcn32/hw_translate_dcn32.c | 386 +- + .../display/dc/gpio/dcn401/hw_translate_dcn401.c | 394 +- + .../amd/display/dc/gpio/dcn42/hw_translate_dcn42.c | 191 +- + drivers/gpu/drm/amd/display/dc/gpio/hw_translate.c | 86 + + drivers/gpu/drm/amd/display/dc/gpio/hw_translate.h | 21 + + .../drm/amd/display/dc/hubbub/dcn31/dcn31_hubbub.c | 8 +- + .../drm/amd/display/dc/hubbub/dcn35/dcn35_hubbub.c | 2 +- + .../drm/amd/display/dc/hubp/dcn401/dcn401_hubp.c | 2 +- + .../gpu/drm/amd/display/dc/hubp/dcn42/dcn42_hubp.c | 6 + + .../drm/amd/display/dc/hwss/dce110/dce110_hwseq.c | 8 +- + .../drm/amd/display/dc/hwss/dce60/dce60_hwseq.c | 8 +- + .../drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.c | 62 +- + .../drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c | 76 +- + .../drm/amd/display/dc/hwss/dcn201/dcn201_hwseq.c | 2 +- + .../drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c | 10 +- + .../drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c | 36 +- + .../drm/amd/display/dc/hwss/dcn35/dcn35_hwseq.c | 3 - + .../drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c | 341 +- + .../drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.h | 12 +- + .../drm/amd/display/dc/hwss/dcn401/dcn401_init.c | 2 + + .../drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c | 327 +- + .../drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.h | 5 +- + drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer.h | 82 + + .../drm/amd/display/dc/hwss/hw_sequencer_private.h | 3 +- + drivers/gpu/drm/amd/display/dc/inc/hw/clk_mgr.h | 12 + + drivers/gpu/drm/amd/display/dc/inc/hw/dccg.h | 1 + + drivers/gpu/drm/amd/display/dc/inc/hw/hubp.h | 2 +- + drivers/gpu/drm/amd/display/dc/inc/link_service.h | 1 + + drivers/gpu/drm/amd/display/dc/inc/reg_helper.h | 19 - + .../drm/amd/display/dc/inc/soc_and_ip_translator.h | 14 - + .../gpu/drm/amd/display/dc/link/link_detection.c | 2 +- + drivers/gpu/drm/amd/display/dc/link/link_factory.c | 7 +- + .../drm/amd/display/dc/link/protocols/link_ddc.c | 20 +- + .../drm/amd/display/dc/link/protocols/link_ddc.h | 2 + + .../display/dc/link/protocols/link_dp_capability.c | 14 +- + .../dc/link/protocols/link_dp_panel_replay.c | 3 +- + .../dc/link/protocols/link_edp_panel_control.c | 8 +- + .../gpu/drm/amd/display/dc/mpc/dcn20/dcn20_mpc.c | 4 - + .../gpu/drm/amd/display/dc/opp/dcn20/dcn20_opp.c | 51 +- + .../gpu/drm/amd/display/dc/optc/dcn10/dcn10_optc.c | 5 - + .../gpu/drm/amd/display/dc/optc/dcn20/dcn20_optc.c | 5 - + .../gpu/drm/amd/display/dc/optc/dcn31/dcn31_optc.c | 5 - + .../drm/amd/display/dc/optc/dcn314/dcn314_optc.c | 5 - + .../gpu/drm/amd/display/dc/optc/dcn32/dcn32_optc.c | 5 - + .../gpu/drm/amd/display/dc/optc/dcn35/dcn35_optc.c | 5 - + .../drm/amd/display/dc/optc/dcn401/dcn401_optc.c | 5 - + .../drm/amd/display/dc/pg/dcn42/dcn42_pg_cntl.c | 258 +- + .../drm/amd/display/dc/pg/dcn42/dcn42_pg_cntl.h | 22 +- + .../amd/display/dc/resource/dcn30/dcn30_resource.c | 1 - + .../display/dc/resource/dcn302/dcn302_resource.c | 1 - + .../display/dc/resource/dcn303/dcn303_resource.c | 1 - + .../amd/display/dc/resource/dcn31/dcn31_resource.c | 5 +- + .../display/dc/resource/dcn314/dcn314_resource.c | 4 +- + .../display/dc/resource/dcn315/dcn315_resource.c | 5 +- + .../display/dc/resource/dcn316/dcn316_resource.c | 5 +- + .../amd/display/dc/resource/dcn32/dcn32_resource.c | 1 - + .../display/dc/resource/dcn321/dcn321_resource.c | 1 - + .../amd/display/dc/resource/dcn35/dcn35_resource.c | 6 +- + .../display/dc/resource/dcn351/dcn351_resource.c | 6 +- + .../amd/display/dc/resource/dcn36/dcn36_resource.c | 6 +- + .../amd/display/dc/resource/dcn42/dcn42_resource.c | 5 +- + .../display/dc/resource/dcn42b/dcn42b_resource.c | 6 +- + .../amd/display/dc/soc_and_ip_translator/Makefile | 3 + + .../dcn42/dcn42_soc_and_ip_translator.c | 18 +- + .../dcn42/dcn42_soc_and_ip_translator.h | 1 + + .../dcn42b/dcn42b_soc_and_ip_translator.c | 42 + + .../dcn42b/dcn42b_soc_and_ip_translator.h | 17 + + .../soc_and_ip_translator/soc_and_ip_translator.c | 5 +- + drivers/gpu/drm/amd/display/dc/sspl/spl_debug.h | 4 +- + drivers/gpu/drm/amd/display/dmub/inc/dmub_cmd.h | 289 +- + .../drm/amd/display/include/ddc_service_types.h | 1 + + drivers/gpu/drm/amd/display/include/gpio_types.h | 48 + + drivers/gpu/drm/amd/display/modules/power/Makefile | 2 +- + drivers/gpu/drm/amd/display/modules/power/power.c | 12 +- + .../gpu/drm/amd/display/modules/power/power_abm.c | 14 +- + .../drm/amd/display/modules/power/power_replay.c | 9 +- + drivers/gpu/drm/amd/include/amd_shared.h | 3 - + .../amd/include/asic_reg/sdma/sdma_4_4_2_offset.h | 4 + + drivers/gpu/drm/amd/include/kgd_kfd_interface.h | 3 + + drivers/gpu/drm/amd/include/mes_v11_api_def.h | 5 +- + drivers/gpu/drm/amd/include/mes_v12_api_def.h | 5 +- + drivers/gpu/drm/amd/include/soc15_hw_ip.h | 1 + + drivers/gpu/drm/amd/include/v9_structs.h | 4 +- + drivers/gpu/drm/amd/pm/amdgpu_pm.c | 116 +- + drivers/gpu/drm/amd/pm/powerplay/amd_powerplay.c | 21 +- + .../gpu/drm/amd/pm/powerplay/hwmgr/ppatomctrl.c | 38 +- + .../gpu/drm/amd/pm/powerplay/hwmgr/ppatomfwctrl.c | 11 +- + .../amd/pm/powerplay/hwmgr/process_pptables_v1_0.c | 753 +- + .../drm/amd/pm/powerplay/hwmgr/processpptables.c | 450 +- + .../gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c | 60 +- + .../gpu/drm/amd/pm/powerplay/hwmgr/vega10_hwmgr.c | 83 +- + .../pm/powerplay/hwmgr/vega10_processpptables.c | 761 ++- + .../pm/powerplay/hwmgr/vega12_processpptables.c | 7 + + .../pm/powerplay/hwmgr/vega20_processpptables.c | 7 + + drivers/gpu/drm/amd/pm/powerplay/inc/hwmgr.h | 17 + + drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c | 34 +- + drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h | 37 +- + drivers/gpu/drm/amd/pm/swsmu/inc/smu_v11_0.h | 2 - + drivers/gpu/drm/amd/pm/swsmu/inc/smu_v13_0.h | 6 - + drivers/gpu/drm/amd/pm/swsmu/inc/smu_v14_0.h | 4 - + drivers/gpu/drm/amd/pm/swsmu/inc/smu_v15_0.h | 4 - + drivers/gpu/drm/amd/pm/swsmu/smu11/arcturus_ppt.c | 8 +- + drivers/gpu/drm/amd/pm/swsmu/smu11/navi10_ppt.c | 8 +- + .../drm/amd/pm/swsmu/smu11/sienna_cichlid_ppt.c | 8 +- + drivers/gpu/drm/amd/pm/swsmu/smu11/smu_v11_0.c | 82 +- + drivers/gpu/drm/amd/pm/swsmu/smu12/renoir_ppt.c | 1 - + drivers/gpu/drm/amd/pm/swsmu/smu13/aldebaran_ppt.c | 1 - + drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0.c | 94 +- + .../gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c | 7 +- + .../gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_12_ppt.c | 8 +- + .../gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_6_ppt.c | 653 -- + .../gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_7_ppt.c | 7 +- + drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0.c | 85 +- + .../gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c | 9 +- + drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0.c | 85 +- + drivers/gpu/drm/amd/pm/swsmu/smu_cmn.c | 140 + + drivers/gpu/drm/amd/pm/swsmu/smu_cmn.h | 13 + + drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_cmd.c | 3 - + .../drm/amd/ras/ras_mgr/amdgpu_ras_eeprom_i2c.c | 2 +- + drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.c | 76 +- + drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.h | 2 + + .../gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mp1_v13_0.c | 14 + + drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_sys.c | 19 + + drivers/gpu/drm/amd/ras/ras_mgr/ras_sys.h | 3 + + drivers/gpu/drm/amd/ras/rascore/ras.h | 5 + + drivers/gpu/drm/amd/ras/rascore/ras_aca_v1_0.c | 13 +- + drivers/gpu/drm/amd/ras/rascore/ras_core.c | 15 + + drivers/gpu/drm/amd/ras/rascore/ras_eeprom.c | 3 + + drivers/gpu/drm/amd/ras/rascore/ras_eeprom_fw.c | 2 +- + drivers/gpu/drm/amd/ras/rascore/ras_mp1.c | 19 +- + drivers/gpu/drm/amd/ras/rascore/ras_mp1.h | 3 + + drivers/gpu/drm/amd/ras/rascore/ras_mp1_v13_0.c | 13 + + drivers/gpu/drm/amd/ras/rascore/ras_process.c | 5 +- + drivers/gpu/drm/amd/ras/rascore/ras_umc.c | 32 +- + drivers/gpu/drm/amd/ras/rascore/ras_umc.h | 6 + + drivers/gpu/drm/amd/ras/rascore/ras_umc_v12_0.h | 2 + + drivers/gpu/drm/radeon/r600_dpm.c | 21 +- + include/uapi/drm/amdgpu_drm.h | 21 + + 376 files changed, 33042 insertions(+), 21336 deletions(-) + delete mode 100644 drivers/gpu/drm/amd/amdgpu/amdgpu_aca.c + delete mode 100644 drivers/gpu/drm/amd/amdgpu/amdgpu_aca.h + create mode 100644 drivers/gpu/drm/amd/amdgpu/amdgpu_sa.h + create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c + rename drivers/gpu/drm/amd/display/{modules/power/power_helpers.c => amdgpu_dm/amdgpu_dm_audio.h} (50%) + create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c + create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h + create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c + create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h + create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c + create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h + create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h + create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h + create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c + create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c + create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c + create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c + create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c + create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c + create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c + create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c + create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_test_helpers.h + create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c + create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c + create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c + create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_quirks_test.c + create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_services_test.c + create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c + create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_wb_test.c + create mode 100644 drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn42b/dcn42b_soc_and_ip_translator.c + create mode 100644 drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn42b/dcn42b_soc_and_ip_translator.h +$ git am -3 ../patches/0001-amdgpu-Fix-up-drm_atomic_commit-rename.patch +Applying: amdgpu: Fix up drm_atomic_commit rename +Using index info to reconstruct a base tree... +M drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +Falling back to patching base and 3-way merge... +Auto-merging drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +No changes -- Patch already applied. +Merging drm-intel/for-linux-next (cd0121502ce61 drm/i915/psr: Block DC3CO entry during active frame) +$ git merge -m Merge branch 'for-linux-next' of https://gitlab.freedesktop.org/drm/i915/kernel.git drm-intel/for-linux-next +Auto-merging drivers/gpu/drm/i915/display/intel_bios.c +Auto-merging drivers/gpu/drm/i915/display/intel_dp.c +Auto-merging drivers/gpu/drm/i915/display/intel_plane.c +Auto-merging drivers/gpu/drm/i915/display/intel_vrr.c +Auto-merging drivers/gpu/drm/xe/xe_device.c +Merge made by the 'ort' strategy. + .../gpu/intel-display/dp-link-training.rst | 8 + + Documentation/gpu/intel-display/index.rst | 1 + + drivers/gpu/drm/drm_modes.c | 23 + + drivers/gpu/drm/i915/Makefile | 5 +- + drivers/gpu/drm/i915/display/g4x_dp.c | 15 +- + drivers/gpu/drm/i915/display/g4x_hdmi.c | 4 +- + drivers/gpu/drm/i915/display/icl_dsi.c | 5 +- + drivers/gpu/drm/i915/display/intel_alpm.c | 15 +- + drivers/gpu/drm/i915/display/intel_bios.c | 62 +- + drivers/gpu/drm/i915/display/intel_bw.c | 168 ++- + drivers/gpu/drm/i915/display/intel_cdclk.c | 54 +- + drivers/gpu/drm/i915/display/intel_cmtg.c | 280 ++++- + drivers/gpu/drm/i915/display/intel_cmtg.h | 19 +- + drivers/gpu/drm/i915/display/intel_cmtg_regs.h | 24 +- + drivers/gpu/drm/i915/display/intel_color.c | 115 +- + drivers/gpu/drm/i915/display/intel_crt.c | 9 +- + drivers/gpu/drm/i915/display/intel_cursor.c | 2 +- + drivers/gpu/drm/i915/display/intel_cx0_phy.c | 11 + + drivers/gpu/drm/i915/display/intel_cx0_phy.h | 1 - + drivers/gpu/drm/i915/display/intel_ddi.c | 29 +- + drivers/gpu/drm/i915/display/intel_de.c | 40 +- + drivers/gpu/drm/i915/display/intel_display.c | 170 +-- + drivers/gpu/drm/i915/display/intel_display.h | 7 + + drivers/gpu/drm/i915/display/intel_display_core.h | 14 +- + .../gpu/drm/i915/display/intel_display_debugfs.c | 2 + + .../gpu/drm/i915/display/intel_display_device.c | 14 + + .../gpu/drm/i915/display/intel_display_device.h | 3 +- + .../gpu/drm/i915/display/intel_display_driver.c | 202 +++- + .../gpu/drm/i915/display/intel_display_driver.h | 17 +- + drivers/gpu/drm/i915/display/intel_display_irq.c | 19 + + drivers/gpu/drm/i915/display/intel_display_irq.h | 2 + + .../gpu/drm/i915/display/intel_display_limits.h | 2 + + drivers/gpu/drm/i915/display/intel_display_power.c | 185 +++- + drivers/gpu/drm/i915/display/intel_display_power.h | 40 + + .../drm/i915/display/intel_display_power_well.c | 76 +- + .../drm/i915/display/intel_display_power_well.h | 1 + + drivers/gpu/drm/i915/display/intel_display_regs.h | 49 +- + drivers/gpu/drm/i915/display/intel_display_types.h | 57 +- + drivers/gpu/drm/i915/display/intel_display_wa.c | 2 + + drivers/gpu/drm/i915/display/intel_display_wa.h | 1 + + drivers/gpu/drm/i915/display/intel_dmc.c | 15 +- + drivers/gpu/drm/i915/display/intel_dmc_regs.h | 2 + + drivers/gpu/drm/i915/display/intel_dmc_wl.c | 2 +- + drivers/gpu/drm/i915/display/intel_dp.c | 504 ++------- + drivers/gpu/drm/i915/display/intel_dp.h | 16 +- + drivers/gpu/drm/i915/display/intel_dp_link_caps.c | 675 ++++++++++++ + drivers/gpu/drm/i915/display/intel_dp_link_caps.h | 47 + + .../gpu/drm/i915/display/intel_dp_link_training.c | 1093 +++++++++++++++----- + .../gpu/drm/i915/display/intel_dp_link_training.h | 15 + + drivers/gpu/drm/i915/display/intel_dp_mst.c | 8 +- + drivers/gpu/drm/i915/display/intel_dp_test.c | 7 +- + drivers/gpu/drm/i915/display/intel_dp_tunnel.c | 6 +- + drivers/gpu/drm/i915/display/intel_dsb.c | 32 +- + drivers/gpu/drm/i915/display/intel_dvo.c | 5 +- + drivers/gpu/drm/i915/display/intel_gmbus.c | 148 +-- + drivers/gpu/drm/i915/display/intel_gmbus.h | 22 +- + drivers/gpu/drm/i915/display/intel_hdmi.c | 71 +- + drivers/gpu/drm/i915/display/intel_hdmi.h | 2 + + drivers/gpu/drm/i915/display/intel_initial_plane.c | 2 +- + drivers/gpu/drm/i915/display/intel_lpe_audio.c | 4 +- + drivers/gpu/drm/i915/display/intel_lt_phy.c | 20 +- + drivers/gpu/drm/i915/display/intel_lvds.c | 5 +- + drivers/gpu/drm/i915/display/intel_panel.c | 170 ++- + drivers/gpu/drm/i915/display/intel_panel.h | 6 +- + drivers/gpu/drm/i915/display/intel_parent.c | 6 +- + drivers/gpu/drm/i915/display/intel_parent.h | 4 +- + drivers/gpu/drm/i915/display/intel_plane.c | 52 +- + drivers/gpu/drm/i915/display/intel_plane.h | 5 +- + drivers/gpu/drm/i915/display/intel_psr.c | 283 ++--- + drivers/gpu/drm/i915/display/intel_psr.h | 1 + + drivers/gpu/drm/i915/display/intel_psr_regs.h | 1 + + drivers/gpu/drm/i915/display/intel_sdvo.c | 7 +- + drivers/gpu/drm/i915/display/intel_tv.c | 5 +- + drivers/gpu/drm/i915/display/intel_vrr.c | 29 +- + drivers/gpu/drm/i915/display/intel_vrr.h | 4 +- + drivers/gpu/drm/i915/display/vlv_dsi.c | 5 +- + drivers/gpu/drm/i915/gem/i915_gem_object.h | 7 - + drivers/gpu/drm/i915/gem/i915_gem_pages.c | 128 --- + drivers/gpu/drm/i915/gem/i915_gem_panic.c | 147 +++ + drivers/gpu/drm/i915/gem/i915_gem_panic.h | 11 + + drivers/gpu/drm/i915/gt/intel_gsc.c | 2 +- + drivers/gpu/drm/i915/gvt/edid.c | 14 +- + drivers/gpu/drm/i915/i915_driver.c | 181 +--- + drivers/gpu/drm/i915/i915_panic.c | 35 - + drivers/gpu/drm/i915/i915_panic.h | 9 - + drivers/gpu/drm/xe/Makefile | 1 + + drivers/gpu/drm/xe/display/xe_display.c | 284 ++--- + drivers/gpu/drm/xe/display/xe_display.h | 12 +- + drivers/gpu/drm/xe/display/xe_panic.c | 26 +- + drivers/gpu/drm/xe/xe_device.c | 4 +- + drivers/gpu/drm/xe/xe_pm.c | 2 + + include/drm/drm_modes.h | 1 + + include/drm/intel/display_parent_interface.h | 4 +- + include/drm/intel/mchbar_regs.h | 2 - + 94 files changed, 3849 insertions(+), 2053 deletions(-) + create mode 100644 Documentation/gpu/intel-display/dp-link-training.rst + create mode 100644 drivers/gpu/drm/i915/display/intel_dp_link_caps.c + create mode 100644 drivers/gpu/drm/i915/display/intel_dp_link_caps.h + create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_panic.c + create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_panic.h + delete mode 100644 drivers/gpu/drm/i915/i915_panic.c + delete mode 100644 drivers/gpu/drm/i915/i915_panic.h +Merging drm-msm/msm-next (9a967125427e0 drm/msm/adreno: add Adreno 810 GPU support) +$ git merge -m Merge branch 'msm-next' of https://gitlab.freedesktop.org/drm/msm.git drm-msm/msm-next +Already up to date. +Merging drm-msm-lumag/msm-next-lumag (8de061ca149c9 drm/msm: Restore second parameter name in purge() and evict()) +$ git merge -m Merge branch 'msm-next-lumag' of https://gitlab.freedesktop.org/lumag/msm.git drm-msm-lumag/msm-next-lumag +Already up to date. +Merging drm-xe/drm-xe-next (3a8bfa1f2af71 drm/xe: Documentation: fix chars used for subsection) +$ git merge -m Merge branch 'drm-xe-next' of https://gitlab.freedesktop.org/drm/xe/kernel.git drm-xe/drm-xe-next +Auto-merging drivers/gpu/drm/xe/Makefile +Auto-merging drivers/gpu/drm/xe/tests/xe_rtp_test.c +CONFLICT (content): Merge conflict in drivers/gpu/drm/xe/tests/xe_rtp_test.c +Auto-merging drivers/gpu/drm/xe/xe_device.c +Auto-merging drivers/gpu/drm/xe/xe_drm_client.c +Auto-merging drivers/gpu/drm/xe/xe_drm_ras.c +Auto-merging drivers/gpu/drm/xe/xe_guc_submit.c +Auto-merging drivers/gpu/drm/xe/xe_guc_tlb_inval.c +Auto-merging drivers/gpu/drm/xe/xe_hw_engine.c +CONFLICT (content): Merge conflict in drivers/gpu/drm/xe/xe_hw_engine.c +Auto-merging drivers/gpu/drm/xe/xe_hw_error.c +Auto-merging drivers/gpu/drm/xe/xe_oa.c +Auto-merging drivers/gpu/drm/xe/xe_pt.c +Auto-merging drivers/gpu/drm/xe/xe_reg_whitelist.c +CONFLICT (content): Merge conflict in drivers/gpu/drm/xe/xe_reg_whitelist.c +Auto-merging drivers/gpu/drm/xe/xe_reg_whitelist.h +Auto-merging drivers/gpu/drm/xe/xe_rtp.c +Auto-merging drivers/gpu/drm/xe/xe_rtp.h +Auto-merging drivers/gpu/drm/xe/xe_rtp_types.h +Auto-merging drivers/gpu/drm/xe/xe_svm.c +Auto-merging drivers/gpu/drm/xe/xe_tuning.c +CONFLICT (content): Merge conflict in drivers/gpu/drm/xe/xe_tuning.c +Auto-merging drivers/gpu/drm/xe/xe_userptr.c +Auto-merging drivers/gpu/drm/xe/xe_wa.c +CONFLICT (content): Merge conflict in drivers/gpu/drm/xe/xe_wa.c +Auto-merging kernel/events/core.c +Resolved 'drivers/gpu/drm/xe/tests/xe_rtp_test.c' using previous resolution. +Resolved 'drivers/gpu/drm/xe/xe_hw_engine.c' using previous resolution. +Resolved 'drivers/gpu/drm/xe/xe_reg_whitelist.c' using previous resolution. +Resolved 'drivers/gpu/drm/xe/xe_tuning.c' using previous resolution. +Resolved 'drivers/gpu/drm/xe/xe_wa.c' using previous resolution. +Automatic merge failed; fix conflicts and then commit the result. +$ git commit --no-edit -v -a +[master bcbcd9b05fb17] Merge branch 'drm-xe-next' of https://gitlab.freedesktop.org/drm/xe/kernel.git +$ git diff -M --stat --summary HEAD^.. + .../ABI/testing/sysfs-driver-intel-xe-hwmon | 7 + + drivers/gpu/drm/xe/Kconfig.profile | 71 ++++-- + drivers/gpu/drm/xe/Makefile | 1 + + drivers/gpu/drm/xe/tests/Makefile | 1 + + drivers/gpu/drm/xe/tests/xe_pci.c | 54 ++--- + drivers/gpu/drm/xe/tests/xe_rtp_tables_test.c | 112 +++++++++ + drivers/gpu/drm/xe/tests/xe_rtp_test.c | 52 +++++ + drivers/gpu/drm/xe/tests/xe_wa_test.c | 3 - + drivers/gpu/drm/xe/xe_debugfs.c | 1 - + drivers/gpu/drm/xe/xe_device.c | 30 ++- + drivers/gpu/drm/xe/xe_device.h | 17 +- + drivers/gpu/drm/xe/xe_device_types.h | 10 +- + drivers/gpu/drm/xe/xe_drm_client.c | 5 +- + drivers/gpu/drm/xe/xe_drm_ras.c | 41 ++-- + drivers/gpu/drm/xe/xe_eu_stall.c | 5 +- + drivers/gpu/drm/xe/xe_exec.c | 22 +- + drivers/gpu/drm/xe/xe_exec_queue_types.h | 2 - + drivers/gpu/drm/xe/xe_execlist.c | 7 - + drivers/gpu/drm/xe/xe_ggtt.c | 8 +- + drivers/gpu/drm/xe/xe_gt.c | 14 +- + drivers/gpu/drm/xe/xe_gt_mcr.c | 19 +- + drivers/gpu/drm/xe/xe_guc_submit.c | 18 +- + drivers/gpu/drm/xe/xe_guc_tlb_inval.c | 6 - + drivers/gpu/drm/xe/xe_hw_engine.c | 218 ++++++++--------- + drivers/gpu/drm/xe/xe_hw_error.c | 23 +- + drivers/gpu/drm/xe/xe_i2c.c | 3 - + drivers/gpu/drm/xe/xe_lrc.c | 8 +- + drivers/gpu/drm/xe/xe_mmio.c | 100 +++++--- + drivers/gpu/drm/xe/xe_module.c | 3 - + drivers/gpu/drm/xe/xe_module.h | 1 - + drivers/gpu/drm/xe/xe_oa.c | 25 +- + drivers/gpu/drm/xe/xe_observation.c | 32 ++- + drivers/gpu/drm/xe/xe_observation.h | 3 +- + drivers/gpu/drm/xe/xe_pci.c | 176 +++++++++----- + drivers/gpu/drm/xe/xe_pci_error.c | 122 ++++++++++ + drivers/gpu/drm/xe/xe_pci_error.h | 13 ++ + drivers/gpu/drm/xe/xe_pci_types.h | 1 + + drivers/gpu/drm/xe/xe_pcode.c | 8 +- + drivers/gpu/drm/xe/xe_pcode.h | 2 +- + drivers/gpu/drm/xe/xe_pt.c | 20 +- + drivers/gpu/drm/xe/xe_pxp.c | 35 ++- + drivers/gpu/drm/xe/xe_query.c | 7 +- + drivers/gpu/drm/xe/xe_ras.c | 260 +++++++++++++++++++++ + drivers/gpu/drm/xe/xe_ras.h | 5 + + drivers/gpu/drm/xe/xe_ras_types.h | 51 ++++ + drivers/gpu/drm/xe/xe_reg_whitelist.c | 5 +- + drivers/gpu/drm/xe/xe_reg_whitelist.h | 4 + + drivers/gpu/drm/xe/xe_rtp.c | 10 +- + drivers/gpu/drm/xe/xe_rtp.h | 19 ++ + drivers/gpu/drm/xe/xe_rtp_types.h | 33 ++- + drivers/gpu/drm/xe/xe_step.c | 38 +-- + drivers/gpu/drm/xe/xe_step.h | 8 +- + drivers/gpu/drm/xe/xe_sysctrl_mailbox.c | 28 +++ + drivers/gpu/drm/xe/xe_sysctrl_mailbox.h | 3 + + drivers/gpu/drm/xe/xe_sysctrl_mailbox_types.h | 4 + + drivers/gpu/drm/xe/xe_tile.c | 4 +- + drivers/gpu/drm/xe/xe_tuning.c | 3 +- + drivers/gpu/drm/xe/xe_tuning.h | 6 + + drivers/gpu/drm/xe/xe_vm_madvise.c | 19 +- + drivers/gpu/drm/xe/xe_wa.c | 15 +- + drivers/gpu/drm/xe/xe_wa.h | 7 + + drivers/gpu/drm/xe/xe_wa_oob.rules | 1 + + include/drm/intel/pciids.h | 5 +- + include/linux/perf_event.h | 31 ++- + include/uapi/drm/xe_drm.h | 17 +- + kernel/events/core.c | 18 ++ + 66 files changed, 1454 insertions(+), 446 deletions(-) + create mode 100644 drivers/gpu/drm/xe/tests/xe_rtp_tables_test.c + create mode 100644 drivers/gpu/drm/xe/xe_pci_error.c + create mode 100644 drivers/gpu/drm/xe/xe_pci_error.h +$ git am -3 ../patches/0001-drm-xe-Fix-up-merge-issue.patch +Applying: drm: xe: Fix up merge issue +Using index info to reconstruct a base tree... +M drivers/gpu/drm/xe/xe_ttm_vram_mgr.c +Falling back to patching base and 3-way merge... +Auto-merging drivers/gpu/drm/xe/xe_ttm_vram_mgr.c +No changes -- Patch already applied. +Merging drm-rust/for-linux-next (78900738d981c gpu: nova: fix rust-analyzer generation) +$ git merge -m Merge branch 'for-linux-next' of https://gitlab.freedesktop.org/drm/rust/kernel.git drm-rust/for-linux-next +Merge made by the 'ort' strategy. + Documentation/gpu/nova/core/fsp.rst | 142 ++++++ + Documentation/gpu/nova/index.rst | 1 + + drivers/gpu/Makefile | 58 ++- + drivers/gpu/drm/Makefile | 2 +- + drivers/gpu/drm/nova/Makefile | 5 +- + drivers/gpu/drm/tyr/regs.rs | 14 +- + drivers/gpu/nova-core/.gitignore | 1 + + drivers/gpu/nova-core/Makefile | 5 +- + drivers/gpu/nova-core/bitfield.rs | 329 ------------ + drivers/gpu/nova-core/falcon.rs | 195 ++++--- + drivers/gpu/nova-core/falcon/fsp.rs | 44 +- + drivers/gpu/nova-core/falcon/gsp.rs | 32 +- + drivers/gpu/nova-core/falcon/hal.rs | 14 +- + drivers/gpu/nova-core/falcon/hal/ga102.rs | 29 +- + drivers/gpu/nova-core/falcon/hal/tu102.rs | 24 +- + drivers/gpu/nova-core/fb.rs | 4 +- + drivers/gpu/nova-core/fb/hal/gb202.rs | 28 +- + drivers/gpu/nova-core/fb/hal/gh100.rs | 31 +- + drivers/gpu/nova-core/fb/regs.rs | 25 + + drivers/gpu/nova-core/firmware.rs | 25 +- + drivers/gpu/nova-core/firmware/booter.rs | 25 +- + drivers/gpu/nova-core/firmware/fwsec.rs | 19 +- + drivers/gpu/nova-core/firmware/fwsec/bootloader.rs | 16 +- + drivers/gpu/nova-core/fsp.rs | 103 ++-- + drivers/gpu/nova-core/gpu.rs | 136 +++-- + drivers/gpu/nova-core/gsp.rs | 38 +- + drivers/gpu/nova-core/gsp/boot.rs | 54 +- + drivers/gpu/nova-core/gsp/cmdq.rs | 3 +- + drivers/gpu/nova-core/gsp/commands.rs | 13 +- + drivers/gpu/nova-core/gsp/fw.rs | 19 +- + drivers/gpu/nova-core/gsp/fw/commands.rs | 42 +- + drivers/gpu/nova-core/gsp/hal.rs | 27 +- + drivers/gpu/nova-core/gsp/hal/gh100.rs | 52 +- + drivers/gpu/nova-core/gsp/hal/tu102.rs | 118 +++-- + drivers/gpu/nova-core/gsp/regs.rs | 11 + + drivers/gpu/nova-core/gsp/sequencer.rs | 31 +- + drivers/gpu/nova-core/mctp.rs | 86 ++-- + drivers/gpu/nova-core/nova_core.rs | 5 +- + drivers/gpu/nova-core/nova_core_exports.c | 15 + + drivers/gpu/nova-core/regs.rs | 70 ++- + drivers/gpu/nova-core/vbios.rs | 64 +-- + rust/kernel/drm/device.rs | 2 +- + rust/kernel/drm/gem/mod.rs | 2 +- + rust/kernel/drm/gem/shmem.rs | 558 ++++++++++++++++++++- + rust/kernel/drm/gpuvm/mod.rs | 12 +- + rust/kernel/drm/gpuvm/sm_ops.rs | 4 +- + rust/kernel/faux.rs | 18 +- + 47 files changed, 1520 insertions(+), 1031 deletions(-) + create mode 100644 Documentation/gpu/nova/core/fsp.rst + create mode 100644 drivers/gpu/nova-core/.gitignore + delete mode 100644 drivers/gpu/nova-core/bitfield.rs + create mode 100644 drivers/gpu/nova-core/fb/regs.rs + create mode 100644 drivers/gpu/nova-core/gsp/regs.rs + create mode 100644 drivers/gpu/nova-core/nova_core_exports.c +$ git am -3 ../patches/0001-drm-nova-Fix-up-merge-issue.patch +Applying: drm: nova: Fix up merge issue +Using index info to reconstruct a base tree... +M drivers/gpu/nova-core/gsp/cmdq.rs +Falling back to patching base and 3-way merge... +Auto-merging drivers/gpu/nova-core/gsp/cmdq.rs +No changes -- Patch already applied. +Merging drm-nova/nova-next (93296e9d9528f gpu: nova-core: vbios: store reference to Device where relevant) +$ git merge -m Merge branch 'nova-next' of https://gitlab.freedesktop.org/drm/nova.git drm-nova/nova-next +Already up to date. +Merging etnaviv/etnaviv/next (6bde14ba5f7ef drm/etnaviv: add optional reset support) +$ git merge -m Merge branch 'etnaviv/next' of https://git.pengutronix.de/git/lst/linux etnaviv/etnaviv/next +Already up to date. +Merging fbdev/for-next (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/deller/linux-fbdev.git fbdev/for-next +Already up to date. +$ git am -3 ../patches/0001-fix-up-for-drm-hyperv-Remove-reference-to-hyperv_fb-.patch +Applying: fix up for "drm/hyperv: Remove reference to hyperv_fb driver" +$ git reset HEAD^ +Unstaged changes after reset: +M drivers/gpu/drm/hyperv/Kconfig +$ git add -A . +$ git commit -v -a --amend +warning: notes ref refs/notes/commits is invalid +[master 09ca5fab511a0] Merge branch 'for-linux-next' of https://gitlab.freedesktop.org/drm/rust/kernel.git + Date: Mon Jul 6 14:53:13 2026 +0100 +Merging regmap/for-next (1c790e0326461 Merge remote-tracking branch 'regmap/for-7.3' into regmap-next) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/broonie/regmap.git regmap/for-next +Merge made by the 'ort' strategy. + drivers/base/regmap/internal.h | 2 +- + drivers/base/regmap/regcache-flat.c | 4 +--- + drivers/base/regmap/regcache-maple.c | 8 +++----- + drivers/base/regmap/regcache-rbtree.c | 8 +++----- + drivers/base/regmap/regmap-irq.c | 22 ++++++++++++++++++++++ + include/linux/regmap.h | 2 ++ + 6 files changed, 32 insertions(+), 14 deletions(-) +Merging sound/for-next (bd8f218761898 ALSA: core: propagate actual error code from register_chrdev) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound.git sound/for-next +Auto-merging include/sound/hda_codec.h +Auto-merging sound/hda/codecs/realtek/alc269.c +Merge made by the 'ort' strategy. + include/sound/hda_codec.h | 1 + + sound/core/control.c | 4 +- + sound/core/oss/mixer_oss.c | 4 +- + sound/core/oss/pcm_oss.c | 4 +- + sound/core/pcm_native.c | 16 +- + sound/core/rawmidi.c | 4 +- + sound/core/seq/seq_clientmgr.c | 5 +- + sound/core/sound.c | 14 +- + sound/core/timer.c | 5 +- + sound/hda/codecs/hdmi/hdmi.c | 4 +- + sound/hda/codecs/helpers/razer_blade16_2025.c | 820 ++++++++++++++++++++++++++ + sound/hda/codecs/realtek/alc269.c | 21 + + sound/hda/common/codec.c | 5 +- + sound/hda/controllers/intel.c | 11 + + sound/hda/core/controller.c | 10 +- + sound/hda/core/stream.c | 40 +- + sound/usb/card.c | 70 ++- + sound/usb/mixer_quirks.c | 20 + + sound/usb/quirks-table.h | 67 +++ + 19 files changed, 1050 insertions(+), 75 deletions(-) + create mode 100644 sound/hda/codecs/helpers/razer_blade16_2025.c +Merging ieee1394/for-next (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/ieee1394/linux1394.git ieee1394/for-next +Already up to date. +Merging sound-asoc/for-next (be44d21728b66 Merge remote-tracking branch 'asoc/for-7.3' into asoc-next) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git sound-asoc/for-next +Auto-merging sound/soc/codecs/rt1320-sdw.c +Auto-merging sound/soc/codecs/rt700-sdw.c +Auto-merging sound/soc/codecs/sma1303.c +Auto-merging sound/soc/fsl/mpc5200_psc_ac97.c +Auto-merging sound/soc/mediatek/mt8365/mt8365-mt6357.c +Merge made by the 'ort' strategy. + .../devicetree/bindings/sound/cirrus,cs35l36.yaml | 240 ++++++ + .../devicetree/bindings/sound/cs35l36.txt | 168 ----- + .../bindings/sound/loongson,ls-audio-card.yaml | 53 +- + .../bindings/sound/loongson,ls2k1000-i2s.yaml | 22 +- + .../bindings/sound/mediatek,mtk-btcvsd-snd.yaml | 59 ++ + .../devicetree/bindings/sound/mtk-btcvsd-snd.txt | 24 - + include/sound/soc_sdw_utils.h | 31 +- + include/sound/sof/dai-amd.h | 1 + + include/sound/sof/dai.h | 3 + + include/uapi/sound/sof/tokens.h | 1 + + sound/soc/amd/acp-config.c | 12 + + sound/soc/amd/acp/acp-sdw-legacy-mach.c | 2 +- + sound/soc/amd/acp/acp-sdw-sof-mach.c | 2 +- + sound/soc/amd/mach-config.h | 1 + + sound/soc/au1x/psc-ac97.c | 4 +- + sound/soc/codecs/Kconfig | 32 +- + sound/soc/codecs/es8328.c | 5 + + sound/soc/codecs/es8389.c | 236 +++++- + sound/soc/codecs/es8389.h | 11 +- + sound/soc/codecs/hdac_hda.c | 4 +- + sound/soc/codecs/hdac_hdmi.c | 10 +- + sound/soc/codecs/lpass-va-macro.c | 131 ++-- + sound/soc/codecs/lpass-wsa-macro.c | 139 ++-- + sound/soc/codecs/max98090.c | 1 + + sound/soc/codecs/max98390.c | 8 +- + sound/soc/codecs/rt1320-sdw.c | 816 +++++++++++++++++---- + sound/soc/codecs/rt1320-sdw.h | 12 + + sound/soc/codecs/rt5677.c | 17 + + sound/soc/codecs/rt700-sdw.c | 6 +- + sound/soc/codecs/sma1303.c | 2 + + sound/soc/codecs/tlv320aic26.c | 6 + + sound/soc/fsl/fsl_asrc.c | 10 +- + sound/soc/fsl/fsl_audmix.c | 35 +- + sound/soc/fsl/fsl_easrc.c | 36 +- + sound/soc/fsl/fsl_esai.c | 16 +- + sound/soc/fsl/fsl_spdif.c | 8 +- + sound/soc/fsl/fsl_ssi.c | 13 +- + sound/soc/fsl/fsl_xcvr.c | 29 +- + sound/soc/fsl/imx-audio-rpmsg.c | 25 +- + sound/soc/fsl/imx-pcm-rpmsg.c | 69 +- + sound/soc/fsl/mpc5200_dma.c | 56 +- + sound/soc/fsl/mpc5200_psc_ac97.c | 34 +- + sound/soc/fsl/mpc5200_psc_i2s.c | 1 + + sound/soc/intel/atom/sst-atom-controls.c | 67 +- + sound/soc/intel/atom/sst-mfld-platform-pcm.c | 59 +- + sound/soc/intel/atom/sst/sst_ipc.c | 5 +- + sound/soc/intel/atom/sst/sst_pci.c | 4 +- + sound/soc/intel/atom/sst/sst_pvt.c | 9 +- + sound/soc/intel/avs/apl.c | 8 +- + sound/soc/intel/avs/control.c | 7 +- + sound/soc/intel/avs/core.c | 4 +- + sound/soc/intel/avs/debug.h | 10 +- + sound/soc/intel/avs/debugfs.c | 19 +- + sound/soc/intel/avs/ipc.c | 59 +- + sound/soc/intel/avs/loader.c | 15 +- + sound/soc/intel/avs/path.c | 30 +- + sound/soc/intel/avs/utils.c | 45 +- + sound/soc/intel/boards/sof_sdw.c | 2 +- + sound/soc/loongson/loongson_card.c | 171 ++++- + sound/soc/loongson/loongson_i2s_plat.c | 42 +- + sound/soc/mediatek/Kconfig | 2 +- + sound/soc/mediatek/mt8173/mt8173-rt5650.c | 2 +- + sound/soc/mediatek/mt8365/mt8365-mt6357.c | 9 +- + sound/soc/meson/Makefile | 2 + + sound/soc/meson/aiu-encoder-i2s.c | 281 +++++-- + sound/soc/meson/aiu-formatter-i2s.c | 104 +++ + sound/soc/meson/aiu.c | 32 +- + sound/soc/meson/aiu.h | 4 + + sound/soc/meson/g12a-toacodec.c | 2 +- + sound/soc/meson/g12a-tohdmitx.c | 2 +- + sound/soc/meson/gx-formatter.c | 282 +++++++ + sound/soc/meson/gx-formatter.h | 56 ++ + sound/soc/meson/gx-interface.h | 48 ++ + sound/soc/meson/t9015.c | 6 +- + sound/soc/renesas/fsi.c | 9 +- + sound/soc/samsung/aries_wm8994.c | 1 + + sound/soc/samsung/i2s.c | 33 +- + sound/soc/samsung/spdif.c | 8 +- + sound/soc/sdw_utils/soc_sdw_bridge_cs35l56.c | 4 +- + sound/soc/sdw_utils/soc_sdw_utils.c | 7 +- + sound/soc/sof/amd/Kconfig | 10 + + sound/soc/sof/amd/Makefile | 2 + + sound/soc/sof/amd/acp-dsp-offset.h | 17 + + sound/soc/sof/amd/acp-ipc.c | 13 +- + sound/soc/sof/amd/acp-loader.c | 37 +- + sound/soc/sof/amd/acp.c | 267 ++++++- + sound/soc/sof/amd/acp.h | 20 + + sound/soc/sof/amd/acp7x.c | 185 +++++ + sound/soc/sof/amd/pci-acp7x.c | 116 +++ + sound/soc/sof/ipc3-pcm.c | 6 + + sound/soc/sof/ipc3-topology.c | 39 +- + sound/soc/sof/ipc4-topology.c | 2 + + sound/soc/sof/nocodec.c | 16 +- + sound/soc/sof/topology.c | 15 +- + sound/soc/ti/j721e-evm.c | 4 +- + 95 files changed, 3482 insertions(+), 1108 deletions(-) + create mode 100644 Documentation/devicetree/bindings/sound/cirrus,cs35l36.yaml + delete mode 100644 Documentation/devicetree/bindings/sound/cs35l36.txt + create mode 100644 Documentation/devicetree/bindings/sound/mediatek,mtk-btcvsd-snd.yaml + delete mode 100644 Documentation/devicetree/bindings/sound/mtk-btcvsd-snd.txt + create mode 100644 sound/soc/meson/aiu-formatter-i2s.c + create mode 100644 sound/soc/meson/gx-formatter.c + create mode 100644 sound/soc/meson/gx-formatter.h + create mode 100644 sound/soc/meson/gx-interface.h + create mode 100644 sound/soc/sof/amd/acp7x.c + create mode 100644 sound/soc/sof/amd/pci-acp7x.c +Merging modules/modules-next (36d6b929bb0c7 rust: module_param: add missing newline to pr_warn_once) +$ git merge -m Merge branch 'modules-next' of https://git.kernel.org/pub/scm/linux/kernel/git/modules/linux.git modules/modules-next +Already up to date. +Merging input/next (c8f174900926d Input: sur40 - fix MAX_CONTACTS value based on PixelSense specification) +$ git merge -m Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git input/next +Auto-merging Documentation/devicetree/bindings/input/microchip,cap11xx.yaml +Merge made by the 'ort' strategy. + .../devicetree/bindings/input/imagis,isa1200.yaml | 141 ++++++ + .../bindings/input/microchip,cap11xx.yaml | 92 +++- + .../devicetree/bindings/input/syna,rmi4.yaml | 11 +- + drivers/input/keyboard/cap11xx.c | 275 +++++++---- + drivers/input/misc/Kconfig | 12 + + drivers/input/misc/Makefile | 1 + + drivers/input/misc/isa1200.c | 533 +++++++++++++++++++++ + drivers/input/mouse/synaptics.c | 1 + + drivers/input/touchscreen/mms114.c | 245 +++++----- + drivers/input/touchscreen/sur40.c | 4 +- + 10 files changed, 1085 insertions(+), 230 deletions(-) + create mode 100644 Documentation/devicetree/bindings/input/imagis,isa1200.yaml + create mode 100644 drivers/input/misc/isa1200.c +Merging block/for-next (39dffa2bee1ba Merge branch 'io_uring-7.2' into for-next) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux.git block/for-next +Merge made by the 'ort' strategy. + block/blk-mq.c | 3 ++- + block/blk-wbt.c | 21 ++++++++++++++++++++- + block/blk-zoned.c | 42 ++++++++++++++++++++++++++++-------------- + block/blk.h | 4 ++-- + block/genhd.c | 2 +- + drivers/block/ublk_drv.c | 13 ++++++++++++- + include/linux/io_uring_types.h | 2 ++ + io_uring/io-wq.c | 5 +++++ + io_uring/io_uring.c | 2 ++ + io_uring/memmap.c | 2 +- + io_uring/msg_ring.c | 34 +++++++++++++++++++++++++++------- + io_uring/sqpoll.c | 7 ++++++- + io_uring/uring_cmd.c | 4 ++-- + 13 files changed, 110 insertions(+), 31 deletions(-) +$ git am -3 ../patches/0001-Revert-block-remove-bio_last_bvec_all.patch +Applying: Revert "block: remove bio_last_bvec_all" +$ git reset HEAD^ +Unstaged changes after reset: +M Documentation/block/biovecs.rst +M include/linux/bio.h +$ git add -A . +$ git commit -v -a --amend +warning: notes ref refs/notes/commits is invalid +[master d997703d9c51b] Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux.git + Date: Mon Jul 6 14:53:25 2026 +0100 +Merging device-mapper/for-next (55c99f2d901c7 dm-zoned-metadata: Use strscpy() to copy device name) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/device-mapper/linux-dm.git device-mapper/for-next +Already up to date. +Merging libata/for-next (e64e6b5dc8675 ata: libata-scsi: scale DSM TRIM payload by MAX PAGES PER DSM COMMAND) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/libata/linux libata/for-next +Merge made by the 'ort' strategy. + drivers/ata/libata-core.c | 6 +- + drivers/ata/libata-scsi.c | 418 +++++++++++++++++++++++++++++++++++----------- + drivers/ata/libata.h | 2 +- + include/linux/ata.h | 13 ++ + 4 files changed, 335 insertions(+), 104 deletions(-) +Merging pcmcia/pcmcia-next (b3c26ea81ccc5 pcmcia: remove obsolete host controller drivers) +$ git merge -m Merge branch 'pcmcia-next' of https://git.kernel.org/pub/scm/linux/kernel/git/brodo/linux.git pcmcia/pcmcia-next +Already up to date. +Merging mmc/next (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/ulfh/mmc.git mmc/next +Already up to date. +Merging mfd/for-mfd-next (f658393698772 MAINTAINERS: Add a mailing list entry to MFD) +$ git merge -m Merge branch 'for-mfd-next' of https://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd.git mfd/for-mfd-next +Auto-merging MAINTAINERS +Auto-merging drivers/mfd/cs42l43-i2c.c +Auto-merging drivers/mfd/cs42l43-sdw.c +Merge made by the 'ort' strategy. + Documentation/devicetree/bindings/Makefile | 2 +- + .../bindings/mfd/marvell,88pm886-a1.yaml | 2 +- + .../devicetree/bindings/mfd/qcom,spmi-pmic.yaml | 1 + + .../devicetree/bindings/mfd/syscon-common.yaml | 34 ++++++ + Documentation/devicetree/bindings/mfd/syscon.yaml | 116 ------------------- + MAINTAINERS | 11 ++ + drivers/mfd/88pm886.c | 21 +++- + drivers/mfd/Kconfig | 1 - + drivers/mfd/axp20x.c | 2 +- + drivers/mfd/cs42l43-i2c.c | 2 - + drivers/mfd/cs42l43-sdw.c | 7 -- + drivers/mfd/cs42l43.c | 15 +-- + drivers/mfd/ipaq-micro.c | 2 +- + drivers/mfd/mt6397-core.c | 3 + + drivers/mfd/rohm-bd71828.c | 125 +++++++++++++++------ + drivers/mfd/rohm-bd718x7.c | 123 ++++++++++++++------ + drivers/mfd/si476x-cmd.c | 1 - + drivers/mfd/si476x-i2c.c | 46 +++----- + include/linux/mfd/88pm886.h | 5 + + include/linux/mfd/cs42l43.h | 2 - + include/linux/mfd/si476x-core.h | 5 +- + include/linux/mfd/si476x-platform.h | 2 - + 22 files changed, 286 insertions(+), 242 deletions(-) +Merging backlight/for-backlight-next (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'for-backlight-next' of https://git.kernel.org/pub/scm/linux/kernel/git/lee/backlight.git backlight/for-backlight-next +Already up to date. +Merging battery/for-next (a888754e51e91 Documentation: ABI: sysfs-class-reboot-mode-reboot_modes: fix doc warnings) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply.git battery/for-next +Already up to date. +Merging regulator/for-next (69d7343e4df1e Merge remote-tracking branch 'regulator/for-7.3' into regulator-next) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/broonie/regulator.git regulator/for-next +Auto-merging drivers/regulator/adp5055-regulator.c +Auto-merging drivers/regulator/max77675-regulator.c +Auto-merging drivers/regulator/pv88080-regulator.c +Auto-merging drivers/regulator/rtq2208-regulator.c +Auto-merging drivers/regulator/tps6287x-regulator.c +Merge made by the 'ort' strategy. + .../regulator/qcom,sdm845-refgen-regulator.yaml | 31 +- + .../bindings/regulator/richtek,rtq2208.yaml | 4 +- + .../bindings/regulator/rohm,bd71815-regulator.yaml | 27 +- + .../bindings/regulator/rohm,bd71828-regulator.yaml | 20 +- + .../bindings/regulator/rohm,bd71837-regulator.yaml | 16 +- + .../bindings/regulator/rohm,bd71847-regulator.yaml | 16 +- + .../bindings/regulator/rohm,bd72720-regulator.yaml | 40 +-- + .../bindings/regulator/rohm,pmic-states.yaml | 57 ++++ + drivers/regulator/88pg86x.c | 4 +- + drivers/regulator/Kconfig | 7 + + drivers/regulator/Makefile | 1 + + drivers/regulator/act8865-regulator.c | 2 +- + drivers/regulator/ad5398.c | 4 +- + drivers/regulator/adp5055-regulator.c | 4 +- + drivers/regulator/as3722-regulator.c | 1 - + drivers/regulator/aw37503-regulator.c | 4 +- + drivers/regulator/da9121-regulator.c | 20 +- + drivers/regulator/da9210-regulator.c | 4 +- + drivers/regulator/da9211-regulator.c | 18 +- + drivers/regulator/fan53880.c | 4 +- + drivers/regulator/isl6271a-regulator.c | 2 +- + drivers/regulator/isl9305.c | 4 +- + drivers/regulator/lp3971.c | 2 +- + drivers/regulator/lp3972.c | 2 +- + drivers/regulator/lp872x.c | 4 +- + drivers/regulator/lp8755.c | 4 +- + drivers/regulator/ltc3589.c | 6 +- + drivers/regulator/ltc3676.c | 2 +- + drivers/regulator/max14577-regulator.c | 103 +++++- + drivers/regulator/max1586.c | 2 +- + drivers/regulator/max20086-regulator.c | 8 +- + drivers/regulator/max20411-regulator.c | 2 +- + drivers/regulator/max77503-regulator.c | 2 +- + drivers/regulator/max77675-regulator.c | 2 +- + drivers/regulator/max77826-regulator.c | 2 +- + drivers/regulator/max77838-regulator.c | 2 +- + drivers/regulator/max77857-regulator.c | 8 +- + drivers/regulator/max8649.c | 2 +- + drivers/regulator/max8893.c | 2 +- + drivers/regulator/max8952.c | 2 +- + drivers/regulator/max8973-regulator.c | 6 +- + drivers/regulator/max8998.c | 1 - + drivers/regulator/mcp16502.c | 2 +- + drivers/regulator/mp5416.c | 6 +- + drivers/regulator/mp8859.c | 4 +- + drivers/regulator/mp886x.c | 6 +- + drivers/regulator/mpq7920.c | 4 +- + drivers/regulator/mt6311-regulator.c | 4 +- + drivers/regulator/pf530x-regulator.c | 8 +- + drivers/regulator/pf8x00-regulator.c | 8 +- + drivers/regulator/pv88060-regulator.c | 4 +- + drivers/regulator/pv88080-regulator.c | 8 +- + drivers/regulator/pv88090-regulator.c | 4 +- + drivers/regulator/qcom-refgen-regulator.c | 113 ++++++- + drivers/regulator/rtq2208-regulator.c | 7 +- + drivers/regulator/sc2730-regulator.c | 375 +++++++++++++++++++++ + drivers/regulator/sgm3804-regulator.c | 2 +- + drivers/regulator/slg51000-regulator.c | 4 +- + drivers/regulator/sy8106a-regulator.c | 2 +- + drivers/regulator/sy8824x.c | 8 +- + drivers/regulator/sy8827n.c | 4 +- + drivers/regulator/tps51632-regulator.c | 4 +- + drivers/regulator/tps62360-regulator.c | 10 +- + drivers/regulator/tps6286x-regulator.c | 10 +- + drivers/regulator/tps6287x-regulator.c | 10 +- + drivers/regulator/tps65023-regulator.c | 6 +- + drivers/regulator/tps65132-regulator.c | 4 +- + drivers/regulator/tps6594-regulator.c | 19 +- + include/linux/mfd/max14577-private.h | 3 + + 69 files changed, 849 insertions(+), 244 deletions(-) + create mode 100644 Documentation/devicetree/bindings/regulator/rohm,pmic-states.yaml + create mode 100644 drivers/regulator/sc2730-regulator.c +Merging security/next (149b192e376d7 lsm: clarify security_task_prctl() hook documentation) +$ git merge -m Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/lsm.git security/next +Merge made by the 'ort' strategy. + security/security.c | 5 ++--- + 1 file changed, 2 insertions(+), 3 deletions(-) +Merging apparmor/apparmor-next (dda61023f976d apparmor: fix alternate loaders ability to load compressed policy) +$ git merge -m Merge branch 'apparmor-next' of https://git.kernel.org/pub/scm/linux/kernel/git/jj/linux-apparmor apparmor/apparmor-next +Merge made by the 'ort' strategy. + security/apparmor/af_unix.c | 1 + + security/apparmor/apparmorfs.c | 129 +++++++++++++++++++++++++++++- + security/apparmor/domain.c | 1 + + security/apparmor/include/apparmorfs.h | 3 + + security/apparmor/include/capability.h | 1 + + security/apparmor/include/path.h | 3 + + security/apparmor/include/policy.h | 4 +- + security/apparmor/include/policy_unpack.h | 4 +- + security/apparmor/include/procattr.h | 2 + + security/apparmor/include/task.h | 5 ++ + security/apparmor/policy.c | 8 +- + security/apparmor/policy_unpack.c | 24 +++++- + 12 files changed, 174 insertions(+), 11 deletions(-) +Merging integrity/next-integrity (35d6f5e788dae doc: security: Add documentation of exporting and deleting IMA measurements) +$ git merge -m Merge branch 'next-integrity' of https://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-integrity integrity/next-integrity +Already up to date. +Merging selinux/next (ae3d476caeba0 Automated merge of 'dev' into 'next') +$ git merge -m Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/selinux.git selinux/next +Merge made by the 'ort' strategy. + security/selinux/avc.c | 3 +-- + security/selinux/hooks.c | 44 +++++++++++++++++++++++++++++------------- + security/selinux/selinuxfs.c | 19 ++++++++++++------ + security/selinux/ss/policydb.c | 11 ++++++++--- + security/selinux/ss/services.c | 18 ++++++++--------- + 5 files changed, 62 insertions(+), 33 deletions(-) +Merging smack/next (a7c44fd9f80e3 smack: restrict smackfs/{direct,mapped} values to 0-255) +$ git merge -m Merge branch 'next' of https://github.com/cschaufler/smack-next smack/next +Auto-merging security/smack/smack_lsm.c +Merge made by the 'ort' strategy. + security/smack/smack.h | 6 +- + security/smack/smack_access.c | 9 ++ + security/smack/smack_lsm.c | 69 +++++++++---- + security/smack/smackfs.c | 224 +++++++++++++----------------------------- + 4 files changed, 130 insertions(+), 178 deletions(-) +Merging tomoyo/master (110ef9f94f84d apparmor: temporarily disable in syzbot kernels.) +$ git merge -m Merge branch 'master' of git://git.code.sf.net/p/tomoyo/tomoyo.git tomoyo/master +Auto-merging fs/namespace.c +Auto-merging kernel/locking/lockdep.c +Auto-merging lib/Kconfig.debug +Auto-merging net/core/dev.c +Auto-merging net/socket.c +Merge made by the 'ort' strategy. + drivers/block/loop.c | 87 ++++++++++++++++++++- + fs/namespace.c | 5 ++ + include/linux/netdevice.h | 15 ++++ + include/net/dst.h | 22 +++++- + include/net/sock.h | 8 +- + kernel/locking/lockdep.c | 2 + + kernel/softirq.c | 4 + + kernel/workqueue.c | 4 + + lib/Kconfig.debug | 7 ++ + net/bridge/br_nf_core.c | 1 + + net/core/dev.c | 188 ++++++++++++++++++++++++++++++++++++++++++++++ + net/core/dst.c | 85 +++++++++++++++++++++ + net/core/lock_debug.c | 1 + + net/socket.c | 32 ++++++-- + security/apparmor/Kconfig | 1 + + 15 files changed, 449 insertions(+), 13 deletions(-) +Merging tpmdd-tpm/for-next-tpm (4ece5759c1a29 tpm: tpm_i2c_nuvoton: disable IRQ on wait timeout) +$ git merge -m Merge branch 'for-next-tpm' of https://git.kernel.org/pub/scm/linux/kernel/git/jarkko/linux-tpmdd.git tpmdd-tpm/for-next-tpm +Merge made by the 'ort' strategy. + drivers/char/tpm/tpm_i2c_nuvoton.c | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) +Merging tpmdd-keys/for-next-keys (51cb1aa1250c3 Merge tag 'loongarch-7.2' of git://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson) +$ git merge -m Merge branch 'for-next-keys' of https://git.kernel.org/pub/scm/linux/kernel/git/jarkko/linux-tpmdd.git tpmdd-keys/for-next-keys +Already up to date. +Merging watchdog/watchdog-next (bc54a071a02dc dt-bindings: watchdog: Document Qualcomm Maili watchdog) +$ git merge -m Merge branch 'watchdog-next' of https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git watchdog/watchdog-next +Auto-merging drivers/watchdog/cros_ec_wdt.c +Auto-merging drivers/watchdog/max63xx_wdt.c +Auto-merging drivers/watchdog/max77620_wdt.c +Merge made by the 'ort' strategy. + Documentation/devicetree/bindings/watchdog/qcom-wdt.yaml | 1 + + Documentation/watchdog/watchdog-parameters.rst | 4 ++-- + drivers/watchdog/cros_ec_wdt.c | 4 ++-- + drivers/watchdog/max63xx_wdt.c | 14 +++++++------- + drivers/watchdog/max77620_wdt.c | 6 +++--- + 5 files changed, 15 insertions(+), 14 deletions(-) +Merging iommu/next (dd8a3c6cd531d Merge branches 'apple/dart', 'arm/smmu/updates', 'arm/smmu/bindings', 'rockchip', 'verisilicon', 'riscv', 'intel/vt-d', 'amd/amd-vi' and 'core' into next) +$ git merge -m Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/iommu/linux.git iommu/next +Already up to date. +Merging audit/next (e46c818d8fa66 Automated merge of 'dev' into 'next') +$ git merge -m Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/audit.git audit/next +Merge made by the 'ort' strategy. + include/asm-generic/audit_change_attr.h | 3 +++ + include/asm-generic/audit_read.h | 31 +++++++++++++++++++++++++++++++ + include/asm-generic/audit_write.h | 3 +++ + kernel/audit.c | 10 +++++----- + 4 files changed, 42 insertions(+), 5 deletions(-) +Merging devicetree/for-next (0108f362e4106 dt-bindings: sram: qcom,imem: Narrow allowed reboot modes) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git devicetree/for-next +Auto-merging Documentation/devicetree/bindings/arm/qcom.yaml +Auto-merging Documentation/devicetree/bindings/regulator/richtek,rtq2208.yaml +Merge made by the 'ort' strategy. + .../devicetree/bindings/arm/qcom-soc.yaml | 4 +- + Documentation/devicetree/bindings/arm/qcom.yaml | 4 +- + .../bindings/arm/samsung/samsung-soc.yaml | 4 +- + Documentation/devicetree/bindings/arm/xen.txt | 62 ------------ + Documentation/devicetree/bindings/arm/xen.yaml | 110 +++++++++++++++++++++ + .../bindings/clock/qcom,dispcc-sm8x50.yaml | 2 +- + .../bindings/clock/qcom,gcc-apq8064.yaml | 2 +- + .../bindings/clock/qcom,gcc-apq8084.yaml | 2 +- + .../bindings/clock/qcom,gcc-ipq6018.yaml | 2 +- + .../bindings/clock/qcom,gcc-ipq8064.yaml | 2 +- + .../bindings/clock/qcom,gcc-mdm9607.yaml | 2 +- + .../bindings/clock/qcom,gcc-mdm9615.yaml | 2 +- + .../bindings/clock/qcom,gcc-msm8660.yaml | 2 +- + .../bindings/clock/qcom,gcc-msm8909.yaml | 2 +- + .../bindings/clock/qcom,gcc-msm8916.yaml | 2 +- + .../bindings/clock/qcom,gcc-msm8953.yaml | 2 +- + .../bindings/clock/qcom,gcc-msm8974.yaml | 2 +- + .../devicetree/bindings/clock/qcom,gcc-sdm660.yaml | 2 +- + .../devicetree/bindings/clock/qcom,gpucc.yaml | 2 +- + .../bindings/clock/qcom,ipq5018-gcc.yaml | 2 +- + .../bindings/clock/qcom,ipq9574-gcc.yaml | 2 +- + .../bindings/clock/qcom,qca8k-nsscc.yaml | 2 +- + .../bindings/clock/qcom,qcm2290-gpucc.yaml | 2 +- + .../devicetree/bindings/clock/qcom,rpmcc.yaml | 2 +- + .../bindings/clock/qcom,sc7280-lpasscorecc.yaml | 2 +- + .../bindings/clock/qcom,sc8280xp-lpasscc.yaml | 2 +- + .../bindings/clock/qcom,sm6115-lpasscc.yaml | 2 +- + .../bindings/clock/qcom,sm8350-videocc.yaml | 2 +- + .../devicetree/bindings/clock/qcom,videocc.yaml | 2 +- + .../bindings/clock/samsung,exynos5260-clock.yaml | 6 +- + .../bindings/clock/samsung,exynos5410-clock.yaml | 2 +- + .../bindings/clock/samsung,exynos5433-clock.yaml | 2 +- + .../bindings/clock/samsung,exynos7-clock.yaml | 2 +- + .../bindings/clock/samsung,exynos850-clock.yaml | 2 +- + .../bindings/clock/samsung,exynosautov9-clock.yaml | 2 +- + .../clock/samsung,exynosautov920-clock.yaml | 2 +- + .../bindings/clock/samsung,s5pv210-clock.yaml | 2 +- + .../bindings/display/msm/dsi-controller-main.yaml | 20 ++-- + .../bindings/display/samsung/samsung,fimd.yaml | 4 +- + .../bindings/i2c/samsung,s3c2410-i2c.yaml | 2 +- + .../bindings/interconnect/qcom,msm8998-bwmon.yaml | 2 +- + .../bindings/interconnect/samsung,exynos-bus.yaml | 14 +-- + .../devicetree/bindings/leds/qcom,pm8058-led.yaml | 4 +- + .../devicetree/bindings/leds/skyworks,aat1290.yaml | 6 +- + .../devicetree/bindings/media/cec/cec-gpio.yaml | 2 +- + .../bindings/mmc/samsung,exynos-dw-mshc.yaml | 2 +- + .../bindings/phy/samsung,mipi-video-phy.yaml | 4 +- + .../devicetree/bindings/phy/samsung,usb2-phy.yaml | 2 +- + .../bindings/phy/samsung,usb3-drd-phy.yaml | 2 +- + .../bindings/pinctrl/samsung,pinctrl.yaml | 2 +- + .../bindings/power/renesas,rcar-sysc.yaml | 2 +- + .../bindings/power/reset/restart-handler.yaml | 8 +- + .../bindings/regulator/maxim,max77802.yaml | 4 +- + .../bindings/regulator/richtek,rtq2208.yaml | 2 +- + .../bindings/serial/qcom,msm-uartdm.yaml | 2 +- + .../devicetree/bindings/slimbus/slimbus.yaml | 4 +- + .../bindings/soc/qcom/qcom,apr-services.yaml | 2 +- + .../bindings/soc/qcom/qcom,rpmh-rsc.yaml | 8 +- + .../devicetree/bindings/soc/qcom/qcom,wcnss.yaml | 2 +- + .../bindings/soc/renesas/renesas-soc.yaml | 4 +- + .../devicetree/bindings/sound/qcom,q6asm-dais.yaml | 2 +- + .../devicetree/bindings/sram/qcom,imem.yaml | 20 ++++ + .../bindings/thermal/samsung,exynos-thermal.yaml | 4 +- + .../devicetree/bindings/usb/qcom,dwc3.yaml | 12 +-- + .../devicetree/bindings/usb/qcom,snps-dwc3.yaml | 12 +-- + 65 files changed, 237 insertions(+), 169 deletions(-) + delete mode 100644 Documentation/devicetree/bindings/arm/xen.txt + create mode 100644 Documentation/devicetree/bindings/arm/xen.yaml +Merging dt-krzk/for-next (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux-dt.git dt-krzk/for-next +Already up to date. +Merging mailbox/for-next (36cac4b5101f8 mailbox: imx: Don't force-thread the primary handler) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/jassibrar/mailbox.git mailbox/for-next +Already up to date. +Merging spi/for-next (a1766c1c03f82 Merge remote-tracking branch 'spi/for-7.3' into spi-next) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi.git spi/for-next +Auto-merging drivers/spi/spi-atcspi200.c +Merge made by the 'ort' strategy. + .../bindings/spi/cavium,octeon-3010-spi.yaml | 61 ++++++++++++++++++++++ + .../devicetree/bindings/spi/spi-octeon.txt | 33 ------------ + .../devicetree/bindings/spi/st,stm32-qspi.yaml | 3 ++ + drivers/spi/spi-atcspi200.c | 5 +- + drivers/spi/spi-bcm-qspi.c | 6 ++- + drivers/spi/spi-bcm63xx-hsspi.c | 6 ++- + drivers/spi/spi-bcm63xx.c | 5 +- + drivers/spi/spi-bcmbca-hsspi.c | 6 ++- + drivers/spi/spi-fsl-dspi.c | 21 ++++++-- + drivers/spi/spi-npcm-fiu.c | 15 +++--- + drivers/spi/spi-nxp-fspi.c | 31 +++++++---- + drivers/spi/spi-qpic-snand.c | 6 ++- + 12 files changed, 140 insertions(+), 58 deletions(-) + create mode 100644 Documentation/devicetree/bindings/spi/cavium,octeon-3010-spi.yaml + delete mode 100644 Documentation/devicetree/bindings/spi/spi-octeon.txt +Merging tip/master (aa6d9def48ea4 Merge branch into tip/master: 'x86/msr') +$ git merge -m Merge branch 'master' of https://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git tip/master +Auto-merging include/linux/cpuset.h +Auto-merging include/linux/sched.h +Auto-merging kernel/cgroup/cpuset.c +Auto-merging kernel/events/core.c +Auto-merging kernel/fork.c +Auto-merging kernel/irq/manage.c +Auto-merging tools/lib/bpf/usdt.c +Merge made by the 'ort' strategy. + Documentation/arch/x86/amd-memory-encryption.rst | 2 +- + .../interrupt-controller/realtek,rtl-intc.yaml | 5 +- + arch/x86/events/amd/uncore.c | 31 + + arch/x86/events/core.c | 3 +- + arch/x86/events/intel/core.c | 60 +- + arch/x86/events/intel/ds.c | 13 - + arch/x86/events/intel/lbr.c | 14 +- + arch/x86/events/intel/uncore.c | 227 ++-- + arch/x86/events/intel/uncore.h | 39 +- + arch/x86/events/intel/uncore_discovery.c | 21 +- + arch/x86/events/intel/uncore_discovery.h | 6 +- + arch/x86/events/intel/uncore_nhmex.c | 3 +- + arch/x86/events/intel/uncore_snb.c | 82 +- + arch/x86/events/intel/uncore_snbep.c | 77 +- + arch/x86/events/perf_event.h | 4 +- + arch/x86/hyperv/hv_apic.c | 12 +- + arch/x86/include/asm/linkage.h | 2 +- + arch/x86/include/asm/microcode.h | 2 - + arch/x86/include/asm/processor.h | 1 + + arch/x86/include/asm/resctrl.h | 5 +- + arch/x86/kernel/alternative.c | 12 +- + arch/x86/kernel/apic/apic.c | 39 +- + arch/x86/kernel/cpu/amd.c | 34 +- + arch/x86/kernel/cpu/centaur.c | 35 +- + arch/x86/kernel/cpu/common.c | 12 +- + arch/x86/kernel/cpu/hygon.c | 5 +- + arch/x86/kernel/cpu/intel.c | 43 +- + arch/x86/kernel/cpu/mce/amd.c | 87 +- + arch/x86/kernel/cpu/mce/core.c | 2 +- + arch/x86/kernel/cpu/mce/p5.c | 16 +- + arch/x86/kernel/cpu/mce/winchip.c | 10 +- + arch/x86/kernel/cpu/microcode/intel.c | 36 - + arch/x86/kernel/cpu/resctrl/core.c | 7 +- + arch/x86/kernel/cpu/resctrl/monitor.c | 27 +- + arch/x86/kernel/cpu/resctrl/pseudo_lock.c | 12 +- + arch/x86/kernel/cpu/transmeta.c | 9 +- + arch/x86/kernel/cpu/zhaoxin.c | 12 +- + arch/x86/kernel/fpu/core.c | 18 +- + arch/x86/kernel/tsc.c | 6 +- + arch/x86/kernel/tsc_msr.c | 15 +- + arch/x86/kernel/uprobes.c | 408 ++++--- + arch/x86/lib/msr-smp.c | 8 +- + arch/x86/mm/ident_map.c | 2 +- + arch/x86/pci/mmconfig-shared.c | 8 +- + arch/x86/platform/intel-quark/imr.c | 4 +- + arch/x86/platform/olpc/olpc-xo1-sci.c | 11 +- + arch/x86/ras/Kconfig | 23 - + drivers/edac/ie31200_edac.c | 10 +- + drivers/edac/mce_amd.c | 8 +- + drivers/hwmon/hwmon-vid.c | 11 +- + drivers/irqchip/irq-realtek-rtl.c | 152 ++- + drivers/ras/Kconfig | 24 +- + include/asm-generic/vdso/vsyscall.h | 4 +- + include/linux/cpuset.h | 6 + + include/linux/sched.h | 4 +- + include/linux/uprobes.h | 5 - + include/vdso/datapage.h | 6 +- + include/vdso/helpers.h | 4 +- + include/vdso/processor.h | 4 +- + include/vdso/vsyscall.h | 4 +- + kernel/cgroup/cpuset.c | 22 + + kernel/events/core.c | 41 +- + kernel/events/uprobes.c | 10 - + kernel/fork.c | 1 - + kernel/irq/irqdomain.c | 8 +- + kernel/irq/manage.c | 6 +- + kernel/sched/core.c | 5 +- + kernel/sched/deadline.c | 6 +- + kernel/sched/debug.c | 92 +- + kernel/sched/fair.c | 1224 +++++++++++--------- + kernel/sched/pelt.c | 6 +- + kernel/sched/psi.c | 2 +- + kernel/sched/rt.c | 3 +- + kernel/sched/sched.h | 49 +- + kernel/smp.c | 26 +- + kernel/time/hrtimer.c | 7 - + tools/lib/bpf/features.c | 4 +- + tools/lib/bpf/usdt.c | 16 +- + tools/testing/selftests/bpf/bench.c | 20 +- + tools/testing/selftests/bpf/benchs/bench_trigger.c | 38 +- + .../selftests/bpf/benchs/run_bench_uprobes.sh | 2 +- + .../selftests/bpf/prog_tests/uprobe_syscall.c | 325 +++++- + tools/testing/selftests/bpf/prog_tests/usdt.c | 74 +- + tools/testing/selftests/bpf/progs/test_usdt.c | 25 + + tools/testing/selftests/bpf/usdt.h | 2 +- + tools/testing/selftests/bpf/usdt_2.c | 15 +- + .../selftests/rseq/rseq-x86-thread-pointer.h | 4 +- + 87 files changed, 2325 insertions(+), 1460 deletions(-) + delete mode 100644 arch/x86/ras/Kconfig +$ git am -3 ../patches/0001-vdso-Merge-fixup-with-mm.patch +Applying: vdso: Merge fixup with mm +Using index info to reconstruct a base tree... +M kernel/time/namespace_vdso.c +Falling back to patching base and 3-way merge... +No changes -- Patch already applied. +Merging kexec/kexec-next (af78cec42d1b0 Merge branch 'crashkernel-cma' into kexec-next) +$ git merge -m Merge branch 'kexec-next' of https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git kexec/kexec-next +Auto-merging Documentation/admin-guide/kernel-parameters.txt +Auto-merging arch/riscv/mm/init.c +Merge made by the 'ort' strategy. + Documentation/admin-guide/kernel-parameters.txt | 16 ++-- + arch/arm64/kernel/machine_kexec_file.c | 40 +++------- + arch/arm64/mm/init.c | 5 +- + arch/loongarch/kernel/machine_kexec_file.c | 40 +++------- + arch/powerpc/include/asm/kexec_ranges.h | 1 - + arch/powerpc/kexec/crash.c | 5 +- + arch/powerpc/kexec/ranges.c | 101 +----------------------- + arch/riscv/kernel/machine_kexec_file.c | 39 +++------ + arch/riscv/mm/init.c | 5 +- + arch/x86/kernel/crash.c | 89 +++------------------ + drivers/of/fdt.c | 9 ++- + drivers/of/kexec.c | 9 +++ + include/linux/crash_core.h | 9 +++ + include/linux/crash_reserve.h | 4 +- + include/linux/kexec.h | 2 +- + kernel/crash_core.c | 89 ++++++++++++++++++++- + kernel/kexec_file.c | 27 +++++++ + 17 files changed, 208 insertions(+), 282 deletions(-) +Merging liveupdate/next (af78cec42d1b0 Merge branch 'crashkernel-cma' into kexec-next) +$ git merge -m Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git liveupdate/next +Already up to date. +Merging clockevents/timers/drivers/next (a9ac745bc320c clocksource: move NXP timer selection to drivers/clocksource) +$ git merge -m Merge branch 'timers/drivers/next' of https://git.kernel.org/pub/scm/linux/kernel/git/daniel.lezcano/linux.git clockevents/timers/drivers/next +Already up to date. +Merging edac/edac-for-next (23f1fdb15eeb9 Merge ras/edac-misc into for-next) +$ git merge -m Merge branch 'edac-for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/ras/ras.git edac/edac-for-next +Auto-merging CREDITS +Auto-merging MAINTAINERS +Merge made by the 'ort' strategy. + CREDITS | 4 +++ + MAINTAINERS | 10 ++----- + drivers/edac/debugfs.c | 65 +------------------------------------------- + drivers/edac/edac_mc_sysfs.c | 2 +- + include/linux/edac.h | 3 -- + 5 files changed, 9 insertions(+), 75 deletions(-) +Merging ftrace/for-next (030bb1d8bb077 Merge probes/for-next) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace.git ftrace/for-next +Auto-merging MAINTAINERS +Auto-merging Makefile +Auto-merging arch/x86/Kconfig +Auto-merging init/Kconfig +Auto-merging init/main.c +Auto-merging lib/Makefile +Merge made by the 'ort' strategy. + Documentation/admin-guide/bootconfig.rst | 81 +++ + Documentation/trace/eprobetrace.rst | 7 +- + Documentation/trace/fprobetrace.rst | 10 + + Documentation/trace/kprobetrace.rst | 11 + + MAINTAINERS | 1 + + Makefile | 27 +- + arch/x86/Kconfig | 1 + + arch/x86/kernel/setup.c | 14 +- + include/asm-generic/kprobes.h | 4 +- + include/linux/bootconfig.h | 14 + + init/Kconfig | 36 ++ + init/main.c | 52 +- + kernel/trace/Kconfig | 12 + + kernel/trace/trace.c | 8 +- + kernel/trace/trace_eprobe.c | 2 + + kernel/trace/trace_fprobe.c | 6 +- + kernel/trace/trace_kprobe.c | 2 + + kernel/trace/trace_probe.c | 612 +++++++++++++++++---- + kernel/trace/trace_probe.h | 102 ++-- + kernel/trace/trace_probe_tmpl.h | 27 +- + kernel/trace/trace_uprobe.c | 3 + + lib/Makefile | 16 + + lib/bootconfig.c | 139 ++++- + lib/embedded-cmdline.S | 16 + + samples/trace_events/trace-events-sample.c | 40 +- + samples/trace_events/trace-events-sample.h | 34 +- + tools/bootconfig/Makefile | 4 +- + tools/bootconfig/scripts/ftrace2bconf.sh | 2 + + .../ftrace/test.d/dynevent/btf_probe_event.tc | 51 ++ + .../test.d/dynevent/btf_typecast_accepted.tc | 103 ++++ + .../test.d/dynevent/eprobes_syntax_errors.tc | 12 +- + .../ftrace/test.d/dynevent/fprobe_syntax_errors.tc | 12 + + .../ftrace/test.d/kprobe/kprobe_syntax_errors.tc | 12 + + .../ftrace/test.d/kprobe/uprobe_syntax_errors.tc | 5 + + 34 files changed, 1274 insertions(+), 204 deletions(-) + create mode 100644 lib/embedded-cmdline.S + create mode 100644 tools/testing/selftests/ftrace/test.d/dynevent/btf_probe_event.tc + create mode 100644 tools/testing/selftests/ftrace/test.d/dynevent/btf_typecast_accepted.tc +$ git am -3 ../patches/0001-ftrace-Fix-semantic-conflict-with-mm-tree.patch +Applying: ftrace: Fix semantic conflict with mm tree +Using index info to reconstruct a base tree... +M kernel/trace/trace_printk.c +Falling back to patching base and 3-way merge... +Auto-merging kernel/trace/trace_printk.c +No changes -- Patch already applied. +Merging rcu/next (e853c1b28580e Merge branches 'rcutorture.2026.05.24' and 'misc.2026.05.24' into rcu-merge.2026.05.24) +$ git merge -m Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/rcu/linux rcu/next +Already up to date. +Merging paulmck/non-rcu/next (48f7a50c027dd stop_machine: Fix the documentation for a NULL cpus argument) +$ git merge -m Merge branch 'non-rcu/next' of https://git.kernel.org/pub/scm/linux/kernel/git/paulmck/linux-rcu.git paulmck/non-rcu/next +Already up to date. +Merging kvm/next (a204badd8432f Merge branch 'kvm-chainsaw' into HEAD) +$ git merge -m Merge branch 'next' of git://git.kernel.org/pub/scm/virt/kvm/kvm.git kvm/next +Auto-merging arch/x86/kvm/mmu/mmu.c +Auto-merging arch/x86/kvm/mmu/spte.c +Auto-merging arch/x86/kvm/pmu.c +Auto-merging arch/x86/kvm/svm/svm.c +Auto-merging arch/x86/kvm/vmx/vmx.c +Merge made by the 'ort' strategy. + arch/x86/include/asm/kvm_host.h | 655 +--- + arch/x86/kvm/Makefile | 4 +- + arch/x86/kvm/cpuid.c | 1 + + arch/x86/kvm/fpu.h | 26 + + arch/x86/kvm/hyperv.c | 7 +- + arch/x86/kvm/hyperv.h | 3 +- + arch/x86/kvm/ioapic.c | 1 + + arch/x86/kvm/ioapic.h | 12 + + arch/x86/kvm/irq.c | 7 + + arch/x86/kvm/irq.h | 6 + + arch/x86/kvm/lapic.h | 8 + + arch/x86/kvm/mmu.h | 121 +- + arch/x86/kvm/mmu/mmu.c | 541 +-- + arch/x86/kvm/mmu/mmu_internal.h | 66 - + arch/x86/kvm/mmu/paging_tmpl.h | 88 +- + arch/x86/kvm/mmu/spte.c | 4 +- + arch/x86/kvm/mmu/spte.h | 69 +- + arch/x86/kvm/mmu/tdp_mmu.c | 3 +- + arch/x86/kvm/msrs.c | 2745 ++++++++++++++ + arch/x86/kvm/msrs.h | 156 + + arch/x86/kvm/mtrr.c | 2 +- + arch/x86/kvm/pmu.c | 12 + + arch/x86/kvm/regs.c | 874 +++++ + arch/x86/kvm/regs.h | 60 + + arch/x86/kvm/svm/nested.c | 14 +- + arch/x86/kvm/svm/svm.c | 6 +- + arch/x86/kvm/tss.h | 7 + + arch/x86/kvm/vmx/nested.c | 16 +- + arch/x86/kvm/vmx/vmx.c | 26 +- + arch/x86/kvm/x86.c | 3762 +------------------- + arch/x86/kvm/x86.h | 462 ++- + tools/testing/selftests/kvm/Makefile.kvm | 1 + + tools/testing/selftests/kvm/include/x86/pmu.h | 6 + + .../testing/selftests/kvm/include/x86/processor.h | 10 + + tools/testing/selftests/kvm/include/x86/svm_util.h | 5 +- + tools/testing/selftests/kvm/include/x86/vmx.h | 4 +- + tools/testing/selftests/kvm/lib/x86/memstress.c | 19 +- + tools/testing/selftests/kvm/lib/x86/processor.c | 45 +- + tools/testing/selftests/kvm/lib/x86/svm.c | 6 +- + tools/testing/selftests/kvm/lib/x86/vmx.c | 6 +- + tools/testing/selftests/kvm/x86/aperfmperf_test.c | 9 +- + .../selftests/kvm/x86/evmcs_smm_controls_test.c | 5 +- + tools/testing/selftests/kvm/x86/hyperv_evmcs.c | 6 +- + tools/testing/selftests/kvm/x86/hyperv_svm_test.c | 6 +- + tools/testing/selftests/kvm/x86/kvm_buslock_test.c | 9 +- + .../selftests/kvm/x86/nested_close_kvm_test.c | 12 +- + .../selftests/kvm/x86/nested_dirty_log_test.c | 8 +- + .../selftests/kvm/x86/nested_emulation_test.c | 4 +- + .../selftests/kvm/x86/nested_exceptions_test.c | 9 +- + .../selftests/kvm/x86/nested_invalid_cr3_test.c | 10 +- + .../selftests/kvm/x86/nested_tdp_fault_test.c | 9 +- + .../selftests/kvm/x86/nested_tsc_adjust_test.c | 10 +- + .../selftests/kvm/x86/nested_tsc_scaling_test.c | 10 +- + .../selftests/kvm/x86/nested_vmsave_vmload_test.c | 6 +- + tools/testing/selftests/kvm/x86/smm_test.c | 8 +- + tools/testing/selftests/kvm/x86/state_test.c | 11 +- + tools/testing/selftests/kvm/x86/svm_int_ctl_test.c | 5 +- + .../selftests/kvm/x86/svm_lbr_nested_state.c | 6 +- + .../selftests/kvm/x86/svm_nested_clear_efer_svme.c | 7 +- + .../selftests/kvm/x86/svm_nested_pat_test.c | 8 +- + .../selftests/kvm/x86/svm_nested_shutdown_test.c | 5 +- + .../kvm/x86/svm_nested_soft_inject_test.c | 6 +- + .../selftests/kvm/x86/svm_nested_vmcb12_gpa.c | 13 +- + .../selftests/kvm/x86/svm_pmu_host_guest_test.c | 215 ++ + tools/testing/selftests/kvm/x86/svm_vmcall_test.c | 5 +- + .../selftests/kvm/x86/triple_fault_event_test.c | 9 +- + .../selftests/kvm/x86/vmx_apic_access_test.c | 5 +- + .../selftests/kvm/x86/vmx_apicv_updates_test.c | 4 +- + .../kvm/x86/vmx_invalid_nested_guest_state.c | 6 +- + .../selftests/kvm/x86/vmx_nested_la57_state_test.c | 5 +- + .../selftests/kvm/x86/vmx_preemption_timer_test.c | 5 +- + 71 files changed, 5273 insertions(+), 5029 deletions(-) + create mode 100644 arch/x86/kvm/msrs.c + create mode 100644 arch/x86/kvm/msrs.h + create mode 100644 arch/x86/kvm/regs.c + create mode 100644 tools/testing/selftests/kvm/x86/svm_pmu_host_guest_test.c +Merging kvm-arm/next (1ee27dacbe5dc Merge branch kvm-arm64/nv-mmu-7.2 into kvmarm-master/next) +$ git merge -m Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/kvmarm/kvmarm.git kvm-arm/next +Already up to date. +Merging kvms390/next (babe08404e199 KVM: s390: Return failure in case of failure in kvm_s390_set_cmma_bits()) +$ git merge -m Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/kvms390/linux.git kvms390/next +Already up to date. +Merging kvm-ppc/topic/ppc-kvm (5200f5f493f79 Linux 7.1-rc4) +$ git merge -m Merge branch 'topic/ppc-kvm' of https://git.kernel.org/pub/scm/linux/kernel/git/powerpc/linux.git kvm-ppc/topic/ppc-kvm +Already up to date. +Merging kvm-riscv/riscv_kvm_next (52738352a6f29 riscv: kvm: Use endian-specific __lelong for NACL shared memory) +$ git merge -m Merge branch 'riscv_kvm_next' of https://github.com/kvm-riscv/linux.git kvm-riscv/riscv_kvm_next +Already up to date. +Merging kvm-x86/next (50406d35f5635 Merge tag 'kvm-x86-selftests_l2_stacks-7.3' of https://github.com/kvm-x86/linux into HEAD) +$ git merge -m Merge branch 'next' of https://github.com/kvm-x86/linux.git kvm-x86/next +Already up to date. +$ git am -3 ../patches/0001-KVM-selftests-Fix-up-semantic-changes.patch +Applying: KVM: selftests: Fix up semantic changes +Using index info to reconstruct a base tree... +M tools/testing/selftests/kvm/lib/kvm_util.c +Falling back to patching base and 3-way merge... +Auto-merging tools/testing/selftests/kvm/lib/kvm_util.c +No changes -- Patch already applied. +Merging xen-tip/linux-next (fcd245ea7528d x86/Xen: correct commentary and parameter naming of xen_exchange_memory()) +$ git merge -m Merge branch 'linux-next' of https://git.kernel.org/pub/scm/linux/kernel/git/xen/tip.git xen-tip/linux-next +Already up to date. +Merging percpu/for-next (8f0b4cce4481f Linux 6.19-rc1) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/dennis/percpu.git percpu/for-next +Already up to date. +Merging workqueues/for-next (ecf5aad9a4417 workqueue: annotate racy PWQ_STAT_CPU_TIME update in wq_worker_tick()) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/tj/wq.git workqueues/for-next +Auto-merging kernel/workqueue.c +Merge made by the 'ort' strategy. + kernel/workqueue.c | 134 ++++++++++++++++++++++++++++++++++++++---- + tools/workqueue/wq_dump.py | 10 ++-- + tools/workqueue/wq_monitor.py | 6 +- + 3 files changed, 129 insertions(+), 21 deletions(-) +Merging sched-ext/for-next (57194a3172ba0 Merge branch 'for-7.3' into for-next) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/tj/sched_ext.git sched-ext/for-next +Merge made by the 'ort' strategy. + include/linux/sched/ext.h | 4 +- + kernel/rcu/tree.c | 3 + + kernel/rcu/tree_exp.h | 5 +- + kernel/rcu/tree_stall.h | 13 +- + kernel/sched/build_policy.c | 2 + + kernel/sched/ext/arena.c | 13 +- + kernel/sched/ext/ext.c | 1354 +++++------------------------- + kernel/sched/ext/internal.h | 209 ++++- + kernel/sched/ext/sub.c | 668 +++++++++++++++ + kernel/sched/ext/sub.h | 161 ++++ + tools/sched_ext/include/scx/common.bpf.h | 5 +- + tools/sched_ext/include/scx/compat.bpf.h | 17 - + tools/sched_ext/include/scx/compat.h | 10 +- + tools/sched_ext/scx_cpu0.c | 2 +- + tools/sched_ext/scx_flatcg.bpf.c | 5 +- + tools/sched_ext/scx_sdt.c | 2 +- + tools/sched_ext/scx_simple.c | 2 +- + tools/sched_ext/scx_userland.c | 2 +- + 18 files changed, 1293 insertions(+), 1184 deletions(-) + create mode 100644 kernel/sched/ext/sub.c + create mode 100644 kernel/sched/ext/sub.h +Merging drivers-x86/for-next (ff7836fa850c2 platform/x86/amd/hsmp: Gate the data plane on a fully initialized socket) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git drivers-x86/for-next +Auto-merging drivers/platform/x86/msi-ec.c +Merge made by the 'ort' strategy. + .../ABI/testing/sysfs-driver-uniwill-laptop | 25 + + .../admin-guide/laptops/uniwill-laptop.rst | 27 + + drivers/platform/surface/surface_aggregator_hub.c | 9 +- + .../platform/surface/surface_aggregator_tabletsw.c | 9 +- + drivers/platform/x86/amd/hsmp/acpi.c | 53 +- + drivers/platform/x86/amd/hsmp/hsmp.c | 13 + + drivers/platform/x86/asus-armoury.c | 20 + + drivers/platform/x86/asus-wmi.c | 24 + + drivers/platform/x86/dell/dell-wmi-base.c | 19 +- + drivers/platform/x86/dell/dell-wmi-ddv.c | 22 +- + drivers/platform/x86/dell/dell-wmi-privacy.c | 4 +- + .../x86/dell/dell-wmi-sysman/biosattr-interface.c | 1 - + drivers/platform/x86/hp/hp-wmi.c | 210 ++++--- + drivers/platform/x86/lenovo/ymc.c | 8 +- + drivers/platform/x86/msi-ec.c | 81 +++ + drivers/platform/x86/msi-wmi.c | 97 ++-- + drivers/platform/x86/toshiba_bluetooth.c | 6 +- + drivers/platform/x86/uniwill/uniwill-acpi.c | 609 ++++++++++++++++++++- + drivers/platform/x86/uniwill/uniwill-wmi.h | 2 + + drivers/power/supply/surface_battery.c | 11 +- + drivers/power/supply/surface_charger.c | 7 +- + include/linux/platform_data/x86/asus-wmi.h | 5 + + 22 files changed, 1104 insertions(+), 158 deletions(-) +$ git am -3 ../patches/0001-hid-Fix-up-mismerge.patch +Applying: hid: Fix up mismerge +$ git reset HEAD^ +Unstaged changes after reset: +M drivers/hid/hid-asus.c +$ git add -A . +$ git commit -v -a --amend +warning: notes ref refs/notes/commits is invalid +[master a0f2389910914] Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git + Date: Mon Jul 6 14:54:08 2026 +0100 +Merging chrome-platform/for-next (d1ceb2b232471 platform/chrome: sensorhub: Fix memory overread in ring handler) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/chrome-platform/linux.git chrome-platform/for-next +Auto-merging drivers/platform/chrome/cros_ec_chardev.c +Auto-merging drivers/platform/chrome/cros_ec_debugfs.c +Auto-merging drivers/platform/chrome/cros_ec_lightbar.c +Auto-merging drivers/platform/chrome/cros_ec_sensorhub.c +Auto-merging drivers/platform/chrome/cros_ec_sysfs.c +Auto-merging drivers/platform/chrome/cros_ec_vbc.c +Auto-merging drivers/platform/chrome/cros_kbd_led_backlight.c +Auto-merging drivers/platform/chrome/cros_usbpd_logger.c +Auto-merging drivers/platform/chrome/cros_usbpd_notify.c +Auto-merging drivers/platform/chrome/wilco_ec/core.c +Auto-merging drivers/platform/chrome/wilco_ec/debugfs.c +Auto-merging drivers/platform/chrome/wilco_ec/telemetry.c +Merge made by the 'ort' strategy. + drivers/platform/chrome/chromeos_of_hw_prober.c | 2 +- + drivers/platform/chrome/cros_ec_chardev.c | 4 ++-- + drivers/platform/chrome/cros_ec_debugfs.c | 4 ++-- + drivers/platform/chrome/cros_ec_lightbar.c | 4 ++-- + drivers/platform/chrome/cros_ec_sensorhub.c | 4 ++-- + drivers/platform/chrome/cros_ec_sensorhub_ring.c | 17 ++++++++++++++++- + drivers/platform/chrome/cros_ec_sysfs.c | 4 ++-- + drivers/platform/chrome/cros_ec_typec.c | 6 ++++++ + drivers/platform/chrome/cros_ec_vbc.c | 4 ++-- + drivers/platform/chrome/cros_kbd_led_backlight.c | 4 ++-- + drivers/platform/chrome/cros_usbpd_logger.c | 4 ++-- + drivers/platform/chrome/cros_usbpd_notify.c | 4 ++-- + drivers/platform/chrome/wilco_ec/core.c | 4 ++-- + drivers/platform/chrome/wilco_ec/debugfs.c | 4 ++-- + drivers/platform/chrome/wilco_ec/telemetry.c | 4 ++-- + 15 files changed, 47 insertions(+), 26 deletions(-) +Merging chrome-platform-firmware/for-firmware-next (dd06eb60e86a1 docs: ABI: testing: Fix typo) +$ git merge -m Merge branch 'for-firmware-next' of https://git.kernel.org/pub/scm/linux/kernel/git/chrome-platform/linux.git chrome-platform-firmware/for-firmware-next +Merge made by the 'ort' strategy. + Documentation/ABI/testing/sysfs-firmware-gsmi | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) +Merging hsi/for-next (254f49634ee16 Linux 7.1-rc1) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-hsi.git hsi/for-next +Already up to date. +Merging leds-lj/for-leds-next (7ddc04d1bd08f leds: trigger: netdev: Extend speeds up to 100G) +$ git merge -m Merge branch 'for-leds-next' of https://git.kernel.org/pub/scm/linux/kernel/git/lee/leds.git leds-lj/for-leds-next +Auto-merging drivers/leds/rgb/leds-pwm-multicolor.c +Merge made by the 'ort' strategy. + drivers/leds/rgb/leds-pwm-multicolor.c | 2 ++ + drivers/leds/trigger/ledtrig-netdev.c | 46 +++++++++++++++++++++++++++++++++- + include/linux/leds.h | 4 +++ + 3 files changed, 51 insertions(+), 1 deletion(-) +Merging ipmi/for-next (6d920a75df9a8 ipmi: si: Fix NULL pointer dereference after failed registration) +$ git merge -m Merge branch 'for-next' of https://github.com/cminyard/linux-ipmi.git ipmi/for-next +Merge made by the 'ort' strategy. + drivers/char/ipmi/ipmb_dev_int.c | 5 +++-- + drivers/char/ipmi/ipmi_msghandler.c | 1 + + 2 files changed, 4 insertions(+), 2 deletions(-) +Merging driver-core/driver-core-next (5dcef303b29f0 rust: io: fix example in `register!` macro) +$ git merge -m Merge branch 'driver-core-next' of https://git.kernel.org/pub/scm/linux/kernel/git/driver-core/driver-core.git driver-core/driver-core-next +Merge made by the 'ort' strategy. + drivers/base/isa.c | 16 +++++++++------- + drivers/base/module.c | 2 +- + fs/debugfs/file.c | 3 ++- + rust/kernel/io/register.rs | 2 +- + 4 files changed, 13 insertions(+), 10 deletions(-) +Merging usb/usb-next (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'usb-next' of https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb.git usb/usb-next +Already up to date. +Merging thunderbolt/next (d49d71ebec8f0 thunderbolt: xdomain: Notify peers after enumeration) +$ git merge -m Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/westeri/thunderbolt.git thunderbolt/next +Auto-merging drivers/net/thunderbolt/main.c +Auto-merging drivers/thunderbolt/stream.c +Auto-merging drivers/thunderbolt/tb.c +Auto-merging include/linux/thunderbolt.h +Merge made by the 'ort' strategy. + drivers/net/thunderbolt/main.c | 4 ++-- + drivers/thunderbolt/dma_test.c | 4 ++-- + drivers/thunderbolt/domain.c | 4 +--- + drivers/thunderbolt/nhi.c | 2 ++ + drivers/thunderbolt/pci.c | 28 +++++++++++++++++++++++----- + drivers/thunderbolt/stream.c | 4 ++-- + drivers/thunderbolt/switch.c | 11 ++++++++++- + drivers/thunderbolt/tb.c | 21 +++++++++++++++++++++ + drivers/thunderbolt/tb.h | 1 + + drivers/thunderbolt/xdomain.c | 4 ++++ + include/linux/thunderbolt.h | 8 +++++++- + 11 files changed, 75 insertions(+), 16 deletions(-) +Merging usb-serial/usb-next (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'usb-next' of https://git.kernel.org/pub/scm/linux/kernel/git/johan/usb-serial.git usb-serial/usb-next +Already up to date. +Merging tty/tty-next (e508a176d86f5 tty: goldfish: use guard() for locks) +$ git merge -m Merge branch 'tty-next' of https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/tty.git tty/tty-next +Auto-merging drivers/tty/goldfish.c +Merge made by the 'ort' strategy. + drivers/tty/goldfish.c | 23 ++++++++++++++--------- + include/linux/goldfish.h | 24 ------------------------ + 2 files changed, 14 insertions(+), 33 deletions(-) +Merging char-misc/char-misc-next (9e32d2a978473 rust: binder: enable `clippy::cast_lossless`) +$ git merge -m Merge branch 'char-misc-next' of https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc.git char-misc/char-misc-next +Auto-merging drivers/android/binder/allocation.rs +Auto-merging drivers/android/binder/freeze.rs +Auto-merging drivers/android/binder/node.rs +Auto-merging drivers/android/binder/process.rs +Auto-merging drivers/android/binder/thread.rs +Auto-merging drivers/android/binder/transaction.rs +Merge made by the 'ort' strategy. + drivers/android/binder/allocation.rs | 4 ++-- + drivers/android/binder/defs.rs | 8 +++++--- + drivers/android/binder/freeze.rs | 2 +- + drivers/android/binder/node.rs | 14 ++++++++------ + drivers/android/binder/node/wrapper.rs | 2 +- + drivers/android/binder/page_range.rs | 8 ++++---- + drivers/android/binder/process.rs | 14 +++++++------- + drivers/android/binder/rust_binder_main.rs | 10 ++-------- + drivers/android/binder/thread.rs | 24 +++++++++++++----------- + drivers/android/binder/trace.rs | 4 +++- + drivers/android/binder/transaction.rs | 14 ++++++++------ + 11 files changed, 54 insertions(+), 50 deletions(-) +$ git am -3 ../patches/0001-bus-mhi-Fix-up-interaction-with-net-next-tree.patch +Applying: bus: mhi: Fix up interaction with net-next tree +Using index info to reconstruct a base tree... +M drivers/net/wireless/ath/ath12k/wifi7/mhi.c +Falling back to patching base and 3-way merge... +No changes -- Patch already applied. +Merging coresight/next (98495b5a4d77d coresight: ultrasoc-smb: Fix OOB write in smb_sync_perf_buffer()) +$ git merge -m Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/coresight/linux.git coresight/next +Already up to date. +Merging fastrpc/for-next (17ec912dc87bf Merge branches 'fastrpc-fixes' and 'fastrpc-for-7.3' into fastrpc-for-next) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/srini/fastrpc.git fastrpc/for-next +Auto-merging MAINTAINERS +Merge made by the 'ort' strategy. + MAINTAINERS | 2 +- + drivers/misc/fastrpc.c | 46 ++++++++++++++++++++++++++++------------------ + 2 files changed, 29 insertions(+), 19 deletions(-) +Merging fpga/for-next (43a1974da6bc7 fpga: microchip-spi: fix zero header_size OOB read in mpf_ops_parse_header()) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/fpga/linux-fpga.git fpga/for-next +Already up to date. +Merging icc/icc-next (94fe92d2f662b Merge branch 'icc-misc' into icc-next) +$ git merge -m Merge branch 'icc-next' of https://git.kernel.org/pub/scm/linux/kernel/git/djakov/icc.git icc/icc-next +Already up to date. +Merging iio/togreg (fef4337eb2888 iio: temperature: tmp117: add TI TMP119 support) +$ git merge -m Merge branch 'togreg' of https://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio.git iio/togreg +Auto-merging Documentation/devicetree/bindings/vendor-prefixes.yaml +Auto-merging MAINTAINERS +Auto-merging drivers/iio/accel/adxl355_spi.c +Auto-merging drivers/iio/accel/adxl367_spi.c +Auto-merging drivers/iio/accel/adxl372_spi.c +Auto-merging drivers/iio/accel/adxl380_spi.c +Auto-merging drivers/iio/accel/bma220_spi.c +Auto-merging drivers/iio/accel/bma400_spi.c +Auto-merging drivers/iio/accel/bmc150-accel-core.c +Auto-merging drivers/iio/accel/bmc150-accel-spi.c +Auto-merging drivers/iio/accel/fxls8962af-core.c +Auto-merging drivers/iio/accel/fxls8962af-spi.c +Auto-merging drivers/iio/accel/hid-sensor-accel-3d.c +Auto-merging drivers/iio/accel/kxsd9-spi.c +Auto-merging drivers/iio/accel/st_accel_spi.c +Auto-merging drivers/iio/accel/stk8ba50.c +CONFLICT (content): Merge conflict in drivers/iio/accel/stk8ba50.c +Auto-merging drivers/iio/adc/Kconfig +Auto-merging drivers/iio/adc/ad4000.c +Auto-merging drivers/iio/adc/ad4080.c +Auto-merging drivers/iio/adc/ad4134.c +Auto-merging drivers/iio/adc/ad4851.c +Auto-merging drivers/iio/adc/ad7124.c +Auto-merging drivers/iio/adc/ad7173.c +Auto-merging drivers/iio/adc/ad7191.c +Auto-merging drivers/iio/adc/ad7192.c +Auto-merging drivers/iio/adc/ad7280a.c +Auto-merging drivers/iio/adc/ad7292.c +Auto-merging drivers/iio/adc/ad7298.c +Auto-merging drivers/iio/adc/hi8435.c +Auto-merging drivers/iio/adc/hx711.c +Auto-merging drivers/iio/adc/max1027.c +Auto-merging drivers/iio/adc/max1118.c +Auto-merging drivers/iio/adc/max14001.c +Auto-merging drivers/iio/adc/mcp320x.c +Auto-merging drivers/iio/adc/mcp3911.c +Auto-merging drivers/iio/adc/rohm-bd79112.c +Auto-merging drivers/iio/adc/rtq6056.c +Auto-merging drivers/iio/adc/ti-adc0832.c +Auto-merging drivers/iio/adc/ti-adc084s021.c +Auto-merging drivers/iio/adc/ti-adc108s102.c +Auto-merging drivers/iio/adc/ti-adc128s052.c +Auto-merging drivers/iio/adc/ti-adc161s626.c +Auto-merging drivers/iio/adc/ti-ads1018.c +Auto-merging drivers/iio/adc/ti-ads124s08.c +Auto-merging drivers/iio/adc/ti-ads131m02.c +Auto-merging drivers/iio/adc/ti-ads8688.c +Auto-merging drivers/iio/adc/ti-tlc4541.c +Auto-merging drivers/iio/amplifiers/ad8366.c +Auto-merging drivers/iio/chemical/bme680_spi.c +Auto-merging drivers/iio/dac/ad3530r.c +Auto-merging drivers/iio/dac/ad5446-spi.c +Auto-merging drivers/iio/dac/ad5706r.c +Auto-merging drivers/iio/dac/ad5758.c +Auto-merging drivers/iio/dac/ad7293.c +Auto-merging drivers/iio/dac/ad7303.c +Auto-merging drivers/iio/dac/ad9739a.c +Auto-merging drivers/iio/dac/ltc2664.c +Auto-merging drivers/iio/dac/ltc2688.c +Auto-merging drivers/iio/dac/max22007.c +Auto-merging drivers/iio/dac/max5522.c +Auto-merging drivers/iio/dac/mcp4821.c +Auto-merging drivers/iio/dac/ti-dac082s085.c +Auto-merging drivers/iio/filter/admv8818.c +Auto-merging drivers/iio/frequency/adf4350.c +Auto-merging drivers/iio/frequency/admv1013.c +Auto-merging drivers/iio/frequency/admv1014.c +Auto-merging drivers/iio/frequency/adrf6780.c +Auto-merging drivers/iio/gyro/fxas21002c_spi.c +Auto-merging drivers/iio/gyro/hid-sensor-gyro-3d.c +Auto-merging drivers/iio/gyro/st_gyro_spi.c +Auto-merging drivers/iio/humidity/hid-sensor-humidity.c +Auto-merging drivers/iio/imu/adis16475.c +Auto-merging drivers/iio/imu/adis16480.c +Auto-merging drivers/iio/imu/bmi160/bmi160_spi.c +Auto-merging drivers/iio/imu/bmi270/bmi270_spi.c +Auto-merging drivers/iio/imu/bmi323/bmi323_spi.c +Auto-merging drivers/iio/imu/fxos8700_spi.c +Auto-merging drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c +Auto-merging drivers/iio/imu/inv_icm45600/inv_icm45600_spi.c +Auto-merging drivers/iio/imu/inv_mpu6050/inv_mpu_spi.c +Auto-merging drivers/iio/imu/st_lsm9ds0/st_lsm9ds0_spi.c +Auto-merging drivers/iio/light/hid-sensor-als.c +Auto-merging drivers/iio/light/hid-sensor-prox.c +Auto-merging drivers/iio/light/opt3001.c +Auto-merging drivers/iio/light/st_uvis25_spi.c +Auto-merging drivers/iio/magnetometer/bmc150_magn_spi.c +Auto-merging drivers/iio/magnetometer/mmc5983.c +Auto-merging drivers/iio/magnetometer/st_magn_spi.c +Auto-merging drivers/iio/orientation/hid-sensor-incl-3d.c +Auto-merging drivers/iio/orientation/hid-sensor-rotation.c +Auto-merging drivers/iio/position/hid-sensor-custom-intel-hinge.c +Auto-merging drivers/iio/potentiometer/max5481.c +Auto-merging drivers/iio/potentiometer/max5487.c +Auto-merging drivers/iio/potentiometer/mcp41010.c +Auto-merging drivers/iio/potentiometer/mcp4131.c +Auto-merging drivers/iio/pressure/abp2030pa_spi.c +Auto-merging drivers/iio/pressure/hid-sensor-press.c +Auto-merging drivers/iio/pressure/hsc030pa_spi.c +Auto-merging drivers/iio/pressure/mprls0025pa_spi.c +Auto-merging drivers/iio/pressure/ms5611_spi.c +Auto-merging drivers/iio/pressure/st_pressure_spi.c +Auto-merging drivers/iio/pressure/zpa2326_spi.c +Auto-merging drivers/iio/proximity/as3935.c +Auto-merging drivers/iio/proximity/vl53l1x-i2c.c +Auto-merging drivers/iio/resolver/ad2s1200.c +Auto-merging drivers/iio/temperature/hid-sensor-temperature.c +Auto-merging drivers/iio/temperature/ltc2983.c +Auto-merging drivers/iio/temperature/max31856.c +Auto-merging drivers/iio/temperature/max31865.c +Auto-merging drivers/iio/temperature/maxim_thermocouple.c +Auto-merging drivers/staging/iio/frequency/ad9832.c +Auto-merging drivers/staging/iio/frequency/ad9834.c +Auto-merging lib/vsprintf.c +Resolved 'drivers/iio/accel/stk8ba50.c' using previous resolution. +Automatic merge failed; fix conflicts and then commit the result. +$ git commit --no-edit -v -a +[master 6b991666401a8] Merge branch 'togreg' of https://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio.git +$ git diff -M --stat --summary HEAD^.. + Documentation/ABI/testing/sysfs-bus-iio | 10 +- + Documentation/ABI/testing/sysfs-bus-iio-adc-ad7192 | 2 +- + Documentation/ABI/testing/sysfs-bus-iio-frequency | 11 + + .../ABI/testing/sysfs-bus-iio-frequency-adf4350 | 10 - + .../devicetree/bindings/iio/adc/avia-hx711.yaml | 21 + + .../devicetree/bindings/iio/dac/adi,ad5686.yaml | 72 +- + .../devicetree/bindings/iio/dac/adi,ad5696.yaml | 73 +- + .../bindings/iio/frequency/adi,adf41513.yaml | 227 ++++ + .../iio/magnetometer/qstcorp,qmc5883l.yaml | 52 + + .../bindings/iio/temperature/ti,tmp117.yaml | 16 +- + .../devicetree/bindings/vendor-prefixes.yaml | 2 + + Documentation/iio/adf41513.rst | 199 ++++ + Documentation/iio/index.rst | 1 + + MAINTAINERS | 18 +- + drivers/iio/TODO | 13 +- + drivers/iio/accel/adxl345_spi.c | 4 +- + drivers/iio/accel/adxl355_spi.c | 4 +- + drivers/iio/accel/adxl367_spi.c | 2 +- + drivers/iio/accel/adxl372_spi.c | 4 +- + drivers/iio/accel/adxl380_spi.c | 8 +- + drivers/iio/accel/bma220_spi.c | 2 +- + drivers/iio/accel/bma400_spi.c | 2 +- + drivers/iio/accel/bmc150-accel-core.c | 6 +- + drivers/iio/accel/bmc150-accel-spi.c | 18 +- + drivers/iio/accel/bmi088-accel-spi.c | 6 +- + drivers/iio/accel/fxls8962af-core.c | 2 + + drivers/iio/accel/fxls8962af-spi.c | 4 +- + drivers/iio/accel/hid-sensor-accel-3d.c | 6 +- + drivers/iio/accel/kxsd9-spi.c | 2 +- + drivers/iio/accel/mma7455_spi.c | 4 +- + drivers/iio/accel/sca3000.c | 8 +- + drivers/iio/accel/sca3300.c | 4 +- + drivers/iio/accel/st_accel_spi.c | 40 +- + drivers/iio/accel/stk8312.c | 12 +- + drivers/iio/accel/stk8ba50.c | 9 +- + drivers/iio/adc/Kconfig | 3 +- + drivers/iio/adc/ad4000.c | 62 +- + drivers/iio/adc/ad4030.c | 14 +- + drivers/iio/adc/ad4080.c | 22 +- + drivers/iio/adc/ad4130.c | 12 +- + drivers/iio/adc/ad4134.c | 2 +- + drivers/iio/adc/ad4170-4.c | 6 +- + drivers/iio/adc/ad4851.c | 18 +- + drivers/iio/adc/ad7091r8.c | 6 +- + drivers/iio/adc/ad7124.c | 4 +- + drivers/iio/adc/ad7173.c | 26 +- + drivers/iio/adc/ad7191.c | 2 +- + drivers/iio/adc/ad7192.c | 10 +- + drivers/iio/adc/ad7266.c | 4 +- + drivers/iio/adc/ad7280a.c | 2 +- + drivers/iio/adc/ad7292.c | 2 +- + drivers/iio/adc/ad7298.c | 2 +- + drivers/iio/adc/ad7380.c | 36 +- + drivers/iio/adc/ad7476.c | 60 +- + drivers/iio/adc/ad7606_spi.c | 22 +- + drivers/iio/adc/ad7766.c | 12 +- + drivers/iio/adc/ad7768-1.c | 8 +- + drivers/iio/adc/ad7780.c | 8 +- + drivers/iio/adc/ad7791.c | 10 +- + drivers/iio/adc/ad7793.c | 18 +- + drivers/iio/adc/ad7887.c | 2 +- + drivers/iio/adc/ad7923.c | 14 +- + drivers/iio/adc/ad7944.c | 6 +- + drivers/iio/adc/ad7949.c | 6 +- + drivers/iio/adc/ad9467.c | 14 +- + drivers/iio/adc/ade9000.c | 2 +- + drivers/iio/adc/hi8435.c | 2 +- + drivers/iio/adc/hx711.c | 98 +- + drivers/iio/adc/max1027.c | 12 +- + drivers/iio/adc/max1118.c | 6 +- + drivers/iio/adc/max11205.c | 4 +- + drivers/iio/adc/max11410.c | 2 +- + drivers/iio/adc/max1241.c | 8 +- + drivers/iio/adc/max14001.c | 4 +- + drivers/iio/adc/mcp320x.c | 26 +- + drivers/iio/adc/mcp3564.c | 24 +- + drivers/iio/adc/mcp3911.c | 14 +- + drivers/iio/adc/rohm-bd79112.c | 2 +- + drivers/iio/adc/rtq6056.c | 4 +- + drivers/iio/adc/stm32-dfsdm-adc.c | 12 +- + drivers/iio/adc/ti-adc0832.c | 8 +- + drivers/iio/adc/ti-adc084s021.c | 2 +- + drivers/iio/adc/ti-adc108s102.c | 2 +- + drivers/iio/adc/ti-adc12138.c | 6 +- + drivers/iio/adc/ti-adc128s052.c | 24 +- + drivers/iio/adc/ti-adc161s626.c | 4 +- + drivers/iio/adc/ti-ads1018.c | 4 +- + drivers/iio/adc/ti-ads124s08.c | 4 +- + drivers/iio/adc/ti-ads1298.c | 2 +- + drivers/iio/adc/ti-ads131e08.c | 6 +- + drivers/iio/adc/ti-ads131m02.c | 10 +- + drivers/iio/adc/ti-ads7950.c | 24 +- + drivers/iio/adc/ti-ads8688.c | 4 +- + drivers/iio/adc/ti-lmp92064.c | 2 +- + drivers/iio/adc/ti-tlc4541.c | 4 +- + drivers/iio/adc/ti-tsc2046.c | 25 +- + drivers/iio/addac/ad74115.c | 2 +- + drivers/iio/amplifiers/ad8366.c | 26 +- + drivers/iio/amplifiers/ada4250.c | 2 +- + drivers/iio/chemical/bme680_spi.c | 2 +- + drivers/iio/chemical/ens160_spi.c | 2 +- + drivers/iio/chemical/scd30_core.c | 6 +- + drivers/iio/dac/ad3530r.c | 8 +- + drivers/iio/dac/ad5064.c | 32 +- + drivers/iio/dac/ad5360.c | 16 +- + drivers/iio/dac/ad5380.c | 32 +- + drivers/iio/dac/ad5446-spi.c | 62 +- + drivers/iio/dac/ad5449.c | 14 +- + drivers/iio/dac/ad5504.c | 4 +- + drivers/iio/dac/ad5624r_spi.c | 12 +- + drivers/iio/dac/ad5686.c | 40 +- + drivers/iio/dac/ad5686.h | 4 + + drivers/iio/dac/ad5706r.c | 2 +- + drivers/iio/dac/ad5755.c | 10 +- + drivers/iio/dac/ad5758.c | 2 +- + drivers/iio/dac/ad5761.c | 8 +- + drivers/iio/dac/ad5764.c | 8 +- + drivers/iio/dac/ad5766.c | 4 +- + drivers/iio/dac/ad5770r.c | 2 +- + drivers/iio/dac/ad5791.c | 10 +- + drivers/iio/dac/ad7293.c | 2 +- + drivers/iio/dac/ad7303.c | 2 +- + drivers/iio/dac/ad8801.c | 4 +- + drivers/iio/dac/ad9739a.c | 2 +- + drivers/iio/dac/ltc1660.c | 4 +- + drivers/iio/dac/ltc2632.c | 44 +- + drivers/iio/dac/ltc2664.c | 4 +- + drivers/iio/dac/ltc2688.c | 2 +- + drivers/iio/dac/max22007.c | 2 +- + drivers/iio/dac/max5522.c | 31 +- + drivers/iio/dac/mcp4821.c | 12 +- + drivers/iio/dac/mcp4922.c | 8 +- + drivers/iio/dac/rohm-bd79703.c | 8 +- + drivers/iio/dac/ti-dac082s085.c | 12 +- + drivers/iio/dac/ti-dac7311.c | 6 +- + drivers/iio/dac/ti-dac7612.c | 2 +- + drivers/iio/filter/admv8818.c | 2 +- + drivers/iio/frequency/Kconfig | 10 + + drivers/iio/frequency/Makefile | 1 + + drivers/iio/frequency/ad9523.c | 3 +- + drivers/iio/frequency/adf41513.c | 1246 ++++++++++++++++++++ + drivers/iio/frequency/adf4350.c | 4 +- + drivers/iio/frequency/adf4371.c | 4 +- + drivers/iio/frequency/adf4377.c | 85 +- + drivers/iio/frequency/admv1013.c | 2 +- + drivers/iio/frequency/admv1014.c | 2 +- + drivers/iio/frequency/adrf6780.c | 2 +- + drivers/iio/gyro/adis16080.c | 4 +- + drivers/iio/gyro/adis16136.c | 8 +- + drivers/iio/gyro/adis16260.c | 12 +- + drivers/iio/gyro/adxrs450.c | 4 +- + drivers/iio/gyro/bmg160_spi.c | 7 +- + drivers/iio/gyro/fxas21002c_spi.c | 2 +- + drivers/iio/gyro/hid-sensor-gyro-3d.c | 6 +- + drivers/iio/gyro/st_gyro_spi.c | 18 +- + drivers/iio/health/afe4403.c | 2 +- + drivers/iio/humidity/hid-sensor-humidity.c | 6 +- + drivers/iio/humidity/hts221_spi.c | 2 +- + drivers/iio/iio_core_trigger.h | 2 +- + drivers/iio/imu/adis16400.c | 30 +- + drivers/iio/imu/adis16460.c | 2 +- + drivers/iio/imu/adis16475.c | 54 +- + drivers/iio/imu/adis16480.c | 40 +- + drivers/iio/imu/bmi160/bmi160_spi.c | 4 +- + drivers/iio/imu/bmi270/bmi270_spi.c | 4 +- + drivers/iio/imu/bmi323/bmi323_core.c | 2 +- + drivers/iio/imu/bmi323/bmi323_spi.c | 2 +- + drivers/iio/imu/fxos8700_spi.c | 2 +- + drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c | 14 +- + drivers/iio/imu/inv_icm45600/inv_icm45600_spi.c | 16 +- + drivers/iio/imu/inv_mpu6050/inv_mpu_spi.c | 34 +- + drivers/iio/imu/smi240.c | 2 +- + drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_spi.c | 48 +- + drivers/iio/imu/st_lsm9ds0/st_lsm9ds0_spi.c | 4 +- + drivers/iio/industrialio-core.c | 49 +- + drivers/iio/light/hid-sensor-als.c | 6 +- + drivers/iio/light/hid-sensor-prox.c | 4 +- + drivers/iio/light/opt3001.c | 381 +++--- + drivers/iio/light/st_uvis25_spi.c | 2 +- + drivers/iio/light/tcs3472.c | 317 ++++- + drivers/iio/magnetometer/Kconfig | 11 + + drivers/iio/magnetometer/Makefile | 2 + + drivers/iio/magnetometer/bmc150_magn.c | 50 +- + drivers/iio/magnetometer/bmc150_magn_spi.c | 6 +- + drivers/iio/magnetometer/hmc5843_spi.c | 6 +- + drivers/iio/magnetometer/mmc5983.c | 2 +- + drivers/iio/magnetometer/qmc5883l.c | 516 ++++++++ + drivers/iio/magnetometer/st_magn_spi.c | 12 +- + drivers/iio/orientation/hid-sensor-incl-3d.c | 6 +- + drivers/iio/orientation/hid-sensor-rotation.c | 6 +- + .../iio/position/hid-sensor-custom-intel-hinge.c | 6 +- + drivers/iio/potentiometer/max5481.c | 8 +- + drivers/iio/potentiometer/max5487.c | 6 +- + drivers/iio/potentiometer/mcp41010.c | 12 +- + drivers/iio/potentiometer/mcp4131.c | 128 +- + drivers/iio/potentiometer/x9250.c | 4 +- + drivers/iio/pressure/abp2030pa_spi.c | 2 +- + drivers/iio/pressure/bmp280-core.c | 32 +- + drivers/iio/pressure/bmp280-spi.c | 14 +- + drivers/iio/pressure/hid-sensor-press.c | 6 +- + drivers/iio/pressure/hsc030pa_spi.c | 2 +- + drivers/iio/pressure/mpl115_spi.c | 2 +- + drivers/iio/pressure/mprls0025pa_spi.c | 2 +- + drivers/iio/pressure/ms5611_spi.c | 4 +- + drivers/iio/pressure/st_pressure_spi.c | 24 +- + drivers/iio/pressure/zpa2326_spi.c | 2 +- + drivers/iio/proximity/as3935.c | 2 +- + drivers/iio/proximity/vl53l1x-i2c.c | 39 +- + drivers/iio/resolver/ad2s1200.c | 4 +- + drivers/iio/resolver/ad2s1210.c | 2 +- + drivers/iio/resolver/ad2s90.c | 2 +- + drivers/iio/temperature/hid-sensor-temperature.c | 6 +- + drivers/iio/temperature/ltc2983.c | 10 +- + drivers/iio/temperature/max31856.c | 2 +- + drivers/iio/temperature/max31865.c | 2 +- + drivers/iio/temperature/maxim_thermocouple.c | 18 +- + drivers/iio/temperature/tmp117.c | 13 + + drivers/iio/test/iio-test-format.c | 97 +- + drivers/iio/test/iio-test-rescale.c | 2 +- + drivers/staging/iio/adc/ad7816.c | 6 +- + drivers/staging/iio/addac/adt7316-spi.c | 13 +- + drivers/staging/iio/frequency/ad9832.c | 4 +- + drivers/staging/iio/frequency/ad9834.c | 8 +- + include/linux/iio/types.h | 20 + + include/linux/kstrtox.h | 3 + + include/linux/math64.h | 18 + + lib/kstrtox.c | 115 +- + lib/kstrtox.h | 17 +- + lib/math/div64.c | 15 + + lib/math/test_mul_u64_u64_div_u64.c | 1 + + lib/test-kstrtox.c | 182 +++ + lib/vsprintf.c | 2 +- + 232 files changed, 4615 insertions(+), 1403 deletions(-) + create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-frequency + create mode 100644 Documentation/devicetree/bindings/iio/frequency/adi,adf41513.yaml + create mode 100644 Documentation/devicetree/bindings/iio/magnetometer/qstcorp,qmc5883l.yaml + create mode 100644 Documentation/iio/adf41513.rst + create mode 100644 drivers/iio/frequency/adf41513.c + create mode 100644 drivers/iio/magnetometer/qmc5883l.c +Merging nfc/for-next (ed85d4cbbfaa4 nfc: llcp: bound SNL TLV parsing to the skb and add length checks) +$ git merge -m Merge branch 'for-next' of https://codeberg.org/linux-nfc/linux.git nfc/for-next +Auto-merging MAINTAINERS +Auto-merging net/nfc/nci/rsp.c +CONFLICT (content): Merge conflict in net/nfc/nci/rsp.c +Resolved 'net/nfc/nci/rsp.c' using previous resolution. +Automatic merge failed; fix conflicts and then commit the result. +$ git commit --no-edit -v -a +[master 6209372122c43] Merge branch 'for-next' of https://codeberg.org/linux-nfc/linux.git +$ git diff -M --stat --summary HEAD^.. + net/nfc/nci/rsp.c | 9 +++++++++ + 1 file changed, 9 insertions(+) +Merging phy-next/next (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/phy/linux-phy.git phy-next/next +Already up to date. +Merging soundwire/next (90af3209742db soundwire: dmi-quirks: Disable ghost Realtek on Asus Expertbook) +$ git merge -m Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/vkoul/soundwire.git soundwire/next +Merge made by the 'ort' strategy. + .../bindings/soundwire/qcom,soundwire.yaml | 20 ++++++++++---------- + drivers/soundwire/dmi-quirks.c | 7 +++++++ + drivers/soundwire/qcom.c | 9 ++++++--- + 3 files changed, 23 insertions(+), 13 deletions(-) +Merging extcon/extcon-next (254f49634ee16 Linux 7.1-rc1) +$ git merge -m Merge branch 'extcon-next' of https://git.kernel.org/pub/scm/linux/kernel/git/chanwoo/extcon.git extcon/extcon-next +Already up to date. +Merging gnss/gnss-next (5d6919055dec1 Linux 7.1-rc3) +$ git merge -m Merge branch 'gnss-next' of https://git.kernel.org/pub/scm/linux/kernel/git/johan/gnss.git gnss/gnss-next +Already up to date. +Merging vfio/next (785562e31dbcd vfio: selftests: Ensure libvfio output dirs are always created) +$ git merge -m Merge branch 'next' of https://github.com/awilliam/linux-vfio.git vfio/next +Already up to date. +Merging w1/for-next (169ae5e65e5aa w1: ds28e17: reject an oversize length on an I2C block read) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux-w1.git w1/for-next +Merge made by the 'ort' strategy. + Documentation/devicetree/bindings/w1/omap-hdq.txt | 22 ------- + Documentation/devicetree/bindings/w1/ti,hdq.yaml | 70 +++++++++++++++++++++++ + Documentation/w1/masters/omap-hdq.rst | 2 +- + drivers/w1/masters/ds2482.c | 8 +++ + drivers/w1/slaves/w1_ds28e17.c | 8 +++ + 5 files changed, 87 insertions(+), 23 deletions(-) + delete mode 100644 Documentation/devicetree/bindings/w1/omap-hdq.txt + create mode 100644 Documentation/devicetree/bindings/w1/ti,hdq.yaml +Merging spmi/spmi-next (3443eec9c55d1 spmi: use kzalloc_flex in main allocation) +$ git merge -m Merge branch 'spmi-next' of https://git.kernel.org/pub/scm/linux/kernel/git/sboyd/spmi.git spmi/spmi-next +Already up to date. +Merging staging/staging-next (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'staging-next' of https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging.git staging/staging-next +Already up to date. +Merging counter-next/counter-next (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'counter-next' of https://git.kernel.org/pub/scm/linux/kernel/git/wbg/counter.git counter-next/counter-next +Already up to date. +Merging mux/for-next (59b723cd2adba Linux 6.12-rc6) +$ git merge -m Merge branch 'for-next' of https://gitlab.com/peda-linux/mux.git mux/for-next +Already up to date. +Merging dmaengine/next (0d3e3376b289c dmaengine: pl330: remove debugfs file on teardown) +$ git merge -m Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/vkoul/dmaengine.git dmaengine/next +Auto-merging drivers/crypto/atmel-aes.c +Merge made by the 'ort' strategy. + .../devicetree/bindings/dma/mediatek,uart-dma.yaml | 1 + + .../bindings/dma/xilinx/xlnx,axi-dma.yaml | 13 +- + Documentation/driver-api/dmaengine/client.rst | 9 ++ + drivers/crypto/atmel-aes.c | 10 +- + drivers/dma/dmaengine.c | 2 + + drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c | 2 +- + drivers/dma/dw-edma/dw-edma-core.c | 41 ++++-- + drivers/dma/hisi_dma.c | 2 +- + drivers/dma/mediatek/mtk-uart-apdma.c | 2 +- + drivers/dma/pl330.c | 19 ++- + drivers/dma/tegra210-adma.c | 12 +- + drivers/dma/xilinx/xilinx_dma.c | 64 +++++---- + drivers/dma/xilinx/zynqmp_dma.c | 6 +- + drivers/nvme/target/pci-epf.c | 33 +---- + drivers/pci/endpoint/functions/pci-epf-mhi.c | 52 +++---- + drivers/pci/endpoint/functions/pci-epf-test.c | 8 +- + include/linux/dmaengine.h | 149 +++++++++++++++++++-- + 17 files changed, 282 insertions(+), 143 deletions(-) +Merging cgroup/for-next (ec98784b247b2 Merge branch 'for-7.3' into for-next) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/tj/cgroup.git cgroup/for-next +Auto-merging Documentation/admin-guide/cgroup-v2.rst +Auto-merging kernel/cgroup/cpuset.c +Merge made by the 'ort' strategy. + Documentation/admin-guide/cgroup-v2.rst | 34 +++++++++++++-- + include/linux/cgroup-defs.h | 2 +- + include/linux/cgroup.h | 2 +- + kernel/cgroup/cpuset.c | 49 +++++++++++++--------- + tools/cgroup/iocost_monitor.py | 10 ++--- + .../selftests/cgroup/lib/include/cgroup_util.h | 1 + + tools/testing/selftests/cgroup/test_cpu.c | 43 ++++++++++++++++--- + tools/testing/selftests/cgroup/test_cpuset_prs.sh | 11 ++++- + 8 files changed, 115 insertions(+), 37 deletions(-) +Merging scsi/for-next (cf1af0ccca54d Merge branch 'misc' into for-next) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/jejb/scsi.git scsi/for-next +Merge made by the 'ort' strategy. +Merging scsi-mkp/for-next (57a6ed0b41677 scsi: bfa: Reduce kernel stack usage in bfa_fcs_lport_fdmi_build_portattr_block()) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/mkp/scsi.git scsi-mkp/for-next +Auto-merging drivers/scsi/scsi_lib.c +Merge made by the 'ort' strategy. + drivers/scsi/bfa/bfa_fcs_lport.c | 2 +- + drivers/scsi/scsi_lib.c | 8 -------- + drivers/scsi/scsi_priv.h | 1 + + drivers/xen/xen-scsiback.c | 30 +++++++++++++++++++++++------- + include/scsi/scsi_device.h | 1 - + 5 files changed, 25 insertions(+), 17 deletions(-) +Merging vhost/linux-next (1f673033c0273 vduse: Add suspend) +$ git merge -m Merge branch 'linux-next' of https://git.kernel.org/pub/scm/linux/kernel/git/mst/vhost.git vhost/linux-next +Auto-merging drivers/vdpa/vdpa_user/vduse_dev.c +Merge made by the 'ort' strategy. + drivers/vdpa/vdpa_user/vduse_dev.c | 136 +++++++++++++++++++++++++++++++++---- + include/uapi/linux/vduse.h | 29 +++++++- + 2 files changed, 150 insertions(+), 15 deletions(-) +Merging rpmsg/for-next (8ca01dbc70b58 Merge branch 'rproc-next' into for-next) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/remoteproc/linux.git rpmsg/for-next +Merge made by the 'ort' strategy. + .../devicetree/bindings/remoteproc/qcom,adsp.yaml | 82 +++++------ + .../remoteproc/qcom,kaanapali-soccp-pas.yaml | 156 +++++++++++++++++++++ + .../bindings/remoteproc/qcom,milos-pas.yaml | 26 +++- + .../bindings/remoteproc/qcom,pas-common.yaml | 28 ++-- + .../bindings/remoteproc/qcom,qcs404-pas.yaml | 22 ++- + .../bindings/remoteproc/qcom,sa8775p-pas.yaml | 22 ++- + .../bindings/remoteproc/qcom,sc7180-pas.yaml | 28 ++++ + .../bindings/remoteproc/qcom,sc8280xp-pas.yaml | 28 ++++ + .../bindings/remoteproc/qcom,sdx55-pas.yaml | 24 +++- + .../bindings/remoteproc/qcom,shikra-pas.yaml | 20 +++ + .../bindings/remoteproc/qcom,sm6115-pas.yaml | 28 ++++ + .../bindings/remoteproc/qcom,sm6350-pas.yaml | 28 ++++ + .../bindings/remoteproc/qcom,sm6375-pas.yaml | 28 ++++ + .../bindings/remoteproc/qcom,sm8150-pas.yaml | 28 ++++ + .../bindings/remoteproc/qcom,sm8350-pas.yaml | 28 ++++ + .../bindings/remoteproc/qcom,sm8550-pas.yaml | 28 ++++ + drivers/firmware/xilinx/zynqmp.c | 93 ++++++++++++ + drivers/remoteproc/imx_dsp_rproc.c | 41 ++---- + drivers/remoteproc/imx_rproc.c | 40 +----- + drivers/remoteproc/keystone_remoteproc.c | 2 +- + drivers/remoteproc/omap_remoteproc.c | 2 +- + drivers/remoteproc/qcom_common.h | 6 + + drivers/remoteproc/qcom_q6v5.c | 3 +- + drivers/remoteproc/qcom_q6v5_pas.c | 86 ++++++++++++ + drivers/remoteproc/qcom_sysmon.c | 19 +++ + drivers/remoteproc/rcar_rproc.c | 41 +----- + drivers/remoteproc/remoteproc_internal.h | 54 +++++++ + drivers/remoteproc/st_remoteproc.c | 31 +--- + drivers/remoteproc/stm32_rproc.c | 39 +----- + drivers/remoteproc/xlnx_r5_remoteproc.c | 138 +++--------------- + include/linux/firmware/xlnx-zynqmp.h | 12 ++ + 31 files changed, 843 insertions(+), 368 deletions(-) + create mode 100644 Documentation/devicetree/bindings/remoteproc/qcom,kaanapali-soccp-pas.yaml +Merging gpio-brgl/gpio/for-next (8fe6fa0f223f4 gpio: sifive: add missing MODULE_DEVICE_TABLE()) +$ git merge -m Merge branch 'gpio/for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux.git gpio-brgl/gpio/for-next +Merge made by the 'ort' strategy. + .../bindings/gpio/realtek,rtd1625-gpio.yaml | 71 +++ + drivers/gpio/Kconfig | 14 +- + drivers/gpio/Makefile | 1 + + drivers/gpio/gpio-amd-fch.c | 22 +- + drivers/gpio/gpio-mt7621.c | 40 +- + drivers/gpio/gpio-mvebu.c | 40 +- + drivers/gpio/gpio-nomadik.c | 64 +-- + drivers/gpio/gpio-pca9570.c | 2 +- + drivers/gpio/gpio-rcar.c | 1 - + drivers/gpio/gpio-rockchip.c | 16 +- + drivers/gpio/gpio-rtd1625.c | 611 +++++++++++++++++++++ + drivers/gpio/gpio-sifive.c | 1 + + drivers/gpio/gpio-tb10x.c | 5 +- + drivers/gpio/gpiolib-cdev.c | 9 +- + drivers/gpio/gpiolib-kunit.c | 280 +++++++++- + 15 files changed, 1063 insertions(+), 114 deletions(-) + create mode 100644 Documentation/devicetree/bindings/gpio/realtek,rtd1625-gpio.yaml + create mode 100644 drivers/gpio/gpio-rtd1625.c +Merging gpio-intel/for-next (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/andy/linux-gpio-intel.git gpio-intel/for-next +Already up to date. +Merging pinctrl/for-next (94cb9e8f27079 pinctrl: s32cc: implement GPIO functionality) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-pinctrl.git pinctrl/for-next +Auto-merging MAINTAINERS +Merge made by the 'ort' strategy. + .../pinctrl/aspeed,ast2700-soc1-pinctrl.yaml | 1 + + .../bindings/pinctrl/nxp,s32g2-siul2-pinctrl.yaml | 90 ++- + MAINTAINERS | 7 + + drivers/pinctrl/aspeed/pinctrl-aspeed-g7-soc1.c | 6 +- + drivers/pinctrl/bcm/pinctrl-bcm2835.c | 1 - + drivers/pinctrl/freescale/pinctrl-imx1-core.c | 7 +- + drivers/pinctrl/nxp/Kconfig | 3 +- + drivers/pinctrl/nxp/pinctrl-s32.h | 35 +- + drivers/pinctrl/nxp/pinctrl-s32cc.c | 768 ++++++++++++++++++--- + drivers/pinctrl/nxp/pinctrl-s32g2.c | 47 +- + drivers/pinctrl/pinctrl-tb10x.c | 2 +- + 11 files changed, 856 insertions(+), 111 deletions(-) +Merging pinctrl-intel/for-next (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/pinctrl/intel.git pinctrl-intel/for-next +Already up to date. +Merging pinctrl-renesas/renesas-pinctrl (5a653cedec948 pinctrl: renesas: rza2: Embed pins in the priv struct) +$ git merge -m Merge branch 'renesas-pinctrl' of https://git.kernel.org/pub/scm/linux/kernel/git/geert/renesas-drivers.git pinctrl-renesas/renesas-pinctrl +Merge made by the 'ort' strategy. + drivers/pinctrl/renesas/pinctrl-rza2.c | 26 +++++++++++--------------- + 1 file changed, 11 insertions(+), 15 deletions(-) +Merging pinctrl-samsung/for-next (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/pinctrl/samsung.git pinctrl-samsung/for-next +Already up to date. +Merging pinctrl-qcom/pinctrl-qcom/for-next (251b53103a2e5 pinctrl: qcom: Add the tlmm driver for Maili platform) +$ git merge -m Merge branch 'pinctrl-qcom/for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux.git pinctrl-qcom/pinctrl-qcom/for-next +Auto-merging drivers/pinctrl/qcom/pinctrl-msm.c +Merge made by the 'ort' strategy. + .../bindings/pinctrl/qcom,maili-tlmm.yaml | 120 ++ + .../bindings/pinctrl/qcom,pmic-gpio.yaml | 3 + + drivers/pinctrl/qcom/Kconfig.msm | 10 + + drivers/pinctrl/qcom/Makefile | 1 + + drivers/pinctrl/qcom/pinctrl-maili.c | 1625 ++++++++++++++++++++ + drivers/pinctrl/qcom/pinctrl-msm.c | 9 +- + drivers/pinctrl/qcom/pinctrl-spmi-gpio.c | 1 + + drivers/pinctrl/qcom/tlmm-test.c | 3 - + 8 files changed, 1763 insertions(+), 9 deletions(-) + create mode 100644 Documentation/devicetree/bindings/pinctrl/qcom,maili-tlmm.yaml + create mode 100644 drivers/pinctrl/qcom/pinctrl-maili.c +Merging pwm/pwm/for-next (1a4920940ebfd pwm: Use named initializers for platform_device_id arrays) +$ git merge -m Merge branch 'pwm/for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/ukleinek/linux.git pwm/pwm/for-next +Auto-merging drivers/pwm/pwm-adp5585.c +Auto-merging drivers/pwm/pwm-pxa.c +Merge made by the 'ort' strategy. + drivers/pwm/pwm-adp5585.c | 4 ++-- + drivers/pwm/pwm-mc33xs2410.c | 2 +- + drivers/pwm/pwm-pxa.c | 12 ++++++------ + 3 files changed, 9 insertions(+), 9 deletions(-) +Merging ktest/for-next (932cdaf3e273a ktest: Add logfile to failure directory) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/rostedt/linux-ktest.git ktest/for-next +Already up to date. +Merging kselftest/next (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest.git kselftest/next +Already up to date. +Merging kunit/test (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'test' of https://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest.git kunit/test +Already up to date. +Merging kunit-next/kunit (643ec8ff7d8ed kunit: Add example of test suite that can be skipped at runtime) +$ git merge -m Merge branch 'kunit' of https://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest.git kunit-next/kunit +Merge made by the 'ort' strategy. + include/kunit/test.h | 1 + + lib/kunit/debugfs.c | 28 ++++++++++++++++++++-------- + lib/kunit/kunit-example-test.c | 29 +++++++++++++++++++++++++++++ + lib/kunit/test.c | 17 ++++++++++++++++- + rust/kernel/kunit.rs | 1 + + 5 files changed, 67 insertions(+), 9 deletions(-) +Merging livepatching/for-next (0839c8963b7b2 Merge tag 'livepatching-for-7.2' of git://git.kernel.org/pub/scm/linux/kernel/git/livepatching/livepatching) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/livepatching/livepatching.git livepatching/for-next +Already up to date. +Merging rtc/rtc-next (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'rtc-next' of https://git.kernel.org/pub/scm/linux/kernel/git/abelloni/linux.git rtc/rtc-next +Already up to date. +Merging nvdimm/libnvdimm-for-next (86e411b6ec277 MAINTAINERS: nvdimm: Include maintainer profile) +$ git merge -m Merge branch 'libnvdimm-for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/nvdimm/nvdimm.git nvdimm/libnvdimm-for-next +Already up to date. +Merging at24/at24/for-next (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'at24/for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux.git at24/at24/for-next +Already up to date. +Merging ntb/ntb-next (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'ntb-next' of https://github.com/jonmason/ntb.git ntb/ntb-next +Already up to date. +Merging seccomp/for-next/seccomp (41fa043273841 selftests/seccomp: Add hard-coded __NR_uprobe for x86_64) +$ git merge -m Merge branch 'for-next/seccomp' of https://git.kernel.org/pub/scm/linux/kernel/git/kees/linux.git seccomp/for-next/seccomp +Already up to date. +Merging slimbus/for-next (c922423ce66bc slimbus: qcom-ngd-ctrl: Use the unified QMI service ID instead of defining it locally) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/srini/slimbus.git slimbus/for-next +Merge made by the 'ort' strategy. + drivers/slimbus/qcom-ngd-ctrl.c | 5 ++--- + 1 file changed, 2 insertions(+), 3 deletions(-) +Merging nvmem/for-next (a3122a19f00b7 nvmem: layouts: u-boot-env: check earlier for ethaddr length) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/srini/nvmem.git nvmem/for-next +Auto-merging drivers/nvmem/nintendo-otp.c +Merge made by the 'ort' strategy. + .../bindings/nvmem/airoha,smc-efuses.yaml | 67 +++++++++++ + .../bindings/nvmem/microchip,lan9662-otpc.yaml | 1 + + .../devicetree/bindings/nvmem/qcom,qfprom.yaml | 4 + + drivers/nvmem/Kconfig | 22 +++- + drivers/nvmem/Makefile | 2 + + drivers/nvmem/airoha-smc-efuses.c | 125 +++++++++++++++++++++ + drivers/nvmem/core.c | 4 + + drivers/nvmem/lan9662-otpc.c | 12 +- + drivers/nvmem/layouts/u-boot-env.c | 5 +- + drivers/nvmem/nintendo-otp.c | 9 +- + drivers/nvmem/rockchip-otp.c | 9 +- + 11 files changed, 236 insertions(+), 24 deletions(-) + create mode 100644 Documentation/devicetree/bindings/nvmem/airoha,smc-efuses.yaml + create mode 100644 drivers/nvmem/airoha-smc-efuses.c +Merging hyperv/hyperv-next (a4ffc59238be8 mshv: add bounds check on vp_index in mshv_intercept_isr()) +$ git merge -m Merge branch 'hyperv-next' of https://git.kernel.org/pub/scm/linux/kernel/git/hyperv/linux.git hyperv/hyperv-next +Already up to date. +Merging auxdisplay/for-next (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/andy/linux-auxdisplay.git auxdisplay/for-next +Already up to date. +Merging kgdb/kgdb/for-next (fdbdd0ccb30af kdb: remove redundant check for scancode 0xe0) +$ git merge -m Merge branch 'kgdb/for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/danielt/linux.git kgdb/kgdb/for-next +Already up to date. +Merging hmm/hmm (19272b37aa4f8 Linux 6.16-rc1) +$ git merge -m Merge branch 'hmm' of https://git.kernel.org/pub/scm/linux/kernel/git/rdma/rdma.git hmm/hmm +Already up to date. +Merging cfi/cfi/next (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'cfi/next' of https://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux.git cfi/cfi/next +Already up to date. +Merging mhi/mhi-next (32845111e8ea6 bus: mhi: host: pci_generic: Fix the physical function check) +$ git merge -m Merge branch 'mhi-next' of https://git.kernel.org/pub/scm/linux/kernel/git/mani/mhi.git mhi/mhi-next +Already up to date. +$ git am -3 ../patches/0001-fix-up-for-net-qrtr-Drop-the-MHI-auto_queue-feature-.patch +Applying: fix up for "net: qrtr: Drop the MHI auto_queue feature for IPCR DL channels" +Using index info to reconstruct a base tree... +M drivers/net/wireless/ath/ath12k/wifi7/mhi.c +Falling back to patching base and 3-way merge... +No changes -- Patch already applied. +Merging memblock/for-next (717cd4552ae42 Merge branch 'numa_memblks-redundant-work' into for-next) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/rppt/memblock.git memblock/for-next +Auto-merging arch/riscv/mm/init.c +Auto-merging drivers/base/arch_numa.c +Auto-merging mm/hugetlb.c +Auto-merging mm/mm_init.c +Merge made by the 'ort' strategy. + arch/arm/mm/kasan_init.c | 6 ---- + arch/arm64/mm/kasan_init.c | 3 -- + arch/loongarch/kernel/numa.c | 1 - + arch/loongarch/mm/kasan_init.c | 3 -- + arch/powerpc/mm/kasan/init_book3e_64.c | 3 -- + arch/powerpc/mm/kasan/init_book3s_64.c | 3 -- + arch/riscv/mm/init.c | 2 -- + arch/riscv/mm/kasan_init.c | 3 -- + arch/x86/mm/amdtopology.c | 1 - + arch/x86/mm/numa.c | 1 - + drivers/acpi/numa/srat.c | 2 -- + drivers/base/arch_numa.c | 4 --- + drivers/of/of_numa.c | 5 +-- + mm/hugetlb.c | 7 ++-- + mm/memblock.c | 4 +-- + mm/mm_init.c | 63 +++------------------------------- + mm/numa_memblks.c | 41 ++++++++++------------ + 17 files changed, 27 insertions(+), 125 deletions(-) +Merging cxl/next (bdc0a9797abd9 Merge branch 'for-7.3/cxl-type2-support' into cxl-for-next) +$ git merge -m Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/cxl/cxl.git cxl/next +Merge made by the 'ort' strategy. + Documentation/driver-api/cxl/linux/dax-driver.rst | 4 +- + drivers/cxl/core/core.h | 2 + + drivers/cxl/core/mbox.c | 65 +--------------------- + drivers/cxl/core/mce.c | 27 +++++---- + drivers/cxl/core/memdev.c | 67 +++++++++++++++++++++++ + drivers/cxl/core/pci.c | 1 + + drivers/cxl/core/port.c | 1 + + drivers/cxl/core/region.c | 43 +++++---------- + drivers/cxl/core/regs.c | 1 + + drivers/cxl/cxl.h | 8 +-- + drivers/cxl/cxlmem.h | 4 +- + drivers/cxl/cxlpci.h | 12 ---- + drivers/cxl/pci.c | 7 +-- + include/cxl/cxl.h | 2 + + include/cxl/pci.h | 22 ++++++++ + 15 files changed, 132 insertions(+), 134 deletions(-) + create mode 100644 include/cxl/pci.h +Merging zstd/zstd-next (65d1f5507ed2c zstd: Import upstream v1.5.7) +$ git merge -m Merge branch 'zstd-next' of https://github.com/terrelln/linux.git zstd/zstd-next +Already up to date. +Merging efi/next (48a4282157823 efi/capsule-loader: fix incorrect sizeof in phys array reallocation) +$ git merge -m Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/efi/efi.git efi/next +Already up to date. +Merging unicode/for-next (bcfee135d5847 utf8: Remove unused utf8_normalize) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/krisman/unicode.git unicode/for-next +Auto-merging fs/unicode/utf8-core.c +Merge made by the 'ort' strategy. + fs/unicode/utf8-core.c | 22 ---------------------- + include/linux/unicode.h | 3 --- + 2 files changed, 25 deletions(-) +Merging slab/slab/for-next (ea0c7a9244594 docs: ABI: sysfs-kernel-slab: mark cpu_partial attributes deprecated) +$ git merge -m Merge branch 'slab/for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/vbabka/slab.git slab/slab/for-next +Auto-merging Documentation/admin-guide/kernel-parameters.txt +Merge made by the 'ort' strategy. + Documentation/ABI/testing/sysfs-kernel-slab | 18 ++++++---- + Documentation/admin-guide/kernel-parameters.txt | 5 +++ + mm/mempool.c | 35 +++++++++++++------ + mm/slub.c | 46 ++++++------------------- + 4 files changed, 51 insertions(+), 53 deletions(-) +$ git am -3 ../patches/0001-drm-panthor-I-love-Werror.patch +Applying: drm/panthor: I love -Werror +Using index info to reconstruct a base tree... +M drivers/gpu/drm/panthor/panthor_mmu.c +Falling back to patching base and 3-way merge... +Auto-merging drivers/gpu/drm/panthor/panthor_mmu.c +No changes -- Patch already applied. +Merging random/master (bb9ff576fdff4 virt: vmgenid: remap memory as decrypted) +$ git merge -m Merge branch 'master' of https://git.kernel.org/pub/scm/linux/kernel/git/crng/random.git random/master +Merge made by the 'ort' strategy. + drivers/virt/vmgenid.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) +Merging landlock/next (98c522eb8d64c landlock: Fix kernel-doc for the nested quiet layer flag) +$ git merge -m Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/mic/linux.git landlock/next +Merge made by the 'ort' strategy. + security/landlock/net.c | 8 +++ + security/landlock/ruleset.h | 4 +- + tools/testing/selftests/landlock/net_test.c | 94 +++++++++++++++++++++++++++++ + 3 files changed, 104 insertions(+), 2 deletions(-) +Merging sysctl/sysctl-next (f63a9df7e3f9f sysctl: fix uninitialized variable in proc_do_large_bitmap) +$ git merge -m Merge branch 'sysctl-next' of https://git.kernel.org/pub/scm/linux/kernel/git/sysctl/sysctl.git sysctl/sysctl-next +Already up to date. +Merging execve/for-next/execve (9bf092c97b86a sched: update task_struct->comm comment) +$ git merge -m Merge branch 'for-next/execve' of https://git.kernel.org/pub/scm/linux/kernel/git/kees/linux.git execve/for-next/execve +Already up to date. +Merging bitmap/bitmap-for-next (40d3c88df9d82 bitops: make the *_bit_le functions use unsigned long) +$ git merge -m Merge branch 'bitmap-for-next' of https://github.com/norov/linux.git bitmap/bitmap-for-next +Merge made by the 'ort' strategy. + drivers/firmware/psci/psci_checker.c | 15 ++------------- + include/asm-generic/bitops/le.h | 18 +++++++++--------- + include/linux/bitmap.h | 4 ++-- + include/linux/bits.h | 6 +++--- + tools/include/linux/bits.h | 6 +++--- + 5 files changed, 19 insertions(+), 30 deletions(-) +Merging hte/for-next (005b25ad11717 hte: tegra194: Add Tegra264 GTE support) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/pateldipen1984/linux.git hte/for-next +Already up to date. +Merging kspp/for-next/kspp (c2606fb910d7c Merge branch 'for-next/hardening' into for-next/kspp) +$ git merge -m Merge branch 'for-next/kspp' of https://git.kernel.org/pub/scm/linux/kernel/git/kees/linux.git kspp/for-next/kspp +Merge made by the 'ort' strategy. +Merging nolibc/for-next (a3b2181459a2c tools/nolibc: mark arg1 operand in __nolibc_syscall0() as write-only) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/nolibc/linux-nolibc.git nolibc/for-next +Merge made by the 'ort' strategy. + tools/include/nolibc/arch-sparc.h | 2 +- + tools/include/nolibc/unistd.h | 58 ++++++++++++++++++++++++++++ + tools/testing/selftests/nolibc/nolibc-test.c | 53 +++++++++++++++++++++++++ + 3 files changed, 112 insertions(+), 1 deletion(-) +Merging iommufd/for-next (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/jgg/iommufd.git iommufd/for-next +Already up to date. +Merging turbostat/next (1c996a37fd244 tools/power turbostat: pmt: Improve sscanf() hygiene) +$ git merge -m Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/lenb/linux.git turbostat/next +Merge made by the 'ort' strategy. + tools/power/x86/turbostat/turbostat.8 | 6 +- + tools/power/x86/turbostat/turbostat.c | 567 ++++++++++++++++------------------ + 2 files changed, 266 insertions(+), 307 deletions(-) +Merging pwrseq/pwrseq/for-next (162ea02941a93 power: sequencing: pwrseq-pcie-m2: Add QCC2072 BT PCI device ID) +$ git merge -m Merge branch 'pwrseq/for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux.git pwrseq/pwrseq/for-next +Auto-merging drivers/power/sequencing/pwrseq-pcie-m2.c +Merge made by the 'ort' strategy. + drivers/power/sequencing/pwrseq-pcie-m2.c | 8 ++++++++ + 1 file changed, 8 insertions(+) +Merging capabilities-next/caps-next (0715881360074 ipc: don't audit capability check in ipc_permissions()) +$ git merge -m Merge branch 'caps-next' of https://git.kernel.org/pub/scm/linux/kernel/git/sergeh/linux.git capabilities-next/caps-next +Already up to date. +$ git am -3 ../patches/0001-sign-file-Fix-up-merge-issue.patch +Applying: sign-file: Fix up merge issue +Using index info to reconstruct a base tree... +M scripts/sign-file.c +Falling back to patching base and 3-way merge... +Auto-merging scripts/sign-file.c +No changes -- Patch already applied. +Merging ipe/next (028ef9c96e961 Linux 7.0) +$ git merge -m Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/wufan/ipe.git ipe/next +Already up to date. +Merging kcsan/next (07a1a6562ce29 kcsan: Silence -Wmaybe-uninitialized when calling __kcsan_check_access()) +$ git merge -m Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/melver/linux.git kcsan/next +Already up to date. +Merging crc/crc-next (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'crc-next' of https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/linux.git crc/crc-next +Already up to date. +Merging keys-next/keys-next (965e9a2cf23b0 pkcs7: Change a pr_warn() to pr_warn_once()) +$ git merge -m Merge branch 'keys-next' of https://git.kernel.org/pub/scm/linux/kernel/git/dhowells/linux-fs.git keys-next/keys-next +Already up to date. +Merging fwctl/for-next (dc59e4fea9d83 Linux 7.2-rc1) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/fwctl/fwctl.git fwctl/for-next +Already up to date. +Merging devsec-tsm/next (3177779ae17db virt: coco: change tsm_class to a const struct) +$ git merge -m Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/devsec/tsm.git devsec-tsm/next +Already up to date. +Merging hisilicon/for-next (6cdd694551d6a Merge branch 'next/dt64' into for-next) +$ git merge -m Merge branch 'for-next' of https://github.com/hisilicon/linux-hisi.git hisilicon/for-next +Merge made by the 'ort' strategy. +Merging device-id/device-id-rework (995832b2cebe6 Replace by more specific (c files)) +$ git merge -m Merge branch 'device-id-rework' of https://git.kernel.org/pub/scm/linux/kernel/git/ukleinek/linux.git device-id/device-id-rework +Already up to date. +Merging kthread/for-next (fa39ec4f89f26 doc: Add housekeeping documentation) +$ git merge -m Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/frederic/linux-dynticks.git kthread/for-next +Already up to date. diff --git a/localversion-next b/localversion-next new file mode 100644 index 000000000000..7218c6c4244e --- /dev/null +++ b/localversion-next @@ -0,0 +1 @@ +-next-20260706 From 4766aabcf3464ed2660440b1f3e6bc849301d697 Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Tue, 23 Jun 2026 16:24:04 +0530 Subject: [PATCH 528/562] clk: qcom: gcc-nord: mark PCIe link clocks as critical The PCIe link AHB and XO clocks must remain enabled for proper operation. Representing them as clk_branch instances allows them to be gated, which is undesirable. Remove their clk_branch definitions and register their CBCRs as critical clocks instead so they remain enabled. This matches the handling of similar always-on clocks in other Qualcomm clock drivers. Fixes: a4f780cd5c7a ("clk: qcom: gcc: Add multiple global clock controller driver for Nord SoC") Signed-off-by: Taniya Das Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260623-nords_mm_v1-v1-1-860c84539804@oss.qualcomm.com --- drivers/clk/qcom/gcc-nord.c | 37 +++++++------------------------------ 1 file changed, 7 insertions(+), 30 deletions(-) diff --git a/drivers/clk/qcom/gcc-nord.c b/drivers/clk/qcom/gcc-nord.c index 7c7c2171ac96..8f832a069809 100644 --- a/drivers/clk/qcom/gcc-nord.c +++ b/drivers/clk/qcom/gcc-nord.c @@ -1184,34 +1184,6 @@ static struct clk_branch gcc_pcie_d_slv_q2a_axi_clk = { }, }; -static struct clk_branch gcc_pcie_link_ahb_clk = { - .halt_reg = 0x52464, - .halt_check = BRANCH_HALT, - .clkr = { - .enable_reg = 0x52464, - .enable_mask = BIT(0), - .hw.init = &(const struct clk_init_data) { - .name = "gcc_pcie_link_ahb_clk", - .ops = &clk_branch2_ops, - }, - }, -}; - -static struct clk_branch gcc_pcie_link_xo_clk = { - .halt_reg = 0x52468, - .halt_check = BRANCH_HALT_VOTED, - .hwcg_reg = 0x52468, - .hwcg_bit = 1, - .clkr = { - .enable_reg = 0x52468, - .enable_mask = BIT(0), - .hw.init = &(const struct clk_init_data) { - .name = "gcc_pcie_link_xo_clk", - .ops = &clk_branch2_ops, - }, - }, -}; - static struct clk_branch gcc_pcie_noc_async_bridge_clk = { .halt_reg = 0x52048, .halt_check = BRANCH_HALT_SKIP, @@ -1757,8 +1729,6 @@ static struct clk_regmap *gcc_nord_clocks[] = { [GCC_PCIE_D_PIPE_CLK_SRC] = &gcc_pcie_d_pipe_clk_src.clkr, [GCC_PCIE_D_SLV_AXI_CLK] = &gcc_pcie_d_slv_axi_clk.clkr, [GCC_PCIE_D_SLV_Q2A_AXI_CLK] = &gcc_pcie_d_slv_q2a_axi_clk.clkr, - [GCC_PCIE_LINK_AHB_CLK] = &gcc_pcie_link_ahb_clk.clkr, - [GCC_PCIE_LINK_XO_CLK] = &gcc_pcie_link_xo_clk.clkr, [GCC_PCIE_NOC_ASYNC_BRIDGE_CLK] = &gcc_pcie_noc_async_bridge_clk.clkr, [GCC_PCIE_NOC_CNOC_SF_QX_CLK] = &gcc_pcie_noc_cnoc_sf_qx_clk.clkr, [GCC_PCIE_NOC_M_CFG_CLK] = &gcc_pcie_noc_m_cfg_clk.clkr, @@ -1849,9 +1819,16 @@ static const struct regmap_config gcc_nord_regmap_config = { .fast_io = true, }; +static const u32 gcc_nord_critical_cbcrs[] = { + 0x52464, /* GCC_PCIE_LINK_AHB_CLK */ + 0x52468, /* GCC_PCIE_LINK_XO_CLK */ +}; + static const struct qcom_cc_driver_data gcc_nord_driver_data = { .dfs_rcgs = gcc_nord_dfs_clocks, .num_dfs_rcgs = ARRAY_SIZE(gcc_nord_dfs_clocks), + .clk_cbcrs = gcc_nord_critical_cbcrs, + .num_clk_cbcrs = ARRAY_SIZE(gcc_nord_critical_cbcrs), }; static const struct qcom_cc_desc gcc_nord_desc = { From d271a26db161ca54a1b8d3a54ad1183c7b683430 Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Tue, 23 Jun 2026 16:24:05 +0530 Subject: [PATCH 529/562] clk: qcom: negcc-nord: keep GPU2 CFG clock enabled via critical CBCR The GPU2 CFG clock must remain enabled for correct operation and should not be exposed as a controllable clk_branch. Remove the clk_branch and mark its CBCR as critical instead to prevent unintended gating. This follows the same approach as 'nw_gcc_gpu_cfg_ahb_clk' and aligns with other always-on clocks in Qualcomm CC drivers. Fixes: a4f780cd5c7a ("clk: qcom: gcc: Add multiple global clock controller driver for Nord SoC") Signed-off-by: Taniya Das Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260623-nords_mm_v1-v1-2-860c84539804@oss.qualcomm.com --- drivers/clk/qcom/negcc-nord.c | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/drivers/clk/qcom/negcc-nord.c b/drivers/clk/qcom/negcc-nord.c index 355850a875ac..767cf1842891 100644 --- a/drivers/clk/qcom/negcc-nord.c +++ b/drivers/clk/qcom/negcc-nord.c @@ -951,21 +951,6 @@ static struct clk_branch ne_gcc_gp2_clk = { }, }; -static struct clk_branch ne_gcc_gpu_2_cfg_clk = { - .halt_reg = 0x34004, - .halt_check = BRANCH_HALT_VOTED, - .hwcg_reg = 0x34004, - .hwcg_bit = 1, - .clkr = { - .enable_reg = 0x34004, - .enable_mask = BIT(0), - .hw.init = &(const struct clk_init_data) { - .name = "ne_gcc_gpu_2_cfg_clk", - .ops = &clk_branch2_ops, - }, - }, -}; - static struct clk_branch ne_gcc_gpu_2_gpll0_clk_src = { .halt_check = BRANCH_HALT_DELAY, .clkr = { @@ -1816,7 +1801,6 @@ static struct clk_regmap *ne_gcc_nord_clocks[] = { [NE_GCC_GPLL0] = &ne_gcc_gpll0.clkr, [NE_GCC_GPLL0_OUT_EVEN] = &ne_gcc_gpll0_out_even.clkr, [NE_GCC_GPLL2] = &ne_gcc_gpll2.clkr, - [NE_GCC_GPU_2_CFG_CLK] = &ne_gcc_gpu_2_cfg_clk.clkr, [NE_GCC_GPU_2_GPLL0_CLK_SRC] = &ne_gcc_gpu_2_gpll0_clk_src.clkr, [NE_GCC_GPU_2_GPLL0_DIV_CLK_SRC] = &ne_gcc_gpu_2_gpll0_div_clk_src.clkr, [NE_GCC_GPU_2_HSCNOC_GFX_CLK] = &ne_gcc_gpu_2_hscnoc_gfx_clk.clkr, @@ -1945,10 +1929,16 @@ static void clk_nord_regs_configure(struct device *dev, struct regmap *regmap) qcom_branch_set_force_mem_core(regmap, ne_gcc_ufs_phy_axi_clk, true); } +static const u32 ne_gcc_nord_critical_cbcrs[] = { + 0x34004, /* NE_GCC_GPU_2_CFG_CLK */ +}; + static const struct qcom_cc_driver_data ne_gcc_nord_driver_data = { .dfs_rcgs = ne_gcc_nord_dfs_clocks, .num_dfs_rcgs = ARRAY_SIZE(ne_gcc_nord_dfs_clocks), .clk_regs_configure = clk_nord_regs_configure, + .clk_cbcrs = ne_gcc_nord_critical_cbcrs, + .num_clk_cbcrs = ARRAY_SIZE(ne_gcc_nord_critical_cbcrs), }; static const struct qcom_cc_desc ne_gcc_nord_desc = { From a0348ae4945e4c8385bc980a0323a69f10766b75 Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Tue, 23 Jun 2026 16:24:06 +0530 Subject: [PATCH 530/562] dt-bindings: clock: qcom: Document Nord display clock controller Add Device Tree binding documentation for the display clock controller on the Qualcomm Nord SoC. The Nord platform contains two instances of the display clock controller, DISPCC_0 and DISPCC_1. Update the bindings to include compatible strings for both instances. Signed-off-by: Taniya Das Link: https://lore.kernel.org/r/20260623-nords_mm_v1-v1-3-860c84539804@oss.qualcomm.com --- .../bindings/clock/qcom,sm8550-dispcc.yaml | 3 + include/dt-bindings/clock/qcom,nord-dispcc.h | 115 ++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 include/dt-bindings/clock/qcom,nord-dispcc.h diff --git a/Documentation/devicetree/bindings/clock/qcom,sm8550-dispcc.yaml b/Documentation/devicetree/bindings/clock/qcom,sm8550-dispcc.yaml index 591ce91b8d54..61f58fbd5bd2 100644 --- a/Documentation/devicetree/bindings/clock/qcom,sm8550-dispcc.yaml +++ b/Documentation/devicetree/bindings/clock/qcom,sm8550-dispcc.yaml @@ -16,6 +16,7 @@ description: | See also: - include/dt-bindings/clock/qcom,kaanapali-dispcc.h + - include/dt-bindings/clock/qcom,nord-dispcc.h - include/dt-bindings/clock/qcom,sm8550-dispcc.h - include/dt-bindings/clock/qcom,sm8650-dispcc.h - include/dt-bindings/clock/qcom,sm8750-dispcc.h @@ -25,6 +26,8 @@ properties: compatible: enum: - qcom,kaanapali-dispcc + - qcom,nord-dispcc0 + - qcom,nord-dispcc1 - qcom,sar2130p-dispcc - qcom,sm8550-dispcc - qcom,sm8650-dispcc diff --git a/include/dt-bindings/clock/qcom,nord-dispcc.h b/include/dt-bindings/clock/qcom,nord-dispcc.h new file mode 100644 index 000000000000..9f6c9979e0f3 --- /dev/null +++ b/include/dt-bindings/clock/qcom,nord-dispcc.h @@ -0,0 +1,115 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef _DT_BINDINGS_CLK_QCOM_DISP_CC_NORD_H +#define _DT_BINDINGS_CLK_QCOM_DISP_CC_NORD_H + +/* DISP_CC_0 clocks */ +#define MDSS_DISP_CC_ACMU_CLK 0 +#define MDSS_DISP_CC_MDSS_ACCU_SHIFT_CLK 1 +#define MDSS_DISP_CC_MDSS_AHB1_CLK 2 +#define MDSS_DISP_CC_MDSS_AHB_CLK 3 +#define MDSS_DISP_CC_MDSS_AHB_CLK_SRC 4 +#define MDSS_DISP_CC_MDSS_BYTE0_CLK 5 +#define MDSS_DISP_CC_MDSS_BYTE0_CLK_SRC 6 +#define MDSS_DISP_CC_MDSS_BYTE0_DIV_CLK_SRC 7 +#define MDSS_DISP_CC_MDSS_BYTE0_INTF_CLK 8 +#define MDSS_DISP_CC_MDSS_BYTE1_CLK 9 +#define MDSS_DISP_CC_MDSS_BYTE1_CLK_SRC 10 +#define MDSS_DISP_CC_MDSS_BYTE1_DIV_CLK_SRC 11 +#define MDSS_DISP_CC_MDSS_BYTE1_INTF_CLK 12 +#define MDSS_DISP_CC_MDSS_DPTX0_AUX_CLK 13 +#define MDSS_DISP_CC_MDSS_DPTX0_AUX_CLK_SRC 14 +#define MDSS_DISP_CC_MDSS_DPTX0_CRYPTO_CLK 15 +#define MDSS_DISP_CC_MDSS_DPTX0_LINK_CLK 16 +#define MDSS_DISP_CC_MDSS_DPTX0_LINK_CLK_SRC 17 +#define MDSS_DISP_CC_MDSS_DPTX0_LINK_DIV_CLK_SRC 18 +#define MDSS_DISP_CC_MDSS_DPTX0_LINK_INTF_CLK 19 +#define MDSS_DISP_CC_MDSS_DPTX0_PIXEL0_CLK 20 +#define MDSS_DISP_CC_MDSS_DPTX0_PIXEL0_CLK_SRC 21 +#define MDSS_DISP_CC_MDSS_DPTX0_PIXEL1_CLK 22 +#define MDSS_DISP_CC_MDSS_DPTX0_PIXEL1_CLK_SRC 23 +#define MDSS_DISP_CC_MDSS_DPTX0_PIXEL2_CLK 24 +#define MDSS_DISP_CC_MDSS_DPTX0_PIXEL2_CLK_SRC 25 +#define MDSS_DISP_CC_MDSS_DPTX0_PIXEL3_CLK 26 +#define MDSS_DISP_CC_MDSS_DPTX0_PIXEL3_CLK_SRC 27 +#define MDSS_DISP_CC_MDSS_DPTX0_USB_ROUTER_LINK_INTF_CLK 28 +#define MDSS_DISP_CC_MDSS_DPTX1_AUX_CLK 29 +#define MDSS_DISP_CC_MDSS_DPTX1_AUX_CLK_SRC 30 +#define MDSS_DISP_CC_MDSS_DPTX1_CRYPTO_CLK 31 +#define MDSS_DISP_CC_MDSS_DPTX1_LINK_CLK 32 +#define MDSS_DISP_CC_MDSS_DPTX1_LINK_CLK_SRC 33 +#define MDSS_DISP_CC_MDSS_DPTX1_LINK_DIV_CLK_SRC 34 +#define MDSS_DISP_CC_MDSS_DPTX1_LINK_INTF_CLK 35 +#define MDSS_DISP_CC_MDSS_DPTX1_PIXEL0_CLK 36 +#define MDSS_DISP_CC_MDSS_DPTX1_PIXEL0_CLK_SRC 37 +#define MDSS_DISP_CC_MDSS_DPTX1_PIXEL1_CLK 38 +#define MDSS_DISP_CC_MDSS_DPTX1_PIXEL1_CLK_SRC 39 +#define MDSS_DISP_CC_MDSS_DPTX1_PIXEL2_CLK 40 +#define MDSS_DISP_CC_MDSS_DPTX1_PIXEL2_CLK_SRC 41 +#define MDSS_DISP_CC_MDSS_DPTX1_PIXEL3_CLK 42 +#define MDSS_DISP_CC_MDSS_DPTX1_PIXEL3_CLK_SRC 43 +#define MDSS_DISP_CC_MDSS_DPTX1_USB_ROUTER_LINK_INTF_CLK 44 +#define MDSS_DISP_CC_MDSS_DPTX2_AUX_CLK 45 +#define MDSS_DISP_CC_MDSS_DPTX2_AUX_CLK_SRC 46 +#define MDSS_DISP_CC_MDSS_DPTX2_CRYPTO_CLK 47 +#define MDSS_DISP_CC_MDSS_DPTX2_LINK_CLK 48 +#define MDSS_DISP_CC_MDSS_DPTX2_LINK_CLK_SRC 49 +#define MDSS_DISP_CC_MDSS_DPTX2_LINK_DIV_CLK_SRC 50 +#define MDSS_DISP_CC_MDSS_DPTX2_LINK_INTF_CLK 51 +#define MDSS_DISP_CC_MDSS_DPTX2_PIXEL0_CLK 52 +#define MDSS_DISP_CC_MDSS_DPTX2_PIXEL0_CLK_SRC 53 +#define MDSS_DISP_CC_MDSS_DPTX2_PIXEL1_CLK 54 +#define MDSS_DISP_CC_MDSS_DPTX2_PIXEL1_CLK_SRC 55 +#define MDSS_DISP_CC_MDSS_DPTX3_AUX_CLK 56 +#define MDSS_DISP_CC_MDSS_DPTX3_AUX_CLK_SRC 57 +#define MDSS_DISP_CC_MDSS_DPTX3_CRYPTO_CLK 58 +#define MDSS_DISP_CC_MDSS_DPTX3_LINK_CLK 59 +#define MDSS_DISP_CC_MDSS_DPTX3_LINK_CLK_SRC 60 +#define MDSS_DISP_CC_MDSS_DPTX3_LINK_DIV_CLK_SRC 61 +#define MDSS_DISP_CC_MDSS_DPTX3_LINK_INTF_CLK 62 +#define MDSS_DISP_CC_MDSS_DPTX3_PIXEL0_CLK 63 +#define MDSS_DISP_CC_MDSS_DPTX3_PIXEL0_CLK_SRC 64 +#define MDSS_DISP_CC_MDSS_ESC0_CLK 65 +#define MDSS_DISP_CC_MDSS_ESC0_CLK_SRC 66 +#define MDSS_DISP_CC_MDSS_ESC1_CLK 67 +#define MDSS_DISP_CC_MDSS_ESC1_CLK_SRC 68 +#define MDSS_DISP_CC_MDSS_MDP1_CLK 69 +#define MDSS_DISP_CC_MDSS_MDP_CLK 70 +#define MDSS_DISP_CC_MDSS_MDP_CLK_SRC 71 +#define MDSS_DISP_CC_MDSS_MDP_LUT1_CLK 72 +#define MDSS_DISP_CC_MDSS_MDP_LUT_CLK 73 +#define MDSS_DISP_CC_MDSS_NON_GDSC_AHB_CLK 74 +#define MDSS_DISP_CC_MDSS_PCLK0_CLK 75 +#define MDSS_DISP_CC_MDSS_PCLK0_CLK_SRC 76 +#define MDSS_DISP_CC_MDSS_PCLK1_CLK 77 +#define MDSS_DISP_CC_MDSS_PCLK1_CLK_SRC 78 +#define MDSS_DISP_CC_MDSS_PCLK2_CLK 79 +#define MDSS_DISP_CC_MDSS_PCLK2_CLK_SRC 80 +#define MDSS_DISP_CC_MDSS_RSCC_AHB_CLK 81 +#define MDSS_DISP_CC_MDSS_RSCC_VSYNC_CLK 82 +#define MDSS_DISP_CC_MDSS_VSYNC1_CLK 83 +#define MDSS_DISP_CC_MDSS_VSYNC_CLK 84 +#define MDSS_DISP_CC_MDSS_VSYNC_CLK_SRC 85 +#define MDSS_DISP_CC_PLL0 86 +#define MDSS_DISP_CC_PLL1 87 +#define MDSS_DISP_CC_PLL2 88 +#define MDSS_DISP_CC_PLL3 89 +#define MDSS_DISP_CC_SLEEP_CLK 90 +#define MDSS_DISP_CC_SLEEP_CLK_SRC 91 +#define MDSS_DISP_CC_SM_DIV_CLK_SRC 92 +#define MDSS_DISP_CC_XO_CLK 93 +#define MDSS_DISP_CC_XO_CLK_SRC 94 + +/* DISP_CC_0 power domains */ +#define MDSS_DISP_CC_MDSS_CORE_GDSC 0 +#define MDSS_DISP_CC_MDSS_CORE_INT2_GDSC 1 + +/* DISP_CC_0 resets */ +#define MDSS_DISP_CC_MDSS_CORE_BCR 0 +#define MDSS_DISP_CC_MDSS_CORE_INT2_BCR 1 +#define MDSS_DISP_CC_MDSS_RSCC_BCR 2 + +#endif From 7037626db5977b3a17f24831b073d4f924796dd5 Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Tue, 23 Jun 2026 16:24:07 +0530 Subject: [PATCH 531/562] clk: qcom: Add Nord display clock controller support Add support for the display clock controllers (DISPCC) on the Qualcomm Nord platform. The platform includes two display clock controller instances, display0 and display1. Register support for both controllers. Signed-off-by: Taniya Das Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260623-nords_mm_v1-v1-4-860c84539804@oss.qualcomm.com --- drivers/clk/qcom/Kconfig | 11 + drivers/clk/qcom/Makefile | 1 + drivers/clk/qcom/dispcc0-nord.c | 2006 +++++++++++++++++++++++++++++++ drivers/clk/qcom/dispcc1-nord.c | 2006 +++++++++++++++++++++++++++++++ 4 files changed, 4024 insertions(+) create mode 100644 drivers/clk/qcom/dispcc0-nord.c create mode 100644 drivers/clk/qcom/dispcc1-nord.c diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig index 7d84c2f1d911..874136a2ad9a 100644 --- a/drivers/clk/qcom/Kconfig +++ b/drivers/clk/qcom/Kconfig @@ -145,6 +145,17 @@ config CLK_KAANAPALI_VIDEOCC Say Y if you want to support video devices and functionality such as video encode/decode. +config CLK_NORD_DISPCC + tristate "Nord Display Clock Controller" + depends on ARM64 || COMPILE_TEST + select CLK_NORD_GCC + default m if ARCH_QCOM + help + Support for the display clock controllers on Qualcomm Technologies, Inc + Nord devices. There are two display clock controllers on Nord SoC. + Say Y if you want to support display devices and functionality such as + splash screen. + config CLK_NORD_GCC tristate "Nord Global Clock Controller" depends on ARM64 || COMPILE_TEST diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile index 58f9a5eb6fd7..4282f43e7078 100644 --- a/drivers/clk/qcom/Makefile +++ b/drivers/clk/qcom/Makefile @@ -37,6 +37,7 @@ obj-$(CONFIG_CLK_KAANAPALI_GCC) += gcc-kaanapali.o obj-$(CONFIG_CLK_KAANAPALI_GPUCC) += gpucc-kaanapali.o gxclkctl-kaanapali.o obj-$(CONFIG_CLK_KAANAPALI_TCSRCC) += tcsrcc-kaanapali.o obj-$(CONFIG_CLK_KAANAPALI_VIDEOCC) += videocc-kaanapali.o +obj-$(CONFIG_CLK_NORD_DISPCC) += dispcc0-nord.o dispcc1-nord.o obj-$(CONFIG_CLK_NORD_GCC) += gcc-nord.o negcc-nord.o nwgcc-nord.o segcc-nord.o obj-$(CONFIG_CLK_NORD_TCSRCC) += tcsrcc-nord.o obj-$(CONFIG_CLK_X1E80100_CAMCC) += camcc-x1e80100.o diff --git a/drivers/clk/qcom/dispcc0-nord.c b/drivers/clk/qcom/dispcc0-nord.c new file mode 100644 index 000000000000..c0097482a1a9 --- /dev/null +++ b/drivers/clk/qcom/dispcc0-nord.c @@ -0,0 +1,2006 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "clk-alpha-pll.h" +#include "clk-branch.h" +#include "clk-pll.h" +#include "clk-rcg.h" +#include "clk-regmap.h" +#include "clk-regmap-divider.h" +#include "clk-regmap-mux.h" +#include "common.h" +#include "gdsc.h" +#include "reset.h" + +enum { + DT_BI_TCXO, + DT_BI_TCXO_AO, + DT_AHB_CLK, + DT_SLEEP_CLK, + + DT_DSI0_PHY_PLL_OUT_BYTECLK, + DT_DSI0_PHY_PLL_OUT_DSICLK, + DT_DSI1_PHY_PLL_OUT_BYTECLK, + DT_DSI1_PHY_PLL_OUT_DSICLK, + + DT_DP0_PHY_PLL_LINK_CLK, + DT_DP0_PHY_PLL_VCO_DIV_CLK, + DT_DP1_PHY_PLL_LINK_CLK, + DT_DP1_PHY_PLL_VCO_DIV_CLK, + DT_DP2_PHY_PLL_LINK_CLK, + DT_DP2_PHY_PLL_VCO_DIV_CLK, + DT_DP3_PHY_PLL_LINK_CLK, + DT_DP3_PHY_PLL_VCO_DIV_CLK, +}; + +enum { + P_BI_TCXO, + P_MDSS_0_DISP_CC_PLL0_OUT_MAIN, + P_MDSS_0_DISP_CC_PLL1_OUT_EVEN, + P_MDSS_0_DISP_CC_PLL1_OUT_MAIN, + P_MDSS_0_DISP_CC_PLL2_OUT_MAIN, + P_MDSS_0_DISP_CC_PLL3_OUT_MAIN, + P_DP0_PHY_PLL_LINK_CLK, + P_DP0_PHY_PLL_VCO_DIV_CLK, + P_DP1_PHY_PLL_LINK_CLK, + P_DP1_PHY_PLL_VCO_DIV_CLK, + P_DP2_PHY_PLL_LINK_CLK, + P_DP2_PHY_PLL_VCO_DIV_CLK, + P_DP3_PHY_PLL_LINK_CLK, + P_DP3_PHY_PLL_VCO_DIV_CLK, + P_DSI0_PHY_PLL_OUT_BYTECLK, + P_DSI0_PHY_PLL_OUT_DSICLK, + P_DSI1_PHY_PLL_OUT_BYTECLK, + P_DSI1_PHY_PLL_OUT_DSICLK, + P_SLEEP_CLK, +}; + +static const struct pll_vco lucid_ole_vco[] = { + { 249600000, 2300000000, 0 }, +}; + +static const struct pll_vco zonda_ole_vco[] = { + { 700000000, 3600000000, 0 }, +}; + +/* 900.0 MHz Configuration */ +static const struct alpha_pll_config mdss_0_disp_cc_pll0_config = { + .l = 0x2e, + .alpha = 0xe000, + .config_ctl_val = 0x20485699, + .config_ctl_hi_val = 0x00182261, + .config_ctl_hi1_val = 0x82aa299c, + .test_ctl_val = 0x00000000, + .test_ctl_hi_val = 0x00000003, + .test_ctl_hi1_val = 0x00009000, + .test_ctl_hi2_val = 0x00000034, + .user_ctl_val = 0x00000000, + .user_ctl_hi_val = 0x00400005, +}; + +static struct clk_alpha_pll mdss_0_disp_cc_pll0 = { + .offset = 0x0, + .config = &mdss_0_disp_cc_pll0_config, + .vco_table = lucid_ole_vco, + .num_vco = ARRAY_SIZE(lucid_ole_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_pll0", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_lucid_evo_ops, + }, + }, +}; + +/* 600.0 MHz Configuration */ +static const struct alpha_pll_config mdss_0_disp_cc_pll1_config = { + .l = 0x1f, + .alpha = 0x4000, + .config_ctl_val = 0x20485699, + .config_ctl_hi_val = 0x00182261, + .config_ctl_hi1_val = 0x82aa299c, + .test_ctl_val = 0x00000000, + .test_ctl_hi_val = 0x00000003, + .test_ctl_hi1_val = 0x00009000, + .test_ctl_hi2_val = 0x00000034, + .user_ctl_val = 0x00000000, + .user_ctl_hi_val = 0x00400005, +}; + +static struct clk_alpha_pll mdss_0_disp_cc_pll1 = { + .offset = 0x1000, + .config = &mdss_0_disp_cc_pll1_config, + .vco_table = lucid_ole_vco, + .num_vco = ARRAY_SIZE(lucid_ole_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_pll1", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_lucid_evo_ops, + }, + }, +}; + +/* 1363.2 MHz Configuration */ +static const struct alpha_pll_config mdss_0_disp_cc_pll2_config = { + .l = 0x47, + .alpha = 0x0, + .config_ctl_val = 0x08240800, + .config_ctl_hi_val = 0x05008001, + .config_ctl_hi1_val = 0x00000000, + .config_ctl_hi2_val = 0x00000000, + .user_ctl_val = 0x00000000, + .user_ctl_hi_val = 0x00000080, +}; + +static struct clk_alpha_pll mdss_0_disp_cc_pll2 = { + .offset = 0x2000, + .config = &mdss_0_disp_cc_pll2_config, + .vco_table = zonda_ole_vco, + .num_vco = ARRAY_SIZE(zonda_ole_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_ZONDA_OLE], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_pll2", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_zonda_ole_ops, + }, + }, +}; + +/* 1363.2 MHz Configuration */ +static const struct alpha_pll_config mdss_0_disp_cc_pll3_config = { + .l = 0x47, + .alpha = 0x0, + .config_ctl_val = 0x08240800, + .config_ctl_hi_val = 0x05008001, + .config_ctl_hi1_val = 0x00000000, + .config_ctl_hi2_val = 0x00000000, + .user_ctl_val = 0x00000000, + .user_ctl_hi_val = 0x00000080, +}; + +static struct clk_alpha_pll mdss_0_disp_cc_pll3 = { + .offset = 0x3000, + .config = &mdss_0_disp_cc_pll3_config, + .vco_table = zonda_ole_vco, + .num_vco = ARRAY_SIZE(zonda_ole_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_ZONDA_OLE], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_pll3", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_zonda_ole_ops, + }, + }, +}; + +static const struct parent_map disp_cc_0_parent_map_0[] = { + { P_BI_TCXO, 0 }, + { P_MDSS_0_DISP_CC_PLL2_OUT_MAIN, 1 }, + { P_DP0_PHY_PLL_VCO_DIV_CLK, 2 }, + { P_DP3_PHY_PLL_VCO_DIV_CLK, 3 }, + { P_DP1_PHY_PLL_VCO_DIV_CLK, 4 }, + { P_MDSS_0_DISP_CC_PLL3_OUT_MAIN, 5 }, + { P_DP2_PHY_PLL_VCO_DIV_CLK, 6 }, +}; + +static const struct clk_parent_data disp_cc_0_parent_data_0[] = { + { .index = DT_BI_TCXO }, + { .hw = &mdss_0_disp_cc_pll2.clkr.hw }, + { .index = DT_DP0_PHY_PLL_VCO_DIV_CLK }, + { .index = DT_DP3_PHY_PLL_VCO_DIV_CLK }, + { .index = DT_DP1_PHY_PLL_VCO_DIV_CLK }, + { .hw = &mdss_0_disp_cc_pll3.clkr.hw }, + { .index = DT_DP2_PHY_PLL_VCO_DIV_CLK }, +}; + +static const struct parent_map disp_cc_0_parent_map_1[] = { + { P_BI_TCXO, 0 }, +}; + +static const struct clk_parent_data disp_cc_0_parent_data_1[] = { + { .index = DT_BI_TCXO }, +}; + +static const struct parent_map disp_cc_0_parent_map_2[] = { + { P_BI_TCXO, 0 }, + { P_DSI0_PHY_PLL_OUT_DSICLK, 1 }, + { P_DSI0_PHY_PLL_OUT_BYTECLK, 2 }, + { P_DSI1_PHY_PLL_OUT_DSICLK, 3 }, + { P_DSI1_PHY_PLL_OUT_BYTECLK, 4 }, +}; + +static const struct clk_parent_data disp_cc_0_parent_data_2[] = { + { .index = DT_BI_TCXO }, + { .index = DT_DSI0_PHY_PLL_OUT_DSICLK }, + { .index = DT_DSI0_PHY_PLL_OUT_BYTECLK }, + { .index = DT_DSI1_PHY_PLL_OUT_DSICLK }, + { .index = DT_DSI1_PHY_PLL_OUT_BYTECLK }, +}; + +static const struct parent_map disp_cc_0_parent_map_3[] = { + { P_BI_TCXO, 0 }, + { P_MDSS_0_DISP_CC_PLL2_OUT_MAIN, 1 }, + { P_DP3_PHY_PLL_VCO_DIV_CLK, 3 }, + { P_MDSS_0_DISP_CC_PLL3_OUT_MAIN, 5 }, + { P_DP2_PHY_PLL_VCO_DIV_CLK, 6 }, +}; + +static const struct clk_parent_data disp_cc_0_parent_data_3[] = { + { .index = DT_BI_TCXO }, + { .hw = &mdss_0_disp_cc_pll2.clkr.hw }, + { .index = DT_DP3_PHY_PLL_VCO_DIV_CLK }, + { .hw = &mdss_0_disp_cc_pll3.clkr.hw }, + { .index = DT_DP2_PHY_PLL_VCO_DIV_CLK }, +}; + +static const struct parent_map disp_cc_0_parent_map_4[] = { + { P_BI_TCXO, 0 }, + { P_DP0_PHY_PLL_LINK_CLK, 1 }, + { P_DP1_PHY_PLL_LINK_CLK, 2 }, + { P_DP2_PHY_PLL_LINK_CLK, 3 }, + { P_DP3_PHY_PLL_LINK_CLK, 4 }, +}; + +static const struct clk_parent_data disp_cc_0_parent_data_4[] = { + { .index = DT_BI_TCXO }, + { .index = DT_DP0_PHY_PLL_LINK_CLK }, + { .index = DT_DP1_PHY_PLL_LINK_CLK }, + { .index = DT_DP2_PHY_PLL_LINK_CLK }, + { .index = DT_DP3_PHY_PLL_LINK_CLK }, +}; + +static const struct parent_map disp_cc_0_parent_map_5[] = { + { P_BI_TCXO, 0 }, + { P_DP2_PHY_PLL_LINK_CLK, 3 }, + { P_DP3_PHY_PLL_LINK_CLK, 4 }, +}; + +static const struct clk_parent_data disp_cc_0_parent_data_5[] = { + { .index = DT_BI_TCXO }, + { .index = DT_DP2_PHY_PLL_LINK_CLK }, + { .index = DT_DP3_PHY_PLL_LINK_CLK }, +}; + +static const struct parent_map disp_cc_0_parent_map_6[] = { + { P_BI_TCXO, 0 }, + { P_DSI0_PHY_PLL_OUT_BYTECLK, 2 }, + { P_DSI1_PHY_PLL_OUT_BYTECLK, 4 }, +}; + +static const struct clk_parent_data disp_cc_0_parent_data_6[] = { + { .index = DT_BI_TCXO }, + { .index = DT_DSI0_PHY_PLL_OUT_BYTECLK }, + { .index = DT_DSI1_PHY_PLL_OUT_BYTECLK }, +}; + +static const struct parent_map disp_cc_0_parent_map_7[] = { + { P_BI_TCXO, 0 }, + { P_MDSS_0_DISP_CC_PLL1_OUT_MAIN, 4 }, + { P_MDSS_0_DISP_CC_PLL1_OUT_EVEN, 6 }, +}; + +static const struct clk_parent_data disp_cc_0_parent_data_7[] = { + { .index = DT_BI_TCXO }, + { .hw = &mdss_0_disp_cc_pll1.clkr.hw }, + { .hw = &mdss_0_disp_cc_pll1.clkr.hw }, +}; + +static const struct parent_map disp_cc_0_parent_map_8[] = { + { P_BI_TCXO, 0 }, + { P_MDSS_0_DISP_CC_PLL0_OUT_MAIN, 1 }, + { P_MDSS_0_DISP_CC_PLL1_OUT_MAIN, 4 }, + { P_MDSS_0_DISP_CC_PLL1_OUT_EVEN, 6 }, +}; + +static const struct clk_parent_data disp_cc_0_parent_data_8[] = { + { .index = DT_BI_TCXO }, + { .hw = &mdss_0_disp_cc_pll0.clkr.hw }, + { .hw = &mdss_0_disp_cc_pll1.clkr.hw }, + { .hw = &mdss_0_disp_cc_pll1.clkr.hw }, +}; + +static const struct parent_map disp_cc_0_parent_map_9[] = { + { P_SLEEP_CLK, 0 }, +}; + +static const struct clk_parent_data disp_cc_0_parent_data_9[] = { + { .index = DT_SLEEP_CLK }, +}; + +static const struct freq_tbl ftbl_mdss_0_disp_cc_mdss_ahb_clk_src[] = { + F(37500000, P_MDSS_0_DISP_CC_PLL1_OUT_MAIN, 16, 0, 0), + F(75000000, P_MDSS_0_DISP_CC_PLL1_OUT_MAIN, 8, 0, 0), + { } +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_ahb_clk_src = { + .cmd_rcgr = 0x837c, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_7, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_ahb_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_ahb_clk_src", + .parent_data = disp_cc_0_parent_data_7, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_7), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_mdss_0_disp_cc_mdss_byte0_clk_src[] = { + F(19200000, P_BI_TCXO, 1, 0, 0), + { } +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_byte0_clk_src = { + .cmd_rcgr = 0x813c, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_2, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_byte0_clk_src", + .parent_data = disp_cc_0_parent_data_2, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_2), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_byte2_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_byte1_clk_src = { + .cmd_rcgr = 0x8158, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_2, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_byte1_clk_src", + .parent_data = disp_cc_0_parent_data_2, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_2), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_byte2_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx0_aux_clk_src = { + .cmd_rcgr = 0x8220, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_1, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx0_aux_clk_src", + .parent_data = disp_cc_0_parent_data_1, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx0_link_clk_src = { + .cmd_rcgr = 0x81a4, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_4, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx0_link_clk_src", + .parent_data = disp_cc_0_parent_data_4, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_4), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_byte2_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx0_pixel0_clk_src = { + .cmd_rcgr = 0x81c0, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_0, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx0_pixel0_clk_src", + .parent_data = disp_cc_0_parent_data_0, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx0_pixel1_clk_src = { + .cmd_rcgr = 0x81d8, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_0, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx0_pixel1_clk_src", + .parent_data = disp_cc_0_parent_data_0, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx0_pixel2_clk_src = { + .cmd_rcgr = 0x81f0, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_0, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx0_pixel2_clk_src", + .parent_data = disp_cc_0_parent_data_0, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx0_pixel3_clk_src = { + .cmd_rcgr = 0x8208, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_0, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx0_pixel3_clk_src", + .parent_data = disp_cc_0_parent_data_0, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx1_aux_clk_src = { + .cmd_rcgr = 0x82b4, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_1, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx1_aux_clk_src", + .parent_data = disp_cc_0_parent_data_1, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx1_link_clk_src = { + .cmd_rcgr = 0x8298, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_4, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx1_link_clk_src", + .parent_data = disp_cc_0_parent_data_4, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_4), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_byte2_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx1_pixel0_clk_src = { + .cmd_rcgr = 0x8238, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_0, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx1_pixel0_clk_src", + .parent_data = disp_cc_0_parent_data_0, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx1_pixel1_clk_src = { + .cmd_rcgr = 0x8250, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_0, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx1_pixel1_clk_src", + .parent_data = disp_cc_0_parent_data_0, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx1_pixel2_clk_src = { + .cmd_rcgr = 0x8268, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_0, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx1_pixel2_clk_src", + .parent_data = disp_cc_0_parent_data_0, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx1_pixel3_clk_src = { + .cmd_rcgr = 0x8280, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_0, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx1_pixel3_clk_src", + .parent_data = disp_cc_0_parent_data_0, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx2_aux_clk_src = { + .cmd_rcgr = 0x8318, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_1, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx2_aux_clk_src", + .parent_data = disp_cc_0_parent_data_1, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx2_link_clk_src = { + .cmd_rcgr = 0x82cc, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_5, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx2_link_clk_src", + .parent_data = disp_cc_0_parent_data_5, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_5), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_byte2_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx2_pixel0_clk_src = { + .cmd_rcgr = 0x82e8, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_3, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx2_pixel0_clk_src", + .parent_data = disp_cc_0_parent_data_3, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_3), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx2_pixel1_clk_src = { + .cmd_rcgr = 0x8300, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_3, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx2_pixel1_clk_src", + .parent_data = disp_cc_0_parent_data_3, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_3), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx3_aux_clk_src = { + .cmd_rcgr = 0x8364, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_1, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx3_aux_clk_src", + .parent_data = disp_cc_0_parent_data_1, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx3_link_clk_src = { + .cmd_rcgr = 0x8348, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_5, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx3_link_clk_src", + .parent_data = disp_cc_0_parent_data_5, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_5), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_byte2_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_dptx3_pixel0_clk_src = { + .cmd_rcgr = 0x8330, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_3, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx3_pixel0_clk_src", + .parent_data = disp_cc_0_parent_data_3, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_3), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_esc0_clk_src = { + .cmd_rcgr = 0x8174, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_6, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_esc0_clk_src", + .parent_data = disp_cc_0_parent_data_6, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_6), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_esc1_clk_src = { + .cmd_rcgr = 0x818c, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_6, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_esc1_clk_src", + .parent_data = disp_cc_0_parent_data_6, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_6), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_mdss_0_disp_cc_mdss_mdp_clk_src[] = { + F(300000000, P_MDSS_0_DISP_CC_PLL0_OUT_MAIN, 3, 0, 0), + F(417000000, P_MDSS_0_DISP_CC_PLL0_OUT_MAIN, 3, 0, 0), + F(532000000, P_MDSS_0_DISP_CC_PLL0_OUT_MAIN, 3, 0, 0), + F(650000000, P_MDSS_0_DISP_CC_PLL0_OUT_MAIN, 3, 0, 0), + F(710000000, P_MDSS_0_DISP_CC_PLL0_OUT_MAIN, 3, 0, 0), + { } +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_mdp_clk_src = { + .cmd_rcgr = 0x810c, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_8, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_mdp_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_mdp_clk_src", + .parent_data = disp_cc_0_parent_data_8, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_8), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_pclk0_clk_src = { + .cmd_rcgr = 0x80c4, + .mnd_width = 8, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_2, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_pclk0_clk_src", + .parent_data = disp_cc_0_parent_data_2, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_2), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_pixel_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_pclk1_clk_src = { + .cmd_rcgr = 0x80dc, + .mnd_width = 8, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_2, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_pclk1_clk_src", + .parent_data = disp_cc_0_parent_data_2, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_2), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_pixel_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_pclk2_clk_src = { + .cmd_rcgr = 0x80f4, + .mnd_width = 8, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_2, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_pclk2_clk_src", + .parent_data = disp_cc_0_parent_data_2, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_2), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_pixel_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_mdss_vsync_clk_src = { + .cmd_rcgr = 0x8124, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_1, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_vsync_clk_src", + .parent_data = disp_cc_0_parent_data_1, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_mdss_0_disp_cc_sleep_clk_src[] = { + F(32000, P_SLEEP_CLK, 1, 0, 0), + { } +}; + +static struct clk_rcg2 mdss_0_disp_cc_sleep_clk_src = { + .cmd_rcgr = 0xe064, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_9, + .freq_tbl = ftbl_mdss_0_disp_cc_sleep_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_sleep_clk_src", + .parent_data = disp_cc_0_parent_data_9, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_9), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_ops, + }, +}; + +static struct clk_rcg2 mdss_0_disp_cc_xo_clk_src = { + .cmd_rcgr = 0xe044, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_0_parent_map_1, + .freq_tbl = ftbl_mdss_0_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_xo_clk_src", + .parent_data = disp_cc_0_parent_data_1, + .num_parents = ARRAY_SIZE(disp_cc_0_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_ops, + }, +}; + +static struct clk_regmap_div mdss_0_disp_cc_mdss_byte0_div_clk_src = { + .reg = 0x8154, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_byte0_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_byte0_clk_src.clkr.hw, + }, + .num_parents = 1, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_regmap_div mdss_0_disp_cc_mdss_byte1_div_clk_src = { + .reg = 0x8170, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_byte1_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_byte1_clk_src.clkr.hw, + }, + .num_parents = 1, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_regmap_div mdss_0_disp_cc_mdss_dptx0_link_div_clk_src = { + .reg = 0x81bc, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx0_link_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx0_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_regmap_div mdss_0_disp_cc_mdss_dptx1_link_div_clk_src = { + .reg = 0x82b0, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx1_link_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx1_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_regmap_div mdss_0_disp_cc_mdss_dptx2_link_div_clk_src = { + .reg = 0x82e4, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx2_link_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx2_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_regmap_div mdss_0_disp_cc_mdss_dptx3_link_div_clk_src = { + .reg = 0x8360, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx3_link_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx3_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_accu_shift_clk = { + .halt_reg = 0xe060, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0xe060, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_accu_shift_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_xo_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_ahb1_clk = { + .halt_reg = 0xa028, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0xa028, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_ahb1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_ahb_clk = { + .halt_reg = 0x80c0, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x80c0, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_byte0_clk = { + .halt_reg = 0x8034, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8034, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_byte0_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_byte0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_byte0_intf_clk = { + .halt_reg = 0x8038, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8038, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_byte0_intf_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_byte0_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_byte1_clk = { + .halt_reg = 0x803c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x803c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_byte1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_byte1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_byte1_intf_clk = { + .halt_reg = 0x8040, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8040, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_byte1_intf_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_byte1_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx0_aux_clk = { + .halt_reg = 0x806c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x806c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx0_aux_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx0_aux_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx0_crypto_clk = { + .halt_reg = 0x8058, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x8058, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx0_crypto_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx0_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx0_link_clk = { + .halt_reg = 0x804c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x804c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx0_link_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx0_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx0_link_intf_clk = { + .halt_reg = 0x8054, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8054, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx0_link_intf_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx0_link_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx0_pixel0_clk = { + .halt_reg = 0x805c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x805c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx0_pixel0_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx0_pixel0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx0_pixel1_clk = { + .halt_reg = 0x8060, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8060, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx0_pixel1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx0_pixel1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx0_pixel2_clk = { + .halt_reg = 0x8064, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8064, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx0_pixel2_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx0_pixel2_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx0_pixel3_clk = { + .halt_reg = 0x8068, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8068, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx0_pixel3_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx0_pixel3_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx0_usb_router_link_intf_clk = { + .halt_reg = 0x8050, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8050, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx0_usb_router_link_intf_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx0_link_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx1_aux_clk = { + .halt_reg = 0x8090, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8090, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx1_aux_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx1_aux_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx1_crypto_clk = { + .halt_reg = 0x808c, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x808c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx1_crypto_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx1_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx1_link_clk = { + .halt_reg = 0x8080, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8080, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx1_link_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx1_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx1_link_intf_clk = { + .halt_reg = 0x8088, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8088, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx1_link_intf_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx1_link_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx1_pixel0_clk = { + .halt_reg = 0x8070, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8070, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx1_pixel0_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx1_pixel0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx1_pixel1_clk = { + .halt_reg = 0x8074, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8074, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx1_pixel1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx1_pixel1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx1_pixel2_clk = { + .halt_reg = 0x8078, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8078, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx1_pixel2_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx1_pixel2_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx1_pixel3_clk = { + .halt_reg = 0x807c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x807c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx1_pixel3_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx1_pixel3_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx1_usb_router_link_intf_clk = { + .halt_reg = 0x8084, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8084, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx1_usb_router_link_intf_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx1_link_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx2_aux_clk = { + .halt_reg = 0x80a8, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x80a8, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx2_aux_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx2_aux_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx2_crypto_clk = { + .halt_reg = 0x80a4, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x80a4, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx2_crypto_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx2_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx2_link_clk = { + .halt_reg = 0x809c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x809c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx2_link_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx2_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx2_link_intf_clk = { + .halt_reg = 0x80a0, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x80a0, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx2_link_intf_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx2_link_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx2_pixel0_clk = { + .halt_reg = 0x8094, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8094, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx2_pixel0_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx2_pixel0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx2_pixel1_clk = { + .halt_reg = 0x8098, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8098, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx2_pixel1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx2_pixel1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx3_aux_clk = { + .halt_reg = 0x80b8, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x80b8, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx3_aux_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx3_aux_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx3_crypto_clk = { + .halt_reg = 0x80bc, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x80bc, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx3_crypto_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx3_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx3_link_clk = { + .halt_reg = 0x80b0, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x80b0, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx3_link_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx3_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx3_link_intf_clk = { + .halt_reg = 0x80b4, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x80b4, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx3_link_intf_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx3_link_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_dptx3_pixel0_clk = { + .halt_reg = 0x80ac, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x80ac, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_dptx3_pixel0_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_dptx3_pixel0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_esc0_clk = { + .halt_reg = 0x8044, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8044, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_esc0_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_esc0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_esc1_clk = { + .halt_reg = 0x8048, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8048, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_esc1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_esc1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_mdp1_clk = { + .halt_reg = 0xa004, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0xa004, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_mdp1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_mdp_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_mdp_clk = { + .halt_reg = 0x8010, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8010, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_mdp_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_mdp_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_mdp_lut1_clk = { + .halt_reg = 0xa014, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0xa014, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_mdp_lut1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_mdp_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_mdp_lut_clk = { + .halt_reg = 0x8020, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x8020, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_mdp_lut_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_mdp_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_non_gdsc_ahb_clk = { + .halt_reg = 0xc004, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0xc004, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_non_gdsc_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_pclk0_clk = { + .halt_reg = 0x8004, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8004, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_pclk0_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_pclk0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_pclk1_clk = { + .halt_reg = 0x8008, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8008, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_pclk1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_pclk1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_pclk2_clk = { + .halt_reg = 0x800c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x800c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_pclk2_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_pclk2_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_vsync1_clk = { + .halt_reg = 0xa024, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0xa024, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_vsync1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_vsync_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_0_disp_cc_mdss_vsync_clk = { + .halt_reg = 0x8030, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8030, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_0_disp_cc_mdss_vsync_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_0_disp_cc_mdss_vsync_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct gdsc mdss_0_disp_cc_mdss_core_gdsc = { + .gdscr = 0x9000, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "mdss_0_disp_cc_mdss_core_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .flags = HW_CTRL_TRIGGER | POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct gdsc mdss_0_disp_cc_mdss_core_int2_gdsc = { + .gdscr = 0xb000, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "mdss_0_disp_cc_mdss_core_int2_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .flags = HW_CTRL_TRIGGER | POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct clk_regmap *disp_cc_0_nord_clocks[] = { + [MDSS_DISP_CC_MDSS_ACCU_SHIFT_CLK] = &mdss_0_disp_cc_mdss_accu_shift_clk.clkr, + [MDSS_DISP_CC_MDSS_AHB1_CLK] = &mdss_0_disp_cc_mdss_ahb1_clk.clkr, + [MDSS_DISP_CC_MDSS_AHB_CLK] = &mdss_0_disp_cc_mdss_ahb_clk.clkr, + [MDSS_DISP_CC_MDSS_AHB_CLK_SRC] = &mdss_0_disp_cc_mdss_ahb_clk_src.clkr, + [MDSS_DISP_CC_MDSS_BYTE0_CLK] = &mdss_0_disp_cc_mdss_byte0_clk.clkr, + [MDSS_DISP_CC_MDSS_BYTE0_CLK_SRC] = &mdss_0_disp_cc_mdss_byte0_clk_src.clkr, + [MDSS_DISP_CC_MDSS_BYTE0_DIV_CLK_SRC] = &mdss_0_disp_cc_mdss_byte0_div_clk_src.clkr, + [MDSS_DISP_CC_MDSS_BYTE0_INTF_CLK] = &mdss_0_disp_cc_mdss_byte0_intf_clk.clkr, + [MDSS_DISP_CC_MDSS_BYTE1_CLK] = &mdss_0_disp_cc_mdss_byte1_clk.clkr, + [MDSS_DISP_CC_MDSS_BYTE1_CLK_SRC] = &mdss_0_disp_cc_mdss_byte1_clk_src.clkr, + [MDSS_DISP_CC_MDSS_BYTE1_DIV_CLK_SRC] = &mdss_0_disp_cc_mdss_byte1_div_clk_src.clkr, + [MDSS_DISP_CC_MDSS_BYTE1_INTF_CLK] = &mdss_0_disp_cc_mdss_byte1_intf_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_AUX_CLK] = &mdss_0_disp_cc_mdss_dptx0_aux_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_AUX_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx0_aux_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_CRYPTO_CLK] = &mdss_0_disp_cc_mdss_dptx0_crypto_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_LINK_CLK] = &mdss_0_disp_cc_mdss_dptx0_link_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_LINK_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx0_link_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_LINK_DIV_CLK_SRC] = + &mdss_0_disp_cc_mdss_dptx0_link_div_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_LINK_INTF_CLK] = &mdss_0_disp_cc_mdss_dptx0_link_intf_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_PIXEL0_CLK] = &mdss_0_disp_cc_mdss_dptx0_pixel0_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_PIXEL0_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx0_pixel0_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_PIXEL1_CLK] = &mdss_0_disp_cc_mdss_dptx0_pixel1_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_PIXEL1_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx0_pixel1_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_PIXEL2_CLK] = &mdss_0_disp_cc_mdss_dptx0_pixel2_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_PIXEL2_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx0_pixel2_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_PIXEL3_CLK] = &mdss_0_disp_cc_mdss_dptx0_pixel3_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_PIXEL3_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx0_pixel3_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_USB_ROUTER_LINK_INTF_CLK] = + &mdss_0_disp_cc_mdss_dptx0_usb_router_link_intf_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_AUX_CLK] = &mdss_0_disp_cc_mdss_dptx1_aux_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_AUX_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx1_aux_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_CRYPTO_CLK] = &mdss_0_disp_cc_mdss_dptx1_crypto_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_LINK_CLK] = &mdss_0_disp_cc_mdss_dptx1_link_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_LINK_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx1_link_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_LINK_DIV_CLK_SRC] = + &mdss_0_disp_cc_mdss_dptx1_link_div_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_LINK_INTF_CLK] = &mdss_0_disp_cc_mdss_dptx1_link_intf_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_PIXEL0_CLK] = &mdss_0_disp_cc_mdss_dptx1_pixel0_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_PIXEL0_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx1_pixel0_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_PIXEL1_CLK] = &mdss_0_disp_cc_mdss_dptx1_pixel1_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_PIXEL1_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx1_pixel1_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_PIXEL2_CLK] = &mdss_0_disp_cc_mdss_dptx1_pixel2_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_PIXEL2_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx1_pixel2_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_PIXEL3_CLK] = &mdss_0_disp_cc_mdss_dptx1_pixel3_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_PIXEL3_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx1_pixel3_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_USB_ROUTER_LINK_INTF_CLK] = + &mdss_0_disp_cc_mdss_dptx1_usb_router_link_intf_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_AUX_CLK] = &mdss_0_disp_cc_mdss_dptx2_aux_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_AUX_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx2_aux_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_CRYPTO_CLK] = &mdss_0_disp_cc_mdss_dptx2_crypto_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_LINK_CLK] = &mdss_0_disp_cc_mdss_dptx2_link_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_LINK_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx2_link_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_LINK_DIV_CLK_SRC] = + &mdss_0_disp_cc_mdss_dptx2_link_div_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_LINK_INTF_CLK] = &mdss_0_disp_cc_mdss_dptx2_link_intf_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_PIXEL0_CLK] = &mdss_0_disp_cc_mdss_dptx2_pixel0_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_PIXEL0_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx2_pixel0_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_PIXEL1_CLK] = &mdss_0_disp_cc_mdss_dptx2_pixel1_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_PIXEL1_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx2_pixel1_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_AUX_CLK] = &mdss_0_disp_cc_mdss_dptx3_aux_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_AUX_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx3_aux_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_CRYPTO_CLK] = &mdss_0_disp_cc_mdss_dptx3_crypto_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_LINK_CLK] = &mdss_0_disp_cc_mdss_dptx3_link_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_LINK_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx3_link_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_LINK_DIV_CLK_SRC] = + &mdss_0_disp_cc_mdss_dptx3_link_div_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_LINK_INTF_CLK] = &mdss_0_disp_cc_mdss_dptx3_link_intf_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_PIXEL0_CLK] = &mdss_0_disp_cc_mdss_dptx3_pixel0_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_PIXEL0_CLK_SRC] = &mdss_0_disp_cc_mdss_dptx3_pixel0_clk_src.clkr, + [MDSS_DISP_CC_MDSS_ESC0_CLK] = &mdss_0_disp_cc_mdss_esc0_clk.clkr, + [MDSS_DISP_CC_MDSS_ESC0_CLK_SRC] = &mdss_0_disp_cc_mdss_esc0_clk_src.clkr, + [MDSS_DISP_CC_MDSS_ESC1_CLK] = &mdss_0_disp_cc_mdss_esc1_clk.clkr, + [MDSS_DISP_CC_MDSS_ESC1_CLK_SRC] = &mdss_0_disp_cc_mdss_esc1_clk_src.clkr, + [MDSS_DISP_CC_MDSS_MDP1_CLK] = &mdss_0_disp_cc_mdss_mdp1_clk.clkr, + [MDSS_DISP_CC_MDSS_MDP_CLK] = &mdss_0_disp_cc_mdss_mdp_clk.clkr, + [MDSS_DISP_CC_MDSS_MDP_CLK_SRC] = &mdss_0_disp_cc_mdss_mdp_clk_src.clkr, + [MDSS_DISP_CC_MDSS_MDP_LUT1_CLK] = &mdss_0_disp_cc_mdss_mdp_lut1_clk.clkr, + [MDSS_DISP_CC_MDSS_MDP_LUT_CLK] = &mdss_0_disp_cc_mdss_mdp_lut_clk.clkr, + [MDSS_DISP_CC_MDSS_NON_GDSC_AHB_CLK] = &mdss_0_disp_cc_mdss_non_gdsc_ahb_clk.clkr, + [MDSS_DISP_CC_MDSS_PCLK0_CLK] = &mdss_0_disp_cc_mdss_pclk0_clk.clkr, + [MDSS_DISP_CC_MDSS_PCLK0_CLK_SRC] = &mdss_0_disp_cc_mdss_pclk0_clk_src.clkr, + [MDSS_DISP_CC_MDSS_PCLK1_CLK] = &mdss_0_disp_cc_mdss_pclk1_clk.clkr, + [MDSS_DISP_CC_MDSS_PCLK1_CLK_SRC] = &mdss_0_disp_cc_mdss_pclk1_clk_src.clkr, + [MDSS_DISP_CC_MDSS_PCLK2_CLK] = &mdss_0_disp_cc_mdss_pclk2_clk.clkr, + [MDSS_DISP_CC_MDSS_PCLK2_CLK_SRC] = &mdss_0_disp_cc_mdss_pclk2_clk_src.clkr, + [MDSS_DISP_CC_MDSS_VSYNC1_CLK] = &mdss_0_disp_cc_mdss_vsync1_clk.clkr, + [MDSS_DISP_CC_MDSS_VSYNC_CLK] = &mdss_0_disp_cc_mdss_vsync_clk.clkr, + [MDSS_DISP_CC_MDSS_VSYNC_CLK_SRC] = &mdss_0_disp_cc_mdss_vsync_clk_src.clkr, + [MDSS_DISP_CC_PLL0] = &mdss_0_disp_cc_pll0.clkr, + [MDSS_DISP_CC_PLL1] = &mdss_0_disp_cc_pll1.clkr, + [MDSS_DISP_CC_PLL2] = &mdss_0_disp_cc_pll2.clkr, + [MDSS_DISP_CC_PLL3] = &mdss_0_disp_cc_pll3.clkr, + [MDSS_DISP_CC_SLEEP_CLK_SRC] = &mdss_0_disp_cc_sleep_clk_src.clkr, + [MDSS_DISP_CC_XO_CLK_SRC] = &mdss_0_disp_cc_xo_clk_src.clkr, +}; + +static struct gdsc *disp_cc_0_nord_gdscs[] = { + [MDSS_DISP_CC_MDSS_CORE_GDSC] = &mdss_0_disp_cc_mdss_core_gdsc, + [MDSS_DISP_CC_MDSS_CORE_INT2_GDSC] = &mdss_0_disp_cc_mdss_core_int2_gdsc, +}; + +static const struct qcom_reset_map disp_cc_0_nord_resets[] = { + [MDSS_DISP_CC_MDSS_CORE_BCR] = { 0x8000 }, + [MDSS_DISP_CC_MDSS_CORE_INT2_BCR] = { 0xa000 }, + [MDSS_DISP_CC_MDSS_RSCC_BCR] = { 0xc000 }, +}; + +static struct clk_alpha_pll *disp_cc_0_nord_plls[] = { + &mdss_0_disp_cc_pll0, + &mdss_0_disp_cc_pll1, + &mdss_0_disp_cc_pll2, + &mdss_0_disp_cc_pll3, +}; + +static u32 disp_cc_0_nord_critical_cbcrs[] = { + 0xc00c, /* MDSS_DISP_CC_AHB_CLK */ + 0xc008, /* MDSS_DISP_CC_VSYNC_CLK */ + 0xe07c, /* MDSS_DISP_CC_SLEEP_CLK */ + 0xe05c, /* MDSS_DISP_CC_XO_CLK */ +}; + +static const struct regmap_config disp_cc_0_nord_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x1a00c, + .fast_io = true, +}; + +static struct qcom_cc_driver_data disp_cc_0_nord_driver_data = { + .alpha_plls = disp_cc_0_nord_plls, + .num_alpha_plls = ARRAY_SIZE(disp_cc_0_nord_plls), + .clk_cbcrs = disp_cc_0_nord_critical_cbcrs, + .num_clk_cbcrs = ARRAY_SIZE(disp_cc_0_nord_critical_cbcrs), +}; + +static const struct qcom_cc_desc disp_cc_0_nord_desc = { + .config = &disp_cc_0_nord_regmap_config, + .clks = disp_cc_0_nord_clocks, + .num_clks = ARRAY_SIZE(disp_cc_0_nord_clocks), + .resets = disp_cc_0_nord_resets, + .num_resets = ARRAY_SIZE(disp_cc_0_nord_resets), + .gdscs = disp_cc_0_nord_gdscs, + .num_gdscs = ARRAY_SIZE(disp_cc_0_nord_gdscs), + .use_rpm = true, + .driver_data = &disp_cc_0_nord_driver_data, +}; + +static const struct of_device_id disp_cc_0_nord_match_table[] = { + { .compatible = "qcom,nord-dispcc0" }, + { } +}; +MODULE_DEVICE_TABLE(of, disp_cc_0_nord_match_table); + +static int disp_cc_0_nord_probe(struct platform_device *pdev) +{ + return qcom_cc_probe(pdev, &disp_cc_0_nord_desc); +} + +static struct platform_driver disp_cc_0_nord_driver = { + .probe = disp_cc_0_nord_probe, + .driver = { + .name = "dispcc0-nord", + .of_match_table = disp_cc_0_nord_match_table, + }, +}; + +module_platform_driver(disp_cc_0_nord_driver); + +MODULE_DESCRIPTION("QTI DISPCC0 NORD Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/clk/qcom/dispcc1-nord.c b/drivers/clk/qcom/dispcc1-nord.c new file mode 100644 index 000000000000..29b4497cd336 --- /dev/null +++ b/drivers/clk/qcom/dispcc1-nord.c @@ -0,0 +1,2006 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "clk-alpha-pll.h" +#include "clk-branch.h" +#include "clk-pll.h" +#include "clk-rcg.h" +#include "clk-regmap.h" +#include "clk-regmap-divider.h" +#include "clk-regmap-mux.h" +#include "common.h" +#include "gdsc.h" +#include "reset.h" + +enum { + DT_BI_TCXO, + DT_BI_TCXO_AO, + DT_AHB_CLK, + DT_SLEEP_CLK, + + DT_DSI0_PHY_PLL_OUT_BYTECLK, + DT_DSI0_PHY_PLL_OUT_DSICLK, + DT_DSI1_PHY_PLL_OUT_BYTECLK, + DT_DSI1_PHY_PLL_OUT_DSICLK, + + DT_DP0_PHY_PLL_LINK_CLK, + DT_DP0_PHY_PLL_VCO_DIV_CLK, + DT_DP1_PHY_PLL_LINK_CLK, + DT_DP1_PHY_PLL_VCO_DIV_CLK, + DT_DP2_PHY_PLL_LINK_CLK, + DT_DP2_PHY_PLL_VCO_DIV_CLK, + DT_DP3_PHY_PLL_LINK_CLK, + DT_DP3_PHY_PLL_VCO_DIV_CLK, +}; + +enum { + P_BI_TCXO, + P_MDSS_1_DISP_CC_PLL0_OUT_MAIN, + P_MDSS_1_DISP_CC_PLL1_OUT_EVEN, + P_MDSS_1_DISP_CC_PLL1_OUT_MAIN, + P_MDSS_1_DISP_CC_PLL2_OUT_MAIN, + P_MDSS_1_DISP_CC_PLL3_OUT_MAIN, + P_DP0_PHY_PLL_LINK_CLK, + P_DP0_PHY_PLL_VCO_DIV_CLK, + P_DP1_PHY_PLL_LINK_CLK, + P_DP1_PHY_PLL_VCO_DIV_CLK, + P_DP2_PHY_PLL_LINK_CLK, + P_DP2_PHY_PLL_VCO_DIV_CLK, + P_DP3_PHY_PLL_LINK_CLK, + P_DP3_PHY_PLL_VCO_DIV_CLK, + P_DSI0_PHY_PLL_OUT_BYTECLK, + P_DSI0_PHY_PLL_OUT_DSICLK, + P_DSI1_PHY_PLL_OUT_BYTECLK, + P_DSI1_PHY_PLL_OUT_DSICLK, + P_SLEEP_CLK, +}; + +static const struct pll_vco lucid_ole_vco[] = { + { 249600000, 2300000000, 0 }, +}; + +static const struct pll_vco zonda_ole_vco[] = { + { 700000000, 3600000000, 0 }, +}; + +/* 900.0 MHz Configuration */ +static const struct alpha_pll_config mdss_1_disp_cc_pll0_config = { + .l = 0x2e, + .alpha = 0xe000, + .config_ctl_val = 0x20485699, + .config_ctl_hi_val = 0x00182261, + .config_ctl_hi1_val = 0x82aa299c, + .test_ctl_val = 0x00000000, + .test_ctl_hi_val = 0x00000003, + .test_ctl_hi1_val = 0x00009000, + .test_ctl_hi2_val = 0x00000034, + .user_ctl_val = 0x00000000, + .user_ctl_hi_val = 0x00400005, +}; + +static struct clk_alpha_pll mdss_1_disp_cc_pll0 = { + .offset = 0x0, + .config = &mdss_1_disp_cc_pll0_config, + .vco_table = lucid_ole_vco, + .num_vco = ARRAY_SIZE(lucid_ole_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_pll0", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_lucid_evo_ops, + }, + }, +}; + +/* 600.0 MHz Configuration */ +static const struct alpha_pll_config mdss_1_disp_cc_pll1_config = { + .l = 0x1f, + .alpha = 0x4000, + .config_ctl_val = 0x20485699, + .config_ctl_hi_val = 0x00182261, + .config_ctl_hi1_val = 0x82aa299c, + .test_ctl_val = 0x00000000, + .test_ctl_hi_val = 0x00000003, + .test_ctl_hi1_val = 0x00009000, + .test_ctl_hi2_val = 0x00000034, + .user_ctl_val = 0x00000000, + .user_ctl_hi_val = 0x00400005, +}; + +static struct clk_alpha_pll mdss_1_disp_cc_pll1 = { + .offset = 0x1000, + .config = &mdss_1_disp_cc_pll1_config, + .vco_table = lucid_ole_vco, + .num_vco = ARRAY_SIZE(lucid_ole_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_pll1", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_lucid_evo_ops, + }, + }, +}; + +/* 1363.2 MHz Configuration */ +static const struct alpha_pll_config mdss_1_disp_cc_pll2_config = { + .l = 0x47, + .alpha = 0x0, + .config_ctl_val = 0x08240800, + .config_ctl_hi_val = 0x05008001, + .config_ctl_hi1_val = 0x00000000, + .config_ctl_hi2_val = 0x00000000, + .user_ctl_val = 0x00000000, + .user_ctl_hi_val = 0x00000080, +}; + +static struct clk_alpha_pll mdss_1_disp_cc_pll2 = { + .offset = 0x2000, + .config = &mdss_1_disp_cc_pll2_config, + .vco_table = zonda_ole_vco, + .num_vco = ARRAY_SIZE(zonda_ole_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_ZONDA_OLE], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_pll2", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_zonda_ole_ops, + }, + }, +}; + +/* 1363.2 MHz Configuration */ +static const struct alpha_pll_config mdss_1_disp_cc_pll3_config = { + .l = 0x47, + .alpha = 0x0, + .config_ctl_val = 0x08240800, + .config_ctl_hi_val = 0x05008001, + .config_ctl_hi1_val = 0x00000000, + .config_ctl_hi2_val = 0x00000000, + .user_ctl_val = 0x00000000, + .user_ctl_hi_val = 0x00000080, +}; + +static struct clk_alpha_pll mdss_1_disp_cc_pll3 = { + .offset = 0x3000, + .config = &mdss_1_disp_cc_pll3_config, + .vco_table = zonda_ole_vco, + .num_vco = ARRAY_SIZE(zonda_ole_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_ZONDA_OLE], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_pll3", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_zonda_ole_ops, + }, + }, +}; + +static const struct parent_map disp_cc_1_parent_map_0[] = { + { P_BI_TCXO, 0 }, + { P_MDSS_1_DISP_CC_PLL2_OUT_MAIN, 1 }, + { P_DP0_PHY_PLL_VCO_DIV_CLK, 2 }, + { P_DP3_PHY_PLL_VCO_DIV_CLK, 3 }, + { P_DP1_PHY_PLL_VCO_DIV_CLK, 4 }, + { P_MDSS_1_DISP_CC_PLL3_OUT_MAIN, 5 }, + { P_DP2_PHY_PLL_VCO_DIV_CLK, 6 }, +}; + +static const struct clk_parent_data disp_cc_1_parent_data_0[] = { + { .index = DT_BI_TCXO }, + { .hw = &mdss_1_disp_cc_pll2.clkr.hw }, + { .index = DT_DP0_PHY_PLL_VCO_DIV_CLK }, + { .index = DT_DP3_PHY_PLL_VCO_DIV_CLK }, + { .index = DT_DP1_PHY_PLL_VCO_DIV_CLK }, + { .hw = &mdss_1_disp_cc_pll3.clkr.hw }, + { .index = DT_DP2_PHY_PLL_VCO_DIV_CLK }, +}; + +static const struct parent_map disp_cc_1_parent_map_1[] = { + { P_BI_TCXO, 0 }, +}; + +static const struct clk_parent_data disp_cc_1_parent_data_1[] = { + { .index = DT_BI_TCXO }, +}; + +static const struct parent_map disp_cc_1_parent_map_2[] = { + { P_BI_TCXO, 0 }, + { P_DSI0_PHY_PLL_OUT_DSICLK, 1 }, + { P_DSI0_PHY_PLL_OUT_BYTECLK, 2 }, + { P_DSI1_PHY_PLL_OUT_DSICLK, 3 }, + { P_DSI1_PHY_PLL_OUT_BYTECLK, 4 }, +}; + +static const struct clk_parent_data disp_cc_1_parent_data_2[] = { + { .index = DT_BI_TCXO }, + { .index = DT_DSI0_PHY_PLL_OUT_DSICLK }, + { .index = DT_DSI0_PHY_PLL_OUT_BYTECLK }, + { .index = DT_DSI1_PHY_PLL_OUT_DSICLK }, + { .index = DT_DSI1_PHY_PLL_OUT_BYTECLK }, +}; + +static const struct parent_map disp_cc_1_parent_map_3[] = { + { P_BI_TCXO, 0 }, + { P_MDSS_1_DISP_CC_PLL2_OUT_MAIN, 1 }, + { P_DP3_PHY_PLL_VCO_DIV_CLK, 3 }, + { P_MDSS_1_DISP_CC_PLL3_OUT_MAIN, 5 }, + { P_DP2_PHY_PLL_VCO_DIV_CLK, 6 }, +}; + +static const struct clk_parent_data disp_cc_1_parent_data_3[] = { + { .index = DT_BI_TCXO }, + { .hw = &mdss_1_disp_cc_pll2.clkr.hw }, + { .index = DT_DP3_PHY_PLL_VCO_DIV_CLK }, + { .hw = &mdss_1_disp_cc_pll3.clkr.hw }, + { .index = DT_DP2_PHY_PLL_VCO_DIV_CLK }, +}; + +static const struct parent_map disp_cc_1_parent_map_4[] = { + { P_BI_TCXO, 0 }, + { P_DP0_PHY_PLL_LINK_CLK, 1 }, + { P_DP1_PHY_PLL_LINK_CLK, 2 }, + { P_DP2_PHY_PLL_LINK_CLK, 3 }, + { P_DP3_PHY_PLL_LINK_CLK, 4 }, +}; + +static const struct clk_parent_data disp_cc_1_parent_data_4[] = { + { .index = DT_BI_TCXO }, + { .index = DT_DP0_PHY_PLL_LINK_CLK }, + { .index = DT_DP1_PHY_PLL_LINK_CLK }, + { .index = DT_DP2_PHY_PLL_LINK_CLK }, + { .index = DT_DP3_PHY_PLL_LINK_CLK }, +}; + +static const struct parent_map disp_cc_1_parent_map_5[] = { + { P_BI_TCXO, 0 }, + { P_DP2_PHY_PLL_LINK_CLK, 3 }, + { P_DP3_PHY_PLL_LINK_CLK, 4 }, +}; + +static const struct clk_parent_data disp_cc_1_parent_data_5[] = { + { .index = DT_BI_TCXO }, + { .index = DT_DP2_PHY_PLL_LINK_CLK }, + { .index = DT_DP3_PHY_PLL_LINK_CLK }, +}; + +static const struct parent_map disp_cc_1_parent_map_6[] = { + { P_BI_TCXO, 0 }, + { P_DSI0_PHY_PLL_OUT_BYTECLK, 2 }, + { P_DSI1_PHY_PLL_OUT_BYTECLK, 4 }, +}; + +static const struct clk_parent_data disp_cc_1_parent_data_6[] = { + { .index = DT_BI_TCXO }, + { .index = DT_DSI0_PHY_PLL_OUT_BYTECLK }, + { .index = DT_DSI1_PHY_PLL_OUT_BYTECLK }, +}; + +static const struct parent_map disp_cc_1_parent_map_7[] = { + { P_BI_TCXO, 0 }, + { P_MDSS_1_DISP_CC_PLL1_OUT_MAIN, 4 }, + { P_MDSS_1_DISP_CC_PLL1_OUT_EVEN, 6 }, +}; + +static const struct clk_parent_data disp_cc_1_parent_data_7[] = { + { .index = DT_BI_TCXO }, + { .hw = &mdss_1_disp_cc_pll1.clkr.hw }, + { .hw = &mdss_1_disp_cc_pll1.clkr.hw }, +}; + +static const struct parent_map disp_cc_1_parent_map_8[] = { + { P_BI_TCXO, 0 }, + { P_MDSS_1_DISP_CC_PLL0_OUT_MAIN, 1 }, + { P_MDSS_1_DISP_CC_PLL1_OUT_MAIN, 4 }, + { P_MDSS_1_DISP_CC_PLL1_OUT_EVEN, 6 }, +}; + +static const struct clk_parent_data disp_cc_1_parent_data_8[] = { + { .index = DT_BI_TCXO }, + { .hw = &mdss_1_disp_cc_pll0.clkr.hw }, + { .hw = &mdss_1_disp_cc_pll1.clkr.hw }, + { .hw = &mdss_1_disp_cc_pll1.clkr.hw }, +}; + +static const struct parent_map disp_cc_1_parent_map_9[] = { + { P_SLEEP_CLK, 0 }, +}; + +static const struct clk_parent_data disp_cc_1_parent_data_9[] = { + { .index = DT_SLEEP_CLK }, +}; + +static const struct freq_tbl ftbl_mdss_1_disp_cc_mdss_ahb_clk_src[] = { + F(37500000, P_MDSS_1_DISP_CC_PLL1_OUT_MAIN, 16, 0, 0), + F(75000000, P_MDSS_1_DISP_CC_PLL1_OUT_MAIN, 8, 0, 0), + { } +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_ahb_clk_src = { + .cmd_rcgr = 0x837c, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_7, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_ahb_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_ahb_clk_src", + .parent_data = disp_cc_1_parent_data_7, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_7), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_mdss_1_disp_cc_mdss_byte0_clk_src[] = { + F(19200000, P_BI_TCXO, 1, 0, 0), + { } +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_byte0_clk_src = { + .cmd_rcgr = 0x813c, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_2, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_byte0_clk_src", + .parent_data = disp_cc_1_parent_data_2, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_2), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_byte2_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_byte1_clk_src = { + .cmd_rcgr = 0x8158, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_2, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_byte1_clk_src", + .parent_data = disp_cc_1_parent_data_2, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_2), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_byte2_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx0_aux_clk_src = { + .cmd_rcgr = 0x8220, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_1, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx0_aux_clk_src", + .parent_data = disp_cc_1_parent_data_1, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx0_link_clk_src = { + .cmd_rcgr = 0x81a4, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_4, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx0_link_clk_src", + .parent_data = disp_cc_1_parent_data_4, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_4), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_byte2_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx0_pixel0_clk_src = { + .cmd_rcgr = 0x81c0, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_0, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx0_pixel0_clk_src", + .parent_data = disp_cc_1_parent_data_0, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx0_pixel1_clk_src = { + .cmd_rcgr = 0x81d8, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_0, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx0_pixel1_clk_src", + .parent_data = disp_cc_1_parent_data_0, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx0_pixel2_clk_src = { + .cmd_rcgr = 0x81f0, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_0, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx0_pixel2_clk_src", + .parent_data = disp_cc_1_parent_data_0, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx0_pixel3_clk_src = { + .cmd_rcgr = 0x8208, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_0, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx0_pixel3_clk_src", + .parent_data = disp_cc_1_parent_data_0, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx1_aux_clk_src = { + .cmd_rcgr = 0x82b4, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_1, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx1_aux_clk_src", + .parent_data = disp_cc_1_parent_data_1, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx1_link_clk_src = { + .cmd_rcgr = 0x8298, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_4, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx1_link_clk_src", + .parent_data = disp_cc_1_parent_data_4, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_4), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_byte2_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx1_pixel0_clk_src = { + .cmd_rcgr = 0x8238, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_0, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx1_pixel0_clk_src", + .parent_data = disp_cc_1_parent_data_0, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx1_pixel1_clk_src = { + .cmd_rcgr = 0x8250, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_0, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx1_pixel1_clk_src", + .parent_data = disp_cc_1_parent_data_0, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx1_pixel2_clk_src = { + .cmd_rcgr = 0x8268, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_0, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx1_pixel2_clk_src", + .parent_data = disp_cc_1_parent_data_0, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx1_pixel3_clk_src = { + .cmd_rcgr = 0x8280, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_0, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx1_pixel3_clk_src", + .parent_data = disp_cc_1_parent_data_0, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx2_aux_clk_src = { + .cmd_rcgr = 0x8318, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_1, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx2_aux_clk_src", + .parent_data = disp_cc_1_parent_data_1, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx2_link_clk_src = { + .cmd_rcgr = 0x82cc, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_5, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx2_link_clk_src", + .parent_data = disp_cc_1_parent_data_5, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_5), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_byte2_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx2_pixel0_clk_src = { + .cmd_rcgr = 0x82e8, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_3, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx2_pixel0_clk_src", + .parent_data = disp_cc_1_parent_data_3, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_3), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx2_pixel1_clk_src = { + .cmd_rcgr = 0x8300, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_3, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx2_pixel1_clk_src", + .parent_data = disp_cc_1_parent_data_3, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_3), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx3_aux_clk_src = { + .cmd_rcgr = 0x8364, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_1, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx3_aux_clk_src", + .parent_data = disp_cc_1_parent_data_1, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx3_link_clk_src = { + .cmd_rcgr = 0x8348, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_5, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx3_link_clk_src", + .parent_data = disp_cc_1_parent_data_5, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_5), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_byte2_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_dptx3_pixel0_clk_src = { + .cmd_rcgr = 0x8330, + .mnd_width = 16, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_3, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx3_pixel0_clk_src", + .parent_data = disp_cc_1_parent_data_3, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_3), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_dp_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_esc0_clk_src = { + .cmd_rcgr = 0x8174, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_6, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_esc0_clk_src", + .parent_data = disp_cc_1_parent_data_6, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_6), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_esc1_clk_src = { + .cmd_rcgr = 0x818c, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_6, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_esc1_clk_src", + .parent_data = disp_cc_1_parent_data_6, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_6), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_mdss_1_disp_cc_mdss_mdp_clk_src[] = { + F(300000000, P_MDSS_1_DISP_CC_PLL0_OUT_MAIN, 3, 0, 0), + F(417000000, P_MDSS_1_DISP_CC_PLL0_OUT_MAIN, 3, 0, 0), + F(532000000, P_MDSS_1_DISP_CC_PLL0_OUT_MAIN, 3, 0, 0), + F(650000000, P_MDSS_1_DISP_CC_PLL0_OUT_MAIN, 3, 0, 0), + F(710000000, P_MDSS_1_DISP_CC_PLL0_OUT_MAIN, 3, 0, 0), + { } +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_mdp_clk_src = { + .cmd_rcgr = 0x810c, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_8, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_mdp_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_mdp_clk_src", + .parent_data = disp_cc_1_parent_data_8, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_8), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_pclk0_clk_src = { + .cmd_rcgr = 0x80c4, + .mnd_width = 8, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_2, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_pclk0_clk_src", + .parent_data = disp_cc_1_parent_data_2, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_2), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_pixel_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_pclk1_clk_src = { + .cmd_rcgr = 0x80dc, + .mnd_width = 8, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_2, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_pclk1_clk_src", + .parent_data = disp_cc_1_parent_data_2, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_2), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_pixel_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_pclk2_clk_src = { + .cmd_rcgr = 0x80f4, + .mnd_width = 8, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_2, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_pclk2_clk_src", + .parent_data = disp_cc_1_parent_data_2, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_2), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_pixel_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_mdss_vsync_clk_src = { + .cmd_rcgr = 0x8124, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_1, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_vsync_clk_src", + .parent_data = disp_cc_1_parent_data_1, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_mdss_1_disp_cc_sleep_clk_src[] = { + F(32000, P_SLEEP_CLK, 1, 0, 0), + { } +}; + +static struct clk_rcg2 mdss_1_disp_cc_sleep_clk_src = { + .cmd_rcgr = 0xe064, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_9, + .freq_tbl = ftbl_mdss_1_disp_cc_sleep_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_sleep_clk_src", + .parent_data = disp_cc_1_parent_data_9, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_9), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_ops, + }, +}; + +static struct clk_rcg2 mdss_1_disp_cc_xo_clk_src = { + .cmd_rcgr = 0xe044, + .mnd_width = 0, + .hid_width = 5, + .parent_map = disp_cc_1_parent_map_1, + .freq_tbl = ftbl_mdss_1_disp_cc_mdss_byte0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_xo_clk_src", + .parent_data = disp_cc_1_parent_data_1, + .num_parents = ARRAY_SIZE(disp_cc_1_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_ops, + }, +}; + +static struct clk_regmap_div mdss_1_disp_cc_mdss_byte0_div_clk_src = { + .reg = 0x8154, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_byte0_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_byte0_clk_src.clkr.hw, + }, + .num_parents = 1, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_regmap_div mdss_1_disp_cc_mdss_byte1_div_clk_src = { + .reg = 0x8170, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_byte1_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_byte1_clk_src.clkr.hw, + }, + .num_parents = 1, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_regmap_div mdss_1_disp_cc_mdss_dptx0_link_div_clk_src = { + .reg = 0x81bc, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx0_link_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx0_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_regmap_div mdss_1_disp_cc_mdss_dptx1_link_div_clk_src = { + .reg = 0x82b0, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx1_link_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx1_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_regmap_div mdss_1_disp_cc_mdss_dptx2_link_div_clk_src = { + .reg = 0x82e4, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx2_link_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx2_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_regmap_div mdss_1_disp_cc_mdss_dptx3_link_div_clk_src = { + .reg = 0x8360, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx3_link_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx3_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_accu_shift_clk = { + .halt_reg = 0xe060, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0xe060, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_accu_shift_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_xo_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_ahb1_clk = { + .halt_reg = 0xa028, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0xa028, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_ahb1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_ahb_clk = { + .halt_reg = 0x80c0, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x80c0, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_byte0_clk = { + .halt_reg = 0x8034, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8034, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_byte0_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_byte0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_byte0_intf_clk = { + .halt_reg = 0x8038, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8038, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_byte0_intf_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_byte0_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_byte1_clk = { + .halt_reg = 0x803c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x803c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_byte1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_byte1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_byte1_intf_clk = { + .halt_reg = 0x8040, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8040, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_byte1_intf_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_byte1_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx0_aux_clk = { + .halt_reg = 0x806c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x806c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx0_aux_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx0_aux_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx0_crypto_clk = { + .halt_reg = 0x8058, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x8058, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx0_crypto_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx0_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx0_link_clk = { + .halt_reg = 0x804c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x804c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx0_link_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx0_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx0_link_intf_clk = { + .halt_reg = 0x8054, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8054, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx0_link_intf_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx0_link_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx0_pixel0_clk = { + .halt_reg = 0x805c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x805c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx0_pixel0_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx0_pixel0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx0_pixel1_clk = { + .halt_reg = 0x8060, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8060, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx0_pixel1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx0_pixel1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx0_pixel2_clk = { + .halt_reg = 0x8064, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8064, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx0_pixel2_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx0_pixel2_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx0_pixel3_clk = { + .halt_reg = 0x8068, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8068, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx0_pixel3_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx0_pixel3_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx0_usb_router_link_intf_clk = { + .halt_reg = 0x8050, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8050, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx0_usb_router_link_intf_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx0_link_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx1_aux_clk = { + .halt_reg = 0x8090, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8090, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx1_aux_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx1_aux_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx1_crypto_clk = { + .halt_reg = 0x808c, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x808c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx1_crypto_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx1_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx1_link_clk = { + .halt_reg = 0x8080, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8080, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx1_link_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx1_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx1_link_intf_clk = { + .halt_reg = 0x8088, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8088, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx1_link_intf_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx1_link_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx1_pixel0_clk = { + .halt_reg = 0x8070, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8070, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx1_pixel0_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx1_pixel0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx1_pixel1_clk = { + .halt_reg = 0x8074, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8074, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx1_pixel1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx1_pixel1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx1_pixel2_clk = { + .halt_reg = 0x8078, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8078, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx1_pixel2_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx1_pixel2_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx1_pixel3_clk = { + .halt_reg = 0x807c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x807c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx1_pixel3_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx1_pixel3_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx1_usb_router_link_intf_clk = { + .halt_reg = 0x8084, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8084, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx1_usb_router_link_intf_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx1_link_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx2_aux_clk = { + .halt_reg = 0x80a8, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x80a8, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx2_aux_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx2_aux_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx2_crypto_clk = { + .halt_reg = 0x80a4, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x80a4, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx2_crypto_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx2_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx2_link_clk = { + .halt_reg = 0x809c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x809c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx2_link_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx2_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx2_link_intf_clk = { + .halt_reg = 0x80a0, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x80a0, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx2_link_intf_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx2_link_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx2_pixel0_clk = { + .halt_reg = 0x8094, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8094, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx2_pixel0_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx2_pixel0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx2_pixel1_clk = { + .halt_reg = 0x8098, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8098, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx2_pixel1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx2_pixel1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx3_aux_clk = { + .halt_reg = 0x80b8, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x80b8, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx3_aux_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx3_aux_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx3_crypto_clk = { + .halt_reg = 0x80bc, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x80bc, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx3_crypto_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx3_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx3_link_clk = { + .halt_reg = 0x80b0, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x80b0, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx3_link_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx3_link_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx3_link_intf_clk = { + .halt_reg = 0x80b4, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x80b4, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx3_link_intf_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx3_link_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_dptx3_pixel0_clk = { + .halt_reg = 0x80ac, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x80ac, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_dptx3_pixel0_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_dptx3_pixel0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_esc0_clk = { + .halt_reg = 0x8044, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8044, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_esc0_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_esc0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_esc1_clk = { + .halt_reg = 0x8048, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8048, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_esc1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_esc1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_mdp1_clk = { + .halt_reg = 0xa004, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0xa004, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_mdp1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_mdp_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_mdp_clk = { + .halt_reg = 0x8010, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8010, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_mdp_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_mdp_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_mdp_lut1_clk = { + .halt_reg = 0xa014, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0xa014, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_mdp_lut1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_mdp_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_mdp_lut_clk = { + .halt_reg = 0x8020, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x8020, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_mdp_lut_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_mdp_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_non_gdsc_ahb_clk = { + .halt_reg = 0xc004, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0xc004, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_non_gdsc_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_pclk0_clk = { + .halt_reg = 0x8004, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8004, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_pclk0_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_pclk0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_pclk1_clk = { + .halt_reg = 0x8008, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8008, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_pclk1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_pclk1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_pclk2_clk = { + .halt_reg = 0x800c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x800c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_pclk2_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_pclk2_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_vsync1_clk = { + .halt_reg = 0xa024, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0xa024, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_vsync1_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_vsync_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch mdss_1_disp_cc_mdss_vsync_clk = { + .halt_reg = 0x8030, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x8030, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "mdss_1_disp_cc_mdss_vsync_clk", + .parent_hws = (const struct clk_hw*[]) { + &mdss_1_disp_cc_mdss_vsync_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct gdsc mdss_1_disp_cc_mdss_core_gdsc = { + .gdscr = 0x9000, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "mdss_1_disp_cc_mdss_core_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .flags = HW_CTRL_TRIGGER | POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct gdsc mdss_1_disp_cc_mdss_core_int2_gdsc = { + .gdscr = 0xb000, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "mdss_1_disp_cc_mdss_core_int2_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .flags = HW_CTRL_TRIGGER | POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct clk_regmap *disp_cc_1_nord_clocks[] = { + [MDSS_DISP_CC_MDSS_ACCU_SHIFT_CLK] = &mdss_1_disp_cc_mdss_accu_shift_clk.clkr, + [MDSS_DISP_CC_MDSS_AHB1_CLK] = &mdss_1_disp_cc_mdss_ahb1_clk.clkr, + [MDSS_DISP_CC_MDSS_AHB_CLK] = &mdss_1_disp_cc_mdss_ahb_clk.clkr, + [MDSS_DISP_CC_MDSS_AHB_CLK_SRC] = &mdss_1_disp_cc_mdss_ahb_clk_src.clkr, + [MDSS_DISP_CC_MDSS_BYTE0_CLK] = &mdss_1_disp_cc_mdss_byte0_clk.clkr, + [MDSS_DISP_CC_MDSS_BYTE0_CLK_SRC] = &mdss_1_disp_cc_mdss_byte0_clk_src.clkr, + [MDSS_DISP_CC_MDSS_BYTE0_DIV_CLK_SRC] = &mdss_1_disp_cc_mdss_byte0_div_clk_src.clkr, + [MDSS_DISP_CC_MDSS_BYTE0_INTF_CLK] = &mdss_1_disp_cc_mdss_byte0_intf_clk.clkr, + [MDSS_DISP_CC_MDSS_BYTE1_CLK] = &mdss_1_disp_cc_mdss_byte1_clk.clkr, + [MDSS_DISP_CC_MDSS_BYTE1_CLK_SRC] = &mdss_1_disp_cc_mdss_byte1_clk_src.clkr, + [MDSS_DISP_CC_MDSS_BYTE1_DIV_CLK_SRC] = &mdss_1_disp_cc_mdss_byte1_div_clk_src.clkr, + [MDSS_DISP_CC_MDSS_BYTE1_INTF_CLK] = &mdss_1_disp_cc_mdss_byte1_intf_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_AUX_CLK] = &mdss_1_disp_cc_mdss_dptx0_aux_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_AUX_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx0_aux_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_CRYPTO_CLK] = &mdss_1_disp_cc_mdss_dptx0_crypto_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_LINK_CLK] = &mdss_1_disp_cc_mdss_dptx0_link_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_LINK_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx0_link_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_LINK_DIV_CLK_SRC] = + &mdss_1_disp_cc_mdss_dptx0_link_div_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_LINK_INTF_CLK] = &mdss_1_disp_cc_mdss_dptx0_link_intf_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_PIXEL0_CLK] = &mdss_1_disp_cc_mdss_dptx0_pixel0_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_PIXEL0_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx0_pixel0_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_PIXEL1_CLK] = &mdss_1_disp_cc_mdss_dptx0_pixel1_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_PIXEL1_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx0_pixel1_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_PIXEL2_CLK] = &mdss_1_disp_cc_mdss_dptx0_pixel2_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_PIXEL2_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx0_pixel2_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_PIXEL3_CLK] = &mdss_1_disp_cc_mdss_dptx0_pixel3_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_PIXEL3_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx0_pixel3_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX0_USB_ROUTER_LINK_INTF_CLK] = + &mdss_1_disp_cc_mdss_dptx0_usb_router_link_intf_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_AUX_CLK] = &mdss_1_disp_cc_mdss_dptx1_aux_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_AUX_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx1_aux_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_CRYPTO_CLK] = &mdss_1_disp_cc_mdss_dptx1_crypto_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_LINK_CLK] = &mdss_1_disp_cc_mdss_dptx1_link_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_LINK_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx1_link_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_LINK_DIV_CLK_SRC] = + &mdss_1_disp_cc_mdss_dptx1_link_div_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_LINK_INTF_CLK] = &mdss_1_disp_cc_mdss_dptx1_link_intf_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_PIXEL0_CLK] = &mdss_1_disp_cc_mdss_dptx1_pixel0_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_PIXEL0_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx1_pixel0_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_PIXEL1_CLK] = &mdss_1_disp_cc_mdss_dptx1_pixel1_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_PIXEL1_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx1_pixel1_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_PIXEL2_CLK] = &mdss_1_disp_cc_mdss_dptx1_pixel2_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_PIXEL2_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx1_pixel2_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_PIXEL3_CLK] = &mdss_1_disp_cc_mdss_dptx1_pixel3_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_PIXEL3_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx1_pixel3_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX1_USB_ROUTER_LINK_INTF_CLK] = + &mdss_1_disp_cc_mdss_dptx1_usb_router_link_intf_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_AUX_CLK] = &mdss_1_disp_cc_mdss_dptx2_aux_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_AUX_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx2_aux_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_CRYPTO_CLK] = &mdss_1_disp_cc_mdss_dptx2_crypto_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_LINK_CLK] = &mdss_1_disp_cc_mdss_dptx2_link_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_LINK_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx2_link_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_LINK_DIV_CLK_SRC] = + &mdss_1_disp_cc_mdss_dptx2_link_div_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_LINK_INTF_CLK] = &mdss_1_disp_cc_mdss_dptx2_link_intf_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_PIXEL0_CLK] = &mdss_1_disp_cc_mdss_dptx2_pixel0_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_PIXEL0_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx2_pixel0_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_PIXEL1_CLK] = &mdss_1_disp_cc_mdss_dptx2_pixel1_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX2_PIXEL1_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx2_pixel1_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_AUX_CLK] = &mdss_1_disp_cc_mdss_dptx3_aux_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_AUX_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx3_aux_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_CRYPTO_CLK] = &mdss_1_disp_cc_mdss_dptx3_crypto_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_LINK_CLK] = &mdss_1_disp_cc_mdss_dptx3_link_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_LINK_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx3_link_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_LINK_DIV_CLK_SRC] = + &mdss_1_disp_cc_mdss_dptx3_link_div_clk_src.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_LINK_INTF_CLK] = &mdss_1_disp_cc_mdss_dptx3_link_intf_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_PIXEL0_CLK] = &mdss_1_disp_cc_mdss_dptx3_pixel0_clk.clkr, + [MDSS_DISP_CC_MDSS_DPTX3_PIXEL0_CLK_SRC] = &mdss_1_disp_cc_mdss_dptx3_pixel0_clk_src.clkr, + [MDSS_DISP_CC_MDSS_ESC0_CLK] = &mdss_1_disp_cc_mdss_esc0_clk.clkr, + [MDSS_DISP_CC_MDSS_ESC0_CLK_SRC] = &mdss_1_disp_cc_mdss_esc0_clk_src.clkr, + [MDSS_DISP_CC_MDSS_ESC1_CLK] = &mdss_1_disp_cc_mdss_esc1_clk.clkr, + [MDSS_DISP_CC_MDSS_ESC1_CLK_SRC] = &mdss_1_disp_cc_mdss_esc1_clk_src.clkr, + [MDSS_DISP_CC_MDSS_MDP1_CLK] = &mdss_1_disp_cc_mdss_mdp1_clk.clkr, + [MDSS_DISP_CC_MDSS_MDP_CLK] = &mdss_1_disp_cc_mdss_mdp_clk.clkr, + [MDSS_DISP_CC_MDSS_MDP_CLK_SRC] = &mdss_1_disp_cc_mdss_mdp_clk_src.clkr, + [MDSS_DISP_CC_MDSS_MDP_LUT1_CLK] = &mdss_1_disp_cc_mdss_mdp_lut1_clk.clkr, + [MDSS_DISP_CC_MDSS_MDP_LUT_CLK] = &mdss_1_disp_cc_mdss_mdp_lut_clk.clkr, + [MDSS_DISP_CC_MDSS_NON_GDSC_AHB_CLK] = &mdss_1_disp_cc_mdss_non_gdsc_ahb_clk.clkr, + [MDSS_DISP_CC_MDSS_PCLK0_CLK] = &mdss_1_disp_cc_mdss_pclk0_clk.clkr, + [MDSS_DISP_CC_MDSS_PCLK0_CLK_SRC] = &mdss_1_disp_cc_mdss_pclk0_clk_src.clkr, + [MDSS_DISP_CC_MDSS_PCLK1_CLK] = &mdss_1_disp_cc_mdss_pclk1_clk.clkr, + [MDSS_DISP_CC_MDSS_PCLK1_CLK_SRC] = &mdss_1_disp_cc_mdss_pclk1_clk_src.clkr, + [MDSS_DISP_CC_MDSS_PCLK2_CLK] = &mdss_1_disp_cc_mdss_pclk2_clk.clkr, + [MDSS_DISP_CC_MDSS_PCLK2_CLK_SRC] = &mdss_1_disp_cc_mdss_pclk2_clk_src.clkr, + [MDSS_DISP_CC_MDSS_VSYNC1_CLK] = &mdss_1_disp_cc_mdss_vsync1_clk.clkr, + [MDSS_DISP_CC_MDSS_VSYNC_CLK] = &mdss_1_disp_cc_mdss_vsync_clk.clkr, + [MDSS_DISP_CC_MDSS_VSYNC_CLK_SRC] = &mdss_1_disp_cc_mdss_vsync_clk_src.clkr, + [MDSS_DISP_CC_PLL0] = &mdss_1_disp_cc_pll0.clkr, + [MDSS_DISP_CC_PLL1] = &mdss_1_disp_cc_pll1.clkr, + [MDSS_DISP_CC_PLL2] = &mdss_1_disp_cc_pll2.clkr, + [MDSS_DISP_CC_PLL3] = &mdss_1_disp_cc_pll3.clkr, + [MDSS_DISP_CC_SLEEP_CLK_SRC] = &mdss_1_disp_cc_sleep_clk_src.clkr, + [MDSS_DISP_CC_XO_CLK_SRC] = &mdss_1_disp_cc_xo_clk_src.clkr, +}; + +static struct gdsc *disp_cc_1_nord_gdscs[] = { + [MDSS_DISP_CC_MDSS_CORE_GDSC] = &mdss_1_disp_cc_mdss_core_gdsc, + [MDSS_DISP_CC_MDSS_CORE_INT2_GDSC] = &mdss_1_disp_cc_mdss_core_int2_gdsc, +}; + +static const struct qcom_reset_map disp_cc_1_nord_resets[] = { + [MDSS_DISP_CC_MDSS_CORE_BCR] = { 0x8000 }, + [MDSS_DISP_CC_MDSS_CORE_INT2_BCR] = { 0xa000 }, + [MDSS_DISP_CC_MDSS_RSCC_BCR] = { 0xc000 }, +}; + +static struct clk_alpha_pll *disp_cc_1_nord_plls[] = { + &mdss_1_disp_cc_pll0, + &mdss_1_disp_cc_pll1, + &mdss_1_disp_cc_pll2, + &mdss_1_disp_cc_pll3, +}; + +static u32 disp_cc_1_nord_critical_cbcrs[] = { + 0xc00c, /* MDSS_DISP_CC_RSCC_AHB_CLK */ + 0xc008, /* MDSS_DISP_CC_RSCC_VSYNC CLK */ + 0xe07c, /* MDSS_DISP_CC_SLEEP_CLK */ + 0xe05c, /* MDSS_DISP_CC_XO_CLK */ +}; + +static const struct regmap_config disp_cc_1_nord_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x1a00c, + .fast_io = true, +}; + +static struct qcom_cc_driver_data disp_cc_1_nord_driver_data = { + .alpha_plls = disp_cc_1_nord_plls, + .num_alpha_plls = ARRAY_SIZE(disp_cc_1_nord_plls), + .clk_cbcrs = disp_cc_1_nord_critical_cbcrs, + .num_clk_cbcrs = ARRAY_SIZE(disp_cc_1_nord_critical_cbcrs), +}; + +static const struct qcom_cc_desc disp_cc_1_nord_desc = { + .config = &disp_cc_1_nord_regmap_config, + .clks = disp_cc_1_nord_clocks, + .num_clks = ARRAY_SIZE(disp_cc_1_nord_clocks), + .resets = disp_cc_1_nord_resets, + .num_resets = ARRAY_SIZE(disp_cc_1_nord_resets), + .gdscs = disp_cc_1_nord_gdscs, + .num_gdscs = ARRAY_SIZE(disp_cc_1_nord_gdscs), + .use_rpm = true, + .driver_data = &disp_cc_1_nord_driver_data, +}; + +static const struct of_device_id disp_cc_1_nord_match_table[] = { + { .compatible = "qcom,nord-dispcc1" }, + { } +}; +MODULE_DEVICE_TABLE(of, disp_cc_1_nord_match_table); + +static int disp_cc_1_nord_probe(struct platform_device *pdev) +{ + return qcom_cc_probe(pdev, &disp_cc_1_nord_desc); +} + +static struct platform_driver disp_cc_1_nord_driver = { + .probe = disp_cc_1_nord_probe, + .driver = { + .name = "dispcc1-nord", + .of_match_table = disp_cc_1_nord_match_table, + }, +}; + +module_platform_driver(disp_cc_1_nord_driver); + +MODULE_DESCRIPTION("QTI DISPCC1 NORD Driver"); +MODULE_LICENSE("GPL"); From d4b7981db8be679d8343f9930e580b9b3e7ae21d Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Tue, 23 Jun 2026 16:24:08 +0530 Subject: [PATCH 532/562] dt-bindings: clock: qcom: Document Nord GPU clock controllers Add Device Tree binding documentation for the GPU clock controllers on the Qualcomm Nord platform. The platform includes two GPU clock controller instances, GPUCC and GPUCC2. Document the compatible strings for both controllers. Signed-off-by: Taniya Das Link: https://lore.kernel.org/r/20260623-nords_mm_v1-v1-5-860c84539804@oss.qualcomm.com --- .../bindings/clock/qcom,sm8450-gpucc.yaml | 3 ++ include/dt-bindings/clock/qcom,nord-gpucc.h | 51 +++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 include/dt-bindings/clock/qcom,nord-gpucc.h diff --git a/Documentation/devicetree/bindings/clock/qcom,sm8450-gpucc.yaml b/Documentation/devicetree/bindings/clock/qcom,sm8450-gpucc.yaml index fdbdf605ee69..ba85692240e0 100644 --- a/Documentation/devicetree/bindings/clock/qcom,sm8450-gpucc.yaml +++ b/Documentation/devicetree/bindings/clock/qcom,sm8450-gpucc.yaml @@ -18,6 +18,7 @@ description: | include/dt-bindings/clock/qcom,glymur-gpucc.h include/dt-bindings/clock/qcom,kaanapali-gpucc.h include/dt-bindings/clock/qcom,milos-gpucc.h + include/dt-bindings/clock/qcom,nord-gpucc.h include/dt-bindings/clock/qcom,sar2130p-gpucc.h include/dt-bindings/clock/qcom,sm4450-gpucc.h include/dt-bindings/clock/qcom,sm8450-gpucc.h @@ -33,6 +34,8 @@ properties: - qcom,glymur-gpucc - qcom,kaanapali-gpucc - qcom,milos-gpucc + - qcom,nord-gpu2cc + - qcom,nord-gpucc - qcom,sar2130p-gpucc - qcom,sm4450-gpucc - qcom,sm8450-gpucc diff --git a/include/dt-bindings/clock/qcom,nord-gpucc.h b/include/dt-bindings/clock/qcom,nord-gpucc.h new file mode 100644 index 000000000000..a673e4854d66 --- /dev/null +++ b/include/dt-bindings/clock/qcom,nord-gpucc.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef _DT_BINDINGS_CLK_QCOM_GPU_CC_NORD_H +#define _DT_BINDINGS_CLK_QCOM_GPU_CC_NORD_H + +/* GPU_CC clocks */ +#define GPU_CC_ACD_GFX3D_CLK 0 +#define GPU_CC_ACMU_CLK 1 +#define GPU_CC_AHB_CLK 2 +#define GPU_CC_CRC_AHB_CLK 3 +#define GPU_CC_CX_ACCU_SHIFT_CLK 4 +#define GPU_CC_CX_FF_CLK 5 +#define GPU_CC_CX_GMU_CLK 6 +#define GPU_CC_CXO_AON_CLK 7 +#define GPU_CC_CXO_CLK 8 +#define GPU_CC_DEMET_CLK 9 +#define GPU_CC_DPM_CLK 10 +#define GPU_CC_FF_CLK_SRC 11 +#define GPU_CC_FREQ_MEASURE_CLK 12 +#define GPU_CC_GMU_CLK_SRC 13 +#define GPU_CC_GPU_SMMU_VOTE_CLK 14 +#define GPU_CC_HUB_AON_CLK 15 +#define GPU_CC_HUB_CLK_SRC 16 +#define GPU_CC_HUB_CX_INT_CLK 17 +#define GPU_CC_HUB_DIV_CLK_SRC 18 +#define GPU_CC_MEMNOC_GFX_CLK 19 +#define GPU_CC_MND1X_GFX3D_CLK 20 +#define GPU_CC_MND1X_1_GFX3D_CLK 21 +#define GPU_CC_PLL0 22 +#define GPU_CC_PLL1 23 +#define GPU_CC_SLEEP_CLK 24 + +/* GPU_CC power domains */ +#define GPU_CC_CX_GDSC 0 +#define GPU_CC_GX_GDSC 1 + +/* GPU_CC resets */ +#define GPU_CC_ACD_BCR 0 +#define GPU_CC_CB_BCR 1 +#define GPU_CC_CX_BCR 2 +#define GPU_CC_FAST_HUB_BCR 3 +#define GPU_CC_FF_BCR 4 +#define GPU_CC_GFX3D_AON_BCR 5 +#define GPU_CC_GMU_BCR 6 +#define GPU_CC_GX_BCR 7 +#define GPU_CC_XO_BCR 8 + +#endif From ffdb7f79226fe35182d01bd3b83e9b427b0f4f92 Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Tue, 23 Jun 2026 16:24:09 +0530 Subject: [PATCH 533/562] clk: qcom: gpucc: Add Nord graphics clock controller support Add support for the GPU clock controllers (GPUCC) on the Qualcomm Nord platform. The platform includes two GPU clock controller instances,GPUCC and GPU2CC. Register support for both controllers, which provide clocks required for the graphics subsystem. Signed-off-by: Taniya Das Link: https://lore.kernel.org/r/20260623-nords_mm_v1-v1-6-860c84539804@oss.qualcomm.com --- drivers/clk/qcom/Kconfig | 11 + drivers/clk/qcom/Makefile | 1 + drivers/clk/qcom/gpu2cc-nord.c | 546 ++++++++++++++++++++++++++++++ drivers/clk/qcom/gpucc-nord.c | 593 +++++++++++++++++++++++++++++++++ 4 files changed, 1151 insertions(+) create mode 100644 drivers/clk/qcom/gpu2cc-nord.c create mode 100644 drivers/clk/qcom/gpucc-nord.c diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig index 874136a2ad9a..10dcfa72a0bd 100644 --- a/drivers/clk/qcom/Kconfig +++ b/drivers/clk/qcom/Kconfig @@ -166,6 +166,17 @@ config CLK_NORD_GCC SPI, I2C, USB, SD/UFS, PCIe etc. The clock controller is a combination of GCC, SE_GCC, NE_GCC and NW_GCC. +config CLK_NORD_GPUCC + tristate "Nord Graphics Clock Controller" + depends on ARM64 || COMPILE_TEST + select CLK_NORD_GCC + default m if ARCH_QCOM + help + Support for the graphics clock controllers on Nord devices. There are two + graphics clock controllers on Nord SoC. + Say Y if you want to support graphics controller devices and + functionality such as 3D graphics. + config CLK_X1E80100_CAMCC tristate "X1E80100 Camera Clock Controller" depends on ARM64 || COMPILE_TEST diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile index 4282f43e7078..fb0a5bc94e32 100644 --- a/drivers/clk/qcom/Makefile +++ b/drivers/clk/qcom/Makefile @@ -39,6 +39,7 @@ obj-$(CONFIG_CLK_KAANAPALI_TCSRCC) += tcsrcc-kaanapali.o obj-$(CONFIG_CLK_KAANAPALI_VIDEOCC) += videocc-kaanapali.o obj-$(CONFIG_CLK_NORD_DISPCC) += dispcc0-nord.o dispcc1-nord.o obj-$(CONFIG_CLK_NORD_GCC) += gcc-nord.o negcc-nord.o nwgcc-nord.o segcc-nord.o +obj-$(CONFIG_CLK_NORD_GPUCC) += gpucc-nord.o gpu2cc-nord.o obj-$(CONFIG_CLK_NORD_TCSRCC) += tcsrcc-nord.o obj-$(CONFIG_CLK_X1E80100_CAMCC) += camcc-x1e80100.o obj-$(CONFIG_CLK_X1E80100_DISPCC) += dispcc-x1e80100.o diff --git a/drivers/clk/qcom/gpu2cc-nord.c b/drivers/clk/qcom/gpu2cc-nord.c new file mode 100644 index 000000000000..d1baf019704c --- /dev/null +++ b/drivers/clk/qcom/gpu2cc-nord.c @@ -0,0 +1,546 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "clk-alpha-pll.h" +#include "clk-branch.h" +#include "clk-pll.h" +#include "clk-rcg.h" +#include "clk-regmap.h" +#include "clk-regmap-divider.h" +#include "clk-regmap-mux.h" +#include "common.h" +#include "gdsc.h" +#include "reset.h" + +enum { + DT_BI_TCXO, + DT_GPLL0_OUT_MAIN, + DT_GPLL0_OUT_MAIN_DIV, +}; + +enum { + P_BI_TCXO, + P_GPLL0_OUT_MAIN, + P_GPLL0_OUT_MAIN_DIV, + P_GPU_2_CC_PLL0_OUT_MAIN, + P_GPU_2_CC_PLL1_OUT_MAIN, +}; + +static const struct pll_vco lucid_ole_vco[] = { + { 249600000, 2300000000, 0 }, +}; + +/* 934.0 MHz Configuration */ +static const struct alpha_pll_config gpu_2_cc_pll0_config = { + .l = 0x30, + .alpha = 0xa555, + .config_ctl_val = 0x20485699, + .config_ctl_hi_val = 0x00182261, + .config_ctl_hi1_val = 0x82aa299c, + .test_ctl_val = 0x00000000, + .test_ctl_hi_val = 0x00000003, + .test_ctl_hi1_val = 0x00009000, + .test_ctl_hi2_val = 0x00000034, + .user_ctl_val = 0x00000000, + .user_ctl_hi_val = 0x00400005, +}; + +static struct clk_alpha_pll gpu_2_cc_pll0 = { + .offset = 0x0, + .config = &gpu_2_cc_pll0_config, + .vco_table = lucid_ole_vco, + .num_vco = ARRAY_SIZE(lucid_ole_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_pll0", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_lucid_evo_ops, + }, + }, +}; + +/* 1100.0 MHz Configuration */ +static const struct alpha_pll_config gpu_2_cc_pll1_config = { + .l = 0x39, + .alpha = 0x4aaa, + .config_ctl_val = 0x20485699, + .config_ctl_hi_val = 0x00182261, + .config_ctl_hi1_val = 0x82aa299c, + .test_ctl_val = 0x00000000, + .test_ctl_hi_val = 0x00000003, + .test_ctl_hi1_val = 0x00009000, + .test_ctl_hi2_val = 0x00000034, + .user_ctl_val = 0x00000000, + .user_ctl_hi_val = 0x00400005, +}; + +static struct clk_alpha_pll gpu_2_cc_pll1 = { + .offset = 0x1000, + .config = &gpu_2_cc_pll1_config, + .vco_table = lucid_ole_vco, + .num_vco = ARRAY_SIZE(lucid_ole_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_pll1", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_lucid_evo_ops, + }, + }, +}; + +static const struct parent_map gpu_2_cc_parent_map_0[] = { + { P_BI_TCXO, 0 }, + { P_GPLL0_OUT_MAIN, 5 }, + { P_GPLL0_OUT_MAIN_DIV, 6 }, +}; + +static const struct clk_parent_data gpu_2_cc_parent_data_0[] = { + { .index = DT_BI_TCXO }, + { .index = DT_GPLL0_OUT_MAIN }, + { .index = DT_GPLL0_OUT_MAIN_DIV }, +}; + +static const struct parent_map gpu_2_cc_parent_map_1[] = { + { P_BI_TCXO, 0 }, + { P_GPU_2_CC_PLL0_OUT_MAIN, 1 }, + { P_GPU_2_CC_PLL1_OUT_MAIN, 3 }, + { P_GPLL0_OUT_MAIN, 5 }, + { P_GPLL0_OUT_MAIN_DIV, 6 }, +}; + +static const struct clk_parent_data gpu_2_cc_parent_data_1[] = { + { .index = DT_BI_TCXO }, + { .hw = &gpu_2_cc_pll0.clkr.hw }, + { .hw = &gpu_2_cc_pll1.clkr.hw }, + { .index = DT_GPLL0_OUT_MAIN }, + { .index = DT_GPLL0_OUT_MAIN_DIV }, +}; + +static const struct parent_map gpu_2_cc_parent_map_2[] = { + { P_BI_TCXO, 0 }, + { P_GPU_2_CC_PLL1_OUT_MAIN, 3 }, + { P_GPLL0_OUT_MAIN, 5 }, + { P_GPLL0_OUT_MAIN_DIV, 6 }, +}; + +static const struct clk_parent_data gpu_2_cc_parent_data_2[] = { + { .index = DT_BI_TCXO }, + { .hw = &gpu_2_cc_pll1.clkr.hw }, + { .index = DT_GPLL0_OUT_MAIN }, + { .index = DT_GPLL0_OUT_MAIN_DIV }, +}; + +static const struct freq_tbl ftbl_gpu_2_cc_ff_clk_src[] = { + F(200000000, P_GPLL0_OUT_MAIN, 3, 0, 0), + { } +}; + +static struct clk_rcg2 gpu_2_cc_ff_clk_src = { + .cmd_rcgr = 0x91c4, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gpu_2_cc_parent_map_0, + .freq_tbl = ftbl_gpu_2_cc_ff_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_ff_clk_src", + .parent_data = gpu_2_cc_parent_data_0, + .num_parents = ARRAY_SIZE(gpu_2_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_gpu_2_cc_gmu_clk_src[] = { + F(550000000, P_GPU_2_CC_PLL1_OUT_MAIN, 2, 0, 0), + { } +}; + +static struct clk_rcg2 gpu_2_cc_gmu_clk_src = { + .cmd_rcgr = 0x9174, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gpu_2_cc_parent_map_1, + .freq_tbl = ftbl_gpu_2_cc_gmu_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_gmu_clk_src", + .parent_data = gpu_2_cc_parent_data_1, + .num_parents = ARRAY_SIZE(gpu_2_cc_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 gpu_2_cc_hub_clk_src = { + .cmd_rcgr = 0x91a8, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gpu_2_cc_parent_map_2, + .freq_tbl = ftbl_gpu_2_cc_ff_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_hub_clk_src", + .parent_data = gpu_2_cc_parent_data_2, + .num_parents = ARRAY_SIZE(gpu_2_cc_parent_data_2), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_branch gpu_2_cc_ahb_clk = { + .halt_reg = 0x90cc, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x90cc, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &gpu_2_cc_hub_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_2_cc_crc_ahb_clk = { + .halt_reg = 0x90d0, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x90d0, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_crc_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &gpu_2_cc_hub_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_2_cc_cx_accu_shift_clk = { + .halt_reg = 0x9114, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x9114, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_cx_accu_shift_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_2_cc_cx_ff_clk = { + .halt_reg = 0x9100, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x9100, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_cx_ff_clk", + .parent_hws = (const struct clk_hw*[]) { + &gpu_2_cc_ff_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_2_cc_cx_gmu_clk = { + .halt_reg = 0x90e8, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x90e8, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_cx_gmu_clk", + .parent_hws = (const struct clk_hw*[]) { + &gpu_2_cc_gmu_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_aon_ops, + }, + }, +}; + +static struct clk_branch gpu_2_cc_cxo_clk = { + .halt_reg = 0x90f8, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x90f8, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_cxo_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_2_cc_freq_measure_clk = { + .halt_reg = 0x9008, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x9008, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_freq_measure_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_2_cc_gpu_smmu_vote_clk = { + .halt_reg = 0x7000, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x7000, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_gpu_smmu_vote_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_2_cc_hub_aon_clk = { + .halt_reg = 0x91a4, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x91a4, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_hub_aon_clk", + .parent_hws = (const struct clk_hw*[]) { + &gpu_2_cc_hub_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_aon_ops, + }, + }, +}; + +static struct clk_branch gpu_2_cc_hub_cx_int_clk = { + .halt_reg = 0x90fc, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x90fc, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_hub_cx_int_clk", + .parent_hws = (const struct clk_hw*[]) { + &gpu_2_cc_hub_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_aon_ops, + }, + }, +}; + +static struct clk_branch gpu_2_cc_memnoc_gfx_clk = { + .halt_reg = 0x9104, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x9104, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_memnoc_gfx_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_2_cc_mnd1x_0_gfx3d_clk = { + .halt_reg = 0x9164, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x9164, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_mnd1x_0_gfx3d_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_2_cc_mnd1x_1_gfx3d_clk = { + .halt_reg = 0x9168, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x9168, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_mnd1x_1_gfx3d_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_2_cc_sleep_clk = { + .halt_reg = 0x90e0, + .halt_check = BRANCH_HALT_SKIP, + .clkr = { + .enable_reg = 0x90e0, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_2_cc_sleep_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct gdsc gpu_2_cc_cx_gdsc = { + .gdscr = 0x9090, + .gds_hw_ctrl = 0x90a4, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "gpu_2_cc_cx_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .flags = VOTABLE | RETAIN_FF_ENABLE, +}; + +static struct gdsc gpu_2_cc_gx_gdsc = { + .gdscr = 0x9034, + .clamp_io_ctrl = 0x9504, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "gpu_2_cc_gx_gdsc", + .power_on = gdsc_gx_do_nothing_enable, + }, + .pwrsts = PWRSTS_OFF_ON, + .flags = CLAMP_IO | POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct clk_regmap *gpu_2_cc_nord_clocks[] = { + [GPU_CC_AHB_CLK] = &gpu_2_cc_ahb_clk.clkr, + [GPU_CC_CRC_AHB_CLK] = &gpu_2_cc_crc_ahb_clk.clkr, + [GPU_CC_CX_ACCU_SHIFT_CLK] = &gpu_2_cc_cx_accu_shift_clk.clkr, + [GPU_CC_CX_FF_CLK] = &gpu_2_cc_cx_ff_clk.clkr, + [GPU_CC_CX_GMU_CLK] = &gpu_2_cc_cx_gmu_clk.clkr, + [GPU_CC_CXO_CLK] = &gpu_2_cc_cxo_clk.clkr, + [GPU_CC_FF_CLK_SRC] = &gpu_2_cc_ff_clk_src.clkr, + [GPU_CC_FREQ_MEASURE_CLK] = &gpu_2_cc_freq_measure_clk.clkr, + [GPU_CC_GMU_CLK_SRC] = &gpu_2_cc_gmu_clk_src.clkr, + [GPU_CC_GPU_SMMU_VOTE_CLK] = &gpu_2_cc_gpu_smmu_vote_clk.clkr, + [GPU_CC_HUB_AON_CLK] = &gpu_2_cc_hub_aon_clk.clkr, + [GPU_CC_HUB_CLK_SRC] = &gpu_2_cc_hub_clk_src.clkr, + [GPU_CC_HUB_CX_INT_CLK] = &gpu_2_cc_hub_cx_int_clk.clkr, + [GPU_CC_MEMNOC_GFX_CLK] = &gpu_2_cc_memnoc_gfx_clk.clkr, + [GPU_CC_MND1X_GFX3D_CLK] = &gpu_2_cc_mnd1x_0_gfx3d_clk.clkr, + [GPU_CC_MND1X_1_GFX3D_CLK] = &gpu_2_cc_mnd1x_1_gfx3d_clk.clkr, + [GPU_CC_PLL0] = &gpu_2_cc_pll0.clkr, + [GPU_CC_PLL1] = &gpu_2_cc_pll1.clkr, + [GPU_CC_SLEEP_CLK] = &gpu_2_cc_sleep_clk.clkr, +}; + +static struct gdsc *gpu_2_cc_nord_gdscs[] = { + [GPU_CC_CX_GDSC] = &gpu_2_cc_cx_gdsc, + [GPU_CC_GX_GDSC] = &gpu_2_cc_gx_gdsc, +}; + +static const struct qcom_reset_map gpu_2_cc_nord_resets[] = { + [GPU_CC_ACD_BCR] = { 0x918c }, + [GPU_CC_CB_BCR] = { 0x9198 }, + [GPU_CC_CX_BCR] = { 0x908c }, + [GPU_CC_FAST_HUB_BCR] = { 0x91a0 }, + [GPU_CC_FF_BCR] = { 0x91c0 }, + [GPU_CC_GFX3D_AON_BCR] = { 0x9118 }, + [GPU_CC_GMU_BCR] = { 0x9170 }, + [GPU_CC_GX_BCR] = { 0x9030 }, + [GPU_CC_XO_BCR] = { 0x9000 }, +}; + +static struct clk_alpha_pll *gpu_2_cc_nord_plls[] = { + &gpu_2_cc_pll0, + &gpu_2_cc_pll1, +}; + +static const u32 gpu_2_cc_nord_critical_cbcrs[] = { + 0x9004, /* GPU_2_CC_CXO_AON_CLK */ + 0x900c, /* GPU_2_CC_DEMET_CLK */ +}; + +static const struct regmap_config gpu_2_cc_nord_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x9ff0, + .fast_io = true, +}; + +static const struct qcom_cc_driver_data gpu_2_cc_nord_driver_data = { + .alpha_plls = gpu_2_cc_nord_plls, + .num_alpha_plls = ARRAY_SIZE(gpu_2_cc_nord_plls), + .clk_cbcrs = gpu_2_cc_nord_critical_cbcrs, + .num_clk_cbcrs = ARRAY_SIZE(gpu_2_cc_nord_critical_cbcrs), +}; + +static const struct qcom_cc_desc gpu_2_cc_nord_desc = { + .config = &gpu_2_cc_nord_regmap_config, + .clks = gpu_2_cc_nord_clocks, + .num_clks = ARRAY_SIZE(gpu_2_cc_nord_clocks), + .resets = gpu_2_cc_nord_resets, + .num_resets = ARRAY_SIZE(gpu_2_cc_nord_resets), + .gdscs = gpu_2_cc_nord_gdscs, + .num_gdscs = ARRAY_SIZE(gpu_2_cc_nord_gdscs), + .driver_data = &gpu_2_cc_nord_driver_data, +}; + +static const struct of_device_id gpu_2_cc_nord_match_table[] = { + { .compatible = "qcom,nord-gpu2cc" }, + { } +}; +MODULE_DEVICE_TABLE(of, gpu_2_cc_nord_match_table); + +static int gpu_2_cc_nord_probe(struct platform_device *pdev) +{ + return qcom_cc_probe(pdev, &gpu_2_cc_nord_desc); +} + +static struct platform_driver gpu_2_cc_nord_driver = { + .probe = gpu_2_cc_nord_probe, + .driver = { + .name = "gpu2cc-nord", + .of_match_table = gpu_2_cc_nord_match_table, + }, +}; + +module_platform_driver(gpu_2_cc_nord_driver); + +MODULE_DESCRIPTION("QTI GPU2CC Nord Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/clk/qcom/gpucc-nord.c b/drivers/clk/qcom/gpucc-nord.c new file mode 100644 index 000000000000..407cf7e5ad43 --- /dev/null +++ b/drivers/clk/qcom/gpucc-nord.c @@ -0,0 +1,593 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include + +#include + +#include "clk-alpha-pll.h" +#include "clk-branch.h" +#include "clk-pll.h" +#include "clk-rcg.h" +#include "clk-regmap.h" +#include "clk-regmap-divider.h" +#include "clk-regmap-mux.h" +#include "common.h" +#include "gdsc.h" +#include "reset.h" + +enum { + DT_BI_TCXO, + DT_GPLL0_OUT_MAIN, + DT_GPLL0_OUT_MAIN_DIV, +}; + +enum { + P_BI_TCXO, + P_GPLL0_OUT_MAIN, + P_GPLL0_OUT_MAIN_DIV, + P_GPU_CC_PLL0_OUT_MAIN, + P_GPU_CC_PLL1_OUT_MAIN, +}; + +static const struct pll_vco lucid_ole_vco[] = { + { 249600000, 2300000000, 0 }, +}; + +/* 936.0 MHz Configuration */ +static const struct alpha_pll_config gpu_cc_pll0_config = { + .l = 0x30, + .alpha = 0xc000, + .config_ctl_val = 0x20485699, + .config_ctl_hi_val = 0x00182261, + .config_ctl_hi1_val = 0x82aa299c, + .test_ctl_val = 0x00000000, + .test_ctl_hi_val = 0x00000003, + .test_ctl_hi1_val = 0x00009000, + .test_ctl_hi2_val = 0x00000034, + .user_ctl_val = 0x00000000, + .user_ctl_hi_val = 0x00400005, +}; + +static struct clk_alpha_pll gpu_cc_pll0 = { + .offset = 0x0, + .config = &gpu_cc_pll0_config, + .vco_table = lucid_ole_vco, + .num_vco = ARRAY_SIZE(lucid_ole_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_pll0", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_lucid_evo_ops, + }, + }, +}; + +/* 1250.0 MHz Configuration */ +static const struct alpha_pll_config gpu_cc_pll1_config = { + .l = 0x41, + .alpha = 0x1aaa, + .config_ctl_val = 0x20485699, + .config_ctl_hi_val = 0x00182261, + .config_ctl_hi1_val = 0x82aa299c, + .test_ctl_val = 0x00000000, + .test_ctl_hi_val = 0x00000003, + .test_ctl_hi1_val = 0x00009000, + .test_ctl_hi2_val = 0x00000034, + .user_ctl_val = 0x00000000, + .user_ctl_hi_val = 0x00400005, +}; + +static struct clk_alpha_pll gpu_cc_pll1 = { + .offset = 0x1000, + .config = &gpu_cc_pll1_config, + .vco_table = lucid_ole_vco, + .num_vco = ARRAY_SIZE(lucid_ole_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_pll1", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_lucid_evo_ops, + }, + }, +}; + +static const struct parent_map gpu_cc_parent_map_0[] = { + { P_BI_TCXO, 0 }, + { P_GPLL0_OUT_MAIN, 5 }, + { P_GPLL0_OUT_MAIN_DIV, 6 }, +}; + +static const struct clk_parent_data gpu_cc_parent_data_0[] = { + { .index = DT_BI_TCXO }, + { .index = DT_GPLL0_OUT_MAIN }, + { .index = DT_GPLL0_OUT_MAIN_DIV }, +}; + +static const struct parent_map gpu_cc_parent_map_1[] = { + { P_BI_TCXO, 0 }, + { P_GPU_CC_PLL0_OUT_MAIN, 1 }, + { P_GPU_CC_PLL1_OUT_MAIN, 3 }, + { P_GPLL0_OUT_MAIN, 5 }, + { P_GPLL0_OUT_MAIN_DIV, 6 }, +}; + +static const struct clk_parent_data gpu_cc_parent_data_1[] = { + { .index = DT_BI_TCXO }, + { .hw = &gpu_cc_pll0.clkr.hw }, + { .hw = &gpu_cc_pll1.clkr.hw }, + { .index = DT_GPLL0_OUT_MAIN }, + { .index = DT_GPLL0_OUT_MAIN_DIV }, +}; + +static const struct parent_map gpu_cc_parent_map_2[] = { + { P_BI_TCXO, 0 }, + { P_GPU_CC_PLL1_OUT_MAIN, 3 }, + { P_GPLL0_OUT_MAIN, 5 }, + { P_GPLL0_OUT_MAIN_DIV, 6 }, +}; + +static const struct clk_parent_data gpu_cc_parent_data_2[] = { + { .index = DT_BI_TCXO }, + { .hw = &gpu_cc_pll1.clkr.hw }, + { .index = DT_GPLL0_OUT_MAIN }, + { .index = DT_GPLL0_OUT_MAIN_DIV }, +}; + +static const struct freq_tbl ftbl_gpu_cc_ff_clk_src[] = { + F(200000000, P_GPLL0_OUT_MAIN, 3, 0, 0), + { } +}; + +static struct clk_rcg2 gpu_cc_ff_clk_src = { + .cmd_rcgr = 0x93d4, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gpu_cc_parent_map_0, + .freq_tbl = ftbl_gpu_cc_ff_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_ff_clk_src", + .parent_data = gpu_cc_parent_data_0, + .num_parents = ARRAY_SIZE(gpu_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_gpu_cc_gmu_clk_src[] = { + F(416666667, P_GPU_CC_PLL1_OUT_MAIN, 3, 0, 0), + F(625000000, P_GPU_CC_PLL1_OUT_MAIN, 2, 0, 0), + { } +}; + +static struct clk_rcg2 gpu_cc_gmu_clk_src = { + .cmd_rcgr = 0x92b8, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gpu_cc_parent_map_1, + .freq_tbl = ftbl_gpu_cc_gmu_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_gmu_clk_src", + .parent_data = gpu_cc_parent_data_1, + .num_parents = ARRAY_SIZE(gpu_cc_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_gpu_cc_hub_clk_src[] = { + F(300000000, P_GPLL0_OUT_MAIN, 2, 0, 0), + { } +}; + +static struct clk_rcg2 gpu_cc_hub_clk_src = { + .cmd_rcgr = 0x938c, + .mnd_width = 0, + .hid_width = 5, + .parent_map = gpu_cc_parent_map_2, + .freq_tbl = ftbl_gpu_cc_hub_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_hub_clk_src", + .parent_data = gpu_cc_parent_data_2, + .num_parents = ARRAY_SIZE(gpu_cc_parent_data_2), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_regmap_div gpu_cc_hub_div_clk_src = { + .reg = 0x93cc, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_hub_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &gpu_cc_hub_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_branch gpu_cc_acd_gfx3d_clk = { + .halt_reg = 0x92a8, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x92a8, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_acd_gfx3d_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_cc_acmu_clk = { + .halt_reg = 0x9294, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x9294, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_acmu_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_cc_ahb_clk = { + .halt_reg = 0x9150, + .halt_check = BRANCH_HALT_DELAY, + .clkr = { + .enable_reg = 0x9150, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &gpu_cc_hub_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_cc_crc_ahb_clk = { + .halt_reg = 0x9154, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x9154, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_crc_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &gpu_cc_hub_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_cc_cx_accu_shift_clk = { + .halt_reg = 0x91a4, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x91a4, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_cx_accu_shift_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_cc_cx_ff_clk = { + .halt_reg = 0x9184, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x9184, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_cx_ff_clk", + .parent_hws = (const struct clk_hw*[]) { + &gpu_cc_ff_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_cc_cx_gmu_clk = { + .halt_reg = 0x916c, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x916c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_cx_gmu_clk", + .parent_hws = (const struct clk_hw*[]) { + &gpu_cc_gmu_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_aon_ops, + }, + }, +}; + +static struct clk_branch gpu_cc_cxo_clk = { + .halt_reg = 0x917c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x917c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_cxo_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_cc_dpm_clk = { + .halt_reg = 0x91a8, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x91a8, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_dpm_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_cc_freq_measure_clk = { + .halt_reg = 0x9008, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x9008, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_freq_measure_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_cc_gpu_smmu_vote_clk = { + .halt_reg = 0x7000, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x7000, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_gpu_smmu_vote_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_cc_hub_aon_clk = { + .halt_reg = 0x9388, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x9388, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_hub_aon_clk", + .parent_hws = (const struct clk_hw*[]) { + &gpu_cc_hub_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_aon_ops, + }, + }, +}; + +static struct clk_branch gpu_cc_hub_cx_int_clk = { + .halt_reg = 0x9180, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x9180, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_hub_cx_int_clk", + .parent_hws = (const struct clk_hw*[]) { + &gpu_cc_hub_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_aon_ops, + }, + }, +}; + +static struct clk_branch gpu_cc_memnoc_gfx_clk = { + .halt_reg = 0x9188, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x9188, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_memnoc_gfx_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_cc_mnd1x_gfx3d_clk = { + .halt_reg = 0x92ac, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x92ac, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_mnd1x_gfx3d_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gpu_cc_sleep_clk = { + .halt_reg = 0x9164, + .halt_check = BRANCH_HALT_VOTED, + .clkr = { + .enable_reg = 0x9164, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "gpu_cc_sleep_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct gdsc gpu_cc_cx_gdsc = { + .gdscr = 0x90e8, + .gds_hw_ctrl = 0x9128, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "gpu_cc_cx_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .flags = VOTABLE | RETAIN_FF_ENABLE, +}; + +static struct gdsc gpu_cc_gx_gdsc = { + .gdscr = 0x905c, + .clamp_io_ctrl = 0x9504, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "gpu_cc_gx_gdsc", + .power_on = gdsc_gx_do_nothing_enable, + }, + .pwrsts = PWRSTS_OFF_ON, + .flags = CLAMP_IO | POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct clk_regmap *gpu_cc_nord_clocks[] = { + [GPU_CC_ACD_GFX3D_CLK] = &gpu_cc_acd_gfx3d_clk.clkr, + [GPU_CC_ACMU_CLK] = &gpu_cc_acmu_clk.clkr, + [GPU_CC_AHB_CLK] = &gpu_cc_ahb_clk.clkr, + [GPU_CC_CRC_AHB_CLK] = &gpu_cc_crc_ahb_clk.clkr, + [GPU_CC_CX_ACCU_SHIFT_CLK] = &gpu_cc_cx_accu_shift_clk.clkr, + [GPU_CC_CX_FF_CLK] = &gpu_cc_cx_ff_clk.clkr, + [GPU_CC_CX_GMU_CLK] = &gpu_cc_cx_gmu_clk.clkr, + [GPU_CC_CXO_CLK] = &gpu_cc_cxo_clk.clkr, + [GPU_CC_DPM_CLK] = &gpu_cc_dpm_clk.clkr, + [GPU_CC_FF_CLK_SRC] = &gpu_cc_ff_clk_src.clkr, + [GPU_CC_FREQ_MEASURE_CLK] = &gpu_cc_freq_measure_clk.clkr, + [GPU_CC_GMU_CLK_SRC] = &gpu_cc_gmu_clk_src.clkr, + [GPU_CC_GPU_SMMU_VOTE_CLK] = &gpu_cc_gpu_smmu_vote_clk.clkr, + [GPU_CC_HUB_AON_CLK] = &gpu_cc_hub_aon_clk.clkr, + [GPU_CC_HUB_CLK_SRC] = &gpu_cc_hub_clk_src.clkr, + [GPU_CC_HUB_CX_INT_CLK] = &gpu_cc_hub_cx_int_clk.clkr, + [GPU_CC_HUB_DIV_CLK_SRC] = &gpu_cc_hub_div_clk_src.clkr, + [GPU_CC_MEMNOC_GFX_CLK] = &gpu_cc_memnoc_gfx_clk.clkr, + [GPU_CC_MND1X_GFX3D_CLK] = &gpu_cc_mnd1x_gfx3d_clk.clkr, + [GPU_CC_PLL0] = &gpu_cc_pll0.clkr, + [GPU_CC_PLL1] = &gpu_cc_pll1.clkr, + [GPU_CC_SLEEP_CLK] = &gpu_cc_sleep_clk.clkr, +}; + +static struct gdsc *gpu_cc_nord_gdscs[] = { + [GPU_CC_CX_GDSC] = &gpu_cc_cx_gdsc, + [GPU_CC_GX_GDSC] = &gpu_cc_gx_gdsc, +}; + +static const struct qcom_reset_map gpu_cc_nord_resets[] = { + [GPU_CC_ACD_BCR] = { 0x92f8 }, + [GPU_CC_CB_BCR] = { 0x9340 }, + [GPU_CC_CX_BCR] = { 0x90e4 }, + [GPU_CC_FAST_HUB_BCR] = { 0x9384 }, + [GPU_CC_GFX3D_AON_BCR] = { 0x91ac }, + [GPU_CC_GX_BCR] = { 0x9058 }, + [GPU_CC_XO_BCR] = { 0x9000 }, +}; + +static struct clk_alpha_pll *gpu_cc_nord_plls[] = { + &gpu_cc_pll0, + &gpu_cc_pll1, +}; + +static const u32 gpu_cc_nord_critical_cbcrs[] = { + 0x9004, /* GPU_CC_CXO_AON_CLK */ + 0x900c, /* GPU_CC_DEMET_CLK */ +}; + +static const struct regmap_config gpu_cc_nord_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x9660, + .fast_io = true, +}; + +static const struct qcom_cc_driver_data gpu_cc_nord_driver_data = { + .alpha_plls = gpu_cc_nord_plls, + .num_alpha_plls = ARRAY_SIZE(gpu_cc_nord_plls), + .clk_cbcrs = gpu_cc_nord_critical_cbcrs, + .num_clk_cbcrs = ARRAY_SIZE(gpu_cc_nord_critical_cbcrs), +}; + +static const struct qcom_cc_desc gpu_cc_nord_desc = { + .config = &gpu_cc_nord_regmap_config, + .clks = gpu_cc_nord_clocks, + .num_clks = ARRAY_SIZE(gpu_cc_nord_clocks), + .resets = gpu_cc_nord_resets, + .num_resets = ARRAY_SIZE(gpu_cc_nord_resets), + .gdscs = gpu_cc_nord_gdscs, + .num_gdscs = ARRAY_SIZE(gpu_cc_nord_gdscs), + .driver_data = &gpu_cc_nord_driver_data, +}; + +static const struct of_device_id gpu_cc_nord_match_table[] = { + { .compatible = "qcom,nord-gpucc" }, + { } +}; +MODULE_DEVICE_TABLE(of, gpu_cc_nord_match_table); + +static int gpu_cc_nord_probe(struct platform_device *pdev) +{ + return qcom_cc_probe(pdev, &gpu_cc_nord_desc); +} + +static struct platform_driver gpu_cc_nord_driver = { + .probe = gpu_cc_nord_probe, + .driver = { + .name = "gpucc-nord", + .of_match_table = gpu_cc_nord_match_table, + }, +}; + +module_platform_driver(gpu_cc_nord_driver); + +MODULE_DESCRIPTION("QTI GPUCC Nord Driver"); +MODULE_LICENSE("GPL"); From 0deab399b463e603c10bb3382cc56e46106cee05 Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Sat, 4 Jul 2026 08:44:08 +0800 Subject: [PATCH 534/562] dt-bindings: crypto: qcom,inline-crypto-engine: Document Nord ICE Document Inline Crypto Engine (ICE) on Qualcomm Nord SoC. Acked-by: Krzysztof Kozlowski Reviewed-by: Harshal Dev Link: https://lore.kernel.org/r/20260704004408.2303468-1-shengchao.guo@oss.qualcomm.com Signed-off-by: Shawn Guo --- .../devicetree/bindings/crypto/qcom,inline-crypto-engine.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/crypto/qcom,inline-crypto-engine.yaml b/Documentation/devicetree/bindings/crypto/qcom,inline-crypto-engine.yaml index db895c50e2d2..d690eff2e86d 100644 --- a/Documentation/devicetree/bindings/crypto/qcom,inline-crypto-engine.yaml +++ b/Documentation/devicetree/bindings/crypto/qcom,inline-crypto-engine.yaml @@ -17,6 +17,7 @@ properties: - qcom,hawi-inline-crypto-engine - qcom,kaanapali-inline-crypto-engine - qcom,milos-inline-crypto-engine + - qcom,nord-inline-crypto-engine - qcom,qcs8300-inline-crypto-engine - qcom,sa8775p-inline-crypto-engine - qcom,sc7180-inline-crypto-engine @@ -63,6 +64,7 @@ allOf: enum: - qcom,eliza-inline-crypto-engine - qcom,milos-inline-crypto-engine + - qcom,nord-inline-crypto-engine then: required: From 09ae13d00d1f5f97d91191041efc30e5e2807149 Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Tue, 7 Jul 2026 17:57:08 +0800 Subject: [PATCH 535/562] dt-bindings: i2c: qcom,sa8255p-geni-i2c: Add compatible for Nord SA8797P Add compatible for Nord SA8797P QUP GENI I2C controller, which is compatible with SA8255P controller. Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260707095708.3801043-1-shengchao.guo@oss.qualcomm.com Signed-off-by: Shawn Guo --- .../devicetree/bindings/i2c/qcom,sa8255p-geni-i2c.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/i2c/qcom,sa8255p-geni-i2c.yaml b/Documentation/devicetree/bindings/i2c/qcom,sa8255p-geni-i2c.yaml index a61e40b5cbc1..04caf622b3ba 100644 --- a/Documentation/devicetree/bindings/i2c/qcom,sa8255p-geni-i2c.yaml +++ b/Documentation/devicetree/bindings/i2c/qcom,sa8255p-geni-i2c.yaml @@ -11,7 +11,11 @@ maintainers: properties: compatible: - const: qcom,sa8255p-geni-i2c + oneOf: + - const: qcom,sa8255p-geni-i2c + - items: + - const: qcom,sa8797p-geni-i2c + - const: qcom,sa8255p-geni-i2c reg: maxItems: 1 From 010c67037d4b8e5482927fae4f378931878f7d84 Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Thu, 9 Jul 2026 21:20:07 +0800 Subject: [PATCH 536/562] arm64: dts: qcom: Add device tree for Nord SoC series Add base device tree include (nord.dtsi) for the Nord SoC series describing the core hardware components: - 18 Oryon (qcom,oryon-1-5) cores in three clusters, with PSCI-based power management and CPU/cluster idle states - ARM GICv3 interrupt controller with ITS - TLMM GPIO/pinctrl controller - 8 TSENS thermal sensors with thermal zones - 3 APPS SMMU-500 instances - 3 QUPv3 GENI SE QUP blocks - PDP SCMI channel and mailbox - Watchdog, Crypto, TRNG and TCSR - Reserved memory, CMD-DB and firmware SCM - PSCI and architected timers Co-developed-by: Deepti Jaggi Signed-off-by: Deepti Jaggi Co-developed-by: Bartosz Golaszewski Signed-off-by: Bartosz Golaszewski Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260709132013.4096850-2-shengchao.guo@oss.qualcomm.com Signed-off-by: Shawn Guo --- arch/arm64/boot/dts/qcom/nord.dtsi | 4596 ++++++++++++++++++++++++++++ 1 file changed, 4596 insertions(+) create mode 100644 arch/arm64/boot/dts/qcom/nord.dtsi diff --git a/arch/arm64/boot/dts/qcom/nord.dtsi b/arch/arm64/boot/dts/qcom/nord.dtsi new file mode 100644 index 000000000000..716297bcc9a6 --- /dev/null +++ b/arch/arm64/boot/dts/qcom/nord.dtsi @@ -0,0 +1,4596 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include + +/ { + interrupt-parent = <&intc>; + #address-cells = <2>; + #size-cells = <2>; + + cpus { + #address-cells = <2>; + #size-cells = <0>; + + cpu0: cpu@0 { + device_type = "cpu"; + compatible = "qcom,oryon-1-5"; + reg = <0x0 0x0>; + enable-method = "psci"; + power-domains = <&cpu0_pd>; + power-domain-names = "psci"; + next-level-cache = <&l2_0>; + clocks = <&cpu_perf 0>; + + l2_0: l2-cache { + compatible = "cache"; + cache-level = <2>; + cache-unified; + }; + }; + + cpu1: cpu@100 { + device_type = "cpu"; + compatible = "qcom,oryon-1-5"; + reg = <0x0 0x100>; + enable-method = "psci"; + power-domains = <&cpu1_pd>; + power-domain-names = "psci"; + next-level-cache = <&l2_0>; + clocks = <&cpu_perf 0>; + }; + + cpu2: cpu@200 { + device_type = "cpu"; + compatible = "qcom,oryon-1-5"; + reg = <0x0 0x200>; + enable-method = "psci"; + power-domains = <&cpu2_pd>; + power-domain-names = "psci"; + next-level-cache = <&l2_0>; + clocks = <&cpu_perf 0>; + }; + + cpu3: cpu@300 { + device_type = "cpu"; + compatible = "qcom,oryon-1-5"; + reg = <0x0 0x300>; + enable-method = "psci"; + power-domains = <&cpu3_pd>; + power-domain-names = "psci"; + next-level-cache = <&l2_0>; + clocks = <&cpu_perf 0>; + }; + + cpu4: cpu@400 { + device_type = "cpu"; + compatible = "qcom,oryon-1-5"; + reg = <0x0 0x400>; + enable-method = "psci"; + power-domains = <&cpu4_pd>; + power-domain-names = "psci"; + next-level-cache = <&l2_0>; + clocks = <&cpu_perf 0>; + }; + + cpu5: cpu@500 { + device_type = "cpu"; + compatible = "qcom,oryon-1-5"; + reg = <0x0 0x500>; + enable-method = "psci"; + power-domains = <&cpu5_pd>; + power-domain-names = "psci"; + next-level-cache = <&l2_0>; + clocks = <&cpu_perf 0>; + }; + + cpu6: cpu@10000 { + device_type = "cpu"; + compatible = "qcom,oryon-1-5"; + reg = <0x0 0x10000>; + power-domains = <&cpu6_pd>; + power-domain-names = "psci"; + enable-method = "psci"; + next-level-cache = <&l2_10000>; + clocks = <&cpu_perf 1>; + + l2_10000: l2-cache { + compatible = "cache"; + cache-level = <2>; + cache-unified; + }; + }; + + cpu7: cpu@10100 { + device_type = "cpu"; + compatible = "qcom,oryon-1-5"; + reg = <0x0 0x10100>; + enable-method = "psci"; + power-domains = <&cpu7_pd>; + power-domain-names = "psci"; + next-level-cache = <&l2_10000>; + clocks = <&cpu_perf 1>; + }; + + cpu8: cpu@10200 { + device_type = "cpu"; + compatible = "qcom,oryon-1-5"; + reg = <0x0 0x10200>; + enable-method = "psci"; + power-domains = <&cpu8_pd>; + power-domain-names = "psci"; + next-level-cache = <&l2_10000>; + clocks = <&cpu_perf 1>; + }; + + cpu9: cpu@10300 { + device_type = "cpu"; + compatible = "qcom,oryon-1-5"; + reg = <0x0 0x10300>; + enable-method = "psci"; + power-domains = <&cpu9_pd>; + power-domain-names = "psci"; + next-level-cache = <&l2_10000>; + clocks = <&cpu_perf 1>; + }; + + cpu10: cpu@10400 { + device_type = "cpu"; + compatible = "qcom,oryon-1-5"; + reg = <0x0 0x10400>; + enable-method = "psci"; + power-domains = <&cpu10_pd>; + power-domain-names = "psci"; + next-level-cache = <&l2_10000>; + clocks = <&cpu_perf 1>; + }; + + cpu11: cpu@10500 { + device_type = "cpu"; + compatible = "qcom,oryon-1-5"; + reg = <0x0 0x10500>; + enable-method = "psci"; + power-domains = <&cpu11_pd>; + power-domain-names = "psci"; + next-level-cache = <&l2_10000>; + clocks = <&cpu_perf 1>; + }; + + cpu12: cpu@20000 { + device_type = "cpu"; + compatible = "qcom,oryon-1-5"; + reg = <0x0 0x20000>; + enable-method = "psci"; + power-domains = <&cpu12_pd>; + power-domain-names = "psci"; + next-level-cache = <&l2_20000>; + clocks = <&cpu_perf 2>; + + l2_20000: l2-cache { + compatible = "cache"; + cache-level = <2>; + cache-unified; + }; + }; + + cpu13: cpu@20100 { + device_type = "cpu"; + compatible = "qcom,oryon-1-5"; + reg = <0x0 0x20100>; + enable-method = "psci"; + power-domains = <&cpu13_pd>; + power-domain-names = "psci"; + next-level-cache = <&l2_20000>; + clocks = <&cpu_perf 2>; + }; + + cpu14: cpu@20200 { + device_type = "cpu"; + compatible = "qcom,oryon-1-5"; + reg = <0x0 0x20200>; + enable-method = "psci"; + power-domains = <&cpu14_pd>; + power-domain-names = "psci"; + next-level-cache = <&l2_20000>; + clocks = <&cpu_perf 2>; + }; + + cpu15: cpu@20300 { + device_type = "cpu"; + compatible = "qcom,oryon-1-5"; + reg = <0x0 0x20300>; + enable-method = "psci"; + power-domains = <&cpu15_pd>; + power-domain-names = "psci"; + next-level-cache = <&l2_20000>; + clocks = <&cpu_perf 2>; + }; + + cpu16: cpu@20400 { + device_type = "cpu"; + compatible = "qcom,oryon-1-5"; + reg = <0x0 0x20400>; + enable-method = "psci"; + power-domains = <&cpu16_pd>; + power-domain-names = "psci"; + next-level-cache = <&l2_20000>; + clocks = <&cpu_perf 2>; + }; + + cpu17: cpu@20500 { + device_type = "cpu"; + compatible = "qcom,oryon-1-5"; + reg = <0x0 0x20500>; + enable-method = "psci"; + power-domains = <&cpu17_pd>; + power-domain-names = "psci"; + next-level-cache = <&l2_20000>; + clocks = <&cpu_perf 2>; + }; + + cpu-map { + cluster0 { + core0 { + cpu = <&cpu0>; + }; + + core1 { + cpu = <&cpu1>; + }; + + core2 { + cpu = <&cpu2>; + }; + + core3 { + cpu = <&cpu3>; + }; + + core4 { + cpu = <&cpu4>; + }; + + core5 { + cpu = <&cpu5>; + }; + }; + + cluster1 { + core0 { + cpu = <&cpu6>; + }; + + core1 { + cpu = <&cpu7>; + }; + + core2 { + cpu = <&cpu8>; + }; + + core3 { + cpu = <&cpu9>; + }; + + core4 { + cpu = <&cpu10>; + }; + + core5 { + cpu = <&cpu11>; + }; + }; + + cluster2 { + core0 { + cpu = <&cpu12>; + }; + + core1 { + cpu = <&cpu13>; + }; + + core2 { + cpu = <&cpu14>; + }; + + core3 { + cpu = <&cpu15>; + }; + + core4 { + cpu = <&cpu16>; + }; + + core5 { + cpu = <&cpu17>; + }; + }; + }; + + idle-states { + entry-method = "psci"; + + core_off_c4: cluster-c4 { + compatible = "arm,idle-state"; + idle-state-name = "retention"; + entry-latency-us = <93>; + exit-latency-us = <129>; + min-residency-us = <560>; + arm,psci-suspend-param = <0x00000003>; + }; + }; + + domain-idle-states { + cluster_pwr_dn: cluster-sleep-0 { + compatible = "domain-idle-state"; + arm,psci-suspend-param = <0x01000053>; + entry-latency-us = <2150>; + exit-latency-us = <1983>; + min-residency-us = <9144>; + }; + + domain_ss3: domain-sleep-0 { + compatible = "domain-idle-state"; + arm,psci-suspend-param = <0x02000153>; + entry-latency-us = <2800>; + exit-latency-us = <4400>; + min-residency-us = <10150>; + }; + }; + }; + + firmware: firmware { + scm { + compatible = "qcom,scm-nord", + "qcom,scm"; + qcom,dload-mode = <&tcsr 0x79000>; + }; + + pdp_scmi: scmi { + compatible = "arm,scmi"; + mboxes = <&pdp0_mbox 0>, + <&pdp0_mbox 11>, + <&pdp0_mbox 1>; + mbox-names = "tx", + "tx_reply", + "rx"; + shmem = <&pdp0_a2p>, + <&pdp0_p2a>; + #address-cells = <1>; + #size-cells = <0>; + + cpu_perf: protocol@13 { + reg = <0x13>; + #clock-cells = <1>; + }; + }; + }; + + memory@80000000 { + device_type = "memory"; + /* Size will be updated by bootloader */ + reg = <0x0 0x80000000 0x0 0x0>; + }; + + psci { + compatible = "arm,psci-1.0"; + method = "smc"; + + cpu0_pd: power-domain-cpu0 { + #power-domain-cells = <0>; + power-domains = <&cluster0_pd>; + domain-idle-states = <&core_off_c4>; + }; + + cpu1_pd: power-domain-cpu1 { + #power-domain-cells = <0>; + power-domains = <&cluster0_pd>; + domain-idle-states = <&core_off_c4>; + }; + + cpu2_pd: power-domain-cpu2 { + #power-domain-cells = <0>; + power-domains = <&cluster0_pd>; + domain-idle-states = <&core_off_c4>; + }; + + cpu3_pd: power-domain-cpu3 { + #power-domain-cells = <0>; + power-domains = <&cluster0_pd>; + domain-idle-states = <&core_off_c4>; + }; + + cpu4_pd: power-domain-cpu4 { + #power-domain-cells = <0>; + power-domains = <&cluster0_pd>; + domain-idle-states = <&core_off_c4>; + }; + + cpu5_pd: power-domain-cpu5 { + #power-domain-cells = <0>; + power-domains = <&cluster0_pd>; + domain-idle-states = <&core_off_c4>; + }; + + cpu6_pd: power-domain-cpu6 { + #power-domain-cells = <0>; + power-domains = <&cluster1_pd>; + domain-idle-states = <&core_off_c4>; + }; + + cpu7_pd: power-domain-cpu7 { + #power-domain-cells = <0>; + power-domains = <&cluster1_pd>; + domain-idle-states = <&core_off_c4>; + }; + + cpu8_pd: power-domain-cpu8 { + #power-domain-cells = <0>; + power-domains = <&cluster1_pd>; + domain-idle-states = <&core_off_c4>; + }; + + cpu9_pd: power-domain-cpu9 { + #power-domain-cells = <0>; + power-domains = <&cluster1_pd>; + domain-idle-states = <&core_off_c4>; + }; + + cpu10_pd: power-domain-cpu10 { + #power-domain-cells = <0>; + power-domains = <&cluster1_pd>; + domain-idle-states = <&core_off_c4>; + }; + + cpu11_pd: power-domain-cpu11 { + #power-domain-cells = <0>; + power-domains = <&cluster1_pd>; + domain-idle-states = <&core_off_c4>; + }; + + cpu12_pd: power-domain-cpu12 { + #power-domain-cells = <0>; + power-domains = <&cluster2_pd>; + domain-idle-states = <&core_off_c4>; + }; + + cpu13_pd: power-domain-cpu13 { + #power-domain-cells = <0>; + power-domains = <&cluster2_pd>; + domain-idle-states = <&core_off_c4>; + }; + + cpu14_pd: power-domain-cpu14 { + #power-domain-cells = <0>; + power-domains = <&cluster2_pd>; + domain-idle-states = <&core_off_c4>; + }; + + cpu15_pd: power-domain-cpu15 { + #power-domain-cells = <0>; + power-domains = <&cluster2_pd>; + domain-idle-states = <&core_off_c4>; + }; + + cpu16_pd: power-domain-cpu16 { + #power-domain-cells = <0>; + power-domains = <&cluster2_pd>; + domain-idle-states = <&core_off_c4>; + }; + + cpu17_pd: power-domain-cpu17 { + #power-domain-cells = <0>; + power-domains = <&cluster2_pd>; + domain-idle-states = <&core_off_c4>; + }; + + cluster0_pd: power-domain-cluster0 { + #power-domain-cells = <0>; + power-domains = <&system_pd>; + domain-idle-states = <&cluster_pwr_dn>; + }; + + cluster1_pd: power-domain-cluster1 { + #power-domain-cells = <0>; + power-domains = <&system_pd>; + domain-idle-states = <&cluster_pwr_dn>; + }; + + cluster2_pd: power-domain-cluster2 { + #power-domain-cells = <0>; + power-domains = <&system_pd>; + domain-idle-states = <&cluster_pwr_dn>; + }; + + system_pd: power-domain-system { + #power-domain-cells = <0>; + domain-idle-states = <&domain_ss3>; + }; + }; + + reserved_memory: reserved-memory { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + cpucp_scandump_mem: cpucp-scandump-region@80000000 { + reg = <0x0 0x80000000 0x0 0x800000>; + no-map; + }; + + tme_sail_mem: tme-sail-region@81ff0000 { + reg = <0x0 0x81ff0000 0x0 0x10000>; + no-map; + }; + + tz_sail_mailbox_mem: tz-sail-mailbox-region@82000000 { + reg = <0x0 0x82000000 0x0 0x8000>; + no-map; + }; + + sail_mailbox_mem: sail-mailbox-region@82008000 { + reg = <0x0 0x82008000 0x0 0x1f8000>; + no-map; + }; + + sail_ota_mem: sail-ota-region@82200000 { + reg = <0x0 0x82200000 0x0 0x5ff000>; + no-map; + }; + + sail_vdt_mem: sail-vdt-region@827ff000 { + reg = <0x0 0x827ff000 0x0 0x1000>; + no-map; + }; + + hyp_mem: hyp-region@82800000 { + reg = <0x0 0x82800000 0x0 0x2400000>; + no-map; + }; + + deepsleep_mem: deepsleep-region@84c00000 { + reg = <0x0 0x84c00000 0x0 0x800000>; + no-map; + }; + + deepsleep_backup_mem: deepsleep-backup-region@86a00000 { + reg = <0x0 0x86a00000 0x0 0x200000>; + no-map; + }; + + soccp_fe_vm_0: soccp-fe-vm-0-region@86c00000 { + reg = <0x0 0x86c00000 0x0 0xac000>; + no-map; + }; + + soccp_fe_vm_1: soccp-fe-vm-1-region@86cac000 { + reg = <0x0 0x86cac000 0x0 0x18d000>; + no-map; + }; + + soccp_fe_vm_2: soccp-fe-vm-2-region@86e39000 { + reg = <0x0 0x86e39000 0x0 0x1c7000>; + no-map; + }; + + tme_crash_dump_mem: tme-crash-dump-region@87000000 { + reg = <0x0 0x87000000 0x0 0xa0000>; + no-map; + }; + + tme_log_mem: tme-log-region@87140000 { + reg = <0x0 0x87140000 0x0 0x4000>; + no-map; + }; + + aop_cmd_db_p_mem: aop-cmd-db-p-region@87148000 { + compatible = "qcom,cmd-db"; + reg = <0x0 0x87148000 0x0 0x20000>; + no-map; + }; + + nsp_sync_buffer_mem: nsp-sync-buffer-region@871ff000 { + reg = <0x0 0x871ff000 0x0 0x1000>; + no-map; + }; + + ddr_training_checksum_data_mem: ddr-training-checksum-data-region@87200000 { + reg = <0x0 0x87200000 0x0 0x2000>; + no-map; + }; + + xbl_dtlog_mem: xbl-dtlog-region@87202000 { + reg = <0x0 0x87202000 0x0 0x60000>; + no-map; + }; + + xbl_ramdump_mem: xbl-ramdump-region@87262000 { + reg = <0x0 0x87262000 0x0 0x1c0000>; + no-map; + }; + + uefi_log: uefi-log@87442000 { + reg = <0x0 0x87442000 0x0 0x10000>; + no-map; + }; + + secdata_apss_mem: secdata-apss-region@87452000 { + reg = <0x0 0x87452000 0x0 0x1000>; + no-map; + }; + + antireplay_emulation_mem: antireplay-emulation-region@87453000 { + reg = <0x0 0x87453000 0x0 0x1000>; + no-map; + }; + + soccp_sdi_mem: soccp-sdi-region@87454000 { + reg = <0x0 0x87454000 0x0 0x40000>; + no-map; + }; + + hyp_mem_database_mem: hyp-mem-database-region@87494000 { + reg = <0x0 0x87494000 0x0 0x60000>; + no-map; + }; + + pmic_mini_dump_mem: pmic-mini-dump-region@874f4000 { + reg = <0x0 0x874f4000 0x0 0x80000>; + no-map; + }; + + qup_fw_mem: qup-fw-region@87574000 { + reg = <0x0 0x87574000 0x0 0x20000>; + no-map; + }; + + softsku_mem: softsku-region@87594000 { + reg = <0x0 0x87594000 0x0 0x9000>; + no-map; + }; + + resource_scheduler_mem: resource-scheduler-region@8759d000 { + reg = <0x0 0x8759d000 0x0 0x20000>; + no-map; + }; + + pdp_ns_mem: pdp-ns-mem-region@87600000 { + reg = <0x0 0x87600000 0x0 0x8000>, + <0x0 0x87609000 0x0 0x1f7000>; + no-map; + }; + + pdp0_p2a: scmi-shmem@87608000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0x87608000 0x0 0x80>; + no-map; + }; + + pdp0_a2p: scmi-shmem@87608180 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0x87608180 0x0 0x80>; + no-map; + }; + + tz_stat_mem: tz-stat-region@87a00000 { + reg = <0x0 0x87a00000 0x0 0x100000>; + no-map; + }; + + qdss_apps_mem: qdss-apps-region@87b00000 { + reg = <0x0 0x87b00000 0x0 0x2000000>; + no-map; + }; + + global_sync_mem: global-sync-region@89f00000 { + reg = <0x0 0x89f00000 0x0 0x400000>; + no-map; + }; + + tzffi_mem: tzffi-region@8a300000 { + compatible = "shared-dma-pool"; + reg = <0x0 0x8a300000 0x0 0x1400000>; + no-map; + }; + + gunyah_md_mem: gunyah-md-region@8b700000 { + reg = <0x0 0x8b700000 0x0 0x80000>; + no-map; + }; + + flashless_qntm_tool_mem: flashless-qntm-tool-region@8b780000 { + reg = <0x0 0x8b780000 0x0 0x182000>; + no-map; + }; + + ipa_fw_mem: ipa-fw-region@8bb00000 { + reg = <0x0 0x8bb00000 0x0 0x10000>; + no-map; + }; + + ipa_gsi_mem: ipa-gsi-region@8bb10000 { + reg = <0x0 0x8bb10000 0x0 0xa000>; + no-map; + }; + + gpu_microcode_mem: gpu-microcode-region@8bb1a000 { + reg = <0x0 0x8bb1a000 0x0 0x2000>; + no-map; + }; + + gpu_microcode_2_mem: gpu-microcode-2-region@8bb1c000 { + reg = <0x0 0x8bb1c000 0x0 0x2000>; + no-map; + }; + + soccp_mem: soccp-region@8bc00000 { + reg = <0x0 0x8bc00000 0x0 0x300000>; + no-map; + }; + + cvp_mem: cvp-region@8d100000 { + reg = <0x0 0x8d100000 0x0 0x800000>; + no-map; + }; + + cdsp0_mem: cdsp0-region@8d900000 { + reg = <0x0 0x8d900000 0x0 0x2300000>; + no-map; + }; + + cdsp1_mem: cdsp1-region@8fc00000 { + reg = <0x0 0x8fc00000 0x0 0x2300000>; + no-map; + }; + + cdsp2_mem: cdsp2-region@91f00000 { + reg = <0x0 0x91f00000 0x0 0x2300000>; + no-map; + }; + + cdsp3_mem: cdsp3-region@94200000 { + reg = <0x0 0x94200000 0x0 0x2300000>; + no-map; + }; + + hpass_dsp0_mem: hpass-dsp0-region@96500000 { + reg = <0x0 0x96500000 0x0 0x2800000>; + no-map; + }; + + hpass_dsp1_mem: hpass-dsp1-region@98d00000 { + reg = <0x0 0x98d00000 0x0 0x2800000>; + no-map; + }; + + hpass_dsp2_mem: hpass-dsp2-region@9b500000 { + reg = <0x0 0x9b500000 0x0 0x2800000>; + no-map; + }; + + q6_cdsp0_dtb_mem: q6-cdsp0-dtb-region@9dd00000 { + reg = <0x0 0x9dd00000 0x0 0x80000>; + no-map; + }; + + q6_cdsp1_dtb_mem: q6-cdsp1-dtb-region@9dd80000 { + reg = <0x0 0x9dd80000 0x0 0x80000>; + no-map; + }; + + q6_cdsp2_dtb_mem: q6-cdsp2-dtb-region@9de00000 { + reg = <0x0 0x9de00000 0x0 0x80000>; + no-map; + }; + + q6_cdsp3_dtb_mem: q6-cdsp3-dtb-region@9de80000 { + reg = <0x0 0x9de80000 0x0 0x80000>; + no-map; + }; + + hpass_dsp0_dtb_mem: hpass-dsp0-dtb-region@9df00000 { + reg = <0x0 0x9df00000 0x0 0x80000>; + no-map; + }; + + hpass_dsp1_dtb_mem: hpass-dsp1-dtb-region@9df80000 { + reg = <0x0 0x9df80000 0x0 0x80000>; + no-map; + }; + + hpass_dsp2_dtb_mem: hpass-dsp2-dtb-region@9e000000 { + reg = <0x0 0x9e000000 0x0 0x100000>; + no-map; + }; + + camera_icp_1_mem: camera-icp-1-region@9e100000 { + reg = <0x0 0x9e100000 0x0 0x800000>; + no-map; + }; + + camera_icp_2_mem: camera-icp-2-region@9e900000 { + reg = <0x0 0x9e900000 0x0 0x800000>; + no-map; + }; + + camera_qup_1_mem: camera-qup-1-region@9f100000 { + reg = <0x0 0x9f100000 0x0 0x200000>; + no-map; + }; + + camera_qup_2_mem: camera-qup-2-region@9f300000 { + reg = <0x0 0x9f300000 0x0 0x200000>; + no-map; + }; + + video_mem: video-region@9f500000 { + reg = <0x0 0x9f500000 0x0 0xc00000>; + no-map; + }; + + pil_umd_reserved: mdt-load-region@a0100000 { + reg = <0x0 0xa0100000 0x0 0x100000>; + no-map; + }; + + mm_dspq: mm-dspq-region@ba200000 { + reg = <0x0 0xba200000 0x0 0x200000>; + no-map; + }; + + display_config_reserved: display-config-region@ba400000 { + reg = <0x0 0xba400000 0x0 0xa00000>; + no-map; + }; + + mm_calibration_data_mem: mm-calibration-data-region@bae00000 { + reg = <0x0 0xbae00000 0x0 0x800000>; + no-map; + }; + + audio_config_mem: audio-config-region@bb600000 { + reg = <0x0 0xbb600000 0x0 0xa00000>; + no-map; + }; + + dare_tz_mem: dare-tz-region@bc000000 { + reg = <0x0 0xbc000000 0x0 0xa300000>; + no-map; + }; + + hpass_rpc_remote_heap_mem: hpass-rpc-remote-heap-region@d4600000 { + reg = <0x0 0xd4600000 0x0 0x800000>; + no-map; + }; + + mdf_mem: mdf-region@d4e00000 { + reg = <0x0 0xd4e00000 0x0 0x2000000>; + no-map; + }; + + firmware_mem: firmware-region@d6e00000 { + reg = <0x0 0xd6e00000 0x0 0x800000>; + no-map; + }; + + firmware_shared_mem: firmware-shared-region@d7650000 { + reg = <0x0 0xd7650000 0x0 0x180000>; + no-map; + }; + + firmware_logs_mem: firmware-logs-region@d77d0000 { + reg = <0x0 0xd77d0000 0x0 0x20000>; + no-map; + }; + + sail_p_mem: sail-p-region@8c0000000 { + reg = <0x8 0xc0000000 0x0 0x8000000>; + no-map; + }; + + reserved_mem2: reserved-region@8c8000000 { + reg = <0x8 0xc8000000 0x0 0x18000000>; + no-map; + }; + + dump_mem: mem-dump-region { + alloc-ranges = <0x0 0x00000000 0x0 0xffffffff>; + size = <0x0 0x79b0000>; + }; + }; + + soc: soc@0 { + compatible = "simple-bus"; + #address-cells = <2>; + #size-cells = <2>; + dma-ranges = <0 0 0 0 0x10 0>; + ranges = <0 0 0 0 0x10 0>; + + qupv3_2: geniqup@8c0000 { + compatible = "qcom,geni-se-qup"; + reg = <0x0 0x008c0000 0x0 0x2000>; + #address-cells = <2>; + #size-cells = <2>; + iommus = <&apps_smmu_0 0x15a3 0x0>; + ranges; + + i2c14: i2c@880000 { + compatible = "qcom,geni-i2c"; + reg = <0x0 0x00880000 0x0 0x4000>; + interrupts = ; + + #address-cells = <1>; + #size-cells = <0>; + + status = "disabled"; + }; + + spi14: spi@880000 { + compatible = "qcom,geni-spi"; + reg = <0x0 0x00880000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + uart14: serial@880000 { + compatible = "qcom,geni-uart"; + reg = <0x0 0x00880000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + i2c15: i2c@884000 { + compatible = "qcom,geni-i2c"; + reg = <0x0 0x00884000 0x0 0x4000>; + interrupts = ; + + #address-cells = <1>; + #size-cells = <0>; + + status = "disabled"; + }; + + spi15: spi@884000 { + compatible = "qcom,geni-spi"; + reg = <0x0 0x00884000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + uart15: serial@884000 { + compatible = "qcom,geni-debug-uart"; + reg = <0x0 0x00884000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + i2c16: i2c@888000 { + compatible = "qcom,geni-i2c"; + reg = <0x0 0x00888000 0x0 0x4000>; + interrupts = ; + + #address-cells = <1>; + #size-cells = <0>; + + status = "disabled"; + }; + + spi16: spi@888000 { + compatible = "qcom,geni-spi"; + reg = <0x0 0x00888000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + uart16: serial@888000 { + compatible = "qcom,geni-uart"; + reg = <0x0 0x00888000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + i2c17: i2c@88c000 { + compatible = "qcom,geni-i2c"; + reg = <0x0 0x0088c000 0x0 0x4000>; + interrupts = ; + + #address-cells = <1>; + #size-cells = <0>; + + status = "disabled"; + }; + + spi17: spi@88c000 { + compatible = "qcom,geni-spi"; + reg = <0x0 0x0088c000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + uart17: serial@88c000 { + compatible = "qcom,geni-uart"; + reg = <0x0 0x0088c000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + i2c18: i2c@890000 { + compatible = "qcom,geni-i2c"; + reg = <0x0 0x00890000 0x0 0x4000>; + interrupts = ; + + #address-cells = <1>; + #size-cells = <0>; + + status = "disabled"; + }; + + spi18: spi@890000 { + compatible = "qcom,geni-spi"; + reg = <0x0 0x00890000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + uart18: serial@890000 { + compatible = "qcom,geni-uart"; + reg = <0x0 0x00890000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + i2c19: i2c@894000 { + compatible = "qcom,geni-i2c"; + reg = <0x0 0x00894000 0x0 0x4000>; + interrupts = ; + + #address-cells = <1>; + #size-cells = <0>; + + status = "disabled"; + }; + + spi19: spi@894000 { + compatible = "qcom,geni-spi"; + reg = <0x0 0x00894000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + uart19: serial@894000 { + compatible = "qcom,geni-uart"; + reg = <0x0 0x00894000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + i2c20: i2c@898000 { + compatible = "qcom,geni-i2c"; + reg = <0x0 0x00898000 0x0 0x4000>; + interrupts = ; + + #address-cells = <1>; + #size-cells = <0>; + + status = "disabled"; + }; + + spi20: spi@898000 { + compatible = "qcom,geni-spi"; + reg = <0x0 0x00898000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + uart20: serial@898000 { + compatible = "qcom,geni-uart"; + reg = <0x0 0x00898000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + }; + + qupv3_0: geniqup@9c0000 { + compatible = "qcom,geni-se-qup"; + reg = <0x0 0x009c0000 0x0 0x2000>; + #address-cells = <2>; + #size-cells = <2>; + iommus = <&apps_smmu_2 0x1003 0x0>; + ranges; + + i2c0: i2c@980000 { + compatible = "qcom,geni-i2c"; + reg = <0x0 0x00980000 0x0 0x4000>; + interrupts = ; + + #address-cells = <1>; + #size-cells = <0>; + + status = "disabled"; + }; + + spi0: spi@980000 { + compatible = "qcom,geni-spi"; + reg = <0x0 0x00980000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + uart0: serial@980000 { + compatible = "qcom,geni-uart"; + reg = <0x0 0x00980000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + i2c1: i2c@984000 { + compatible = "qcom,geni-i2c"; + reg = <0x0 0x00984000 0x0 0x4000>; + interrupts = ; + + #address-cells = <1>; + #size-cells = <0>; + + status = "disabled"; + }; + + spi1: spi@984000 { + compatible = "qcom,geni-spi"; + reg = <0x0 0x00984000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + uart1: serial@984000 { + compatible = "qcom,geni-uart"; + reg = <0x0 0x00984000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + i2c2: i2c@988000 { + compatible = "qcom,geni-i2c"; + reg = <0x0 0x00988000 0x0 0x4000>; + interrupts = ; + + #address-cells = <1>; + #size-cells = <0>; + + status = "disabled"; + }; + + spi2: spi@988000 { + compatible = "qcom,geni-spi"; + reg = <0x0 0x00988000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + uart2: serial@988000 { + compatible = "qcom,geni-uart"; + reg = <0x0 0x00988000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + i2c3: i2c@98c000 { + compatible = "qcom,geni-i2c"; + reg = <0x0 0x0098c000 0x0 0x4000>; + interrupts = ; + + #address-cells = <1>; + #size-cells = <0>; + + status = "disabled"; + }; + + spi3: spi@98c000 { + compatible = "qcom,geni-spi"; + reg = <0x0 0x0098c000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + uart3: serial@98c000 { + compatible = "qcom,geni-uart"; + reg = <0x0 0x0098c000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + i2c4: i2c@990000 { + compatible = "qcom,geni-i2c"; + reg = <0x0 0x00990000 0x0 0x4000>; + interrupts = ; + + #address-cells = <1>; + #size-cells = <0>; + + status = "disabled"; + }; + + spi4: spi@990000 { + compatible = "qcom,geni-spi"; + reg = <0x0 0x00990000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + uart4: serial@990000 { + compatible = "qcom,geni-uart"; + reg = <0x0 0x00990000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + i2c5: i2c@994000 { + compatible = "qcom,geni-i2c"; + reg = <0x0 0x00994000 0x0 0x4000>; + interrupts = ; + + #address-cells = <1>; + #size-cells = <0>; + + status = "disabled"; + }; + + spi5: spi@994000 { + compatible = "qcom,geni-spi"; + reg = <0x0 0x00994000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + uart5: serial@994000 { + compatible = "qcom,geni-uart"; + reg = <0x0 0x00994000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + }; + + qupv3_1: geniqup@ac0000 { + compatible = "qcom,geni-se-qup"; + reg = <0x0 0x00ac0000 0x0 0x2000>; + #address-cells = <2>; + #size-cells = <2>; + iommus = <&apps_smmu_2 0x1043 0x0>; + ranges; + + i2c7: i2c@a80000 { + compatible = "qcom,geni-i2c"; + reg = <0x0 0x00a80000 0x0 0x4000>; + interrupts = ; + + #address-cells = <1>; + #size-cells = <0>; + + status = "disabled"; + }; + + spi7: spi@a80000 { + compatible = "qcom,geni-spi"; + reg = <0x0 0x00a80000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + uart7: serial@a80000 { + compatible = "qcom,geni-uart"; + reg = <0x0 0x00a80000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + i2c8: i2c@a84000 { + compatible = "qcom,geni-i2c"; + reg = <0x0 0x00a84000 0x0 0x4000>; + interrupts = ; + + #address-cells = <1>; + #size-cells = <0>; + + status = "disabled"; + }; + + spi8: spi@a84000 { + compatible = "qcom,geni-spi"; + reg = <0x0 0x00a84000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + uart8: serial@a84000 { + compatible = "qcom,geni-uart"; + reg = <0x0 0x00a84000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + i2c9: i2c@a88000 { + compatible = "qcom,geni-i2c"; + reg = <0x0 0x00a88000 0x0 0x4000>; + interrupts = ; + + #address-cells = <1>; + #size-cells = <0>; + + status = "disabled"; + }; + + uart9: serial@a88000 { + compatible = "qcom,geni-uart"; + reg = <0x0 0x00a88000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + i2c10: i2c@a8c000 { + compatible = "qcom,geni-i2c"; + reg = <0x0 0x00a8c000 0x0 0x4000>; + interrupts = ; + + #address-cells = <1>; + #size-cells = <0>; + + status = "disabled"; + }; + + uart10: serial@a8c000 { + compatible = "qcom,geni-uart"; + reg = <0x0 0x00a8c000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + i2c11: i2c@a90000 { + compatible = "qcom,geni-i2c"; + reg = <0x0 0x00a90000 0x0 0x4000>; + interrupts = ; + + #address-cells = <1>; + #size-cells = <0>; + + status = "disabled"; + }; + + spi11: spi@a90000 { + compatible = "qcom,geni-spi"; + reg = <0x0 0x00a90000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + uart11: serial@a90000 { + compatible = "qcom,geni-uart"; + reg = <0x0 0x00a90000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + i2c12: i2c@a94000 { + compatible = "qcom,geni-i2c"; + reg = <0x0 0x00a94000 0x0 0x4000>; + interrupts = ; + + #address-cells = <1>; + #size-cells = <0>; + + status = "disabled"; + }; + + spi12: spi@a94000 { + compatible = "qcom,geni-spi"; + reg = <0x0 0x00a94000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + uart12: serial@a94000 { + compatible = "qcom,geni-uart"; + reg = <0x0 0x00a94000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + i2c13: i2c@a98000 { + compatible = "qcom,geni-i2c"; + reg = <0x0 0x00a98000 0x0 0x4000>; + interrupts = ; + + #address-cells = <1>; + #size-cells = <0>; + + status = "disabled"; + }; + + spi13: spi@a98000 { + compatible = "qcom,geni-spi"; + reg = <0x0 0x00a98000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + + uart13: serial@a98000 { + compatible = "qcom,geni-uart"; + reg = <0x0 0x00a98000 0x0 0x4000>; + interrupts = ; + + status = "disabled"; + }; + }; + + rng: rng@10c3000 { + compatible = "qcom,nord-trng", + "qcom,trng"; + reg = <0x0 0x010c3000 0x0 0x1000>; + }; + + ufs_mem_hc: ufshc@1d44000 { + compatible = "qcom,nord-ufshc", + "qcom,ufshc", + "jedec,ufs-2.0"; + interrupts = ; + lanes-per-direction = <2>; + iommus = <&apps_smmu_0 0x14c0 0x0>; + dma-coherent; + msi-parent = <&gic_its 0x14c0>; + }; + + cryptobam: dma-controller@1dc4000 { + compatible = "qcom,bam-v1.7.4", "qcom,bam-v1.7.0"; + reg = <0x0 0x01dc4000 0x0 0x28000>; + interrupts = ; + #dma-cells = <1>; + iommus = <&apps_smmu_0 0x1689 0>; + qcom,ee = <0>; + qcom,num-ees = <4>; + num-channels = <20>; + qcom,controlled-remotely; + }; + + crypto: crypto@1dfa000 { + compatible = "qcom,nord-qce", "qcom,sm8150-qce", "qcom,qce"; + reg = <0x0 0x01dfa000 0x0 0x6000>; + dmas = <&cryptobam 4>, <&cryptobam 5>; + dma-names = "rx", "tx"; + iommus = <&apps_smmu_0 0x1689 0>; + }; + + tcsr_mutex: hwlock@1f40000 { + compatible = "qcom,tcsr-mutex"; + reg = <0x0 0x01f40000 0x0 0x20000>; + #hwlock-cells = <1>; + }; + + tcsr: syscon@1f60000 { + compatible = "qcom,nord-tcsr", + "syscon"; + reg = <0x0 0x01f60000 0x0 0xa0000>; + }; + + pdc: interrupt-controller@b220000 { + compatible = "qcom,nord-pdc", + "qcom,pdc"; + reg = <0x0 0x0b220000 0x0 0x10000>; + qcom,pdc-ranges = <0 745 43>, + <67 543 31>, + <98 609 32>, + <130 717 12>, + <142 251 5>, + <147 796 16>; + #interrupt-cells = <2>; + interrupt-parent = <&intc>; + interrupt-controller; + }; + + tsens0: thermal-sensor@c22c000 { + compatible = "qcom,nord-tsens", + "qcom,tsens-v2"; + reg = <0x0 0x0c22c000 0x0 0x1000>, + <0x0 0x0c222000 0x0 0x1000>; + interrupts = , + ; + interrupt-names = "uplow", + "critical"; + #qcom,sensors = <16>; + #thermal-sensor-cells = <1>; + }; + + tsens1: thermal-sensor@c22d000 { + compatible = "qcom,nord-tsens", + "qcom,tsens-v2"; + reg = <0x0 0x0c22d000 0x0 0x1000>, + <0x0 0x0c223000 0x0 0x1000>; + interrupts = , + ; + interrupt-names = "uplow", + "critical"; + #qcom,sensors = <16>; + #thermal-sensor-cells = <1>; + }; + + tsens2: thermal-sensor@c22e000 { + compatible = "qcom,nord-tsens", + "qcom,tsens-v2"; + reg = <0x0 0x0c22e000 0x0 0x1000>, + <0x0 0x0c224000 0x0 0x1000>; + interrupts = , + ; + interrupt-names = "uplow", + "critical"; + #qcom,sensors = <16>; + #thermal-sensor-cells = <1>; + }; + + tsens3: thermal-sensor@c22f000 { + compatible = "qcom,nord-tsens", + "qcom,tsens-v2"; + reg = <0x0 0x0c22f000 0x0 0x1000>, + <0x0 0x0c225000 0x0 0x1000>; + interrupts = , + ; + interrupt-names = "uplow", + "critical"; + #qcom,sensors = <16>; + #thermal-sensor-cells = <1>; + }; + + tsens4: thermal-sensor@c230000 { + compatible = "qcom,nord-tsens", + "qcom,tsens-v2"; + reg = <0x0 0x0c230000 0x0 0x1000>, + <0x0 0x0c226000 0x0 0x1000>; + interrupts = , + ; + interrupt-names = "uplow", + "critical"; + #qcom,sensors = <16>; + #thermal-sensor-cells = <1>; + }; + + tsens5: thermal-sensor@c231000 { + compatible = "qcom,nord-tsens", + "qcom,tsens-v2"; + reg = <0x0 0x0c231000 0x0 0x1000>, + <0x0 0x0c227000 0x0 0x1000>; + interrupts = , + ; + interrupt-names = "uplow", + "critical"; + #qcom,sensors = <16>; + #thermal-sensor-cells = <1>; + }; + + tsens6: thermal-sensor@c232000 { + compatible = "qcom,nord-tsens", + "qcom,tsens-v2"; + reg = <0x0 0x0c232000 0x0 0x1000>, + <0x0 0x0c228000 0x0 0x1000>; + interrupts = , + ; + interrupt-names = "uplow", + "critical"; + #qcom,sensors = <16>; + #thermal-sensor-cells = <1>; + }; + + tsens7: thermal-sensor@c233000 { + compatible = "qcom,nord-tsens", + "qcom,tsens-v2"; + reg = <0x0 0x0c233000 0x0 0x1000>, + <0x0 0x0c229000 0x0 0x1000>; + interrupts = , + ; + interrupt-names = "uplow", + "critical"; + #qcom,sensors = <16>; + #thermal-sensor-cells = <1>; + }; + + tlmm: pinctrl@f100000 { + compatible = "qcom,nord-tlmm"; + reg = <0x0 0x0f100000 0x0 0xc0000>; + interrupts = ; + gpio-controller; + #gpio-cells = <2>; + interrupt-controller; + #interrupt-cells = <2>; + gpio-ranges = <&tlmm 0 0 181>; + wakeup-parent = <&pdc>; + }; + + apps_smmu_0: iommu@15a00000 { + compatible = "qcom,nord-smmu-500", + "qcom,smmu-500", + "arm,mmu-500"; + reg = <0x0 0x15a00000 0x0 0x100000>; + #iommu-cells = <2>; + #global-interrupts = <1>; + dma-coherent; + interrupts = , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ; + }; + + apps_smmu_1: iommu@15c00000 { + compatible = "qcom,nord-smmu-500", + "qcom,smmu-500", + "arm,mmu-500"; + reg = <0x0 0x15c00000 0x0 0x100000>; + #iommu-cells = <2>; + #global-interrupts = <1>; + dma-coherent; + interrupts = , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ; + }; + + apps_smmu_2: iommu@15e00000 { + compatible = "qcom,nord-smmu-500", + "qcom,smmu-500", + "arm,mmu-500"; + reg = <0x0 0x15e00000 0x0 0x100000>; + #iommu-cells = <2>; + #global-interrupts = <1>; + dma-coherent; + interrupts = , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ; + }; + + intc: interrupt-controller@17000000 { + compatible = "arm,gic-v3"; + reg = <0x0 0x17000000 0x0 0x10000>, + <0x0 0x17080000 0x0 0x480000>; + interrupts = ; + #interrupt-cells = <3>; + interrupt-controller; + #redistributor-regions = <1>; + redistributor-stride = <0x0 0x40000>; + #address-cells = <2>; + #size-cells = <2>; + ranges; + + gic_its: msi-controller@17040000 { + compatible = "arm,gic-v3-its"; + reg = <0x0 0x17040000 0x0 0x40000>; + msi-controller; + #msi-cells = <1>; + }; + }; + + pdp0_mbox: mailbox@17610000 { + compatible = "qcom,nord-cpucp-mbox", + "qcom,x1e80100-cpucp-mbox"; + reg = <0x0 0x17610000 0x0 0x4c08>, + <0x0 0x19980000 0x0 0x300>; + #mbox-cells = <1>; + interrupts = ; + }; + + memtimer: timer@17810000 { + compatible = "arm,armv7-timer-mem"; + reg = <0x0 0x17810000 0x0 0x1000>; + ranges = <0x0 0x0 0x0 0x20000000>; + #address-cells = <1>; + #size-cells = <1>; + + frame@17811000 { + reg = <0x17811000 0x1000>, + <0x17812000 0x1000>; + interrupts = , + ; + frame-number = <0>; + }; + + frame@17813000 { + reg = <0x17813000 0x1000>; + interrupts = ; + frame-number = <1>; + + status = "disabled"; + }; + + frame@17815000 { + reg = <0x17815000 0x1000>; + interrupts = ; + frame-number = <2>; + + status = "disabled"; + }; + + frame@17817000 { + reg = <0x17817000 0x1000>; + interrupts = ; + frame-number = <3>; + + status = "disabled"; + }; + + frame@17819000 { + reg = <0x17819000 0x1000>; + interrupts = ; + frame-number = <4>; + + status = "disabled"; + }; + + frame@1781b000 { + reg = <0x1781b000 0x1000>; + interrupts = ; + frame-number = <5>; + + status = "disabled"; + }; + + frame@1781d000 { + reg = <0x1781d000 0x1000>; + interrupts = ; + frame-number = <6>; + + status = "disabled"; + }; + }; + + watchdog@17826000 { + compatible = "qcom,apss-wdt-nord", + "qcom,kpss-wdt"; + reg = <0x0 0x17826000 0x0 0x1000>; + clocks = <&sleep_clk>; + interrupts = ; + }; + }; + + arch_timer: timer { + compatible = "arm,armv8-timer"; + interrupts = , + , + , + ; + }; + + thermal_zones: thermal-zones { + ddr-0-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens0 0>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-0-0-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens0 1>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-0-1-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens0 2>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-0-2-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens0 3>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-0-3-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens0 4>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-0-4-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens0 5>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-0-5-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens0 6>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpullc-0-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens0 7>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-1-0-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens0 8>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-1-1-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens0 9>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-1-2-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens0 10>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-1-3-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens0 11>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-1-4-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens0 12>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-1-5-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens0 13>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpullc-1-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens0 14>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + ddr-1-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens0 15>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + ddr-0-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens1 0>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-0-0-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens1 1>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-0-1-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens1 2>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-0-2-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens1 3>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-0-3-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens1 4>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-0-4-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens1 5>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-0-5-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens1 6>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpullc-0-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens1 7>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-1-0-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens1 8>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-1-1-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens1 9>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-1-2-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens1 10>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-1-3-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens1 11>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-1-4-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens1 12>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-1-5-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens1 13>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpullc-1-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens1 14>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + ddr-1-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens1 15>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + amux-2-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens2 0>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-2-0-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens2 1>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-2-1-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens2 2>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-2-2-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens2 3>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-2-3-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens2 4>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-2-4-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens2 5>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-2-5-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens2 6>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpullc-2-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens2 7>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + audhvx-0-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens2 8>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + audhmx-0-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens2 9>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + audhvx-1-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens2 10>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + audhmx-1-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens2 11>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + audhvx-2-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens2 12>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + audhmx-2-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens2 13>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + pcie-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens2 15>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + amux-3-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens3 0>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-2-0-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens3 1>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-2-1-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens3 2>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-2-2-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens3 3>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-2-3-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens3 4>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-2-4-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens3 5>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpu-2-5-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens3 6>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <125000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cpullc-2-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens3 7>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + audhvx-0-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens3 8>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + audhmx-0-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens3 9>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + audhvx-1-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens3 10>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + audhmx-1-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens3 11>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + audhvx-2-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens3 12>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + audhmx-2-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens3 13>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + pcie-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens3 15>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + nsphvx-0-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens4 0>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + nsphvx-0-2-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens4 1>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + nsp-0-0-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens4 2>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + nsp-0-1-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens4 3>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + nsphvx-1-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens4 4>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + nsphvx-1-2-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens4 5>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + nsp-1-0-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens4 6>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + nsp-1-1-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens4 7>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + nsphvx-2-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens4 8>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + nsphvx-2-2-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens4 9>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + nsp-2-0-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens4 10>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + nsp-2-1-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens4 11>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + nsphvx-3-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens4 12>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + nsphvx-3-2-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens4 13>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + nsp-3-0-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens4 14>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + nsp-3-1-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens4 15>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + nsphvx-0-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens5 0>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + nsphvx-0-3-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens5 1>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + nsp-0-0-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens5 2>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + nsp-0-1-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens5 3>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + nsphvx-1-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens5 4>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + nsphvx-1-3-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens5 5>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + nsp-1-0-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens5 6>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + nsp-1-1-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens5 7>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + nsphvx-2-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens5 8>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + nsphvx-2-3-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens5 9>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + nsp-2-0-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens5 10>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + nsp-2-1-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens5 11>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + nsphvx-3-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens5 12>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + nsphvx-3-3-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens5 13>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + nsp-3-0-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens5 14>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + nsp-3-1-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens5 15>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + amux-6-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens6 0>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + gpuss-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens6 1>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + gpuss-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens6 2>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + gpuss-2-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens6 3>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + gpu-0-0-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens6 4>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + gpuss-1-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens6 5>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + gpuss-1-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens6 6>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cv-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens6 7>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + video-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens6 8>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + camera-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens6 9>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + ddr-2-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens6 10>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + ddr-3-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens6 11>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + gpuss-0-1-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens6 12>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + gpuss-1-1-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens6 13>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + gpuss-2-1-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens6 14>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + gpu-0-1-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens6 15>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + amux-7-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens7 0>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + gpuss-0-1-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens7 1>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + gpuss-1-1-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens7 2>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + gpuss-2-1-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens7 3>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + gpu-0-1-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens7 4>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + gpuss-1-2-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens7 5>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + gpu-1-0-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens7 6>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + cv-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens7 7>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + video-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens7 8>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + camera-2-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens7 9>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + ddr-2-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens7 10>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + ddr-3-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens7 11>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + gpuss-0-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens7 12>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + gpuss-1-0-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens7 13>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + gpuss-2-0-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens7 14>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + + gpu-0-0-1-thermal { + polling-delay-passive = <0>; + polling-delay = <0>; + thermal-sensors = <&tsens7 15>; + + trips { + trip-point0 { + temperature = <105000>; + hysteresis = <10000>; + type = "passive"; + }; + + trip-point1 { + temperature = <115000>; + hysteresis = <10000>; + type = "passive"; + }; + }; + }; + }; +}; From 9186c6778ed3a9cb7c4ec3037cc60ede6f42a6f3 Mon Sep 17 00:00:00 2001 From: Deepti Jaggi Date: Thu, 9 Jul 2026 21:20:08 +0800 Subject: [PATCH 537/562] arm64: dts: qcom: Add device tree for Nord GearVM variant Add SoC-level device tree include for Nord GearVM variant, where a VM controls platform resources (clocks, regulators, powerdomains, etc.) as SCMI server. It currently covers: - 64 SCMI shared memory regions reserved at 0xd7600000-0xd763f000 for SMC-based firmware communication channels - Three QUPV3 GENI SE QUP blocks (qupv3_0/1/2) with I2C/SPI/UART controllers using SCMI power and performance domains via scmi11 - UFS host controller with SCMI power domain via scmi3 Signed-off-by: Deepti Jaggi Link: https://lore.kernel.org/r/20260709132013.4096850-3-shengchao.guo@oss.qualcomm.com Signed-off-by: Shawn Guo --- arch/arm64/boot/dts/qcom/nord-gearvm.dtsi | 2847 +++++++++++++++++++++ 1 file changed, 2847 insertions(+) create mode 100644 arch/arm64/boot/dts/qcom/nord-gearvm.dtsi diff --git a/arch/arm64/boot/dts/qcom/nord-gearvm.dtsi b/arch/arm64/boot/dts/qcom/nord-gearvm.dtsi new file mode 100644 index 000000000000..8f29b5f24ef8 --- /dev/null +++ b/arch/arm64/boot/dts/qcom/nord-gearvm.dtsi @@ -0,0 +1,2847 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include "nord.dtsi" + +&firmware { + scmi0: scmi-0 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem0>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi0_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi0_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi0_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi1: scmi-1 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem1>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi1_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi1_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi1_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi2: scmi-2 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem2>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi2_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi2_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi2_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi3: scmi-3 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem3>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi3_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi3_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi3_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi4: scmi-4 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem4>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi4_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi4_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi4_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi5: scmi-5 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem5>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi5_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi5_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi5_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi6: scmi-6 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem6>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi6_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi6_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi6_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi7: scmi-7 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem7>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi7_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi7_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi7_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi8: scmi-8 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem8>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi8_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi8_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi8_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi9: scmi-9 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem9>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi9_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi9_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi9_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi10: scmi-10 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem10>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi10_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi10_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi10_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi11: scmi-11 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem11>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi11_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi11_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi11_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi12: scmi-12 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem12>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi12_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi12_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi12_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi13: scmi-13 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem13>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi13_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi13_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi13_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi14: scmi-14 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem14>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi14_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi14_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi14_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi15: scmi-15 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem15>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi15_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi15_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi15_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi16: scmi-16 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem16>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi16_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi16_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi16_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi17: scmi-17 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem17>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi17_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi17_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi17_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi18: scmi-18 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem18>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi18_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi18_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi18_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi19: scmi-19 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem19>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi19_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi19_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi19_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi20: scmi-20 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem20>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi20_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi20_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi20_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi21: scmi-21 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem21>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi21_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi21_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi21_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi22: scmi-22 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem22>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi22_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi22_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi22_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi23: scmi-23 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem23>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi23_sensor: protocol@15 { + reg = <0x15>; + #thermal-sensor-cells = <1>; + }; + }; + + scmi24: scmi-24 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem24>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi24_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi24_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi24_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi25: scmi-25 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem25>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi25_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi25_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi25_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi26: scmi-26 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem26>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi26_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi26_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi26_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi27: scmi-27 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem27>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi27_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi27_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi27_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi28: scmi-28 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem28>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi28_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi28_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi28_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi29: scmi-29 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem29>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi29_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi29_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi29_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi30: scmi-30 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem30>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi30_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi30_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi30_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi31: scmi-31 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem31>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi31_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi31_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi31_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi32: scmi-32 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem32>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi32_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi32_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi32_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi33: scmi-33 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem33>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi33_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi33_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi33_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi34: scmi-34 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem34>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi34_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi34_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi34_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi35: scmi-35 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem35>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi35_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi35_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi35_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi36: scmi-36 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem36>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi36_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi36_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi36_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi37: scmi-37 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem37>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi37_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi37_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi37_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi38: scmi-38 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem38>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi38_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi38_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi38_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi39: scmi-39 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem39>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi39_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi39_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi39_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi40: scmi-40 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem40>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi40_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi40_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi40_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi41: scmi-41 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem41>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi41_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi41_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi41_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi42: scmi-42 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem42>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi42_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi42_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi42_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi43: scmi-43 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem43>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi43_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi43_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi43_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi44: scmi-44 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem44>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi44_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi44_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi44_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi45: scmi-45 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem45>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi45_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi45_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi45_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi46: scmi-46 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem46>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi46_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi46_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi46_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi47: scmi-47 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem47>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi47_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi47_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi47_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi48: scmi-48 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem48>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi48_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi48_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi48_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi49: scmi-49 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem49>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi49_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi49_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi49_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi50: scmi-50 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem50>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi50_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi50_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi50_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi51: scmi-51 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem51>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi51_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi51_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi51_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi52: scmi-52 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem52>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi52_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi52_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi52_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi53: scmi-53 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem53>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi53_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi53_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi53_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi54: scmi-54 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem54>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi54_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi54_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi54_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi55: scmi-55 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem55>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi55_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi55_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi55_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi56: scmi-56 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem56>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi56_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi56_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi56_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi57: scmi-57 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem57>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi57_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi57_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi57_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi58: scmi-58 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem58>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi58_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi58_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi58_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi59: scmi-59 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem59>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi59_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi59_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi59_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi60: scmi-60 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem60>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi60_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi60_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi60_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi61: scmi-61 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem61>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi61_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi61_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi61_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi62: scmi-62 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem62>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi62_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi62_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi62_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; + + scmi63: scmi-63 { + compatible = "qcom,scmi-smc"; + arm,smc-id = <0xc6008012>; + shmem = <&shmem63>; + interrupts = ; + interrupt-names = "a2p"; + #address-cells = <1>; + #size-cells = <0>; + arm,max-msg = <10>; + arm,max-msg-size = <256>; + arm,max-rx-timeout-ms = <3000>; + + status = "disabled"; + + scmi63_pd: protocol@11 { + reg = <0x11>; + #power-domain-cells = <1>; + }; + + scmi63_dvfs: protocol@13 { + reg = <0x13>; + #power-domain-cells = <1>; + }; + + scmi63_rst: protocol@16 { + reg = <0x16>; + #reset-cells = <1>; + }; + }; +}; +&i2c0 { + compatible = "qcom,sa8797p-geni-i2c", + "qcom,sa8255p-geni-i2c"; + power-domains = <&scmi11_pd 0>, + <&scmi11_dvfs 0>; + power-domain-names = "power", + "perf"; +}; + +&i2c1 { + compatible = "qcom,sa8797p-geni-i2c", + "qcom,sa8255p-geni-i2c"; + power-domains = <&scmi11_pd 1>, + <&scmi11_dvfs 1>; + power-domain-names = "power", + "perf"; +}; + +&i2c2 { + compatible = "qcom,sa8797p-geni-i2c", + "qcom,sa8255p-geni-i2c"; + power-domains = <&scmi11_pd 2>, + <&scmi11_dvfs 2>; + power-domain-names = "power", + "perf"; +}; + +&i2c3 { + compatible = "qcom,sa8797p-geni-i2c", + "qcom,sa8255p-geni-i2c"; + power-domains = <&scmi11_pd 3>, + <&scmi11_dvfs 3>; + power-domain-names = "power", + "perf"; +}; + +&i2c4 { + compatible = "qcom,sa8797p-geni-i2c", + "qcom,sa8255p-geni-i2c"; + power-domains = <&scmi11_pd 4>, + <&scmi11_dvfs 4>; + power-domain-names = "power", + "perf"; +}; + +&i2c5 { + compatible = "qcom,sa8797p-geni-i2c", + "qcom,sa8255p-geni-i2c"; + power-domains = <&scmi11_pd 5>, + <&scmi11_dvfs 5>; + power-domain-names = "power", + "perf"; +}; + +&i2c7 { + compatible = "qcom,sa8797p-geni-i2c", + "qcom,sa8255p-geni-i2c"; + power-domains = <&scmi11_pd 7>, + <&scmi11_dvfs 7>; + power-domain-names = "power", + "perf"; +}; + +&i2c8 { + compatible = "qcom,sa8797p-geni-i2c", + "qcom,sa8255p-geni-i2c"; + power-domains = <&scmi11_pd 8>, + <&scmi11_dvfs 8>; + power-domain-names = "power", + "perf"; +}; + +&i2c9 { + compatible = "qcom,sa8797p-geni-i2c", + "qcom,sa8255p-geni-i2c"; + power-domains = <&scmi11_pd 9>, + <&scmi11_dvfs 9>; + power-domain-names = "power", + "perf"; +}; + +&i2c10 { + compatible = "qcom,sa8797p-geni-i2c", + "qcom,sa8255p-geni-i2c"; + power-domains = <&scmi11_pd 10>, + <&scmi11_dvfs 10>; + power-domain-names = "power", + "perf"; +}; + +&i2c11 { + compatible = "qcom,sa8797p-geni-i2c", + "qcom,sa8255p-geni-i2c"; + power-domains = <&scmi11_pd 11>, + <&scmi11_dvfs 11>; + power-domain-names = "power", + "perf"; +}; + +&i2c12 { + compatible = "qcom,sa8797p-geni-i2c", + "qcom,sa8255p-geni-i2c"; + power-domains = <&scmi11_pd 12>, + <&scmi11_dvfs 12>; + power-domain-names = "power", + "perf"; +}; + +&i2c13 { + compatible = "qcom,sa8797p-geni-i2c", + "qcom,sa8255p-geni-i2c"; + power-domains = <&scmi11_pd 13>, + <&scmi11_dvfs 13>; + power-domain-names = "power", + "perf"; +}; + +&i2c14 { + compatible = "qcom,sa8797p-geni-i2c", + "qcom,sa8255p-geni-i2c"; + power-domains = <&scmi11_pd 14>, + <&scmi11_dvfs 14>; + power-domain-names = "power", + "perf"; +}; + +&i2c15 { + compatible = "qcom,sa8797p-geni-i2c", + "qcom,sa8255p-geni-i2c"; + power-domains = <&scmi11_pd 15>, + <&scmi11_dvfs 15>; + power-domain-names = "power", + "perf"; +}; + +&i2c16 { + compatible = "qcom,sa8797p-geni-i2c", + "qcom,sa8255p-geni-i2c"; + power-domains = <&scmi11_pd 16>, + <&scmi11_dvfs 16>; + power-domain-names = "power", + "perf"; +}; + +&i2c17 { + compatible = "qcom,sa8797p-geni-i2c", + "qcom,sa8255p-geni-i2c"; + power-domains = <&scmi11_pd 17>, + <&scmi11_dvfs 17>; + power-domain-names = "power", + "perf"; +}; + +&i2c18 { + compatible = "qcom,sa8797p-geni-i2c", + "qcom,sa8255p-geni-i2c"; + power-domains = <&scmi11_pd 18>, + <&scmi11_dvfs 18>; + power-domain-names = "power", + "perf"; +}; + +&i2c19 { + compatible = "qcom,sa8797p-geni-i2c", + "qcom,sa8255p-geni-i2c"; + power-domains = <&scmi11_pd 19>, + <&scmi11_dvfs 19>; + power-domain-names = "power", + "perf"; +}; + +&i2c20 { + compatible = "qcom,sa8797p-geni-i2c", + "qcom,sa8255p-geni-i2c"; + power-domains = <&scmi11_pd 20>, + <&scmi11_dvfs 20>; + power-domain-names = "power", + "perf"; +}; + +&qupv3_0 { + compatible = "qcom,sa8797p-geni-se-qup", + "qcom,sa8255p-geni-se-qup"; +}; + +&qupv3_1 { + compatible = "qcom,sa8797p-geni-se-qup", + "qcom,sa8255p-geni-se-qup"; +}; + +&qupv3_2 { + compatible = "qcom,sa8797p-geni-se-qup", + "qcom,sa8255p-geni-se-qup"; +}; + +&reserved_memory { + shmem0: scmi-shmem@d7600000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7600000 0x0 0x1000>; + no-map; + }; + + shmem1: scmi-shmem@d7601000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7601000 0x0 0x1000>; + no-map; + }; + + shmem2: scmi-shmem@d7602000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7602000 0x0 0x1000>; + no-map; + }; + + shmem3: scmi-shmem@d7603000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7603000 0x0 0x1000>; + no-map; + }; + + shmem4: scmi-shmem@d7604000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7604000 0x0 0x1000>; + no-map; + }; + + shmem5: scmi-shmem@d7605000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7605000 0x0 0x1000>; + no-map; + }; + + shmem6: scmi-shmem@d7606000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7606000 0x0 0x1000>; + no-map; + }; + + shmem7: scmi-shmem@d7607000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7607000 0x0 0x1000>; + no-map; + }; + + shmem8: scmi-shmem@d7608000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7608000 0x0 0x1000>; + no-map; + }; + + shmem9: scmi-shmem@d7609000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7609000 0x0 0x1000>; + no-map; + }; + + shmem10: scmi-shmem@d760a000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd760a000 0x0 0x1000>; + no-map; + }; + + shmem11: scmi-shmem@d760b000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd760b000 0x0 0x1000>; + no-map; + }; + + shmem12: scmi-shmem@d760c000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd760c000 0x0 0x1000>; + no-map; + }; + + shmem13: scmi-shmem@d760d000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd760d000 0x0 0x1000>; + no-map; + }; + + shmem14: scmi-shmem@d760e000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd760e000 0x0 0x1000>; + no-map; + }; + + shmem15: scmi-shmem@d760f000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd760f000 0x0 0x1000>; + no-map; + }; + + shmem16: scmi-shmem@d7610000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7610000 0x0 0x1000>; + no-map; + }; + + shmem17: scmi-shmem@d7611000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7611000 0x0 0x1000>; + no-map; + }; + + shmem18: scmi-shmem@d7612000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7612000 0x0 0x1000>; + no-map; + }; + + shmem19: scmi-shmem@d7613000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7613000 0x0 0x1000>; + no-map; + }; + + shmem20: scmi-shmem@d7614000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7614000 0x0 0x1000>; + no-map; + }; + + shmem21: scmi-shmem@d7615000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7615000 0x0 0x1000>; + no-map; + }; + + shmem22: scmi-shmem@d7616000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7616000 0x0 0x1000>; + no-map; + }; + + shmem23: scmi-shmem@d7617000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7617000 0x0 0x1000>; + no-map; + }; + + shmem24: scmi-shmem@d7618000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7618000 0x0 0x1000>; + no-map; + }; + + shmem25: scmi-shmem@d7619000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7619000 0x0 0x1000>; + no-map; + }; + + shmem26: scmi-shmem@d761a000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd761a000 0x0 0x1000>; + no-map; + }; + + shmem27: scmi-shmem@d761b000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd761b000 0x0 0x1000>; + no-map; + }; + + shmem28: scmi-shmem@d761c000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd761c000 0x0 0x1000>; + no-map; + }; + + shmem29: scmi-shmem@d761d000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd761d000 0x0 0x1000>; + no-map; + }; + + shmem30: scmi-shmem@d761e000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd761e000 0x0 0x1000>; + no-map; + }; + + shmem31: scmi-shmem@d761f000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd761f000 0x0 0x1000>; + no-map; + }; + + shmem32: scmi-shmem@d7620000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7620000 0x0 0x1000>; + no-map; + }; + + shmem33: scmi-shmem@d7621000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7621000 0x0 0x1000>; + no-map; + }; + + shmem34: scmi-shmem@d7622000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7622000 0x0 0x1000>; + no-map; + }; + + shmem35: scmi-shmem@d7623000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7623000 0x0 0x1000>; + no-map; + }; + + shmem36: scmi-shmem@d7624000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7624000 0x0 0x1000>; + no-map; + }; + + shmem37: scmi-shmem@d7625000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7625000 0x0 0x1000>; + no-map; + }; + + shmem38: scmi-shmem@d7626000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7626000 0x0 0x1000>; + no-map; + }; + + shmem39: scmi-shmem@d7627000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7627000 0x0 0x1000>; + no-map; + }; + + shmem40: scmi-shmem@d7628000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7628000 0x0 0x1000>; + no-map; + }; + + shmem41: scmi-shmem@d7629000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7629000 0x0 0x1000>; + no-map; + }; + + shmem42: scmi-shmem@d762a000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd762a000 0x0 0x1000>; + no-map; + }; + + shmem43: scmi-shmem@d762b000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd762b000 0x0 0x1000>; + no-map; + }; + + shmem44: scmi-shmem@d762c000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd762c000 0x0 0x1000>; + no-map; + }; + + shmem45: scmi-shmem@d762d000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd762d000 0x0 0x1000>; + no-map; + }; + + shmem46: scmi-shmem@d762e000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd762e000 0x0 0x1000>; + no-map; + }; + + shmem47: scmi-shmem@d762f000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd762f000 0x0 0x1000>; + no-map; + }; + + shmem48: scmi-shmem@d7630000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7630000 0x0 0x1000>; + no-map; + }; + + shmem49: scmi-shmem@d7631000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7631000 0x0 0x1000>; + no-map; + }; + + shmem50: scmi-shmem@d7632000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7632000 0x0 0x1000>; + no-map; + }; + + shmem51: scmi-shmem@d7633000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7633000 0x0 0x1000>; + no-map; + }; + + shmem52: scmi-shmem@d7634000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7634000 0x0 0x1000>; + no-map; + }; + + shmem53: scmi-shmem@d7635000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7635000 0x0 0x1000>; + no-map; + }; + + shmem54: scmi-shmem@d7636000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7636000 0x0 0x1000>; + no-map; + }; + + shmem55: scmi-shmem@d7637000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7637000 0x0 0x1000>; + no-map; + }; + + shmem56: scmi-shmem@d7638000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7638000 0x0 0x1000>; + no-map; + }; + + shmem57: scmi-shmem@d7639000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd7639000 0x0 0x1000>; + no-map; + }; + + shmem58: scmi-shmem@d763a000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd763a000 0x0 0x1000>; + no-map; + }; + + shmem59: scmi-shmem@d763b000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd763b000 0x0 0x1000>; + no-map; + }; + + shmem60: scmi-shmem@d763c000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd763c000 0x0 0x1000>; + no-map; + }; + + shmem61: scmi-shmem@d763d000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd763d000 0x0 0x1000>; + no-map; + }; + + shmem62: scmi-shmem@d763e000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd763e000 0x0 0x1000>; + no-map; + }; + + shmem63: scmi-shmem@d763f000 { + compatible = "arm,scmi-shmem"; + reg = <0x0 0xd763f000 0x0 0x1000>; + no-map; + }; +}; + +&spi0 { + compatible = "qcom,sa8797p-geni-spi", + "qcom,sa8255p-geni-spi"; + power-domains = <&scmi11_pd 0>, + <&scmi11_dvfs 0>; + power-domain-names = "power", + "perf"; +}; + +&spi1 { + compatible = "qcom,sa8797p-geni-spi", + "qcom,sa8255p-geni-spi"; + power-domains = <&scmi11_pd 1>, + <&scmi11_dvfs 1>; + power-domain-names = "power", + "perf"; +}; + +&spi2 { + compatible = "qcom,sa8797p-geni-spi", + "qcom,sa8255p-geni-spi"; + power-domains = <&scmi11_pd 2>, + <&scmi11_dvfs 2>; + power-domain-names = "power", + "perf"; +}; + +&spi3 { + compatible = "qcom,sa8797p-geni-spi", + "qcom,sa8255p-geni-spi"; + power-domains = <&scmi11_pd 3>, + <&scmi11_dvfs 3>; + power-domain-names = "power", + "perf"; +}; + +&spi4 { + compatible = "qcom,sa8797p-geni-spi", + "qcom,sa8255p-geni-spi"; + power-domains = <&scmi11_pd 4>, + <&scmi11_dvfs 4>; + power-domain-names = "power", + "perf"; +}; + +&spi5 { + compatible = "qcom,sa8797p-geni-spi", + "qcom,sa8255p-geni-spi"; + power-domains = <&scmi11_pd 5>, + <&scmi11_dvfs 5>; + power-domain-names = "power", + "perf"; +}; + +&spi7 { + compatible = "qcom,sa8797p-geni-spi", + "qcom,sa8255p-geni-spi"; + power-domains = <&scmi11_pd 7>, + <&scmi11_dvfs 7>; + power-domain-names = "power", + "perf"; +}; + +&spi8 { + compatible = "qcom,sa8797p-geni-spi", + "qcom,sa8255p-geni-spi"; + power-domains = <&scmi11_pd 8>, + <&scmi11_dvfs 8>; + power-domain-names = "power", + "perf"; +}; + +&spi11 { + compatible = "qcom,sa8797p-geni-spi", + "qcom,sa8255p-geni-spi"; + power-domains = <&scmi11_pd 11>, + <&scmi11_dvfs 11>; + power-domain-names = "power", + "perf"; +}; + +&spi12 { + compatible = "qcom,sa8797p-geni-spi", + "qcom,sa8255p-geni-spi"; + power-domains = <&scmi11_pd 12>, + <&scmi11_dvfs 12>; + power-domain-names = "power", + "perf"; +}; + +&spi13 { + compatible = "qcom,sa8797p-geni-spi", + "qcom,sa8255p-geni-spi"; + power-domains = <&scmi11_pd 13>, + <&scmi11_dvfs 13>; + power-domain-names = "power", + "perf"; +}; + +&spi14 { + compatible = "qcom,sa8797p-geni-spi", + "qcom,sa8255p-geni-spi"; + power-domains = <&scmi11_pd 14>, + <&scmi11_dvfs 14>; + power-domain-names = "power", + "perf"; +}; + +&spi15 { + compatible = "qcom,sa8797p-geni-spi", + "qcom,sa8255p-geni-spi"; + power-domains = <&scmi11_pd 15>, + <&scmi11_dvfs 15>; + power-domain-names = "power", + "perf"; +}; + +&spi16 { + compatible = "qcom,sa8797p-geni-spi", + "qcom,sa8255p-geni-spi"; + power-domains = <&scmi11_pd 16>, + <&scmi11_dvfs 16>; + power-domain-names = "power", + "perf"; +}; + +&spi17 { + compatible = "qcom,sa8797p-geni-spi", + "qcom,sa8255p-geni-spi"; + power-domains = <&scmi11_pd 17>, + <&scmi11_dvfs 17>; + power-domain-names = "power", + "perf"; +}; + +&spi18 { + compatible = "qcom,sa8797p-geni-spi", + "qcom,sa8255p-geni-spi"; + power-domains = <&scmi11_pd 18>, + <&scmi11_dvfs 18>; + power-domain-names = "power", + "perf"; +}; + +&spi19 { + compatible = "qcom,sa8797p-geni-spi", + "qcom,sa8255p-geni-spi"; + power-domains = <&scmi11_pd 19>, + <&scmi11_dvfs 19>; + power-domain-names = "power", + "perf"; +}; + +&spi20 { + compatible = "qcom,sa8797p-geni-spi", + "qcom,sa8255p-geni-spi"; + power-domains = <&scmi11_pd 20>, + <&scmi11_dvfs 20>; + power-domain-names = "power", + "perf"; +}; + +&uart0 { + compatible = "qcom,sa8797p-geni-uart", + "qcom,sa8255p-geni-uart"; + power-domains = <&scmi11_pd 0>, + <&scmi11_dvfs 0>; + power-domain-names = "power", + "perf"; +}; + +&uart1 { + compatible = "qcom,sa8797p-geni-uart", + "qcom,sa8255p-geni-uart"; + power-domains = <&scmi11_pd 1>, + <&scmi11_dvfs 1>; + power-domain-names = "power", + "perf"; +}; + +&uart2 { + compatible = "qcom,sa8797p-geni-uart", + "qcom,sa8255p-geni-uart"; + power-domains = <&scmi11_pd 2>, + <&scmi11_dvfs 2>; + power-domain-names = "power", + "perf"; +}; + +&uart3 { + compatible = "qcom,sa8797p-geni-uart", + "qcom,sa8255p-geni-uart"; + power-domains = <&scmi11_pd 3>, + <&scmi11_dvfs 3>; + power-domain-names = "power", + "perf"; +}; + +&uart4 { + compatible = "qcom,sa8797p-geni-uart", + "qcom,sa8255p-geni-uart"; + power-domains = <&scmi11_pd 4>, + <&scmi11_dvfs 4>; + power-domain-names = "power", + "perf"; +}; + +&uart5 { + compatible = "qcom,sa8797p-geni-uart", + "qcom,sa8255p-geni-uart"; + power-domains = <&scmi11_pd 5>, + <&scmi11_dvfs 5>; + power-domain-names = "power", + "perf"; +}; + +&uart7 { + compatible = "qcom,sa8797p-geni-uart", + "qcom,sa8255p-geni-uart"; + power-domains = <&scmi11_pd 7>, + <&scmi11_dvfs 7>; + power-domain-names = "power", + "perf"; +}; + +&uart8 { + compatible = "qcom,sa8797p-geni-uart", + "qcom,sa8255p-geni-uart"; + power-domains = <&scmi11_pd 8>, + <&scmi11_dvfs 8>; + power-domain-names = "power", + "perf"; +}; + +&uart9 { + compatible = "qcom,sa8797p-geni-uart", + "qcom,sa8255p-geni-uart"; + power-domains = <&scmi11_pd 9>, + <&scmi11_dvfs 9>; + power-domain-names = "power", + "perf"; +}; + +&uart10 { + compatible = "qcom,sa8797p-geni-uart", + "qcom,sa8255p-geni-uart"; + power-domains = <&scmi11_pd 10>, + <&scmi11_dvfs 10>; + power-domain-names = "power", + "perf"; +}; + +&uart11 { + compatible = "qcom,sa8797p-geni-uart", + "qcom,sa8255p-geni-uart"; + power-domains = <&scmi11_pd 11>, + <&scmi11_dvfs 11>; + power-domain-names = "power", + "perf"; +}; + +&uart12 { + compatible = "qcom,sa8797p-geni-uart", + "qcom,sa8255p-geni-uart"; + power-domains = <&scmi11_pd 12>, + <&scmi11_dvfs 12>; + power-domain-names = "power", + "perf"; +}; + +&uart13 { + compatible = "qcom,sa8797p-geni-uart", + "qcom,sa8255p-geni-uart"; + power-domains = <&scmi11_pd 13>, + <&scmi11_dvfs 13>; + power-domain-names = "power", + "perf"; +}; + +&uart14 { + compatible = "qcom,sa8797p-geni-uart", + "qcom,sa8255p-geni-uart"; + power-domains = <&scmi11_pd 14>, + <&scmi11_dvfs 14>; + power-domain-names = "power", + "perf"; +}; + +&uart15 { + compatible = "qcom,sa8797p-geni-debug-uart", + "qcom,sa8255p-geni-debug-uart"; + power-domains = <&scmi11_pd 15>, + <&scmi11_dvfs 15>; + power-domain-names = "power", + "perf"; +}; + +&uart16 { + compatible = "qcom,sa8797p-geni-uart", + "qcom,sa8255p-geni-uart"; + power-domains = <&scmi11_pd 16>, + <&scmi11_dvfs 16>; + power-domain-names = "power", + "perf"; +}; + +&uart17 { + compatible = "qcom,sa8797p-geni-uart", + "qcom,sa8255p-geni-uart"; + power-domains = <&scmi11_pd 17>, + <&scmi11_dvfs 17>; + power-domain-names = "power", + "perf"; +}; + +&uart18 { + compatible = "qcom,sa8797p-geni-uart", + "qcom,sa8255p-geni-uart"; + power-domains = <&scmi11_pd 18>, + <&scmi11_dvfs 18>; + power-domain-names = "power", + "perf"; +}; + +&uart19 { + compatible = "qcom,sa8797p-geni-uart", + "qcom,sa8255p-geni-uart"; + power-domains = <&scmi11_pd 19>, + <&scmi11_dvfs 19>; + power-domain-names = "power", + "perf"; +}; + +&uart20 { + compatible = "qcom,sa8797p-geni-uart", + "qcom,sa8255p-geni-uart"; + power-domains = <&scmi11_pd 20>, + <&scmi11_dvfs 20>; + power-domain-names = "power", + "perf"; +}; + +&ufs_mem_hc { + compatible = "qcom,sa8797p-ufshc", + "qcom,sa8255p-ufshc"; + reg = <0x0 0x01d44000 0x0 0x3000>; + power-domains = <&scmi3_pd 0>; +}; From 335e6cda333b6a8bab733b2a9327a79444dfc0bd Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Thu, 9 Jul 2026 21:20:09 +0800 Subject: [PATCH 538/562] dt-bindings: arm: qcom: Document SA8797P Ride board MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Nord is a new generation of SoC series from Qualcomm, and SA8797P is the automotive variant of Nord. SA8797P Ride is the automotive‑grade development board built on SA8797P SoC. Document the board with a fallback on Nord compatible. Acked-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260709132013.4096850-4-shengchao.guo@oss.qualcomm.com Signed-off-by: Shawn Guo --- Documentation/devicetree/bindings/arm/qcom.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/arm/qcom.yaml b/Documentation/devicetree/bindings/arm/qcom.yaml index 9df4074bb582..a7e8dc994b35 100644 --- a/Documentation/devicetree/bindings/arm/qcom.yaml +++ b/Documentation/devicetree/bindings/arm/qcom.yaml @@ -389,6 +389,11 @@ properties: - xiaomi,sagit - const: qcom,msm8998 + - items: + - enum: + - qcom,sa8797p-ride + - const: qcom,nord + - description: Qualcomm Technologies, Inc. Robotics RB1 items: - enum: From 6a8c60ffe67c943e7ffd66c4c2300a5457a1419b Mon Sep 17 00:00:00 2001 From: Deepti Jaggi Date: Thu, 9 Jul 2026 21:20:10 +0800 Subject: [PATCH 539/562] arm64: dts: qcom: Add device tree for SA8797P Ride board Add initial device tree for the Qualcomm SA8797P Ride reference board, which is built on Nord GearVM variant. - Configure UART15 as the primary console and UART4 as the secondary serial port - Enable UFS storage support - Define thermal zones for PMIC dies, UFS, and two SDRAM sensors, all sourced from SCMI sensor protocol on channel 23 Signed-off-by: Deepti Jaggi Link: https://lore.kernel.org/r/20260709132013.4096850-5-shengchao.guo@oss.qualcomm.com Signed-off-by: Shawn Guo --- arch/arm64/boot/dts/qcom/Makefile | 1 + arch/arm64/boot/dts/qcom/sa8797p-ride.dts | 240 ++++++++++++++++++++++ 2 files changed, 241 insertions(+) create mode 100644 arch/arm64/boot/dts/qcom/sa8797p-ride.dts diff --git a/arch/arm64/boot/dts/qcom/Makefile b/arch/arm64/boot/dts/qcom/Makefile index fb1a99a3e01c..8e6ef4116146 100644 --- a/arch/arm64/boot/dts/qcom/Makefile +++ b/arch/arm64/boot/dts/qcom/Makefile @@ -217,6 +217,7 @@ dtb-$(CONFIG_ARCH_QCOM) += qru1000-idp.dtb dtb-$(CONFIG_ARCH_QCOM) += sa8155p-adp.dtb dtb-$(CONFIG_ARCH_QCOM) += sa8295p-adp.dtb dtb-$(CONFIG_ARCH_QCOM) += sa8540p-ride.dtb +dtb-$(CONFIG_ARCH_QCOM) += sa8797p-ride.dtb sc7180-acer-aspire1-el2-dtbs := sc7180-acer-aspire1.dtb sc7180-el2.dtbo dtb-$(CONFIG_ARCH_QCOM) += sc7180-acer-aspire1.dtb sc7180-acer-aspire1-el2.dtb sc7180-ecs-liva-qc710-el2-dtbs := sc7180-ecs-liva-qc710.dtb sc7180-el2.dtbo diff --git a/arch/arm64/boot/dts/qcom/sa8797p-ride.dts b/arch/arm64/boot/dts/qcom/sa8797p-ride.dts new file mode 100644 index 000000000000..4ced77e3482e --- /dev/null +++ b/arch/arm64/boot/dts/qcom/sa8797p-ride.dts @@ -0,0 +1,240 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +/dts-v1/; + +#include "nord-gearvm.dtsi" + +/ { + model = "Qualcomm Technologies, Inc. SA8797P Ride"; + compatible = "qcom,sa8797p-ride", "qcom,nord"; + + aliases { + serial0 = &uart15; + serial1 = &uart4; + }; + + chosen { + stdout-path = "serial0:115200n8"; + }; + + clocks { + xo_board_clk: xo-board-clk { + compatible = "fixed-clock"; + clock-frequency = <38400000>; + #clock-cells = <0>; + }; + + sleep_clk: sleep-clk { + compatible = "fixed-clock"; + clock-frequency = <32000>; + #clock-cells = <0>; + }; + }; +}; + +&scmi3 { + status = "okay"; +}; + +&scmi11 { + status = "okay"; +}; + +&scmi15 { + status = "okay"; +}; + +&scmi23 { + status = "okay"; +}; + +&thermal_zones { + pmic_kobra_thermal: pmic-a-die-thermal { + polling-delay-passive = <100>; + thermal-sensors = <&scmi23_sensor 3>; + + trips { + trip0 { + temperature = <115000>; + hysteresis = <5000>; + type = "passive"; + }; + }; + }; + + pmic_kai_0_thermal: pmic-e-die-thermal { + polling-delay-passive = <100>; + thermal-sensors = <&scmi23_sensor 4>; + + trips { + trip0 { + temperature = <115000>; + hysteresis = <5000>; + type = "passive"; + }; + }; + }; + + pmic_kai_1_thermal: pmic-f-die-thermal { + polling-delay-passive = <100>; + thermal-sensors = <&scmi23_sensor 5>; + + trips { + trip0 { + temperature = <115000>; + hysteresis = <5000>; + type = "passive"; + }; + }; + }; + + pmic_kai_2_thermal: pmic-g-die-thermal { + polling-delay-passive = <100>; + thermal-sensors = <&scmi23_sensor 6>; + + trips { + trip0 { + temperature = <115000>; + hysteresis = <5000>; + type = "passive"; + }; + }; + }; + + pmic_kai_3_thermal: pmic-h-die-thermal { + polling-delay-passive = <100>; + thermal-sensors = <&scmi23_sensor 7>; + + trips { + trip0 { + temperature = <115000>; + hysteresis = <5000>; + type = "passive"; + }; + }; + }; + + pmic_kai_4_thermal: pmic-i-die-thermal { + polling-delay-passive = <100>; + thermal-sensors = <&scmi23_sensor 8>; + + trips { + trip0 { + temperature = <115000>; + hysteresis = <5000>; + type = "passive"; + }; + }; + }; + + pmic_kai_5_thermal: pmic-j-die-thermal { + polling-delay-passive = <100>; + thermal-sensors = <&scmi23_sensor 9>; + + trips { + trip0 { + temperature = <115000>; + hysteresis = <5000>; + type = "passive"; + }; + }; + }; + + pmic_kai_6_thermal: pmic-k-die-thermal { + polling-delay-passive = <100>; + thermal-sensors = <&scmi23_sensor 10>; + + trips { + trip0 { + temperature = <115000>; + hysteresis = <5000>; + type = "passive"; + }; + }; + }; + + pmic_kai_7_thermal: pmic-l-die-thermal { + polling-delay-passive = <100>; + thermal-sensors = <&scmi23_sensor 11>; + + trips { + trip0 { + temperature = <115000>; + hysteresis = <5000>; + type = "passive"; + }; + }; + }; + + pmic_kai_ufs_thermal: ufs-thermal { + polling-delay-passive = <0>; + thermal-sensors = <&scmi23_sensor 0>; + + trips { + trip0 { + temperature = <105000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip1 { + temperature = <115000>; + hysteresis = <5000>; + type = "critical"; + }; + }; + }; + + pmic_kai_sdram0_thermal: sdram0-thermal { + polling-delay-passive = <0>; + thermal-sensors = <&scmi23_sensor 1>; + + trips { + trip0 { + temperature = <105000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip1 { + temperature = <115000>; + hysteresis = <5000>; + type = "critical"; + }; + }; + }; + + pmic_kai_sdram1_thermal: sdram1-thermal { + polling-delay-passive = <0>; + thermal-sensors = <&scmi23_sensor 2>; + + trips { + trip0 { + temperature = <105000>; + hysteresis = <5000>; + type = "passive"; + }; + + trip1 { + temperature = <115000>; + hysteresis = <5000>; + type = "critical"; + }; + }; + }; +}; + +&uart4 { + status = "okay"; +}; + +&uart15 { + status = "okay"; +}; + +&ufs_mem_hc { + status = "okay"; +}; From e7d54bd8ced61215f2c8339244cf8e84751c644a Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Thu, 9 Jul 2026 21:20:11 +0800 Subject: [PATCH 540/562] arm64: dts: qcom: Add device tree for Nord Embedded variant Unlike the GearVM variant, Nord Embedded variant has platform resources (clocks, regulators, powerdomains, pins, etc.) directly controlled by Linux. Add a separate dtsi file extending the existing top-level nord.dtsi with nodes representing these peripherals as well as describing how they are wired up with the already defined components. Co-developed-by: Bartosz Golaszewski Signed-off-by: Bartosz Golaszewski Link: https://lore.kernel.org/r/20260709132013.4096850-6-shengchao.guo@oss.qualcomm.com Signed-off-by: Shawn Guo --- arch/arm64/boot/dts/qcom/nord-embedded.dtsi | 1731 +++++++++++++++++++ 1 file changed, 1731 insertions(+) create mode 100644 arch/arm64/boot/dts/qcom/nord-embedded.dtsi diff --git a/arch/arm64/boot/dts/qcom/nord-embedded.dtsi b/arch/arm64/boot/dts/qcom/nord-embedded.dtsi new file mode 100644 index 000000000000..619025011b56 --- /dev/null +++ b/arch/arm64/boot/dts/qcom/nord-embedded.dtsi @@ -0,0 +1,1731 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nord.dtsi" + +/ { + clk_virt: interconnect-clk-virt { + compatible = "qcom,nord-clk-virt"; + #interconnect-cells = <2>; + qcom,bcm-voters = <&apps_bcm_voter>; + }; + + mc_virt: interconnect-mc-virt { + compatible = "qcom,nord-mc-virt"; + #interconnect-cells = <2>; + qcom,bcm-voters = <&apps_bcm_voter>; + }; +}; + +&crypto { + interconnects = <&aggre1_noc_tile MASTER_CRYPTO_CORE0 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "memory"; +}; + +&i2c0 { + clocks = <&segcc SE_GCC_QUPV3_WRAP0_S0_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_0 QCOM_ICC_TAG_ALWAYS>, + <&aggre2_noc_tile MASTER_QUP_0 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_i2c0_default>; + pinctrl-names = "default"; +}; + +&i2c1 { + clocks = <&segcc SE_GCC_QUPV3_WRAP0_S1_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_0 QCOM_ICC_TAG_ALWAYS>, + <&aggre2_noc_tile MASTER_QUP_0 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_i2c1_default>; + pinctrl-names = "default"; +}; + +&i2c2 { + clocks = <&segcc SE_GCC_QUPV3_WRAP0_S2_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_0 QCOM_ICC_TAG_ALWAYS>, + <&aggre2_noc_tile MASTER_QUP_0 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_i2c2_default>; + pinctrl-names = "default"; +}; + +&i2c3 { + clocks = <&segcc SE_GCC_QUPV3_WRAP0_S3_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_0 QCOM_ICC_TAG_ALWAYS>, + <&aggre2_noc_tile MASTER_QUP_0 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_i2c3_default>; + pinctrl-names = "default"; +}; + +&i2c4 { + clocks = <&segcc SE_GCC_QUPV3_WRAP0_S4_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_0 QCOM_ICC_TAG_ALWAYS>, + <&aggre2_noc_tile MASTER_QUP_0 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_i2c4_default>; + pinctrl-names = "default"; +}; + +&i2c5 { + clocks = <&segcc SE_GCC_QUPV3_WRAP0_S5_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_0 QCOM_ICC_TAG_ALWAYS>, + <&aggre2_noc_tile MASTER_QUP_0 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_i2c5_default>; + pinctrl-names = "default"; +}; + +&i2c7 { + clocks = <&segcc SE_GCC_QUPV3_WRAP1_S0_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_1 QCOM_ICC_TAG_ALWAYS>, + <&aggre2_noc_tile MASTER_QUP_1 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_i2c7_default>; + pinctrl-names = "default"; +}; + +&i2c8 { + clocks = <&segcc SE_GCC_QUPV3_WRAP1_S1_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_1 QCOM_ICC_TAG_ALWAYS>, + <&aggre2_noc_tile MASTER_QUP_1 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_i2c8_default>; + pinctrl-names = "default"; +}; + +&i2c9 { + clocks = <&segcc SE_GCC_QUPV3_WRAP1_S2_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_1 QCOM_ICC_TAG_ALWAYS>, + <&aggre2_noc_tile MASTER_QUP_1 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_i2c9_default>; + pinctrl-names = "default"; +}; + +&i2c10 { + clocks = <&segcc SE_GCC_QUPV3_WRAP1_S3_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_1 QCOM_ICC_TAG_ALWAYS>, + <&aggre2_noc_tile MASTER_QUP_1 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_i2c10_default>; + pinctrl-names = "default"; +}; + +&i2c11 { + clocks = <&segcc SE_GCC_QUPV3_WRAP1_S4_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_1 QCOM_ICC_TAG_ALWAYS>, + <&aggre2_noc_tile MASTER_QUP_1 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_i2c11_default>; + pinctrl-names = "default"; +}; + +&i2c12 { + clocks = <&segcc SE_GCC_QUPV3_WRAP1_S5_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_1 QCOM_ICC_TAG_ALWAYS>, + <&aggre2_noc_tile MASTER_QUP_1 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_i2c12_default>; + pinctrl-names = "default"; +}; + +&i2c13 { + clocks = <&segcc SE_GCC_QUPV3_WRAP1_S6_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_1 QCOM_ICC_TAG_ALWAYS>, + <&aggre2_noc_tile MASTER_QUP_1 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_i2c13_default>; + pinctrl-names = "default"; +}; + +&i2c14 { + clocks = <&negcc NE_GCC_QUPV3_WRAP2_S0_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_2 QCOM_ICC_TAG_ALWAYS>, + <&aggre1_noc_tile MASTER_QUP_2 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_i2c14_default>; + pinctrl-names = "default"; +}; + +&i2c16 { + clocks = <&negcc NE_GCC_QUPV3_WRAP2_S2_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_2 QCOM_ICC_TAG_ALWAYS>, + <&aggre1_noc_tile MASTER_QUP_2 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_i2c16_default>; + pinctrl-names = "default"; +}; + +&i2c17 { + clocks = <&negcc NE_GCC_QUPV3_WRAP2_S3_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_2 QCOM_ICC_TAG_ALWAYS>, + <&aggre1_noc_tile MASTER_QUP_2 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_i2c17_default>; + pinctrl-names = "default"; +}; + +&i2c18 { + clocks = <&negcc NE_GCC_QUPV3_WRAP2_S4_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_2 QCOM_ICC_TAG_ALWAYS>, + <&aggre1_noc_tile MASTER_QUP_2 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_i2c18_default>; + pinctrl-names = "default"; +}; + +&i2c19 { + clocks = <&negcc NE_GCC_QUPV3_WRAP2_S5_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_2 QCOM_ICC_TAG_ALWAYS>, + <&aggre1_noc_tile MASTER_QUP_2 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_i2c19_default>; + pinctrl-names = "default"; +}; + +&i2c20 { + clocks = <&negcc NE_GCC_QUPV3_WRAP2_S6_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_2 QCOM_ICC_TAG_ALWAYS>, + <&aggre1_noc_tile MASTER_QUP_2 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_i2c20_default>; + pinctrl-names = "default"; +}; + +&qupv3_0 { + clocks = <&segcc SE_GCC_QUPV3_WRAP0_M_AHB_CLK>, + <&segcc SE_GCC_QUPV3_WRAP0_S_AHB_CLK>; + clock-names = "m-ahb", "s-ahb"; +}; + +&qupv3_1 { + clocks = <&segcc SE_GCC_QUPV3_WRAP1_M_AHB_CLK>, + <&segcc SE_GCC_QUPV3_WRAP1_S_AHB_CLK>; + clock-names = "m-ahb", "s-ahb"; +}; + +&qupv3_2 { + clocks = <&negcc NE_GCC_QUPV3_WRAP2_M_AHB_CLK>, + <&negcc NE_GCC_QUPV3_WRAP2_S_AHB_CLK>; + clock-names = "m-ahb", + "s-ahb"; +}; + +&spi0 { + clocks = <&segcc SE_GCC_QUPV3_WRAP0_S0_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_0 QCOM_ICC_TAG_ALWAYS>, + <&aggre2_noc_tile MASTER_QUP_0 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_spi0_default>; + pinctrl-names = "default"; +}; + +&spi1 { + clocks = <&segcc SE_GCC_QUPV3_WRAP0_S1_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_0 QCOM_ICC_TAG_ALWAYS>, + <&aggre2_noc_tile MASTER_QUP_0 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_spi1_default>; + pinctrl-names = "default"; +}; + +&spi2 { + clocks = <&segcc SE_GCC_QUPV3_WRAP0_S2_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_0 QCOM_ICC_TAG_ALWAYS>, + <&aggre2_noc_tile MASTER_QUP_0 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_spi2_default>; + pinctrl-names = "default"; +}; + +&spi3 { + clocks = <&segcc SE_GCC_QUPV3_WRAP0_S3_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_0 QCOM_ICC_TAG_ALWAYS>, + <&aggre2_noc_tile MASTER_QUP_0 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_spi3_default>; + pinctrl-names = "default"; +}; + +&spi4 { + clocks = <&segcc SE_GCC_QUPV3_WRAP0_S4_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_0 QCOM_ICC_TAG_ALWAYS>, + <&aggre2_noc_tile MASTER_QUP_0 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_spi4_default>; + pinctrl-names = "default"; +}; + +&spi5 { + clocks = <&segcc SE_GCC_QUPV3_WRAP0_S5_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_0 QCOM_ICC_TAG_ALWAYS>, + <&aggre2_noc_tile MASTER_QUP_0 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_spi5_default>; + pinctrl-names = "default"; +}; + +&spi7 { + clocks = <&segcc SE_GCC_QUPV3_WRAP1_S0_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_1 QCOM_ICC_TAG_ALWAYS>, + <&aggre2_noc_tile MASTER_QUP_1 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_spi7_default>; + pinctrl-names = "default"; +}; + +&spi8 { + clocks = <&segcc SE_GCC_QUPV3_WRAP1_S1_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_1 QCOM_ICC_TAG_ALWAYS>, + <&aggre2_noc_tile MASTER_QUP_1 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_spi8_default>; + pinctrl-names = "default"; +}; + +&spi11 { + clocks = <&segcc SE_GCC_QUPV3_WRAP1_S4_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_1 QCOM_ICC_TAG_ALWAYS>, + <&aggre2_noc_tile MASTER_QUP_1 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_spi11_default>; + pinctrl-names = "default"; +}; + +&spi12 { + clocks = <&segcc SE_GCC_QUPV3_WRAP1_S5_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_1 QCOM_ICC_TAG_ALWAYS>, + <&aggre2_noc_tile MASTER_QUP_1 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_spi12_default>; + pinctrl-names = "default"; +}; + +&spi13 { + clocks = <&segcc SE_GCC_QUPV3_WRAP1_S6_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_1 QCOM_ICC_TAG_ALWAYS>, + <&aggre2_noc_tile MASTER_QUP_1 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_spi13_default>; + pinctrl-names = "default"; +}; + +&spi14 { + clocks = <&negcc NE_GCC_QUPV3_WRAP2_S0_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_2 QCOM_ICC_TAG_ALWAYS>, + <&aggre1_noc_tile MASTER_QUP_2 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_spi14_default>; + pinctrl-names = "default"; +}; + +&spi16 { + clocks = <&negcc NE_GCC_QUPV3_WRAP2_S2_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_2 QCOM_ICC_TAG_ALWAYS>, + <&aggre1_noc_tile MASTER_QUP_2 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_spi16_default>; + pinctrl-names = "default"; +}; + +&spi17 { + clocks = <&negcc NE_GCC_QUPV3_WRAP2_S3_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_2 QCOM_ICC_TAG_ALWAYS>, + <&aggre1_noc_tile MASTER_QUP_2 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_spi17_default>; + pinctrl-names = "default"; +}; + +&spi18 { + clocks = <&negcc NE_GCC_QUPV3_WRAP2_S4_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_2 QCOM_ICC_TAG_ALWAYS>, + <&aggre1_noc_tile MASTER_QUP_2 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_spi18_default>; + pinctrl-names = "default"; +}; + +&spi19 { + clocks = <&negcc NE_GCC_QUPV3_WRAP2_S5_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_2 QCOM_ICC_TAG_ALWAYS>, + <&aggre1_noc_tile MASTER_QUP_2 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_spi19_default>; + pinctrl-names = "default"; +}; + +&spi20 { + clocks = <&negcc NE_GCC_QUPV3_WRAP2_S6_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_2 QCOM_ICC_TAG_ALWAYS>, + <&aggre1_noc_tile MASTER_QUP_2 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config", "qup-memory"; + pinctrl-0 = <&qup_spi20_default>; + pinctrl-names = "default"; +}; + +&soc { + gcc: clock-controller@100000 { + compatible = "qcom,nord-gcc"; + reg = <0x0 0x00100000 0x0 0x1f4200>; + clocks = <&bi_tcxo_div2>, + <&sleep_clk>, + <0>, + <0>, + <0>, + <0>; + #clock-cells = <1>; + #reset-cells = <1>; + #power-domain-cells = <1>; + }; + + cnoc_main: interconnect@1500000 { + compatible = "qcom,nord-cnoc-main"; + reg = <0x0 0x01500000 0x0 0x1d200>; + #interconnect-cells = <2>; + qcom,bcm-voters = <&apps_bcm_voter>; + }; + + config_noc: interconnect@1600000 { + compatible = "qcom,nord-cnoc-cfg"; + reg = <0x0 0x01600000 0x0 0xd200>; + #interconnect-cells = <2>; + qcom,bcm-voters = <&apps_bcm_voter>; + }; + + system_noc: interconnect@1680000 { + compatible = "qcom,nord-system-noc"; + reg = <0x0 0x01680000 0x0 0x1c080>; + #interconnect-cells = <2>; + qcom,bcm-voters = <&apps_bcm_voter>; + }; + + aggre2_noc_tile: interconnect@16c0000 { + compatible = "qcom,nord-aggre2-noc-tile"; + reg = <0x0 0x016c0000 0x0 0x1b400>; + #interconnect-cells = <2>; + qcom,bcm-voters = <&apps_bcm_voter>; + }; + + aggre1_noc: interconnect@16e0000 { + compatible = "qcom,nord-aggre1-noc"; + reg = <0x0 0x016e0000 0x0 0x1c400>; + #interconnect-cells = <2>; + qcom,bcm-voters = <&apps_bcm_voter>; + }; + + aggre2_noc: interconnect@1700000 { + compatible = "qcom,nord-aggre2-noc"; + reg = <0x0 0x01700000 0x0 0x1b400>; + #interconnect-cells = <2>; + qcom,bcm-voters = <&apps_bcm_voter>; + clocks = <&rpmhcc RPMH_IPA_CLK>; + }; + + aggre1_noc_tile: interconnect@1720000 { + compatible = "qcom,nord-aggre1-noc-tile"; + reg = <0x0 0x01720000 0x0 0x23400>; + #interconnect-cells = <2>; + qcom,bcm-voters = <&apps_bcm_voter>; + clocks = <&negcc NE_GCC_AGGRE_NOC_USB2_AXI_CLK>, + <&negcc NE_GCC_AGGRE_NOC_USB3_PRIM_AXI_CLK>, + <&negcc NE_GCC_AGGRE_NOC_USB3_SEC_AXI_CLK>, + <&negcc NE_GCC_AGGRE_NOC_UFS_PHY_AXI_CLK>; + }; + + mmss_noc: interconnect@1780000 { + compatible = "qcom,nord-mmss-noc"; + reg = <0x0 0x01780000 0x0 0x72800>; + #interconnect-cells = <2>; + qcom,bcm-voters = <&apps_bcm_voter>; + }; + + pcie_cfg: interconnect@1ba0000 { + compatible = "qcom,nord-pcie-cfg"; + reg = <0x0 0x01ba0000 0x0 0x7200>; + #interconnect-cells = <2>; + qcom,bcm-voters = <&apps_bcm_voter>; + }; + + pcie_data_outbound: interconnect@1bc0000 { + compatible = "qcom,nord-pcie-data-outbound"; + reg = <0x0 0x01bc0000 0x0 0x17000>; + #interconnect-cells = <2>; + qcom,bcm-voters = <&apps_bcm_voter>; + }; + + pcie_data_inbound: interconnect@1c00000 { + compatible = "qcom,nord-pcie-data-inbound"; + reg = <0x0 0x01c00000 0x0 0x4b080>; + #interconnect-cells = <2>; + qcom,bcm-voters = <&apps_bcm_voter>; + }; + + ufs_mem_phy: phy@1d40000 { + compatible = "qcom,nord-qmp-ufs-phy", + "qcom,sm8650-qmp-ufs-phy"; + reg = <0x0 0x01d40000 0x0 0x2000>; + + clocks = <&rpmhcc RPMH_CXO_CLK>, + <&negcc NE_GCC_UFS_PHY_PHY_AUX_CLK>, + <&tcsrcc TCSR_UFS_CLKREF_EN>; + clock-names = "ref", + "ref_aux", + "qref"; + + resets = <&ufs_mem_hc 0>; + reset-names = "ufsphy"; + + power-domains = <&negcc NE_GCC_UFS_MEM_PHY_GDSC>; + #clock-cells = <1>; + #phy-cells = <0>; + + status = "disabled"; + }; + + ice: crypto@1d48000 { + compatible = "qcom,nord-inline-crypto-engine", + "qcom,inline-crypto-engine"; + reg = <0x0 0x01d48000 0x0 0x10000>; + clocks = <&negcc NE_GCC_UFS_PHY_ICE_CORE_CLK>, + <&negcc NE_GCC_UFS_PHY_AHB_CLK>; + clock-names = "core", + "iface"; + power-domains = <&negcc NE_GCC_UFS_PHY_GDSC>; + }; + + hscnoc: interconnect@2000000 { + compatible = "qcom,nord-hscnoc"; + reg = <0x0 0x02000000 0x0 0xb22000>; + #interconnect-cells = <2>; + qcom,bcm-voters = <&apps_bcm_voter>; + }; + + hpass_ag_noc: interconnect@5fc0000 { + compatible = "qcom,nord-hpass-ag-noc"; + reg = <0x0 0x05fc0000 0x0 0x37080>; + #interconnect-cells = <2>; + qcom,bcm-voters = <&apps_bcm_voter>; + }; + + negcc: clock-controller@8900000 { + compatible = "qcom,nord-negcc"; + reg = <0x0 0x08900000 0x0 0xf4200>; + clocks = <&bi_tcxo_div2>, + <&sleep_clk>, + <0>, + <0>, + <0>, + <0>, + <0>; + #clock-cells = <1>; + #reset-cells = <1>; + #power-domain-cells = <1>; + }; + + segcc: clock-controller@8a00000 { + compatible = "qcom,nord-segcc"; + reg = <0x0 0x08a00000 0x0 0xf4200>; + clocks = <&bi_tcxo_div2>, + <&sleep_clk>; + #clock-cells = <1>; + #reset-cells = <1>; + #power-domain-cells = <1>; + }; + + nwgcc: clock-controller@8b00000 { + compatible = "qcom,nord-nwgcc"; + reg = <0x0 0x08b00000 0x0 0xf4200>; + clocks = <&bi_tcxo_div2>, + <&sleep_clk>; + #clock-cells = <1>; + #reset-cells = <1>; + #power-domain-cells = <1>; + }; + + tcsrcc: clock-controller@f1d9000 { + compatible = "qcom,nord-tcsrcc", + "syscon"; + reg = <0x0 0x0f1d9000 0x0 0xf00c>; + clocks = <&rpmhcc RPMH_CXO_CLK>; + #clock-cells = <1>; + #reset-cells = <1>; + }; + + apps_rsc: rsc@18900000 { + compatible = "qcom,rpmh-rsc"; + reg = <0x0 0x18900000 0x0 0x10000>, + <0x0 0x18910000 0x0 0x10000>, + <0x0 0x18920000 0x0 0x10000>; + reg-names = "drv-0", + "drv-1", + "drv-2"; + interrupts = , + , + ; + qcom,tcs-offset = <0xd00>; + qcom,drv-id = <2>; + qcom,tcs-config = , + , + , + ; + label = "apps_rsc"; + power-domains = <&system_pd>; + + apps_bcm_voter: bcm-voter { + compatible = "qcom,bcm-voter"; + }; + + rpmhcc: clock-controller { + compatible = "qcom,nord-rpmh-clk"; + clocks = <&xo_board>; + clock-names = "xo"; + #clock-cells = <1>; + }; + + rpmhpd: power-controller { + compatible = "qcom,nord-rpmhpd"; + #power-domain-cells = <1>; + operating-points-v2 = <&rpmhpd_opp_table>; + + rpmhpd_opp_table: opp-table { + compatible = "operating-points-v2"; + + rpmhpd_opp_ret: opp-0 { + opp-level = ; + }; + + rpmhpd_opp_min_svs: opp-1 { + opp-level = ; + }; + + rpmhpd_opp_low_svs: opp2 { + opp-level = ; + }; + + rpmhpd_opp_svs: opp3 { + opp-level = ; + }; + + rpmhpd_opp_svs_l1: opp-4 { + opp-level = ; + }; + + rpmhpd_opp_nom: opp-5 { + opp-level = ; + }; + + rpmhpd_opp_nom_l1: opp-6 { + opp-level = ; + }; + + rpmhpd_opp_nom_l2: opp-7 { + opp-level = ; + }; + + rpmhpd_opp_turbo: opp-8 { + opp-level = ; + }; + + rpmhpd_opp_turbo_l1: opp-9 { + opp-level = ; + }; + }; + }; + }; + + nsp_data_noc_0: interconnect@1f200000 { + compatible = "qcom,nord-nsp-data-noc-0"; + reg = <0x0 0x1f200000 0x0 0x2a200>; + #interconnect-cells = <2>; + qcom,bcm-voters = <&apps_bcm_voter>; + }; + + nsp_data_noc_1: interconnect@1f600000 { + compatible = "qcom,nord-nsp-data-noc-1"; + reg = <0x0 0x1f600000 0x0 0x2a200>; + #interconnect-cells = <2>; + qcom,bcm-voters = <&apps_bcm_voter>; + }; + + nsp_data_noc_2: interconnect@1fa00000 { + compatible = "qcom,nord-nsp-data-noc-2"; + reg = <0x0 0x1fa00000 0x0 0x2a200>; + #interconnect-cells = <2>; + qcom,bcm-voters = <&apps_bcm_voter>; + }; + + nsp_data_noc_3: interconnect@1fe00000 { + compatible = "qcom,nord-nsp-data-noc-3"; + reg = <0x0 0x1fe00000 0x0 0x2a200>; + #interconnect-cells = <2>; + qcom,bcm-voters = <&apps_bcm_voter>; + }; +}; + +&tlmm { + qup_i2c0_default: qup-i2c0-default-state { + pins = "gpio111", "gpio112"; + function = "qup0_se0"; + drive-strength = <2>; + bias-pull-up; + }; + + qup_i2c1_default: qup-i2c1-default-state { + pins = "gpio111", "gpio112"; + function = "qup0_se1"; + drive-strength = <2>; + bias-pull-up; + }; + + qup_i2c2_default: qup-i2c2-default-state { + pins = "gpio113", "gpio114"; + function = "qup0_se2"; + drive-strength = <2>; + bias-pull-up; + }; + + qup_i2c3_default: qup-i2c3-default-state { + pins = "gpio115", "gpio116"; + function = "qup0_se3"; + drive-strength = <2>; + bias-pull-up; + }; + + qup_i2c4_default: qup-i2c4-default-state { + pins = "gpio117", "gpio118"; + function = "qup0_se4"; + drive-strength = <2>; + bias-pull-up; + }; + + qup_i2c5_default: qup-i2c5-default-state { + pins = "gpio121", "gpio122"; + function = "qup0_se5"; + drive-strength = <2>; + bias-pull-up; + }; + + qup_i2c7_default: qup-i2c7-default-state { + pins = "gpio123", "gpio124"; + function = "qup1_se0"; + drive-strength = <2>; + bias-pull-up; + }; + + qup_i2c8_default: qup-i2c8-default-state { + pins = "gpio125", "gpio126"; + function = "qup1_se1"; + drive-strength = <2>; + bias-pull-up; + }; + + qup_i2c9_default: qup-i2c9-default-state { + pins = "gpio127", "gpio128"; + function = "qup1_se2"; + drive-strength = <2>; + bias-pull-up; + }; + + qup_i2c10_default: qup-i2c10-default-state { + pins = "gpio129", "gpio130"; + function = "qup1_se3"; + drive-strength = <2>; + bias-pull-up; + }; + + qup_i2c11_default: qup-i2c11-default-state { + pins = "gpio131", "gpio132"; + function = "qup1_se4"; + drive-strength = <2>; + bias-pull-up; + }; + + qup_i2c12_default: qup-i2c12-default-state { + pins = "gpio133", "gpio134"; + function = "qup1_se5"; + drive-strength = <2>; + bias-pull-up; + }; + + qup_i2c13_default: qup-i2c13-default-state { + pins = "gpio137", "gpio138"; + function = "qup1_se6"; + drive-strength = <2>; + bias-pull-up; + }; + + qup_i2c14_default: qup-i2c14-default-state { + pins = "gpio139", "gpio140"; + function = "qup2_se0"; + drive-strength = <2>; + bias-pull-up; + }; + + qup_i2c16_default: qup-i2c16-default-state { + pins = "gpio145", "gpio146"; + function = "qup2_se2"; + drive-strength = <2>; + bias-pull-up; + }; + + qup_i2c17_default: qup-i2c17-default-state { + pins = "gpio150", "gpio151"; + function = "qup2_se3"; + drive-strength = <2>; + bias-pull-up; + }; + + qup_i2c18_default: qup-i2c18-default-state { + pins = "gpio154", "gpio155"; + function = "qup2_se4"; + drive-strength = <2>; + bias-pull-up; + }; + + qup_i2c19_default: qup-i2c19-default-state { + pins = "gpio156", "gpio157"; + function = "qup2_se5"; + drive-strength = <2>; + bias-pull-up; + }; + + qup_i2c20_default: qup-i2c20-default-state { + pins = "gpio158", "gpio159"; + function = "qup2_se6"; + drive-strength = <2>; + bias-pull-up; + }; + + qup_spi0_default: qup-spi0-default-state { + data-pins { + pins = "gpio109", "gpio111", "gpio112"; + function = "qup0_se0"; + drive-strength = <6>; + bias-disable; + }; + + cs-pins { + pins = "gpio110"; + function = "qup0_se0"; + drive-strength = <2>; + bias-disable; + }; + }; + + qup_spi1_default: qup-spi1-default-state { + data-pins { + pins = "gpio109", "gpio111", "gpio112"; + function = "qup0_se1"; + drive-strength = <6>; + bias-disable; + }; + + cs-pins { + pins = "gpio110"; + function = "qup0_se1"; + drive-strength = <2>; + bias-disable; + }; + }; + + qup_spi2_default: qup-spi2-default-state { + data-pins { + pins = "gpio113", "gpio114", "gpio115"; + function = "qup0_se2"; + drive-strength = <6>; + bias-disable; + }; + + cs-pins { + pins = "gpio116"; + function = "qup0_se2"; + drive-strength = <2>; + bias-disable; + }; + }; + + qup_spi3_default: qup-spi3-default-state { + data-pins { + pins = "gpio113", "gpio115", "gpio116"; + function = "qup0_se3"; + drive-strength = <6>; + bias-disable; + }; + + cs-pins { + pins = "gpio114"; + function = "qup0_se3"; + drive-strength = <2>; + bias-disable; + }; + }; + + qup_spi4_default: qup-spi4-default-state { + data-pins { + pins = "gpio117", "gpio118", "gpio119"; + function = "qup0_se4"; + drive-strength = <6>; + bias-disable; + }; + + cs-pins { + pins = "gpio120"; + function = "qup0_se4"; + drive-strength = <2>; + bias-disable; + }; + }; + + qup_spi5_default: qup-spi5-default-state { + data-pins { + pins = "gpio109", "gpio121", "gpio122"; + function = "qup0_se5"; + drive-strength = <6>; + bias-disable; + }; + + cs-pins { + pins = "gpio110"; + function = "qup0_se5"; + drive-strength = <2>; + bias-disable; + }; + }; + + qup_spi7_default: qup-spi7-default-state { + data-pins { + pins = "gpio123", "gpio124", "gpio125"; + function = "qup1_se0"; + drive-strength = <6>; + bias-disable; + }; + + cs-pins { + pins = "gpio126"; + function = "qup1_se0"; + drive-strength = <2>; + bias-disable; + }; + }; + + qup_spi8_default: qup-spi8-default-state { + data-pins { + pins = "gpio123", "gpio125", "gpio126"; + function = "qup1_se1"; + drive-strength = <6>; + bias-disable; + }; + + cs-pins { + pins = "gpio124"; + function = "qup1_se1"; + drive-strength = <2>; + bias-disable; + }; + }; + + qup_spi11_default: qup-spi11-default-state { + data-pins { + pins = "gpio131", "gpio132", "gpio137"; + function = "qup1_se4"; + drive-strength = <6>; + bias-disable; + }; + + cs-pins { + pins = "gpio138"; + function = "qup1_se4"; + drive-strength = <2>; + bias-disable; + }; + }; + + qup_spi12_default: qup-spi12-default-state { + data-pins { + pins = "gpio133", "gpio134", "gpio135"; + function = "qup1_se5"; + drive-strength = <6>; + bias-disable; + }; + + cs-pins { + pins = "gpio136"; + function = "qup1_se5"; + drive-strength = <2>; + bias-disable; + }; + }; + + qup_spi13_default: qup-spi13-default-state { + data-pins { + pins = "gpio131", "gpio137", "gpio138"; + function = "qup1_se6"; + drive-strength = <6>; + bias-disable; + }; + + cs-pins { + pins = "gpio132"; + function = "qup1_se6"; + drive-strength = <2>; + bias-disable; + }; + }; + + qup_spi14_default: qup-spi14-default-state { + data-pins { + pins = "gpio139", "gpio140", "gpio141"; + function = "qup2_se0"; + drive-strength = <6>; + bias-disable; + }; + + cs-pins { + pins = "gpio142"; + function = "qup2_se0"; + drive-strength = <2>; + bias-disable; + }; + }; + + qup_spi16_default: qup-spi16-default-state { + data-pins { + pins = "gpio145", "gpio146", "gpio147"; + function = "qup2_se2"; + drive-strength = <6>; + bias-disable; + }; + + cs-pins { + pins = "gpio148"; + function = "qup2_se2"; + drive-strength = <2>; + bias-disable; + }; + }; + + qup_spi17_default: qup-spi17-default-state { + data-pins { + pins = "gpio150", "gpio151", "gpio152"; + function = "qup2_se3"; + drive-strength = <6>; + bias-disable; + }; + + cs-pins { + pins = "gpio153"; + function = "qup2_se3"; + drive-strength = <2>; + bias-disable; + }; + }; + + qup_spi18_default: qup-spi18-default-state { + data-pins { + pins = "gpio143", "gpio154", "gpio155"; + function = "qup2_se4"; + drive-strength = <6>; + bias-disable; + }; + + cs-pins { + pins = "gpio144"; + function = "qup2_se4"; + drive-strength = <2>; + bias-disable; + }; + }; + + qup_spi19_default: qup-spi19-default-state { + data-pins { + pins = "gpio156", "gpio157", "gpio158"; + function = "qup2_se5"; + drive-strength = <6>; + bias-disable; + }; + + cs-pins { + pins = "gpio159"; + function = "qup2_se5"; + drive-strength = <2>; + bias-disable; + }; + }; + + qup_spi20_default: qup-spi20-default-state { + data-pins { + pins = "gpio156", "gpio158", "gpio159"; + function = "qup2_se6"; + drive-strength = <6>; + bias-disable; + }; + + cs-pins { + pins = "gpio157"; + function = "qup2_se6"; + drive-strength = <2>; + bias-disable; + }; + }; + + qup_uart0_default: qup-uart0-default-state { + pins = "gpio109", "gpio110"; + function = "qup0_se0"; + drive-strength = <2>; + bias-disable; + }; + + qup_uart1_default: qup-uart1-default-state { + pins = "gpio109", "gpio110"; + function = "qup0_se1"; + drive-strength = <2>; + bias-disable; + }; + + qup_uart2_default: qup-uart2-default-state { + pins = "gpio115", "gpio116"; + function = "qup0_se2"; + drive-strength = <2>; + bias-disable; + }; + + qup_uart3_default: qup-uart3-default-state { + pins = "gpio113", "gpio114"; + function = "qup0_se3"; + drive-strength = <2>; + bias-disable; + }; + + qup_uart4_default: qup-uart4-default-state { + pins = "gpio119", "gpio120"; + function = "qup0_se4"; + drive-strength = <2>; + bias-disable; + }; + + qup_uart5_default: qup-uart5-default-state { + pins = "gpio109", "gpio110"; + function = "qup0_se5"; + drive-strength = <2>; + bias-disable; + }; + + qup_uart7_default: qup-uart7-default-state { + pins = "gpio125", "gpio126"; + function = "qup1_se0"; + drive-strength = <2>; + bias-disable; + }; + + qup_uart8_default: qup-uart8-default-state { + pins = "gpio123", "gpio124"; + function = "qup1_se1"; + drive-strength = <2>; + bias-disable; + }; + + qup_uart9_default: qup-uart9-default-state { + pins = "gpio127", "gpio128"; + function = "qup1_se2"; + drive-strength = <2>; + bias-disable; + }; + + qup_uart10_default: qup-uart10-default-state { + pins = "gpio129", "gpio130"; + function = "qup1_se3"; + drive-strength = <2>; + bias-disable; + }; + + qup_uart11_default: qup-uart11-default-state { + pins = "gpio137", "gpio138"; + function = "qup1_se4"; + drive-strength = <2>; + bias-disable; + }; + + qup_uart12_default: qup-uart12-default-state { + pins = "gpio135", "gpio136"; + function = "qup1_se5"; + drive-strength = <2>; + bias-disable; + }; + + qup_uart13_default: qup-uart13-default-state { + pins = "gpio131", "gpio132"; + function = "qup1_se6"; + drive-strength = <2>; + bias-disable; + }; + + qup_uart14_default: qup-uart14-default-state { + pins = "gpio141", "gpio142"; + function = "qup2_se0"; + drive-strength = <2>; + bias-disable; + }; + + qup_uart15_default: qup-uart15-default-state { + pins = "gpio143", "gpio144"; + function = "qup2_se1"; + drive-strength = <2>; + bias-disable; + }; + + qup_uart16_default: qup-uart16-default-state { + pins = "gpio147", "gpio148"; + function = "qup2_se2"; + drive-strength = <2>; + bias-disable; + }; + + qup_uart17_default: qup-uart17-default-state { + pins = "gpio152", "gpio153"; + function = "qup2_se3"; + drive-strength = <2>; + bias-disable; + }; + + qup_uart18_default: qup-uart18-default-state { + pins = "gpio143", "gpio144"; + function = "qup2_se4"; + drive-strength = <2>; + bias-disable; + }; + + qup_uart19_default: qup-uart19-default-state { + pins = "gpio158", "gpio159"; + function = "qup2_se5"; + drive-strength = <2>; + bias-disable; + }; + + qup_uart20_default: qup-uart20-default-state { + pins = "gpio156", "gpio157"; + function = "qup2_se6"; + drive-strength = <2>; + bias-disable; + }; +}; + +&uart0 { + clocks = <&segcc SE_GCC_QUPV3_WRAP0_S0_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_0 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config"; + pinctrl-0 = <&qup_uart0_default>; + pinctrl-names = "default"; +}; + +&uart1 { + clocks = <&segcc SE_GCC_QUPV3_WRAP0_S1_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_0 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config"; + pinctrl-0 = <&qup_uart1_default>; + pinctrl-names = "default"; +}; + +&uart2 { + clocks = <&segcc SE_GCC_QUPV3_WRAP0_S2_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_0 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config"; + pinctrl-0 = <&qup_uart2_default>; + pinctrl-names = "default"; +}; + +&uart3 { + clocks = <&segcc SE_GCC_QUPV3_WRAP0_S3_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_0 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config"; + pinctrl-0 = <&qup_uart3_default>; + pinctrl-names = "default"; +}; + +&uart4 { + clocks = <&segcc SE_GCC_QUPV3_WRAP0_S4_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_0 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config"; + pinctrl-0 = <&qup_uart4_default>; + pinctrl-names = "default"; +}; + +&uart5 { + clocks = <&segcc SE_GCC_QUPV3_WRAP0_S5_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_0 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_0 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config"; + pinctrl-0 = <&qup_uart5_default>; + pinctrl-names = "default"; +}; + +&uart7 { + clocks = <&segcc SE_GCC_QUPV3_WRAP1_S0_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config"; + pinctrl-0 = <&qup_uart7_default>; + pinctrl-names = "default"; +}; + +&uart8 { + clocks = <&segcc SE_GCC_QUPV3_WRAP1_S1_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config"; + pinctrl-0 = <&qup_uart8_default>; + pinctrl-names = "default"; +}; + +&uart9 { + clocks = <&segcc SE_GCC_QUPV3_WRAP1_S2_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config"; + pinctrl-0 = <&qup_uart9_default>; + pinctrl-names = "default"; +}; + +&uart10 { + clocks = <&segcc SE_GCC_QUPV3_WRAP1_S3_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config"; + pinctrl-0 = <&qup_uart10_default>; + pinctrl-names = "default"; +}; + +&uart11 { + clocks = <&segcc SE_GCC_QUPV3_WRAP1_S4_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config"; + pinctrl-0 = <&qup_uart11_default>; + pinctrl-names = "default"; +}; + +&uart12 { + clocks = <&segcc SE_GCC_QUPV3_WRAP1_S5_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config"; + pinctrl-0 = <&qup_uart12_default>; + pinctrl-names = "default"; +}; + +&uart13 { + clocks = <&segcc SE_GCC_QUPV3_WRAP1_S6_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_1 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config"; + pinctrl-0 = <&qup_uart13_default>; + pinctrl-names = "default"; +}; + +&uart14 { + clocks = <&negcc NE_GCC_QUPV3_WRAP2_S0_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_2 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config"; + pinctrl-0 = <&qup_uart14_default>; + pinctrl-names = "default"; +}; + +&uart15 { + clocks = <&negcc NE_GCC_QUPV3_WRAP2_S1_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_2 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config"; + pinctrl-0 = <&qup_uart15_default>; + pinctrl-names = "default"; +}; + +&uart16 { + clocks = <&negcc NE_GCC_QUPV3_WRAP2_S2_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_2 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config"; + pinctrl-0 = <&qup_uart16_default>; + pinctrl-names = "default"; +}; + +&uart17 { + clocks = <&negcc NE_GCC_QUPV3_WRAP2_S3_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_2 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config"; + pinctrl-0 = <&qup_uart17_default>; + pinctrl-names = "default"; +}; + +&uart18 { + clocks = <&negcc NE_GCC_QUPV3_WRAP2_S4_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_2 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config"; + pinctrl-0 = <&qup_uart18_default>; + pinctrl-names = "default"; +}; + +&uart19 { + clocks = <&negcc NE_GCC_QUPV3_WRAP2_S5_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_2 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config"; + pinctrl-0 = <&qup_uart19_default>; + pinctrl-names = "default"; +}; + +&uart20 { + clocks = <&negcc NE_GCC_QUPV3_WRAP2_S6_CLK>; + clock-names = "se"; + interconnects = <&clk_virt MASTER_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS + &clk_virt SLAVE_QUP_CORE_2 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ALWAYS + &config_noc SLAVE_QUP_2 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "qup-core", "qup-config"; + pinctrl-0 = <&qup_uart20_default>; + pinctrl-names = "default"; +}; + +&ufs_mem_hc { + reg = <0x0 0x01d44000 0x0 0x3000>, + <0x0 0x01d60000 0x0 0x15000>; + reg-names = "std", + "mcq"; + + clocks = <&negcc NE_GCC_UFS_PHY_AXI_CLK>, + <&negcc NE_GCC_AGGRE_NOC_UFS_PHY_AXI_CLK>, + <&negcc NE_GCC_UFS_PHY_AHB_CLK>, + <&negcc NE_GCC_UFS_PHY_UNIPRO_CORE_CLK>, + <&tcsrcc TCSR_UFS_CLKREF_EN>, + <&negcc NE_GCC_UFS_PHY_TX_SYMBOL_0_CLK>, + <&negcc NE_GCC_UFS_PHY_RX_SYMBOL_0_CLK>, + <&negcc NE_GCC_UFS_PHY_RX_SYMBOL_1_CLK>; + clock-names = "core_clk", + "bus_aggr_clk", + "iface_clk", + "core_clk_unipro", + "ref_clk", + "tx_lane0_sync_clk", + "rx_lane0_sync_clk", + "rx_lane1_sync_clk"; + + resets = <&negcc NE_GCC_UFS_PHY_BCR>; + reset-names = "rst"; + + interconnects = <&aggre1_noc_tile MASTER_UFS_MEM QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>, + <&hscnoc MASTER_APPSS_PROC QCOM_ICC_TAG_ACTIVE_ONLY + &config_noc SLAVE_UFS_MEM_CFG QCOM_ICC_TAG_ACTIVE_ONLY>; + interconnect-names = "ufs-ddr", + "cpu-ufs"; + + phys = <&ufs_mem_phy>; + phy-names = "ufsphy"; + + power-domains = <&negcc NE_GCC_UFS_PHY_GDSC>; + operating-points-v2 = <&ufs_opp_table>; + required-opps = <&rpmhpd_opp_nom>; + qcom,ice = <&ice>; + #reset-cells = <1>; + + status = "disabled"; + + ufs_opp_table: opp-table { + compatible = "operating-points-v2"; + + opp-100000000 { + opp-hz = /bits/ 64 <100000000>, + /bits/ 64 <0>, + /bits/ 64 <0>, + /bits/ 64 <100000000>, + /bits/ 64 <0>, + /bits/ 64 <0>, + /bits/ 64 <0>, + /bits/ 64 <0>; + required-opps = <&rpmhpd_opp_low_svs>; + }; + + opp-201500000 { + opp-hz = /bits/ 64 <201500000>, + /bits/ 64 <0>, + /bits/ 64 <0>, + /bits/ 64 <201500000>, + /bits/ 64 <0>, + /bits/ 64 <0>, + /bits/ 64 <0>, + /bits/ 64 <0>; + required-opps = <&rpmhpd_opp_svs>; + }; + + opp-403000000 { + opp-hz = /bits/ 64 <403000000>, + /bits/ 64 <0>, + /bits/ 64 <0>, + /bits/ 64 <403000000>, + /bits/ 64 <0>, + /bits/ 64 <0>, + /bits/ 64 <0>, + /bits/ 64 <0>; + required-opps = <&rpmhpd_opp_nom>; + }; + }; +}; From 00e2a6bf993ea7505f8fbae5d3f7975a81dcfcfa Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Thu, 9 Jul 2026 21:20:12 +0800 Subject: [PATCH 541/562] dt-bindings: arm: qcom: Document Nord IQ10 RRD board Qualcomm Dragonwing IQ10 Robotics Reference Design (RRD) board is built on Nord Embedded variant. Document the board. Link: https://lore.kernel.org/r/20260709132013.4096850-7-shengchao.guo@oss.qualcomm.com Signed-off-by: Shawn Guo --- Documentation/devicetree/bindings/arm/qcom.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/arm/qcom.yaml b/Documentation/devicetree/bindings/arm/qcom.yaml index a7e8dc994b35..3dada073a992 100644 --- a/Documentation/devicetree/bindings/arm/qcom.yaml +++ b/Documentation/devicetree/bindings/arm/qcom.yaml @@ -391,6 +391,7 @@ properties: - items: - enum: + - qcom,iq10-rrd - qcom,sa8797p-ride - const: qcom,nord From d5b8d8e2ba3e34221edb158627edb722d7fcf217 Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Thu, 9 Jul 2026 21:20:13 +0800 Subject: [PATCH 542/562] arm64: dts: qcom: Add device tree for IQ10 RRD board Add initial device tree for the Qualcomm IQ10 RRD board, which is built on Nord Embedded variant. Enable the debug UART, UFS storage, PMICs, I2C and SPI. Co-developed-by: Bartosz Golaszewski Signed-off-by: Bartosz Golaszewski Link: https://lore.kernel.org/r/20260709132013.4096850-8-shengchao.guo@oss.qualcomm.com Signed-off-by: Shawn Guo --- arch/arm64/boot/dts/qcom/Makefile | 1 + arch/arm64/boot/dts/qcom/iq10-rrd.dts | 588 ++++++++++++++++++++++++++ 2 files changed, 589 insertions(+) create mode 100644 arch/arm64/boot/dts/qcom/iq10-rrd.dts diff --git a/arch/arm64/boot/dts/qcom/Makefile b/arch/arm64/boot/dts/qcom/Makefile index 8e6ef4116146..75d29d602e77 100644 --- a/arch/arm64/boot/dts/qcom/Makefile +++ b/arch/arm64/boot/dts/qcom/Makefile @@ -40,6 +40,7 @@ dtb-$(CONFIG_ARCH_QCOM) += ipq9574-rdp449.dtb dtb-$(CONFIG_ARCH_QCOM) += ipq9574-rdp453.dtb dtb-$(CONFIG_ARCH_QCOM) += ipq9574-rdp454.dtb dtb-$(CONFIG_ARCH_QCOM) += ipq9650-rdp488.dtb +dtb-$(CONFIG_ARCH_QCOM) += iq10-rrd.dtb dtb-$(CONFIG_ARCH_QCOM) += kaanapali-mtp.dtb dtb-$(CONFIG_ARCH_QCOM) += kaanapali-qrd.dtb dtb-$(CONFIG_ARCH_QCOM) += lemans-evk.dtb diff --git a/arch/arm64/boot/dts/qcom/iq10-rrd.dts b/arch/arm64/boot/dts/qcom/iq10-rrd.dts new file mode 100644 index 000000000000..39d254b49c14 --- /dev/null +++ b/arch/arm64/boot/dts/qcom/iq10-rrd.dts @@ -0,0 +1,588 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2024-2025, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +/dts-v1/; + +#include +#include + +#include "nord-embedded.dtsi" + +/ { + model = "Qualcomm Technologies, Inc. IQ10 RRD"; + compatible = "qcom,iq10-rrd", "qcom,nord"; + + aliases { + serial0 = &uart15; + }; + + chosen { + stdout-path = "serial0:115200n8"; + }; + + clocks { + xo_board: xo-board-clk { + compatible = "fixed-clock"; + clock-frequency = <38400000>; + #clock-cells = <0>; + }; + + sleep_clk: sleep-clk { + compatible = "fixed-clock"; + clock-frequency = <32000>; + #clock-cells = <0>; + }; + + bi_tcxo_div2: bi-tcxo-div2-clk { + compatible = "fixed-factor-clock"; + #clock-cells = <0>; + clocks = <&rpmhcc RPMH_CXO_CLK>; + clock-mult = <1>; + clock-div = <2>; + }; + + bi_tcxo_ao_div2: bi-tcxo-ao-div2-clk { + compatible = "fixed-factor-clock"; + #clock-cells = <0>; + clocks = <&rpmhcc RPMH_CXO_CLK_A>; + clock-mult = <1>; + clock-div = <2>; + }; + }; + + ufs_vdd_hba: regulator-ufs-vdd-hba { + compatible = "regulator-fixed"; + regulator-name = "ufs_vdd_hba"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + regulator-always-on; + }; + + ufs_vccq2: regulator-ufs-vccq2 { + compatible = "regulator-fixed"; + regulator-name = "ufs_vccq2"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + }; +}; + +&apps_rsc { + /* PMIC A - Kobra_MM (PMM8650AU) - SID 0x0, Bus E0 */ + regulators-0 { + compatible = "qcom,pmm8654au-rpmh-regulators"; + qcom,pmic-id = "A_E0"; + + /* LDO Regulators */ + vreg_l4a_1p2: ldo4 { + regulator-name = "vreg_l4a_1p2"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l6a_1p2: ldo6 { + regulator-name = "vreg_l6a_1p2"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l7a_1p2: ldo7 { + regulator-name = "vreg_l7a_1p2"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + regulator-initial-mode = ; + regulator-always-on; + regulator-boot-on; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l8a_1p8: ldo8 { + regulator-name = "vreg_l8a_1p8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-initial-mode = ; + regulator-always-on; + regulator-boot-on; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + /* SMPS Regulators */ + vreg_s1a_vdd2h_l: smps1 { + regulator-name = "vreg_s1a_vdd2h_l"; + regulator-min-microvolt = <900000>; + regulator-max-microvolt = <1100000>; + regulator-initial-mode = ; + regulator-always-on; + regulator-boot-on; + }; + + vreg_s3a_1p8: smps3 { + regulator-name = "vreg_s3a_1p8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-initial-mode = ; + regulator-always-on; + regulator-boot-on; + }; + + vreg_s5a_mv: smps5 { + regulator-name = "vreg_s5a_mv"; + regulator-min-microvolt = <1328000>; + regulator-max-microvolt = <1370000>; + regulator-initial-mode = ; + regulator-always-on; + regulator-boot-on; + }; + + vreg_s6a_vddq_l: smps6 { + regulator-name = "vreg_s6a_vddq_l"; + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <570000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_s8a_vdda_ebi: smps8 { + regulator-name = "vreg_s8a_vdda_ebi"; + regulator-min-microvolt = <300000>; + regulator-max-microvolt = <1200000>; + regulator-initial-mode = ; + regulator-always-on; + regulator-boot-on; + }; + }; + + /* PMIC E - Kai_MV - SID 0x4, Bus E0 */ + regulators-1 { + compatible = "qcom,pmau0102-rpmh-regulators"; + qcom,pmic-id = "E_E0"; + + /* LDO Regulators */ + vreg_l1e_0p9: ldo1 { + regulator-name = "vreg_l1e_0p9"; + regulator-min-microvolt = <936000>; + regulator-max-microvolt = <936000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l2e_0p9: ldo2 { + regulator-name = "vreg_l2e_0p9"; + regulator-min-microvolt = <936000>; + regulator-max-microvolt = <936000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l3e_1p8: ldo3 { + regulator-name = "vreg_l3e_1p8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-initial-mode = ; + regulator-always-on; + regulator-boot-on; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + /* SMPS Regulators */ + vreg_s1e_nsp3: smps1 { + regulator-name = "vreg_s1e_nsp3"; + regulator-min-microvolt = <300000>; + regulator-max-microvolt = <1200000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_s7e_mxa: smps7 { + regulator-name = "vreg_s7e_mxa"; + regulator-min-microvolt = <300000>; + regulator-max-microvolt = <1200000>; + regulator-initial-mode = ; + regulator-always-on; + regulator-boot-on; + }; + }; + + /* PMIC F - Kai_MV - SID 0x5, Bus E0 */ + regulators-2 { + compatible = "qcom,pmau0102-rpmh-regulators"; + qcom,pmic-id = "F_E0"; + + /* LDO Regulators */ + vreg_l1f_vdd2l: ldo1 { + regulator-name = "vreg_l1f_vdd2l"; + regulator-min-microvolt = <904000>; + regulator-max-microvolt = <904000>; + regulator-initial-mode = ; + regulator-always-on; + regulator-boot-on; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l3f_vdd1: ldo3 { + regulator-name = "vreg_l3f_vdd1"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-initial-mode = ; + regulator-always-on; + regulator-boot-on; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + /* SMPS Regulators */ + vreg_s1f_nsp1: smps1 { + regulator-name = "vreg_s1f_nsp1"; + regulator-min-microvolt = <300000>; + regulator-max-microvolt = <1200000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_s7f_lv_sub: smps7 { + regulator-name = "vreg_s7f_lv_sub"; + regulator-min-microvolt = <1036000>; + regulator-max-microvolt = <1136000>; + regulator-initial-mode = ; + regulator-always-on; + regulator-boot-on; + }; + + vreg_s8f_vddq_h: smps8 { + regulator-name = "vreg_s8f_vddq_h"; + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <570000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + }; + + /* PMIC G - Kai_MV - SID 0x6, Bus E0 */ + regulators-3 { + compatible = "qcom,pmau0102-rpmh-regulators"; + qcom,pmic-id = "G_E0"; + + /* LDO Regulators */ + vreg_l2g_0p7: ldo2 { + regulator-name = "vreg_l2g_0p7"; + regulator-min-microvolt = <752000>; + regulator-max-microvolt = <752000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + /* SMPS Regulators */ + vreg_s1g_nsp0: smps1 { + regulator-name = "vreg_s1g_nsp0"; + regulator-min-microvolt = <300000>; + regulator-max-microvolt = <1200000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_s5g_vdd2h_h: smps5 { + regulator-name = "vreg_s5g_vdd2h_h"; + regulator-min-microvolt = <1080000>; + regulator-max-microvolt = <1150000>; + regulator-initial-mode = ; + regulator-always-on; + regulator-boot-on; + }; + }; + + /* PMIC H - Kai_MV - SID 0x7, Bus E0 */ + regulators-4 { + compatible = "qcom,pmau0102-rpmh-regulators"; + qcom,pmic-id = "H_E0"; + + /* LDO Regulators */ + vreg_l1h_0p9: ldo1 { + regulator-name = "vreg_l1h_0p9"; + regulator-min-microvolt = <904000>; + regulator-max-microvolt = <904000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l2h_1p2: ldo2 { + regulator-name = "vreg_l2h_1p2"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + /* SMPS Regulators */ + vreg_s1h_nsp2: smps1 { + regulator-name = "vreg_s1h_nsp2"; + regulator-min-microvolt = <300000>; + regulator-max-microvolt = <1200000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_s5h_mxc: smps5 { + regulator-name = "vreg_s5h_mxc"; + regulator-min-microvolt = <300000>; + regulator-max-microvolt = <1200000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + }; + + /* PMIC I - Kai_MV - SID 0x8, Bus E0 */ + regulators-5 { + compatible = "qcom,pmau0102-rpmh-regulators"; + qcom,pmic-id = "I_E0"; + + /* LDO Regulators */ + vreg_l1i_0p9: ldo1 { + regulator-name = "vreg_l1i_0p9"; + regulator-min-microvolt = <912000>; + regulator-max-microvolt = <912000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l2i_1p2: ldo2 { + regulator-name = "vreg_l2i_1p2"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + regulator-initial-mode = ; + regulator-always-on; + regulator-boot-on; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l3i_2p5: ldo3 { + regulator-name = "vreg_l3i_2p5"; + regulator-min-microvolt = <2504000>; + regulator-max-microvolt = <2504000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + /* SMPS Regulators */ + vreg_s2i_gfx0: smps2 { + regulator-name = "vreg_s2i_gfx0"; + regulator-min-microvolt = <300000>; + regulator-max-microvolt = <1200000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_s7i_mm: smps7 { + regulator-name = "vreg_s7i_mm"; + regulator-min-microvolt = <300000>; + regulator-max-microvolt = <1200000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + }; + + /* PMIC J - Kai_MV - SID 0x9, Bus E0 */ + regulators-6 { + compatible = "qcom,pmau0102-rpmh-regulators"; + qcom,pmic-id = "J_E0"; + + /* SMPS Regulators */ + vreg_s7j_gfx1: smps7 { + regulator-name = "vreg_s7j_gfx1"; + regulator-min-microvolt = <300000>; + regulator-max-microvolt = <1200000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + }; + + /* PMIC K - Kai_MV - SID 0xA, Bus E0 */ + regulators-7 { + compatible = "qcom,pmau0102-rpmh-regulators"; + qcom,pmic-id = "K_E0"; + + /* LDO Regulators */ + vreg_l1k_vdd2l: ldo1 { + regulator-name = "vreg_l1k_vdd2l"; + regulator-min-microvolt = <904000>; + regulator-max-microvolt = <904000>; + regulator-initial-mode = ; + regulator-always-on; + regulator-boot-on; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l2k_0p9: ldo2 { + regulator-name = "vreg_l2k_0p9"; + regulator-min-microvolt = <880000>; + regulator-max-microvolt = <880000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l3k_vdd1: ldo3 { + regulator-name = "vreg_l3k_vdd1"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-initial-mode = ; + regulator-always-on; + regulator-boot-on; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + }; + + /* PMIC L - Kai_MV - SID 0xB, Bus E0 */ + regulators-8 { + compatible = "qcom,pmau0102-rpmh-regulators"; + qcom,pmic-id = "L_E0"; + + /* LDO Regulators */ + vreg_l3l_1p8: ldo3 { + regulator-name = "vreg_l3l_1p8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + /* SMPS Regulators */ + vreg_s1l_nsp_mxc: smps1 { + regulator-name = "vreg_s1l_nsp_mxc"; + regulator-min-microvolt = <300000>; + regulator-max-microvolt = <1200000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_s2l_cx: smps2 { + regulator-name = "vreg_s2l_cx"; + regulator-min-microvolt = <300000>; + regulator-max-microvolt = <1200000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + }; +}; + +&i2c7 { + clock-frequency = <400000>; + status = "okay"; +}; + +&i2c12 { + clock-frequency = <400000>; + status = "okay"; +}; + +&i2c18 { + clock-frequency = <400000>; + status = "okay"; + + audio_dac: dac@31 { + compatible = "ti,pcm1681"; + reg = <0x31>; + + #sound-dai-cells = <0>; + }; +}; + +&i2c19 { + clock-frequency = <400000>; + status = "okay"; +}; + +&spi16 { + status = "okay"; +}; + +&uart4 { + status = "okay"; +}; + +&uart15 { + status = "okay"; +}; + +&ufs_mem_hc { + reset-gpios = <&tlmm 181 GPIO_ACTIVE_LOW>; + + vcc-supply = <&vreg_l3i_2p5>; + vcc-max-microamp = <1300000>; + vccq-supply = <&vreg_l2i_1p2>; + vccq-max-microamp = <1200000>; + vccq2-supply = <&ufs_vccq2>; + vdd-hba-supply = <&ufs_vdd_hba>; + status = "okay"; +}; + +&ufs_mem_phy { + vdda-phy-supply = <&vreg_l1i_0p9>; + vdda-pll-supply = <&vreg_l2h_1p2>; + status = "okay"; +}; From 3d699fc1a256d8198e3f533f953eed1608b49be2 Mon Sep 17 00:00:00 2001 From: Deepti Jaggi Date: Thu, 9 Jul 2026 16:08:48 +0800 Subject: [PATCH 543/562] dt-bindings: mailbox: qcom-ipcc: Document Nord IPCC Document Inter-Processor Communication Controller on Qualcomm Nord SoC. Signed-off-by: Deepti Jaggi Reviewed-by: Bartosz Golaszewski Link: https://lore.kernel.org/r/20260709080848.4070338-1-shengchao.guo@oss.qualcomm.com Signed-off-by: Shawn Guo --- Documentation/devicetree/bindings/mailbox/qcom-ipcc.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/mailbox/qcom-ipcc.yaml b/Documentation/devicetree/bindings/mailbox/qcom-ipcc.yaml index 3839e1f5f904..a378fe8c7148 100644 --- a/Documentation/devicetree/bindings/mailbox/qcom-ipcc.yaml +++ b/Documentation/devicetree/bindings/mailbox/qcom-ipcc.yaml @@ -30,6 +30,7 @@ properties: - qcom,kaanapali-ipcc - qcom,maili-ipcc - qcom,milos-ipcc + - qcom,nord-ipcc - qcom,qcs8300-ipcc - qcom,qdu1000-ipcc - qcom,sa8255p-ipcc From aeebe0d283fe1844711cc474425905951ba953bc Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Thu, 9 Jul 2026 16:20:40 +0800 Subject: [PATCH 544/562] dt-bindings: misc: qcom,fastrpc: Document Nord FastRPC Add compatible for Qualcomm Nord FastRPC which is compatible with Kaanapali FastRPC. Reviewed-by: Bartosz Golaszewski Link: https://lore.kernel.org/r/20260709082040.4070711-1-shengchao.guo@oss.qualcomm.com Signed-off-by: Shawn Guo --- Documentation/devicetree/bindings/misc/qcom,fastrpc.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/misc/qcom,fastrpc.yaml b/Documentation/devicetree/bindings/misc/qcom,fastrpc.yaml index 2876fdd7c6e6..24fc0752c11a 100644 --- a/Documentation/devicetree/bindings/misc/qcom,fastrpc.yaml +++ b/Documentation/devicetree/bindings/misc/qcom,fastrpc.yaml @@ -26,6 +26,7 @@ properties: - enum: - qcom,glymur-fastrpc - qcom,hawi-fastrpc + - qcom,nord-fastrpc - const: qcom,kaanapali-fastrpc label: From a272a29702c09c9b952e7c24c4bd04aedb9f51aa Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Thu, 9 Jul 2026 16:51:47 +0800 Subject: [PATCH 545/562] dt-bindings: soc: qcom,aoss-qmp: Document Nord AOSS side channel Document Always-on Subsystem side channel on Qualcomm Nord SoC. Link: https://lore.kernel.org/r/20260709085149.4072181-2-shengchao.guo@oss.qualcomm.com Signed-off-by: Shawn Guo --- Documentation/devicetree/bindings/soc/qcom/qcom,aoss-qmp.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,aoss-qmp.yaml b/Documentation/devicetree/bindings/soc/qcom/qcom,aoss-qmp.yaml index 8eaa04431d74..e778fa3a6c92 100644 --- a/Documentation/devicetree/bindings/soc/qcom/qcom,aoss-qmp.yaml +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,aoss-qmp.yaml @@ -30,6 +30,7 @@ properties: - qcom,hawi-aoss-qmp - qcom,kaanapali-aoss-qmp - qcom,milos-aoss-qmp + - qcom,nord-aoss-qmp - qcom,qcs615-aoss-qmp - qcom,qcs8300-aoss-qmp - qcom,qdu1000-aoss-qmp From 84a66dd696fe7267e4f732b9d024cbde07b3b49e Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Thu, 9 Jul 2026 16:51:48 +0800 Subject: [PATCH 546/562] dt-bindings: remoteproc: qcom,sm8550-pas: Document Nord ADSP Document ADSP (HPASS DSP) for Nord SoC. The Nord ADSP use CX and MX power domains, so add a new conditional block to enforce the correct power-domain-names for it. It also uses IPCC-based GLINK signalling (shared with Glymur, Kaanapali and SM8750), so add it to the existing IPCC interrupt-names constraint. Link: https://lore.kernel.org/r/20260709085149.4072181-3-shengchao.guo@oss.qualcomm.com Signed-off-by: Shawn Guo --- .../bindings/remoteproc/qcom,sm8550-pas.yaml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,sm8550-pas.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,sm8550-pas.yaml index faf7b2890de8..1a8539d6b788 100644 --- a/Documentation/devicetree/bindings/remoteproc/qcom,sm8550-pas.yaml +++ b/Documentation/devicetree/bindings/remoteproc/qcom,sm8550-pas.yaml @@ -17,6 +17,7 @@ properties: compatible: oneOf: - enum: + - qcom,nord-adsp-pas - qcom,sdx75-mpss-pas - qcom,sm8550-adsp-pas - qcom,sm8550-cdsp-pas @@ -138,6 +139,7 @@ allOf: - qcom,hawi-cdsp-pas - qcom,kaanapali-adsp-pas - qcom,kaanapali-cdsp-pas + - qcom,nord-adsp-pas - qcom,sm8750-adsp-pas then: properties: @@ -242,6 +244,23 @@ allOf: - const: lcx - const: lmx + - if: + properties: + compatible: + contains: + enum: + - qcom,nord-adsp-pas + then: + properties: + power-domains: + items: + - description: CX power domain + - description: MX power domain + power-domain-names: + items: + - const: cx + - const: mx + - if: properties: compatible: From 40babd686149a5c394bc1908dc8cbdf429298a4c Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Thu, 9 Jul 2026 16:51:49 +0800 Subject: [PATCH 547/562] remoteproc: qcom: pas: Add Nord ADSP support The ADSP (HPASS DSP) on Nord SoC is pre-booted by XBL before Linux starts. Add ADSP resource descriptor, and set early_boot flag for attach path rather than a cold boot sequence. Link: https://lore.kernel.org/r/20260709085149.4072181-4-shengchao.guo@oss.qualcomm.com Signed-off-by: Shawn Guo --- drivers/remoteproc/qcom_q6v5_pas.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/drivers/remoteproc/qcom_q6v5_pas.c b/drivers/remoteproc/qcom_q6v5_pas.c index 60a4337d9e51..486d08e75cab 100644 --- a/drivers/remoteproc/qcom_q6v5_pas.c +++ b/drivers/remoteproc/qcom_q6v5_pas.c @@ -1422,6 +1422,27 @@ static const struct qcom_pas_data milos_cdsp_resource = { .smem_host_id = 5, }; +static const struct qcom_pas_data nord_adsp_resource = { + .crash_reason_smem = 423, + .firmware_name = "adsp.mdt", + .dtb_firmware_name = "adsp_dtb.mbn", + .pas_id = 1, + .dtb_pas_id = 36, + .minidump_id = 5, + .auto_boot = true, + .early_boot = true, + .proxy_pd_names = (char*[]){ + "cx", + "mx", + NULL + }, + .load_state = "adsp", + .ssr_name = "lpass", + .sysmon_name = "adsp", + .ssctl_id = 0x14, + .smem_host_id = 2, +}; + static const struct qcom_pas_data sm8450_mpss_resource = { .crash_reason_smem = 421, .firmware_name = "modem.mdt", @@ -1664,6 +1685,7 @@ static const struct of_device_id qcom_pas_of_match[] = { { .compatible = "qcom,milos-cdsp-pas", .data = &milos_cdsp_resource }, { .compatible = "qcom,milos-mpss-pas", .data = &sm8450_mpss_resource }, { .compatible = "qcom,milos-wpss-pas", .data = &sc7280_wpss_resource }, + { .compatible = "qcom,nord-adsp-pas", .data = &nord_adsp_resource }, { .compatible = "qcom,msm8226-adsp-pil", .data = &msm8996_adsp_resource }, { .compatible = "qcom,msm8953-adsp-pil", .data = &msm8996_adsp_resource }, { .compatible = "qcom,msm8974-adsp-pil", .data = &msm8996_adsp_resource }, From ae8a808c50f8d6c9c7f8a176f5384b84d382bfc7 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 7 Jul 2026 13:08:06 +0200 Subject: [PATCH 548/562] arm64: defconfig: enable clock drivers for Qualcomm Nord Enable the two clock drivers we currently support on Nord platforms. Signed-off-by: Bartosz Golaszewski --- arch/arm64/configs/defconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig index 0af40f233db6..062018efb19e 100644 --- a/arch/arm64/configs/defconfig +++ b/arch/arm64/configs/defconfig @@ -1475,6 +1475,8 @@ CONFIG_CLK_KAANAPALI_GCC=y CONFIG_CLK_KAANAPALI_GPUCC=m CONFIG_CLK_KAANAPALI_TCSRCC=m CONFIG_CLK_KAANAPALI_VIDEOCC=m +CONFIG_CLK_NORD_GCC=y +CONFIG_CLK_NORD_TCSRCC=y CONFIG_CLK_X1E80100_CAMCC=m CONFIG_CLK_X1E80100_DISPCC=m CONFIG_CLK_X1E80100_GCC=y From d541a9577d436abda89e092c6a7eea482ba437b6 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 7 Jul 2026 13:10:20 +0200 Subject: [PATCH 549/562] arm64: defconfig: enable the interconnect driver for Qualcomm Nord Enable the Qualcomm Nord interconnect driver in arm64 defconfig. Signed-off-by: Bartosz Golaszewski --- arch/arm64/configs/defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig index 062018efb19e..327564a51810 100644 --- a/arch/arm64/configs/defconfig +++ b/arch/arm64/configs/defconfig @@ -1870,6 +1870,7 @@ CONFIG_INTERCONNECT_QCOM_KAANAPALI=y CONFIG_INTERCONNECT_QCOM_MSM8916=m CONFIG_INTERCONNECT_QCOM_MSM8953=y CONFIG_INTERCONNECT_QCOM_MSM8996=y +CONFIG_INTERCONNECT_QCOM_NORD=y CONFIG_INTERCONNECT_QCOM_OSM_L3=m CONFIG_INTERCONNECT_QCOM_QCM2290=y CONFIG_INTERCONNECT_QCOM_QCS404=m From 75e2d5a940a18342f77769de83f2e0a086d2461f Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Mon, 6 Jul 2026 14:27:13 +0530 Subject: [PATCH 550/562] dt-bindings: clock: qcom: Add video clock controller on Nord SoC Add compatible string for Nord video clock controller and the bindings for Nord Qualcomm SoC. Link: https://lore.kernel.org/r/20260706-nord_videocc_camcc-v1-1-bae3be9e9770@oss.qualcomm.com Signed-off-by: Taniya Das --- .../bindings/clock/qcom,sm8450-videocc.yaml | 2 + include/dt-bindings/clock/qcom,nord-videocc.h | 40 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 include/dt-bindings/clock/qcom,nord-videocc.h diff --git a/Documentation/devicetree/bindings/clock/qcom,sm8450-videocc.yaml b/Documentation/devicetree/bindings/clock/qcom,sm8450-videocc.yaml index 5d77029bfaf8..9b9878e9b9cf 100644 --- a/Documentation/devicetree/bindings/clock/qcom,sm8450-videocc.yaml +++ b/Documentation/devicetree/bindings/clock/qcom,sm8450-videocc.yaml @@ -17,6 +17,7 @@ description: | See also: include/dt-bindings/clock/qcom,glymur-videocc.h include/dt-bindings/clock/qcom,kaanapali-videocc.h + include/dt-bindings/clock/qcom,nord-videocc.h include/dt-bindings/clock/qcom,sm8450-videocc.h include/dt-bindings/clock/qcom,sm8650-videocc.h include/dt-bindings/clock/qcom,sm8750-videocc.h @@ -27,6 +28,7 @@ properties: enum: - qcom,glymur-videocc - qcom,kaanapali-videocc + - qcom,nord-videocc - qcom,sm8450-videocc - qcom,sm8475-videocc - qcom,sm8550-videocc diff --git a/include/dt-bindings/clock/qcom,nord-videocc.h b/include/dt-bindings/clock/qcom,nord-videocc.h new file mode 100644 index 000000000000..8d7546021109 --- /dev/null +++ b/include/dt-bindings/clock/qcom,nord-videocc.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef _DT_BINDINGS_CLK_QCOM_VIDEO_CC_NORD_H +#define _DT_BINDINGS_CLK_QCOM_VIDEO_CC_NORD_H + +/* VIDEO_CC clocks */ +#define VIDEO_CC_AHB_CLK_SRC 0 +#define VIDEO_CC_MVS0_CLK 1 +#define VIDEO_CC_MVS0_CLK_SRC 2 +#define VIDEO_CC_MVS0_DIV_CLK_SRC 3 +#define VIDEO_CC_MVS0_FREERUN_CLK 4 +#define VIDEO_CC_MVS0_SHIFT_CLK 5 +#define VIDEO_CC_MVS0C_CLK 6 +#define VIDEO_CC_MVS0C_DIV2_DIV_CLK_SRC 7 +#define VIDEO_CC_MVS0C_FREERUN_CLK 8 +#define VIDEO_CC_MVS0C_SHIFT_CLK 9 +#define VIDEO_CC_MVS1_CLK 10 +#define VIDEO_CC_MVS1_DIV_CLK_SRC 11 +#define VIDEO_CC_MVS1_FREERUN_CLK 12 +#define VIDEO_CC_MVS1_SHIFT_CLK 13 +#define VIDEO_CC_PLL0 14 +#define VIDEO_CC_SLEEP_CLK_SRC 15 +#define VIDEO_CC_XO_CLK_SRC 16 + +/* VIDEO_CC power domains */ +#define VIDEO_CC_MVS0_GDSC 0 +#define VIDEO_CC_MVS0C_GDSC 1 +#define VIDEO_CC_MVS1_GDSC 2 + +/* VIDEO_CC resets */ +#define VIDEO_CC_INTERFACE_BCR 0 +#define VIDEO_CC_MVS0_BCR 1 +#define VIDEO_CC_MVS0C_CLK_ARES 2 +#define VIDEO_CC_MVS0C_BCR 3 +#define VIDEO_CC_MVS1_BCR 4 + +#endif From f823a9699af2d818b92495b74aa5561851e023bd Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Mon, 6 Jul 2026 14:27:14 +0530 Subject: [PATCH 551/562] dt-bindings: clock: qcom: Add support for Camera Clock Controller for Nord Update the compatible and the bindings for CAMCC support on Nord SoC. Link: https://lore.kernel.org/r/20260706-nord_videocc_camcc-v1-2-bae3be9e9770@oss.qualcomm.com Signed-off-by: Taniya Das --- .../bindings/clock/qcom,sm8450-camcc.yaml | 2 + include/dt-bindings/clock/qcom,nord-camcc.h | 167 ++++++++++++++++++ 2 files changed, 169 insertions(+) create mode 100644 include/dt-bindings/clock/qcom,nord-camcc.h diff --git a/Documentation/devicetree/bindings/clock/qcom,sm8450-camcc.yaml b/Documentation/devicetree/bindings/clock/qcom,sm8450-camcc.yaml index 8492a7ef7324..8e460df9f744 100644 --- a/Documentation/devicetree/bindings/clock/qcom,sm8450-camcc.yaml +++ b/Documentation/devicetree/bindings/clock/qcom,sm8450-camcc.yaml @@ -18,6 +18,7 @@ description: | See also: include/dt-bindings/clock/qcom,kaanapali-camcc.h include/dt-bindings/clock/qcom,kaanapali-cambistmclkcc.h + include/dt-bindings/clock/qcom,nord-camcc.h include/dt-bindings/clock/qcom,sm8450-camcc.h include/dt-bindings/clock/qcom,sm8550-camcc.h include/dt-bindings/clock/qcom,sm8650-camcc.h @@ -29,6 +30,7 @@ properties: enum: - qcom,kaanapali-cambistmclkcc - qcom,kaanapali-camcc + - qcom,nord-camcc - qcom,sm8450-camcc - qcom,sm8475-camcc - qcom,sm8550-camcc diff --git a/include/dt-bindings/clock/qcom,nord-camcc.h b/include/dt-bindings/clock/qcom,nord-camcc.h new file mode 100644 index 000000000000..655fef2084a5 --- /dev/null +++ b/include/dt-bindings/clock/qcom,nord-camcc.h @@ -0,0 +1,167 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef _DT_BINDINGS_CLK_QCOM_CAM_CC_NORD_H +#define _DT_BINDINGS_CLK_QCOM_CAM_CC_NORD_H + +/* CAM_CC clocks */ +#define CAM_CC_CAMNOC_DCD_XO_CLK 0 +#define CAM_CC_CAMNOC_NRT_AXI_CLK 1 +#define CAM_CC_CAMNOC_RT_AXI_CLK 2 +#define CAM_CC_CAMNOC_RT_AXI_CLK_SRC 3 +#define CAM_CC_CAMNOC_XO_CLK 4 +#define CAM_CC_CCI_0_CLK 5 +#define CAM_CC_CCI_0_CLK_SRC 6 +#define CAM_CC_CCI_1_CLK 7 +#define CAM_CC_CCI_1_CLK_SRC 8 +#define CAM_CC_CCI_2_CLK 9 +#define CAM_CC_CCI_2_CLK_SRC 10 +#define CAM_CC_CCI_3_CLK 11 +#define CAM_CC_CCI_3_CLK_SRC 12 +#define CAM_CC_CCI_4_CLK 13 +#define CAM_CC_CCI_4_CLK_SRC 14 +#define CAM_CC_CCU_FAST_AHB_CLK 15 +#define CAM_CC_CORE_AHB_CLK 16 +#define CAM_CC_CPHY_RX_CLK_SRC 17 +#define CAM_CC_CSI0PHYTIMER_CLK 18 +#define CAM_CC_CSI0PHYTIMER_CLK_SRC 19 +#define CAM_CC_CSI1PHYTIMER_CLK 20 +#define CAM_CC_CSI1PHYTIMER_CLK_SRC 21 +#define CAM_CC_CSI2PHYTIMER_CLK 22 +#define CAM_CC_CSI2PHYTIMER_CLK_SRC 23 +#define CAM_CC_CSI3PHYTIMER_CLK 24 +#define CAM_CC_CSI3PHYTIMER_CLK_SRC 25 +#define CAM_CC_CSI4PHYTIMER_CLK 26 +#define CAM_CC_CSI4PHYTIMER_CLK_SRC 27 +#define CAM_CC_CSID_CLK 28 +#define CAM_CC_CSID_CLK_SRC 29 +#define CAM_CC_CSID_CSIPHY_RX_CLK 30 +#define CAM_CC_CSIPHY0_CLK 31 +#define CAM_CC_CSIPHY1_CLK 32 +#define CAM_CC_CSIPHY2_CLK 33 +#define CAM_CC_CSIPHY3_CLK 34 +#define CAM_CC_CSIPHY4_CLK 35 +#define CAM_CC_FAST_AHB_CLK_SRC 36 +#define CAM_CC_GDSC_CLK 37 +#define CAM_CC_ICP_0_AHB_CLK 38 +#define CAM_CC_ICP_0_CLK 39 +#define CAM_CC_ICP_0_CLK_SRC 40 +#define CAM_CC_ICP_1_AHB_CLK 41 +#define CAM_CC_ICP_1_CLK 42 +#define CAM_CC_ICP_1_CLK_SRC 43 +#define CAM_CC_IFE_0_MAIN_CLK 44 +#define CAM_CC_IFE_0_MAIN_CLK_SRC 45 +#define CAM_CC_IFE_0_MAIN_FAST_AHB_CLK 46 +#define CAM_CC_IFE_0_PCP_CLK 47 +#define CAM_CC_IFE_0_PCP_FAST_AHB_CLK 48 +#define CAM_CC_IFE_0_SCALAR_CLK 49 +#define CAM_CC_IFE_0_SCALAR_FAST_AHB_CLK 50 +#define CAM_CC_IFE_0_TMC_CLK 51 +#define CAM_CC_IFE_0_TMC_FAST_AHB_CLK 52 +#define CAM_CC_IFE_1_MAIN_CLK 53 +#define CAM_CC_IFE_1_MAIN_CLK_SRC 54 +#define CAM_CC_IFE_1_MAIN_FAST_AHB_CLK 55 +#define CAM_CC_IFE_1_PCP_CLK 56 +#define CAM_CC_IFE_1_PCP_FAST_AHB_CLK 57 +#define CAM_CC_IFE_1_SCALAR_CLK 58 +#define CAM_CC_IFE_1_SCALAR_FAST_AHB_CLK 59 +#define CAM_CC_IFE_1_TMC_CLK 60 +#define CAM_CC_IFE_1_TMC_FAST_AHB_CLK 61 +#define CAM_CC_IFE_2_MAIN_CLK 62 +#define CAM_CC_IFE_2_MAIN_CLK_SRC 63 +#define CAM_CC_IFE_2_MAIN_FAST_AHB_CLK 64 +#define CAM_CC_IFE_2_PCP_CLK 65 +#define CAM_CC_IFE_2_PCP_FAST_AHB_CLK 66 +#define CAM_CC_IFE_2_SCALAR_CLK 67 +#define CAM_CC_IFE_2_SCALAR_FAST_AHB_CLK 68 +#define CAM_CC_IFE_2_TMC_CLK 69 +#define CAM_CC_IFE_2_TMC_FAST_AHB_CLK 70 +#define CAM_CC_IFE_LITE_AHB_CLK 71 +#define CAM_CC_IFE_LITE_CLK 72 +#define CAM_CC_IFE_LITE_CLK_SRC 73 +#define CAM_CC_IFE_LITE_CPHY_RX_CLK 74 +#define CAM_CC_IFE_LITE_CSID_CLK 75 +#define CAM_CC_IFE_LITE_CSID_CLK_SRC 76 +#define CAM_CC_IPE_0_AHB_CLK 77 +#define CAM_CC_IPE_0_CLK 78 +#define CAM_CC_IPE_0_CLK_SRC 79 +#define CAM_CC_IPE_0_FAST_AHB_CLK 80 +#define CAM_CC_IPE_1_AHB_CLK 81 +#define CAM_CC_IPE_1_CLK 82 +#define CAM_CC_IPE_1_CLK_SRC 83 +#define CAM_CC_IPE_1_FAST_AHB_CLK 84 +#define CAM_CC_PLL0 85 +#define CAM_CC_PLL0_OUT_EVEN 86 +#define CAM_CC_PLL0_OUT_ODD 87 +#define CAM_CC_PLL2 88 +#define CAM_CC_PLL2_OUT_EVEN 89 +#define CAM_CC_PLL3 90 +#define CAM_CC_PLL3_OUT_EVEN 91 +#define CAM_CC_PLL4 92 +#define CAM_CC_PLL4_OUT_EVEN 93 +#define CAM_CC_PLL5 94 +#define CAM_CC_PLL5_OUT_EVEN 95 +#define CAM_CC_PLL6 96 +#define CAM_CC_PLL6_OUT_EVEN 97 +#define CAM_CC_QDSS_DEBUG_CLK 98 +#define CAM_CC_QDSS_DEBUG_CLK_SRC 99 +#define CAM_CC_QDSS_DEBUG_XO_CLK 100 +#define CAM_CC_QUP_AHBM_CLK 101 +#define CAM_CC_QUP_AHBM_CLK_SRC 102 +#define CAM_CC_QUP_CORE_2X_CLK 103 +#define CAM_CC_QUP_CORE_2X_CLK_SRC 104 +#define CAM_CC_QUP_CORE_2X_DIV_CLK_SRC 105 +#define CAM_CC_QUP_CORE_CLK 106 +#define CAM_CC_QUP_SE_CLK 107 +#define CAM_CC_QUP_SE_CLK_SRC 108 +#define CAM_CC_QUP_SLEEP_CLK 109 +#define CAM_CC_SFE_LITE_0_CLK 110 +#define CAM_CC_SFE_LITE_0_FAST_AHB_CLK 111 +#define CAM_CC_SFE_LITE_1_CLK 112 +#define CAM_CC_SFE_LITE_1_FAST_AHB_CLK 113 +#define CAM_CC_SFE_LITE_2_CLK 114 +#define CAM_CC_SFE_LITE_2_FAST_AHB_CLK 115 +#define CAM_CC_SLEEP_CLK 116 +#define CAM_CC_SLEEP_CLK_SRC 117 +#define CAM_CC_SLOW_AHB_CLK_SRC 118 +#define CAM_CC_SM_OBS_CLK 119 +#define CAM_CC_TOP_AHB_CLK 120 +#define CAM_CC_TOP_FAST_AHB_CLK 121 +#define CAM_CC_TOP_IFE_0_CLK 122 +#define CAM_CC_TOP_IFE_1_CLK 123 +#define CAM_CC_TOP_IFE_2_CLK 124 +#define CAM_CC_TOP_IFE_LITE_CLK 125 +#define CAM_CC_TOP_IPE_0_CLK 126 +#define CAM_CC_TOP_IPE_1_CLK 127 +#define CAM_CC_TOP_QUP_AHBM_CLK 128 +#define CAM_CC_TOP_SFE_LITE_0_CLK 129 +#define CAM_CC_TOP_SFE_LITE_1_CLK 130 +#define CAM_CC_TOP_SFE_LITE_2_CLK 131 +#define CAM_CC_TPG_CSIPHY_RX_CLK 132 +#define CAM_CC_XO_CLK_SRC 133 + +/* CAM_CC power domains */ +#define CAM_CC_IFE_0_GDSC 0 +#define CAM_CC_IFE_1_GDSC 1 +#define CAM_CC_IFE_2_GDSC 2 +#define CAM_CC_IPE_0_GDSC 3 +#define CAM_CC_IPE_1_GDSC 4 +#define CAM_CC_TITAN_TOP_GDSC 5 + +/* CAM_CC resets */ +#define CAM_CC_CCU_BCR 0 +#define CAM_CC_ICP_0_BCR 1 +#define CAM_CC_ICP_1_BCR 2 +#define CAM_CC_IFE_0_BCR 3 +#define CAM_CC_IFE_1_BCR 4 +#define CAM_CC_IFE_2_BCR 5 +#define CAM_CC_IPE_0_BCR 6 +#define CAM_CC_IPE_1_BCR 7 +#define CAM_CC_QDSS_DEBUG_BCR 8 +#define CAM_CC_SFE_LITE_0_BCR 9 +#define CAM_CC_SFE_LITE_1_BCR 10 +#define CAM_CC_SFE_LITE_2_BCR 11 + +#endif From 89bd2ff485bedc3185580077dc43da17d5a61bb7 Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Mon, 6 Jul 2026 14:27:15 +0530 Subject: [PATCH 552/562] clk: qcom: videocc-nord: Add video clock controller driver for Nord Add support for the video clock controller for video clients to be able to request for videocc clocks on Nord platform. Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260706-nord_videocc_camcc-v1-3-bae3be9e9770@oss.qualcomm.com Signed-off-by: Taniya Das --- drivers/clk/qcom/Kconfig | 11 + drivers/clk/qcom/Makefile | 1 + drivers/clk/qcom/videocc-nord.c | 507 ++++++++++++++++++++++++++++++++ 3 files changed, 519 insertions(+) create mode 100644 drivers/clk/qcom/videocc-nord.c diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig index 10dcfa72a0bd..444c9e3a11a3 100644 --- a/drivers/clk/qcom/Kconfig +++ b/drivers/clk/qcom/Kconfig @@ -177,6 +177,17 @@ config CLK_NORD_GPUCC Say Y if you want to support graphics controller devices and functionality such as 3D graphics. +config CLK_NORD_VIDEOCC + tristate "Nord VIDEO Clock Controller" + depends on ARM64 || COMPILE_TEST + select CLK_NORD_GCC + default m if ARCH_QCOM + help + Support for the video clock controller on Qualcomm Technologies, Inc. + Nord devices. + Say Y if you want to support video devices and functionality such as + video encode/decode. + config CLK_X1E80100_CAMCC tristate "X1E80100 Camera Clock Controller" depends on ARM64 || COMPILE_TEST diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile index fb0a5bc94e32..140137662065 100644 --- a/drivers/clk/qcom/Makefile +++ b/drivers/clk/qcom/Makefile @@ -41,6 +41,7 @@ obj-$(CONFIG_CLK_NORD_DISPCC) += dispcc0-nord.o dispcc1-nord.o obj-$(CONFIG_CLK_NORD_GCC) += gcc-nord.o negcc-nord.o nwgcc-nord.o segcc-nord.o obj-$(CONFIG_CLK_NORD_GPUCC) += gpucc-nord.o gpu2cc-nord.o obj-$(CONFIG_CLK_NORD_TCSRCC) += tcsrcc-nord.o +obj-$(CONFIG_CLK_NORD_VIDEOCC) += videocc-nord.o obj-$(CONFIG_CLK_X1E80100_CAMCC) += camcc-x1e80100.o obj-$(CONFIG_CLK_X1E80100_DISPCC) += dispcc-x1e80100.o obj-$(CONFIG_CLK_X1E80100_GCC) += gcc-x1e80100.o diff --git a/drivers/clk/qcom/videocc-nord.c b/drivers/clk/qcom/videocc-nord.c new file mode 100644 index 000000000000..ee73e89a01da --- /dev/null +++ b/drivers/clk/qcom/videocc-nord.c @@ -0,0 +1,507 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include + +#include + +#include "clk-alpha-pll.h" +#include "clk-branch.h" +#include "clk-pll.h" +#include "clk-rcg.h" +#include "clk-regmap.h" +#include "clk-regmap-divider.h" +#include "clk-regmap-mux.h" +#include "common.h" +#include "gdsc.h" +#include "reset.h" + +enum { + DT_BI_TCXO, + DT_IFACE, +}; + +enum { + P_BI_TCXO, + P_SLEEP_CLK, + P_VIDEO_CC_PLL0_OUT_MAIN, +}; + +static const struct pll_vco lucid_ole_vco[] = { + { 249600000, 2300000000, 0 }, +}; + +/* 720.0 MHz Configuration */ +static const struct alpha_pll_config video_cc_pll0_config = { + .l = 0x25, + .alpha = 0x8000, + .config_ctl_val = 0x20485699, + .config_ctl_hi_val = 0x00182261, + .config_ctl_hi1_val = 0x82aa299c, + .test_ctl_val = 0x00000000, + .test_ctl_hi_val = 0x00000003, + .test_ctl_hi1_val = 0x00009000, + .test_ctl_hi2_val = 0x00000034, + .user_ctl_val = 0x00000000, + .user_ctl_hi_val = 0x00400005, +}; + +static struct clk_alpha_pll video_cc_pll0 = { + .offset = 0x0, + .config = &video_cc_pll0_config, + .vco_table = lucid_ole_vco, + .num_vco = ARRAY_SIZE(lucid_ole_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_pll0", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_lucid_evo_ops, + }, + }, +}; + +static const struct parent_map video_cc_parent_map_0[] = { + { P_BI_TCXO, 0 }, +}; + +static const struct clk_parent_data video_cc_parent_data_0[] = { + { .index = DT_BI_TCXO }, +}; + +static const struct parent_map video_cc_parent_map_1[] = { + { P_BI_TCXO, 0 }, + { P_VIDEO_CC_PLL0_OUT_MAIN, 1 }, +}; + +static const struct clk_parent_data video_cc_parent_data_1[] = { + { .index = DT_BI_TCXO }, + { .hw = &video_cc_pll0.clkr.hw }, +}; + +static const struct freq_tbl ftbl_video_cc_ahb_clk_src[] = { + F(19200000, P_BI_TCXO, 1, 0, 0), + { } +}; + +static struct clk_rcg2 video_cc_ahb_clk_src = { + .cmd_rcgr = 0x8018, + .mnd_width = 0, + .hid_width = 5, + .parent_map = video_cc_parent_map_0, + .freq_tbl = ftbl_video_cc_ahb_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "video_cc_ahb_clk_src", + .parent_data = video_cc_parent_data_0, + .num_parents = ARRAY_SIZE(video_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_video_cc_mvs0_clk_src[] = { + F(720000000, P_VIDEO_CC_PLL0_OUT_MAIN, 1, 0, 0), + F(1305000000, P_VIDEO_CC_PLL0_OUT_MAIN, 1, 0, 0), + F(1440000000, P_VIDEO_CC_PLL0_OUT_MAIN, 1, 0, 0), + F(1600000000, P_VIDEO_CC_PLL0_OUT_MAIN, 1, 0, 0), + F(1680000000, P_VIDEO_CC_PLL0_OUT_MAIN, 1, 0, 0), + { } +}; + +static struct clk_rcg2 video_cc_mvs0_clk_src = { + .cmd_rcgr = 0x8000, + .mnd_width = 0, + .hid_width = 5, + .parent_map = video_cc_parent_map_1, + .freq_tbl = ftbl_video_cc_mvs0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0_clk_src", + .parent_data = video_cc_parent_data_1, + .num_parents = ARRAY_SIZE(video_cc_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 video_cc_xo_clk_src = { + .cmd_rcgr = 0x80f8, + .mnd_width = 0, + .hid_width = 5, + .parent_map = video_cc_parent_map_0, + .freq_tbl = ftbl_video_cc_ahb_clk_src, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "video_cc_xo_clk_src", + .parent_data = video_cc_parent_data_0, + .num_parents = ARRAY_SIZE(video_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_regmap_div video_cc_mvs0_div_clk_src = { + .reg = 0x809c, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_mvs0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_regmap_div video_cc_mvs0c_div2_div_clk_src = { + .reg = 0x8060, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0c_div2_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_mvs0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_regmap_div video_cc_mvs1_div_clk_src = { + .reg = 0x80d8, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs1_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_mvs0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_branch video_cc_mvs0_clk = { + .halt_reg = 0x807c, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x807c, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x807c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0_clk", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_mvs0_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch video_cc_mvs0_freerun_clk = { + .halt_reg = 0x808c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x808c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0_freerun_clk", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_mvs0_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch video_cc_mvs0_shift_clk = { + .halt_reg = 0x8114, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x8114, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x8114, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0_shift_clk", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_xo_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch video_cc_mvs0c_clk = { + .halt_reg = 0x804c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x804c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0c_clk", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_mvs0c_div2_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch video_cc_mvs0c_freerun_clk = { + .halt_reg = 0x805c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x805c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0c_freerun_clk", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_mvs0c_div2_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch video_cc_mvs0c_shift_clk = { + .halt_reg = 0x811c, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x811c, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x811c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs0c_shift_clk", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_xo_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch video_cc_mvs1_clk = { + .halt_reg = 0x80b8, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x80b8, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x80b8, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs1_clk", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_mvs1_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch video_cc_mvs1_freerun_clk = { + .halt_reg = 0x80c8, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x80c8, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs1_freerun_clk", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_mvs1_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch video_cc_mvs1_shift_clk = { + .halt_reg = 0x8118, + .halt_check = BRANCH_HALT_VOTED, + .hwcg_reg = 0x8118, + .hwcg_bit = 1, + .clkr = { + .enable_reg = 0x8118, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "video_cc_mvs1_shift_clk", + .parent_hws = (const struct clk_hw*[]) { + &video_cc_xo_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct gdsc video_cc_mvs0c_gdsc = { + .gdscr = 0x8034, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0x6, + .pd = { + .name = "video_cc_mvs0c_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct gdsc video_cc_mvs0_gdsc = { + .gdscr = 0x8068, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0x6, + .pd = { + .name = "video_cc_mvs0_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .parent = &video_cc_mvs0c_gdsc.pd, + .flags = HW_CTRL_TRIGGER | POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct gdsc video_cc_mvs1_gdsc = { + .gdscr = 0x80a4, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0x6, + .pd = { + .name = "video_cc_mvs1_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .flags = HW_CTRL_TRIGGER | POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct clk_regmap *video_cc_nord_clocks[] = { + [VIDEO_CC_AHB_CLK_SRC] = &video_cc_ahb_clk_src.clkr, + [VIDEO_CC_MVS0_CLK] = &video_cc_mvs0_clk.clkr, + [VIDEO_CC_MVS0_CLK_SRC] = &video_cc_mvs0_clk_src.clkr, + [VIDEO_CC_MVS0_DIV_CLK_SRC] = &video_cc_mvs0_div_clk_src.clkr, + [VIDEO_CC_MVS0_FREERUN_CLK] = &video_cc_mvs0_freerun_clk.clkr, + [VIDEO_CC_MVS0_SHIFT_CLK] = &video_cc_mvs0_shift_clk.clkr, + [VIDEO_CC_MVS0C_CLK] = &video_cc_mvs0c_clk.clkr, + [VIDEO_CC_MVS0C_DIV2_DIV_CLK_SRC] = &video_cc_mvs0c_div2_div_clk_src.clkr, + [VIDEO_CC_MVS0C_FREERUN_CLK] = &video_cc_mvs0c_freerun_clk.clkr, + [VIDEO_CC_MVS0C_SHIFT_CLK] = &video_cc_mvs0c_shift_clk.clkr, + [VIDEO_CC_MVS1_CLK] = &video_cc_mvs1_clk.clkr, + [VIDEO_CC_MVS1_DIV_CLK_SRC] = &video_cc_mvs1_div_clk_src.clkr, + [VIDEO_CC_MVS1_FREERUN_CLK] = &video_cc_mvs1_freerun_clk.clkr, + [VIDEO_CC_MVS1_SHIFT_CLK] = &video_cc_mvs1_shift_clk.clkr, + [VIDEO_CC_PLL0] = &video_cc_pll0.clkr, + [VIDEO_CC_XO_CLK_SRC] = &video_cc_xo_clk_src.clkr, +}; + +static struct gdsc *video_cc_nord_gdscs[] = { + [VIDEO_CC_MVS0_GDSC] = &video_cc_mvs0_gdsc, + [VIDEO_CC_MVS0C_GDSC] = &video_cc_mvs0c_gdsc, + [VIDEO_CC_MVS1_GDSC] = &video_cc_mvs1_gdsc, +}; + +static const struct qcom_reset_map video_cc_nord_resets[] = { + [VIDEO_CC_INTERFACE_BCR] = { 0x80dc }, + [VIDEO_CC_MVS0_BCR] = { 0x8064 }, + [VIDEO_CC_MVS0C_CLK_ARES] = { 0x804c, 2 }, + [VIDEO_CC_MVS0C_BCR] = { 0x8030 }, + [VIDEO_CC_MVS1_BCR] = { 0x80a0 }, +}; + +static struct clk_alpha_pll *video_cc_nord_plls[] = { + &video_cc_pll0, +}; + +static const u32 video_cc_nord_critical_cbcrs[] = { + 0x80e0, /* VIDEO_CC_AHB_CLK */ + 0x8138, /* VIDEO_CC_SLEEP_CLK */ + 0x8110, /* VIDEO_CC_XO_CLK */ +}; + +static const struct regmap_config video_cc_nord_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0xa060, + .fast_io = true, +}; + +static void videocc_nord_regs_configure(struct device *dev, struct regmap *regmap) +{ + /* + * Enable clk_on sync for MVS0 and MVS0_FREERUN clocks via + * VIDEO_CC_SPARE1 during core reset by default. + */ + regmap_set_bits(regmap, 0x9f24, BIT(0)); +} + +static const struct qcom_cc_driver_data video_cc_nord_driver_data = { + .alpha_plls = video_cc_nord_plls, + .num_alpha_plls = ARRAY_SIZE(video_cc_nord_plls), + .clk_cbcrs = video_cc_nord_critical_cbcrs, + .num_clk_cbcrs = ARRAY_SIZE(video_cc_nord_critical_cbcrs), + .clk_regs_configure = videocc_nord_regs_configure, +}; + +static const struct qcom_cc_desc video_cc_nord_desc = { + .config = &video_cc_nord_regmap_config, + .clks = video_cc_nord_clocks, + .num_clks = ARRAY_SIZE(video_cc_nord_clocks), + .resets = video_cc_nord_resets, + .num_resets = ARRAY_SIZE(video_cc_nord_resets), + .gdscs = video_cc_nord_gdscs, + .num_gdscs = ARRAY_SIZE(video_cc_nord_gdscs), + .use_rpm = true, + .driver_data = &video_cc_nord_driver_data, +}; + +static const struct of_device_id video_cc_nord_match_table[] = { + { .compatible = "qcom,nord-videocc" }, + { } +}; +MODULE_DEVICE_TABLE(of, video_cc_nord_match_table); + +static int video_cc_nord_probe(struct platform_device *pdev) +{ + return qcom_cc_probe(pdev, &video_cc_nord_desc); +} + +static struct platform_driver video_cc_nord_driver = { + .probe = video_cc_nord_probe, + .driver = { + .name = "videocc-nord", + .of_match_table = video_cc_nord_match_table, + }, +}; + +module_platform_driver(video_cc_nord_driver); + +MODULE_DESCRIPTION("QTI VIDEOCC Nord Driver"); +MODULE_LICENSE("GPL"); From ed0dae278d9e7b3b7f58370a0e3269809531822e Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Mon, 6 Jul 2026 14:27:16 +0530 Subject: [PATCH 553/562] clk: qcom: camcc: Add support for camera clock controller for Nord Add support for the Camera Clock Controller (CAMCC) on the Nord platform for camera SW drivers to request for these clocks. Reviewed-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260706-nord_videocc_camcc-v1-4-bae3be9e9770@oss.qualcomm.com Signed-off-by: Taniya Das --- drivers/clk/qcom/Kconfig | 11 + drivers/clk/qcom/Makefile | 1 + drivers/clk/qcom/camcc-nord.c | 2941 +++++++++++++++++++++++++++++++++ 3 files changed, 2953 insertions(+) create mode 100644 drivers/clk/qcom/camcc-nord.c diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig index 444c9e3a11a3..75de828377f8 100644 --- a/drivers/clk/qcom/Kconfig +++ b/drivers/clk/qcom/Kconfig @@ -156,6 +156,17 @@ config CLK_NORD_DISPCC Say Y if you want to support display devices and functionality such as splash screen. +config CLK_NORD_CAMCC + tristate "Nord Camera Clock Controller" + depends on ARM64 || COMPILE_TEST + select CLK_NORD_GCC + default m if ARCH_QCOM + help + Support for the camera clock controller on Qualcomm Technologies, Inc + Nord devices. + Say Y if you want to support camera devices and functionality such as + capturing pictures. + config CLK_NORD_GCC tristate "Nord Global Clock Controller" depends on ARM64 || COMPILE_TEST diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile index 140137662065..3e29a929a4af 100644 --- a/drivers/clk/qcom/Makefile +++ b/drivers/clk/qcom/Makefile @@ -37,6 +37,7 @@ obj-$(CONFIG_CLK_KAANAPALI_GCC) += gcc-kaanapali.o obj-$(CONFIG_CLK_KAANAPALI_GPUCC) += gpucc-kaanapali.o gxclkctl-kaanapali.o obj-$(CONFIG_CLK_KAANAPALI_TCSRCC) += tcsrcc-kaanapali.o obj-$(CONFIG_CLK_KAANAPALI_VIDEOCC) += videocc-kaanapali.o +obj-$(CONFIG_CLK_NORD_CAMCC) += camcc-nord.o obj-$(CONFIG_CLK_NORD_DISPCC) += dispcc0-nord.o dispcc1-nord.o obj-$(CONFIG_CLK_NORD_GCC) += gcc-nord.o negcc-nord.o nwgcc-nord.o segcc-nord.o obj-$(CONFIG_CLK_NORD_GPUCC) += gpucc-nord.o gpu2cc-nord.o diff --git a/drivers/clk/qcom/camcc-nord.c b/drivers/clk/qcom/camcc-nord.c new file mode 100644 index 000000000000..9e3c40cb3ad5 --- /dev/null +++ b/drivers/clk/qcom/camcc-nord.c @@ -0,0 +1,2941 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include + +#include + +#include "clk-alpha-pll.h" +#include "clk-branch.h" +#include "clk-pll.h" +#include "clk-rcg.h" +#include "clk-regmap.h" +#include "clk-regmap-divider.h" +#include "clk-regmap-mux.h" +#include "common.h" +#include "gdsc.h" +#include "reset.h" + +enum { + DT_IFACE, + DT_BI_TCXO, + DT_BI_TCXO_AO, + DT_SLEEP_CLK +}; + +enum { + P_BI_TCXO, + P_CAM_CC_PLL0_OUT_EVEN, + P_CAM_CC_PLL0_OUT_MAIN, + P_CAM_CC_PLL0_OUT_ODD, + P_CAM_CC_PLL2_OUT_EVEN, + P_CAM_CC_PLL3_OUT_EVEN, + P_CAM_CC_PLL4_OUT_EVEN, + P_CAM_CC_PLL5_OUT_EVEN, + P_CAM_CC_PLL6_OUT_EVEN, + P_SLEEP_CLK, +}; + +static const struct pll_vco lucid_ole_vco[] = { + { 249600000, 2300000000, 0 }, +}; + +/* 1200.0 MHz Configuration */ +static const struct alpha_pll_config cam_cc_pll0_config = { + .l = 0x3e, + .alpha = 0x8000, + .config_ctl_val = 0x20485699, + .config_ctl_hi_val = 0x00182261, + .config_ctl_hi1_val = 0x82aa299c, + .test_ctl_val = 0x00000000, + .test_ctl_hi_val = 0x00000003, + .test_ctl_hi1_val = 0x00009000, + .test_ctl_hi2_val = 0x00000034, + .user_ctl_val = 0x00008400, + .user_ctl_hi_val = 0x00400005, +}; + +static struct clk_alpha_pll cam_cc_pll0 = { + .offset = 0x0, + .config = &cam_cc_pll0_config, + .vco_table = lucid_ole_vco, + .num_vco = ARRAY_SIZE(lucid_ole_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_pll0", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO_AO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_lucid_evo_ops, + }, + }, +}; + +static const struct clk_div_table post_div_table_cam_cc_pll0_out_even[] = { + { 0x1, 2 }, + { } +}; + +static struct clk_alpha_pll_postdiv cam_cc_pll0_out_even = { + .offset = 0x0, + .post_div_shift = 10, + .post_div_table = post_div_table_cam_cc_pll0_out_even, + .num_post_div = ARRAY_SIZE(post_div_table_cam_cc_pll0_out_even), + .width = 4, + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE], + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_pll0_out_even", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_pll0.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_alpha_pll_postdiv_lucid_evo_ops, + }, +}; + +static const struct clk_div_table post_div_table_cam_cc_pll0_out_odd[] = { + { 0x2, 3 }, + { } +}; + +static struct clk_alpha_pll_postdiv cam_cc_pll0_out_odd = { + .offset = 0x0, + .post_div_shift = 14, + .post_div_table = post_div_table_cam_cc_pll0_out_odd, + .num_post_div = ARRAY_SIZE(post_div_table_cam_cc_pll0_out_odd), + .width = 4, + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE], + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_pll0_out_odd", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_pll0.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_alpha_pll_postdiv_lucid_evo_ops, + }, +}; + +/* 720.0 MHz Configuration */ +static const struct alpha_pll_config cam_cc_pll2_config = { + .l = 0x25, + .alpha = 0x8000, + .config_ctl_val = 0x20485699, + .config_ctl_hi_val = 0x00182261, + .config_ctl_hi1_val = 0x82aa299c, + .test_ctl_val = 0x00000000, + .test_ctl_hi_val = 0x00000003, + .test_ctl_hi1_val = 0x00009000, + .test_ctl_hi2_val = 0x00000034, + .user_ctl_val = 0x00000400, + .user_ctl_hi_val = 0x00400005, +}; + +static struct clk_alpha_pll cam_cc_pll2 = { + .offset = 0x2000, + .config = &cam_cc_pll2_config, + .vco_table = lucid_ole_vco, + .num_vco = ARRAY_SIZE(lucid_ole_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_pll2", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_lucid_evo_ops, + }, + }, +}; + +static const struct clk_div_table post_div_table_cam_cc_pll2_out_even[] = { + { 0x1, 2 }, + { } +}; + +static struct clk_alpha_pll_postdiv cam_cc_pll2_out_even = { + .offset = 0x2000, + .post_div_shift = 10, + .post_div_table = post_div_table_cam_cc_pll2_out_even, + .num_post_div = ARRAY_SIZE(post_div_table_cam_cc_pll2_out_even), + .width = 4, + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE], + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_pll2_out_even", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_pll2.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_alpha_pll_postdiv_lucid_evo_ops, + }, +}; + +/* 720.0 MHz Configuration */ +static const struct alpha_pll_config cam_cc_pll3_config = { + .l = 0x25, + .alpha = 0x8000, + .config_ctl_val = 0x20485699, + .config_ctl_hi_val = 0x00182261, + .config_ctl_hi1_val = 0x82aa299c, + .test_ctl_val = 0x00000000, + .test_ctl_hi_val = 0x00000003, + .test_ctl_hi1_val = 0x00009000, + .test_ctl_hi2_val = 0x00000034, + .user_ctl_val = 0x00000400, + .user_ctl_hi_val = 0x00400005, +}; + +static struct clk_alpha_pll cam_cc_pll3 = { + .offset = 0x3000, + .config = &cam_cc_pll3_config, + .vco_table = lucid_ole_vco, + .num_vco = ARRAY_SIZE(lucid_ole_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_pll3", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_lucid_evo_ops, + }, + }, +}; + +static const struct clk_div_table post_div_table_cam_cc_pll3_out_even[] = { + { 0x1, 2 }, + { } +}; + +static struct clk_alpha_pll_postdiv cam_cc_pll3_out_even = { + .offset = 0x3000, + .post_div_shift = 10, + .post_div_table = post_div_table_cam_cc_pll3_out_even, + .num_post_div = ARRAY_SIZE(post_div_table_cam_cc_pll3_out_even), + .width = 4, + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE], + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_pll3_out_even", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_pll3.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_alpha_pll_postdiv_lucid_evo_ops, + }, +}; + +/* 720.0 MHz Configuration */ +static const struct alpha_pll_config cam_cc_pll4_config = { + .l = 0x25, + .alpha = 0x8000, + .config_ctl_val = 0x20485699, + .config_ctl_hi_val = 0x00182261, + .config_ctl_hi1_val = 0x82aa299c, + .test_ctl_val = 0x00000000, + .test_ctl_hi_val = 0x00000003, + .test_ctl_hi1_val = 0x00009000, + .test_ctl_hi2_val = 0x00000034, + .user_ctl_val = 0x00000400, + .user_ctl_hi_val = 0x00400005, +}; + +static struct clk_alpha_pll cam_cc_pll4 = { + .offset = 0x4000, + .config = &cam_cc_pll4_config, + .vco_table = lucid_ole_vco, + .num_vco = ARRAY_SIZE(lucid_ole_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_pll4", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_lucid_evo_ops, + }, + }, +}; + +static const struct clk_div_table post_div_table_cam_cc_pll4_out_even[] = { + { 0x1, 2 }, + { } +}; + +static struct clk_alpha_pll_postdiv cam_cc_pll4_out_even = { + .offset = 0x4000, + .post_div_shift = 10, + .post_div_table = post_div_table_cam_cc_pll4_out_even, + .num_post_div = ARRAY_SIZE(post_div_table_cam_cc_pll4_out_even), + .width = 4, + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE], + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_pll4_out_even", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_pll4.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_alpha_pll_postdiv_lucid_evo_ops, + }, +}; + +/* 720.0 MHz Configuration */ +static const struct alpha_pll_config cam_cc_pll5_config = { + .l = 0x25, + .alpha = 0x8000, + .config_ctl_val = 0x20485699, + .config_ctl_hi_val = 0x00182261, + .config_ctl_hi1_val = 0x82aa299c, + .test_ctl_val = 0x00000000, + .test_ctl_hi_val = 0x00000003, + .test_ctl_hi1_val = 0x00009000, + .test_ctl_hi2_val = 0x00000034, + .user_ctl_val = 0x00000400, + .user_ctl_hi_val = 0x00400005, +}; + +static struct clk_alpha_pll cam_cc_pll5 = { + .offset = 0x5000, + .config = &cam_cc_pll5_config, + .vco_table = lucid_ole_vco, + .num_vco = ARRAY_SIZE(lucid_ole_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_pll5", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_lucid_evo_ops, + }, + }, +}; + +static const struct clk_div_table post_div_table_cam_cc_pll5_out_even[] = { + { 0x1, 2 }, + { } +}; + +static struct clk_alpha_pll_postdiv cam_cc_pll5_out_even = { + .offset = 0x5000, + .post_div_shift = 10, + .post_div_table = post_div_table_cam_cc_pll5_out_even, + .num_post_div = ARRAY_SIZE(post_div_table_cam_cc_pll5_out_even), + .width = 4, + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE], + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_pll5_out_even", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_pll5.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_alpha_pll_postdiv_lucid_evo_ops, + }, +}; + +/* 720.0 MHz Configuration */ +static const struct alpha_pll_config cam_cc_pll6_config = { + .l = 0x25, + .alpha = 0x8000, + .config_ctl_val = 0x20485699, + .config_ctl_hi_val = 0x00182261, + .config_ctl_hi1_val = 0x82aa299c, + .test_ctl_val = 0x00000000, + .test_ctl_hi_val = 0x00000003, + .test_ctl_hi1_val = 0x00009000, + .test_ctl_hi2_val = 0x00000034, + .user_ctl_val = 0x00000400, + .user_ctl_hi_val = 0x00400005, +}; + +static struct clk_alpha_pll cam_cc_pll6 = { + .offset = 0x6000, + .config = &cam_cc_pll6_config, + .vco_table = lucid_ole_vco, + .num_vco = ARRAY_SIZE(lucid_ole_vco), + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE], + .clkr = { + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_pll6", + .parent_data = &(const struct clk_parent_data) { + .index = DT_BI_TCXO, + }, + .num_parents = 1, + .ops = &clk_alpha_pll_lucid_evo_ops, + }, + }, +}; + +static const struct clk_div_table post_div_table_cam_cc_pll6_out_even[] = { + { 0x1, 2 }, + { } +}; + +static struct clk_alpha_pll_postdiv cam_cc_pll6_out_even = { + .offset = 0x6000, + .post_div_shift = 10, + .post_div_table = post_div_table_cam_cc_pll6_out_even, + .num_post_div = ARRAY_SIZE(post_div_table_cam_cc_pll6_out_even), + .width = 4, + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE], + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_pll6_out_even", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_pll6.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_alpha_pll_postdiv_lucid_evo_ops, + }, +}; + +static const struct parent_map cam_cc_parent_map_0[] = { + { P_BI_TCXO, 0 }, + { P_CAM_CC_PLL0_OUT_MAIN, 1 }, + { P_CAM_CC_PLL0_OUT_EVEN, 2 }, + { P_CAM_CC_PLL0_OUT_ODD, 3 }, +}; + +static const struct clk_parent_data cam_cc_parent_data_0[] = { + { .index = DT_BI_TCXO }, + { .hw = &cam_cc_pll0.clkr.hw }, + { .hw = &cam_cc_pll0_out_even.clkr.hw }, + { .hw = &cam_cc_pll0_out_odd.clkr.hw }, +}; + +static const struct parent_map cam_cc_parent_map_1[] = { + { P_BI_TCXO, 0 }, + { P_CAM_CC_PLL0_OUT_MAIN, 1 }, + { P_CAM_CC_PLL0_OUT_EVEN, 2 }, + { P_CAM_CC_PLL0_OUT_ODD, 3 }, +}; + +static const struct clk_parent_data cam_cc_parent_data_1[] = { + { .index = DT_BI_TCXO }, + { .hw = &cam_cc_pll0.clkr.hw }, + { .hw = &cam_cc_pll0_out_even.clkr.hw }, + { .hw = &cam_cc_pll0_out_odd.clkr.hw }, +}; + +static const struct parent_map cam_cc_parent_map_2[] = { + { P_BI_TCXO, 0 }, + { P_CAM_CC_PLL0_OUT_MAIN, 1 }, + { P_CAM_CC_PLL0_OUT_EVEN, 2 }, + { P_CAM_CC_PLL3_OUT_EVEN, 3 }, +}; + +static const struct clk_parent_data cam_cc_parent_data_2[] = { + { .index = DT_BI_TCXO }, + { .hw = &cam_cc_pll0.clkr.hw }, + { .hw = &cam_cc_pll0_out_even.clkr.hw }, + { .hw = &cam_cc_pll3_out_even.clkr.hw }, +}; + +static const struct parent_map cam_cc_parent_map_3[] = { + { P_BI_TCXO, 0 }, + { P_CAM_CC_PLL2_OUT_EVEN, 2 }, +}; + +static const struct clk_parent_data cam_cc_parent_data_3[] = { + { .index = DT_BI_TCXO }, + { .hw = &cam_cc_pll2_out_even.clkr.hw }, +}; + +static const struct parent_map cam_cc_parent_map_4[] = { + { P_BI_TCXO, 0 }, + { P_CAM_CC_PLL4_OUT_EVEN, 6 }, +}; + +static const struct clk_parent_data cam_cc_parent_data_4[] = { + { .index = DT_BI_TCXO }, + { .hw = &cam_cc_pll4_out_even.clkr.hw }, +}; + +static const struct parent_map cam_cc_parent_map_5[] = { + { P_BI_TCXO, 0 }, + { P_CAM_CC_PLL5_OUT_EVEN, 6 }, +}; + +static const struct clk_parent_data cam_cc_parent_data_5[] = { + { .index = DT_BI_TCXO }, + { .hw = &cam_cc_pll5_out_even.clkr.hw }, +}; + +static const struct parent_map cam_cc_parent_map_6[] = { + { P_BI_TCXO, 0 }, + { P_CAM_CC_PLL6_OUT_EVEN, 6 }, +}; + +static const struct clk_parent_data cam_cc_parent_data_6[] = { + { .index = DT_BI_TCXO }, + { .hw = &cam_cc_pll6_out_even.clkr.hw }, +}; + +static const struct parent_map cam_cc_parent_map_7[] = { + { P_BI_TCXO, 0 }, + { P_CAM_CC_PLL0_OUT_MAIN, 1 }, + { P_CAM_CC_PLL0_OUT_EVEN, 2 }, +}; + +static const struct clk_parent_data cam_cc_parent_data_7[] = { + { .index = DT_BI_TCXO }, + { .hw = &cam_cc_pll0.clkr.hw }, + { .hw = &cam_cc_pll0_out_even.clkr.hw }, +}; + +static const struct parent_map cam_cc_parent_map_8[] = { + { P_SLEEP_CLK, 0 }, +}; + +static const struct clk_parent_data cam_cc_parent_data_8[] = { + { .index = DT_SLEEP_CLK }, +}; + +static const struct parent_map cam_cc_parent_map_9[] = { + { P_BI_TCXO, 0 }, +}; + +static const struct clk_parent_data cam_cc_parent_data_9[] = { + { .index = DT_BI_TCXO }, +}; + +static const struct freq_tbl ftbl_cam_cc_camnoc_rt_axi_clk_src[] = { + F(300000000, P_CAM_CC_PLL0_OUT_EVEN, 2, 0, 0), + F(400000000, P_CAM_CC_PLL0_OUT_MAIN, 3, 0, 0), + { } +}; + +static struct clk_rcg2 cam_cc_camnoc_rt_axi_clk_src = { + .cmd_rcgr = 0x13244, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_0, + .freq_tbl = ftbl_cam_cc_camnoc_rt_axi_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_camnoc_rt_axi_clk_src", + .parent_data = cam_cc_parent_data_0, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_cam_cc_cci_0_clk_src[] = { + F(37500000, P_CAM_CC_PLL0_OUT_EVEN, 16, 0, 0), + { } +}; + +static struct clk_rcg2 cam_cc_cci_0_clk_src = { + .cmd_rcgr = 0x13110, + .mnd_width = 8, + .hid_width = 5, + .parent_map = cam_cc_parent_map_0, + .freq_tbl = ftbl_cam_cc_cci_0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_cci_0_clk_src", + .parent_data = cam_cc_parent_data_0, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 cam_cc_cci_1_clk_src = { + .cmd_rcgr = 0x13130, + .mnd_width = 8, + .hid_width = 5, + .parent_map = cam_cc_parent_map_0, + .freq_tbl = ftbl_cam_cc_cci_0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_cci_1_clk_src", + .parent_data = cam_cc_parent_data_0, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 cam_cc_cci_2_clk_src = { + .cmd_rcgr = 0x13150, + .mnd_width = 8, + .hid_width = 5, + .parent_map = cam_cc_parent_map_0, + .freq_tbl = ftbl_cam_cc_cci_0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_cci_2_clk_src", + .parent_data = cam_cc_parent_data_0, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 cam_cc_cci_3_clk_src = { + .cmd_rcgr = 0x13170, + .mnd_width = 8, + .hid_width = 5, + .parent_map = cam_cc_parent_map_0, + .freq_tbl = ftbl_cam_cc_cci_0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_cci_3_clk_src", + .parent_data = cam_cc_parent_data_0, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 cam_cc_cci_4_clk_src = { + .cmd_rcgr = 0x13190, + .mnd_width = 8, + .hid_width = 5, + .parent_map = cam_cc_parent_map_0, + .freq_tbl = ftbl_cam_cc_cci_0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_cci_4_clk_src", + .parent_data = cam_cc_parent_data_0, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_cam_cc_cphy_rx_clk_src[] = { + F(300000000, P_CAM_CC_PLL0_OUT_EVEN, 2, 0, 0), + F(400000000, P_CAM_CC_PLL0_OUT_ODD, 1, 0, 0), + F(480000000, P_CAM_CC_PLL0_OUT_MAIN, 2.5, 0, 0), + { } +}; + +static struct clk_rcg2 cam_cc_cphy_rx_clk_src = { + .cmd_rcgr = 0x120ac, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_1, + .freq_tbl = ftbl_cam_cc_cphy_rx_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_cphy_rx_clk_src", + .parent_data = cam_cc_parent_data_1, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_cam_cc_csi0phytimer_clk_src[] = { + F(300000000, P_CAM_CC_PLL0_OUT_EVEN, 2, 0, 0), + F(400000000, P_CAM_CC_PLL0_OUT_ODD, 1, 0, 0), + { } +}; + +static struct clk_rcg2 cam_cc_csi0phytimer_clk_src = { + .cmd_rcgr = 0x10000, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_1, + .freq_tbl = ftbl_cam_cc_csi0phytimer_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_csi0phytimer_clk_src", + .parent_data = cam_cc_parent_data_1, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 cam_cc_csi1phytimer_clk_src = { + .cmd_rcgr = 0x10028, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_1, + .freq_tbl = ftbl_cam_cc_csi0phytimer_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_csi1phytimer_clk_src", + .parent_data = cam_cc_parent_data_1, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 cam_cc_csi2phytimer_clk_src = { + .cmd_rcgr = 0x1004c, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_1, + .freq_tbl = ftbl_cam_cc_csi0phytimer_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_csi2phytimer_clk_src", + .parent_data = cam_cc_parent_data_1, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 cam_cc_csi3phytimer_clk_src = { + .cmd_rcgr = 0x10070, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_1, + .freq_tbl = ftbl_cam_cc_csi0phytimer_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_csi3phytimer_clk_src", + .parent_data = cam_cc_parent_data_1, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 cam_cc_csi4phytimer_clk_src = { + .cmd_rcgr = 0x10094, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_1, + .freq_tbl = ftbl_cam_cc_csi0phytimer_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_csi4phytimer_clk_src", + .parent_data = cam_cc_parent_data_1, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_cam_cc_csid_clk_src[] = { + F(300000000, P_CAM_CC_PLL0_OUT_MAIN, 4, 0, 0), + F(400000000, P_CAM_CC_PLL0_OUT_MAIN, 3, 0, 0), + F(480000000, P_CAM_CC_PLL0_OUT_MAIN, 2.5, 0, 0), + { } +}; + +static struct clk_rcg2 cam_cc_csid_clk_src = { + .cmd_rcgr = 0x13214, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_1, + .freq_tbl = ftbl_cam_cc_csid_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_csid_clk_src", + .parent_data = cam_cc_parent_data_1, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_cam_cc_fast_ahb_clk_src[] = { + F(200000000, P_CAM_CC_PLL0_OUT_EVEN, 3, 0, 0), + F(300000000, P_CAM_CC_PLL0_OUT_MAIN, 4, 0, 0), + F(400000000, P_CAM_CC_PLL0_OUT_MAIN, 3, 0, 0), + { } +}; + +static struct clk_rcg2 cam_cc_fast_ahb_clk_src = { + .cmd_rcgr = 0x131dc, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_0, + .freq_tbl = ftbl_cam_cc_fast_ahb_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_fast_ahb_clk_src", + .parent_data = cam_cc_parent_data_0, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_cam_cc_icp_0_clk_src[] = { + F(360000000, P_CAM_CC_PLL3_OUT_EVEN, 1, 0, 0), + F(480000000, P_CAM_CC_PLL3_OUT_EVEN, 1, 0, 0), + F(600000000, P_CAM_CC_PLL3_OUT_EVEN, 1, 0, 0), + { } +}; + +static struct clk_rcg2 cam_cc_icp_0_clk_src = { + .cmd_rcgr = 0x130a4, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_2, + .freq_tbl = ftbl_cam_cc_icp_0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_icp_0_clk_src", + .parent_data = cam_cc_parent_data_2, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_2), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 cam_cc_icp_1_clk_src = { + .cmd_rcgr = 0x130dc, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_2, + .freq_tbl = ftbl_cam_cc_icp_0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_icp_1_clk_src", + .parent_data = cam_cc_parent_data_2, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_2), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_cam_cc_ife_0_main_clk_src[] = { + F(360000000, P_CAM_CC_PLL4_OUT_EVEN, 1, 0, 0), + F(480000000, P_CAM_CC_PLL4_OUT_EVEN, 1, 0, 0), + F(650000000, P_CAM_CC_PLL4_OUT_EVEN, 1, 0, 0), + { } +}; + +static struct clk_rcg2 cam_cc_ife_0_main_clk_src = { + .cmd_rcgr = 0x12018, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_4, + .freq_tbl = ftbl_cam_cc_ife_0_main_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_0_main_clk_src", + .parent_data = cam_cc_parent_data_4, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_4), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_cam_cc_ife_1_main_clk_src[] = { + F(360000000, P_CAM_CC_PLL5_OUT_EVEN, 1, 0, 0), + F(480000000, P_CAM_CC_PLL5_OUT_EVEN, 1, 0, 0), + F(650000000, P_CAM_CC_PLL5_OUT_EVEN, 1, 0, 0), + { } +}; + +static struct clk_rcg2 cam_cc_ife_1_main_clk_src = { + .cmd_rcgr = 0x120dc, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_5, + .freq_tbl = ftbl_cam_cc_ife_1_main_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_1_main_clk_src", + .parent_data = cam_cc_parent_data_5, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_5), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_cam_cc_ife_2_main_clk_src[] = { + F(360000000, P_CAM_CC_PLL6_OUT_EVEN, 1, 0, 0), + F(480000000, P_CAM_CC_PLL6_OUT_EVEN, 1, 0, 0), + F(650000000, P_CAM_CC_PLL6_OUT_EVEN, 1, 0, 0), + { } +}; + +static struct clk_rcg2 cam_cc_ife_2_main_clk_src = { + .cmd_rcgr = 0x12188, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_6, + .freq_tbl = ftbl_cam_cc_ife_2_main_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_2_main_clk_src", + .parent_data = cam_cc_parent_data_6, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_6), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 cam_cc_ife_lite_clk_src = { + .cmd_rcgr = 0x13000, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_1, + .freq_tbl = ftbl_cam_cc_csid_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_lite_clk_src", + .parent_data = cam_cc_parent_data_1, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 cam_cc_ife_lite_csid_clk_src = { + .cmd_rcgr = 0x13024, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_1, + .freq_tbl = ftbl_cam_cc_csid_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_lite_csid_clk_src", + .parent_data = cam_cc_parent_data_1, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_1), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_cam_cc_ipe_0_clk_src[] = { + F(360000000, P_CAM_CC_PLL2_OUT_EVEN, 1, 0, 0), + F(480000000, P_CAM_CC_PLL2_OUT_EVEN, 1, 0, 0), + F(650000000, P_CAM_CC_PLL2_OUT_EVEN, 1, 0, 0), + { } +}; + +static struct clk_rcg2 cam_cc_ipe_0_clk_src = { + .cmd_rcgr = 0x11018, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_3, + .freq_tbl = ftbl_cam_cc_ipe_0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ipe_0_clk_src", + .parent_data = cam_cc_parent_data_3, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_3), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 cam_cc_ipe_1_clk_src = { + .cmd_rcgr = 0x11074, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_3, + .freq_tbl = ftbl_cam_cc_ipe_0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ipe_1_clk_src", + .parent_data = cam_cc_parent_data_3, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_3), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_cam_cc_qdss_debug_clk_src[] = { + F(200000000, P_CAM_CC_PLL0_OUT_EVEN, 3, 0, 0), + F(300000000, P_CAM_CC_PLL0_OUT_EVEN, 2, 0, 0), + { } +}; + +static struct clk_rcg2 cam_cc_qdss_debug_clk_src = { + .cmd_rcgr = 0x13330, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_0, + .freq_tbl = ftbl_cam_cc_qdss_debug_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_qdss_debug_clk_src", + .parent_data = cam_cc_parent_data_0, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_cam_cc_qup_ahbm_clk_src[] = { + F(150000000, P_CAM_CC_PLL0_OUT_EVEN, 4, 0, 0), + F(240000000, P_CAM_CC_PLL0_OUT_MAIN, 5, 0, 0), + { } +}; + +static struct clk_rcg2 cam_cc_qup_ahbm_clk_src = { + .cmd_rcgr = 0x132e8, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_0, + .freq_tbl = ftbl_cam_cc_qup_ahbm_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_qup_ahbm_clk_src", + .parent_data = cam_cc_parent_data_0, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_cam_cc_qup_core_2x_clk_src[] = { + F(200000000, P_CAM_CC_PLL0_OUT_ODD, 2, 0, 0), + F(300000000, P_CAM_CC_PLL0_OUT_EVEN, 2, 0, 0), + { } +}; + +static struct clk_rcg2 cam_cc_qup_core_2x_clk_src = { + .cmd_rcgr = 0x132a0, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_0, + .freq_tbl = ftbl_cam_cc_qup_core_2x_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_qup_core_2x_clk_src", + .parent_data = cam_cc_parent_data_0, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_rcg2 cam_cc_qup_se_clk_src = { + .cmd_rcgr = 0x13308, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_7, + .freq_tbl = ftbl_cam_cc_cci_0_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_qup_se_clk_src", + .parent_data = cam_cc_parent_data_7, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_7), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_cam_cc_sleep_clk_src[] = { + F(32000, P_SLEEP_CLK, 1, 0, 0), + { } +}; + +static struct clk_rcg2 cam_cc_sleep_clk_src = { + .cmd_rcgr = 0x13384, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_8, + .freq_tbl = ftbl_cam_cc_sleep_clk_src, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_sleep_clk_src", + .parent_data = cam_cc_parent_data_8, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_8), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_cam_cc_slow_ahb_clk_src[] = { + F(80000000, P_CAM_CC_PLL0_OUT_EVEN, 7.5, 0, 0), + { } +}; + +static struct clk_rcg2 cam_cc_slow_ahb_clk_src = { + .cmd_rcgr = 0x131f8, + .mnd_width = 8, + .hid_width = 5, + .parent_map = cam_cc_parent_map_0, + .freq_tbl = ftbl_cam_cc_slow_ahb_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_slow_ahb_clk_src", + .parent_data = cam_cc_parent_data_0, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_0), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static const struct freq_tbl ftbl_cam_cc_xo_clk_src[] = { + F(19200000, P_BI_TCXO, 1, 0, 0), + { } +}; + +static struct clk_rcg2 cam_cc_xo_clk_src = { + .cmd_rcgr = 0x13368, + .mnd_width = 0, + .hid_width = 5, + .parent_map = cam_cc_parent_map_9, + .freq_tbl = ftbl_cam_cc_xo_clk_src, + .hw_clk_ctrl = true, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_xo_clk_src", + .parent_data = cam_cc_parent_data_9, + .num_parents = ARRAY_SIZE(cam_cc_parent_data_9), + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_rcg2_shared_ops, + }, +}; + +static struct clk_regmap_div cam_cc_qup_core_2x_div_clk_src = { + .reg = 0x132cc, + .shift = 0, + .width = 4, + .clkr.hw.init = &(const struct clk_init_data) { + .name = "cam_cc_qup_core_2x_div_clk_src", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_qup_core_2x_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_regmap_div_ro_ops, + }, +}; + +static struct clk_branch cam_cc_camnoc_dcd_xo_clk = { + .halt_reg = 0x13290, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x13290, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_camnoc_dcd_xo_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_xo_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_camnoc_nrt_axi_clk = { + .halt_reg = 0x13278, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x13278, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_camnoc_nrt_axi_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_camnoc_rt_axi_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_camnoc_rt_axi_clk = { + .halt_reg = 0x13260, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x13260, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_camnoc_rt_axi_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_camnoc_rt_axi_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_cci_0_clk = { + .halt_reg = 0x1312c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1312c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_cci_0_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_cci_0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_cci_1_clk = { + .halt_reg = 0x1314c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1314c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_cci_1_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_cci_1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_cci_2_clk = { + .halt_reg = 0x1316c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1316c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_cci_2_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_cci_2_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_cci_3_clk = { + .halt_reg = 0x1318c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1318c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_cci_3_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_cci_3_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_cci_4_clk = { + .halt_reg = 0x131ac, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x131ac, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_cci_4_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_cci_4_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ccu_fast_ahb_clk = { + .halt_reg = 0x1329c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1329c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ccu_fast_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_fast_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_csi0phytimer_clk = { + .halt_reg = 0x1001c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1001c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_csi0phytimer_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_csi0phytimer_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_csi1phytimer_clk = { + .halt_reg = 0x10044, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x10044, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_csi1phytimer_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_csi1phytimer_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_csi2phytimer_clk = { + .halt_reg = 0x10068, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x10068, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_csi2phytimer_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_csi2phytimer_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_csi3phytimer_clk = { + .halt_reg = 0x1008c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1008c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_csi3phytimer_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_csi3phytimer_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_csi4phytimer_clk = { + .halt_reg = 0x100b0, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x100b0, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_csi4phytimer_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_csi4phytimer_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_csid_clk = { + .halt_reg = 0x13230, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x13230, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_csid_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_csid_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_csid_csiphy_rx_clk = { + .halt_reg = 0x10024, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x10024, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_csid_csiphy_rx_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_cphy_rx_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_csiphy0_clk = { + .halt_reg = 0x10020, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x10020, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_csiphy0_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_cphy_rx_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_csiphy1_clk = { + .halt_reg = 0x10048, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x10048, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_csiphy1_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_cphy_rx_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_csiphy2_clk = { + .halt_reg = 0x1006c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1006c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_csiphy2_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_cphy_rx_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_csiphy3_clk = { + .halt_reg = 0x10090, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x10090, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_csiphy3_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_cphy_rx_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_csiphy4_clk = { + .halt_reg = 0x100b4, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x100b4, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_csiphy4_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_cphy_rx_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_icp_0_ahb_clk = { + .halt_reg = 0x130d4, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x130d4, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_icp_0_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_slow_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_icp_0_clk = { + .halt_reg = 0x130c0, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x130c0, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_icp_0_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_icp_0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_icp_1_ahb_clk = { + .halt_reg = 0x1310c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1310c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_icp_1_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_slow_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_icp_1_clk = { + .halt_reg = 0x130f8, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x130f8, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_icp_1_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_icp_1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_0_main_clk = { + .halt_reg = 0x12034, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x12034, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_0_main_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ife_0_main_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_0_main_fast_ahb_clk = { + .halt_reg = 0x12054, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x12054, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_0_main_fast_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_fast_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_0_pcp_clk = { + .halt_reg = 0x12058, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x12058, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_0_pcp_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ife_0_main_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_0_pcp_fast_ahb_clk = { + .halt_reg = 0x12070, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x12070, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_0_pcp_fast_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_fast_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_0_scalar_clk = { + .halt_reg = 0x12074, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x12074, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_0_scalar_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ife_0_main_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_0_scalar_fast_ahb_clk = { + .halt_reg = 0x1208c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1208c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_0_scalar_fast_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_fast_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_0_tmc_clk = { + .halt_reg = 0x12090, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x12090, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_0_tmc_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ife_0_main_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_0_tmc_fast_ahb_clk = { + .halt_reg = 0x120a8, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x120a8, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_0_tmc_fast_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_fast_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_1_main_clk = { + .halt_reg = 0x120f8, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x120f8, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_1_main_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ife_1_main_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_1_main_fast_ahb_clk = { + .halt_reg = 0x12118, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x12118, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_1_main_fast_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_fast_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_1_pcp_clk = { + .halt_reg = 0x1211c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1211c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_1_pcp_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ife_1_main_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_1_pcp_fast_ahb_clk = { + .halt_reg = 0x12134, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x12134, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_1_pcp_fast_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_fast_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_1_scalar_clk = { + .halt_reg = 0x12138, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x12138, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_1_scalar_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ife_1_main_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_1_scalar_fast_ahb_clk = { + .halt_reg = 0x12150, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x12150, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_1_scalar_fast_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_fast_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_1_tmc_clk = { + .halt_reg = 0x12154, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x12154, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_1_tmc_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ife_1_main_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_1_tmc_fast_ahb_clk = { + .halt_reg = 0x1216c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1216c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_1_tmc_fast_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_fast_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_2_main_clk = { + .halt_reg = 0x121a4, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x121a4, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_2_main_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ife_2_main_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_2_main_fast_ahb_clk = { + .halt_reg = 0x121c4, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x121c4, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_2_main_fast_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_fast_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_2_pcp_clk = { + .halt_reg = 0x121c8, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x121c8, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_2_pcp_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ife_2_main_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_2_pcp_fast_ahb_clk = { + .halt_reg = 0x121e0, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x121e0, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_2_pcp_fast_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_fast_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_2_scalar_clk = { + .halt_reg = 0x121e4, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x121e4, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_2_scalar_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ife_2_main_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_2_scalar_fast_ahb_clk = { + .halt_reg = 0x121fc, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x121fc, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_2_scalar_fast_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_fast_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_2_tmc_clk = { + .halt_reg = 0x12200, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x12200, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_2_tmc_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ife_2_main_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_2_tmc_fast_ahb_clk = { + .halt_reg = 0x12218, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x12218, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_2_tmc_fast_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_fast_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_lite_ahb_clk = { + .halt_reg = 0x13054, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x13054, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_lite_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_slow_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_lite_clk = { + .halt_reg = 0x1301c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1301c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_lite_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ife_lite_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_lite_cphy_rx_clk = { + .halt_reg = 0x13050, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x13050, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_lite_cphy_rx_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_cphy_rx_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ife_lite_csid_clk = { + .halt_reg = 0x1303c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1303c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ife_lite_csid_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ife_lite_csid_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ipe_0_ahb_clk = { + .halt_reg = 0x11054, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x11054, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ipe_0_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_slow_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ipe_0_clk = { + .halt_reg = 0x11034, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x11034, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ipe_0_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ipe_0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ipe_0_fast_ahb_clk = { + .halt_reg = 0x11058, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x11058, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ipe_0_fast_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_fast_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ipe_1_ahb_clk = { + .halt_reg = 0x110b0, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x110b0, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ipe_1_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_slow_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ipe_1_clk = { + .halt_reg = 0x11090, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x11090, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ipe_1_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ipe_1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_ipe_1_fast_ahb_clk = { + .halt_reg = 0x110b4, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x110b4, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_ipe_1_fast_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_fast_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_qdss_debug_clk = { + .halt_reg = 0x13348, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x13348, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_qdss_debug_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_qdss_debug_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_qdss_debug_xo_clk = { + .halt_reg = 0x1334c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1334c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_qdss_debug_xo_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_xo_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_qup_ahbm_clk = { + .halt_reg = 0x13300, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x13300, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_qup_ahbm_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_qup_ahbm_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_qup_core_2x_clk = { + .halt_reg = 0x132b8, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x132b8, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_qup_core_2x_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_qup_core_2x_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_qup_core_clk = { + .halt_reg = 0x132d0, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x132d0, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_qup_core_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_qup_core_2x_div_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_qup_se_clk = { + .halt_reg = 0x13320, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x13320, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_qup_se_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_qup_se_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_sfe_lite_0_clk = { + .halt_reg = 0x1305c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1305c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_sfe_lite_0_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ife_0_main_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_sfe_lite_0_fast_ahb_clk = { + .halt_reg = 0x1306c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1306c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_sfe_lite_0_fast_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_fast_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_sfe_lite_1_clk = { + .halt_reg = 0x13074, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x13074, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_sfe_lite_1_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ife_1_main_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_sfe_lite_1_fast_ahb_clk = { + .halt_reg = 0x13084, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x13084, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_sfe_lite_1_fast_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_fast_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_sfe_lite_2_clk = { + .halt_reg = 0x1308c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1308c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_sfe_lite_2_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ife_2_main_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_sfe_lite_2_fast_ahb_clk = { + .halt_reg = 0x1309c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1309c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_sfe_lite_2_fast_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_fast_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_sm_obs_clk = { + .halt_reg = 0x1401c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1401c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_sm_obs_clk", + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_top_ahb_clk = { + .halt_reg = 0x131b0, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x131b0, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_top_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_slow_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_top_fast_ahb_clk = { + .halt_reg = 0x131c4, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x131c4, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_top_fast_ahb_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_fast_ahb_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_top_ife_0_clk = { + .halt_reg = 0x12048, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x12048, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_top_ife_0_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ife_0_main_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_top_ife_1_clk = { + .halt_reg = 0x1210c, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x1210c, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_top_ife_1_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ife_1_main_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_top_ife_2_clk = { + .halt_reg = 0x121b8, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x121b8, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_top_ife_2_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ife_2_main_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_top_ife_lite_clk = { + .halt_reg = 0x13020, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x13020, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_top_ife_lite_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ife_lite_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_top_ipe_0_clk = { + .halt_reg = 0x11048, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x11048, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_top_ipe_0_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ipe_0_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_top_ipe_1_clk = { + .halt_reg = 0x110a4, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x110a4, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_top_ipe_1_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ipe_1_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_top_qup_ahbm_clk = { + .halt_reg = 0x13304, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x13304, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_top_qup_ahbm_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_qup_ahbm_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_top_sfe_lite_0_clk = { + .halt_reg = 0x13060, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x13060, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_top_sfe_lite_0_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ife_0_main_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_top_sfe_lite_1_clk = { + .halt_reg = 0x13078, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x13078, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_top_sfe_lite_1_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ife_1_main_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_top_sfe_lite_2_clk = { + .halt_reg = 0x13090, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x13090, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_top_sfe_lite_2_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_ife_2_main_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch cam_cc_tpg_csiphy_rx_clk = { + .halt_reg = 0x100b8, + .halt_check = BRANCH_HALT, + .clkr = { + .enable_reg = 0x100b8, + .enable_mask = BIT(0), + .hw.init = &(const struct clk_init_data) { + .name = "cam_cc_tpg_csiphy_rx_clk", + .parent_hws = (const struct clk_hw*[]) { + &cam_cc_cphy_rx_clk_src.clkr.hw, + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct gdsc cam_cc_titan_top_gdsc = { + .gdscr = 0x13350, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "cam_cc_titan_top_gdsc", + }, + .pwrsts = PWRSTS_OFF_ON, + .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct gdsc cam_cc_ife_0_gdsc = { + .gdscr = 0x12004, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "cam_cc_ife_0_gdsc", + }, + .parent = &cam_cc_titan_top_gdsc.pd, + .pwrsts = PWRSTS_OFF_ON, + .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct gdsc cam_cc_ife_1_gdsc = { + .gdscr = 0x120c8, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "cam_cc_ife_1_gdsc", + }, + .parent = &cam_cc_titan_top_gdsc.pd, + .pwrsts = PWRSTS_OFF_ON, + .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct gdsc cam_cc_ife_2_gdsc = { + .gdscr = 0x12174, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "cam_cc_ife_2_gdsc", + }, + .parent = &cam_cc_titan_top_gdsc.pd, + .pwrsts = PWRSTS_OFF_ON, + .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct gdsc cam_cc_ipe_0_gdsc = { + .gdscr = 0x11004, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "cam_cc_ipe_0_gdsc", + }, + .parent = &cam_cc_titan_top_gdsc.pd, + .pwrsts = PWRSTS_OFF_ON, + .flags = HW_CTRL_TRIGGER | POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct gdsc cam_cc_ipe_1_gdsc = { + .gdscr = 0x11060, + .en_rest_wait_val = 0x2, + .en_few_wait_val = 0x2, + .clk_dis_wait_val = 0xf, + .pd = { + .name = "cam_cc_ipe_1_gdsc", + }, + .parent = &cam_cc_titan_top_gdsc.pd, + .pwrsts = PWRSTS_OFF_ON, + .flags = HW_CTRL_TRIGGER | POLL_CFG_GDSCR | RETAIN_FF_ENABLE, +}; + +static struct clk_regmap *cam_cc_nord_clocks[] = { + [CAM_CC_CAMNOC_DCD_XO_CLK] = &cam_cc_camnoc_dcd_xo_clk.clkr, + [CAM_CC_CAMNOC_NRT_AXI_CLK] = &cam_cc_camnoc_nrt_axi_clk.clkr, + [CAM_CC_CAMNOC_RT_AXI_CLK] = &cam_cc_camnoc_rt_axi_clk.clkr, + [CAM_CC_CAMNOC_RT_AXI_CLK_SRC] = &cam_cc_camnoc_rt_axi_clk_src.clkr, + [CAM_CC_CCI_0_CLK] = &cam_cc_cci_0_clk.clkr, + [CAM_CC_CCI_0_CLK_SRC] = &cam_cc_cci_0_clk_src.clkr, + [CAM_CC_CCI_1_CLK] = &cam_cc_cci_1_clk.clkr, + [CAM_CC_CCI_1_CLK_SRC] = &cam_cc_cci_1_clk_src.clkr, + [CAM_CC_CCI_2_CLK] = &cam_cc_cci_2_clk.clkr, + [CAM_CC_CCI_2_CLK_SRC] = &cam_cc_cci_2_clk_src.clkr, + [CAM_CC_CCI_3_CLK] = &cam_cc_cci_3_clk.clkr, + [CAM_CC_CCI_3_CLK_SRC] = &cam_cc_cci_3_clk_src.clkr, + [CAM_CC_CCI_4_CLK] = &cam_cc_cci_4_clk.clkr, + [CAM_CC_CCI_4_CLK_SRC] = &cam_cc_cci_4_clk_src.clkr, + [CAM_CC_CCU_FAST_AHB_CLK] = &cam_cc_ccu_fast_ahb_clk.clkr, + [CAM_CC_CPHY_RX_CLK_SRC] = &cam_cc_cphy_rx_clk_src.clkr, + [CAM_CC_CSI0PHYTIMER_CLK] = &cam_cc_csi0phytimer_clk.clkr, + [CAM_CC_CSI0PHYTIMER_CLK_SRC] = &cam_cc_csi0phytimer_clk_src.clkr, + [CAM_CC_CSI1PHYTIMER_CLK] = &cam_cc_csi1phytimer_clk.clkr, + [CAM_CC_CSI1PHYTIMER_CLK_SRC] = &cam_cc_csi1phytimer_clk_src.clkr, + [CAM_CC_CSI2PHYTIMER_CLK] = &cam_cc_csi2phytimer_clk.clkr, + [CAM_CC_CSI2PHYTIMER_CLK_SRC] = &cam_cc_csi2phytimer_clk_src.clkr, + [CAM_CC_CSI3PHYTIMER_CLK] = &cam_cc_csi3phytimer_clk.clkr, + [CAM_CC_CSI3PHYTIMER_CLK_SRC] = &cam_cc_csi3phytimer_clk_src.clkr, + [CAM_CC_CSI4PHYTIMER_CLK] = &cam_cc_csi4phytimer_clk.clkr, + [CAM_CC_CSI4PHYTIMER_CLK_SRC] = &cam_cc_csi4phytimer_clk_src.clkr, + [CAM_CC_CSID_CLK] = &cam_cc_csid_clk.clkr, + [CAM_CC_CSID_CLK_SRC] = &cam_cc_csid_clk_src.clkr, + [CAM_CC_CSID_CSIPHY_RX_CLK] = &cam_cc_csid_csiphy_rx_clk.clkr, + [CAM_CC_CSIPHY0_CLK] = &cam_cc_csiphy0_clk.clkr, + [CAM_CC_CSIPHY1_CLK] = &cam_cc_csiphy1_clk.clkr, + [CAM_CC_CSIPHY2_CLK] = &cam_cc_csiphy2_clk.clkr, + [CAM_CC_CSIPHY3_CLK] = &cam_cc_csiphy3_clk.clkr, + [CAM_CC_CSIPHY4_CLK] = &cam_cc_csiphy4_clk.clkr, + [CAM_CC_FAST_AHB_CLK_SRC] = &cam_cc_fast_ahb_clk_src.clkr, + [CAM_CC_ICP_0_AHB_CLK] = &cam_cc_icp_0_ahb_clk.clkr, + [CAM_CC_ICP_0_CLK] = &cam_cc_icp_0_clk.clkr, + [CAM_CC_ICP_0_CLK_SRC] = &cam_cc_icp_0_clk_src.clkr, + [CAM_CC_ICP_1_AHB_CLK] = &cam_cc_icp_1_ahb_clk.clkr, + [CAM_CC_ICP_1_CLK] = &cam_cc_icp_1_clk.clkr, + [CAM_CC_ICP_1_CLK_SRC] = &cam_cc_icp_1_clk_src.clkr, + [CAM_CC_IFE_0_MAIN_CLK] = &cam_cc_ife_0_main_clk.clkr, + [CAM_CC_IFE_0_MAIN_CLK_SRC] = &cam_cc_ife_0_main_clk_src.clkr, + [CAM_CC_IFE_0_MAIN_FAST_AHB_CLK] = &cam_cc_ife_0_main_fast_ahb_clk.clkr, + [CAM_CC_IFE_0_PCP_CLK] = &cam_cc_ife_0_pcp_clk.clkr, + [CAM_CC_IFE_0_PCP_FAST_AHB_CLK] = &cam_cc_ife_0_pcp_fast_ahb_clk.clkr, + [CAM_CC_IFE_0_SCALAR_CLK] = &cam_cc_ife_0_scalar_clk.clkr, + [CAM_CC_IFE_0_SCALAR_FAST_AHB_CLK] = &cam_cc_ife_0_scalar_fast_ahb_clk.clkr, + [CAM_CC_IFE_0_TMC_CLK] = &cam_cc_ife_0_tmc_clk.clkr, + [CAM_CC_IFE_0_TMC_FAST_AHB_CLK] = &cam_cc_ife_0_tmc_fast_ahb_clk.clkr, + [CAM_CC_IFE_1_MAIN_CLK] = &cam_cc_ife_1_main_clk.clkr, + [CAM_CC_IFE_1_MAIN_CLK_SRC] = &cam_cc_ife_1_main_clk_src.clkr, + [CAM_CC_IFE_1_MAIN_FAST_AHB_CLK] = &cam_cc_ife_1_main_fast_ahb_clk.clkr, + [CAM_CC_IFE_1_PCP_CLK] = &cam_cc_ife_1_pcp_clk.clkr, + [CAM_CC_IFE_1_PCP_FAST_AHB_CLK] = &cam_cc_ife_1_pcp_fast_ahb_clk.clkr, + [CAM_CC_IFE_1_SCALAR_CLK] = &cam_cc_ife_1_scalar_clk.clkr, + [CAM_CC_IFE_1_SCALAR_FAST_AHB_CLK] = &cam_cc_ife_1_scalar_fast_ahb_clk.clkr, + [CAM_CC_IFE_1_TMC_CLK] = &cam_cc_ife_1_tmc_clk.clkr, + [CAM_CC_IFE_1_TMC_FAST_AHB_CLK] = &cam_cc_ife_1_tmc_fast_ahb_clk.clkr, + [CAM_CC_IFE_2_MAIN_CLK] = &cam_cc_ife_2_main_clk.clkr, + [CAM_CC_IFE_2_MAIN_CLK_SRC] = &cam_cc_ife_2_main_clk_src.clkr, + [CAM_CC_IFE_2_MAIN_FAST_AHB_CLK] = &cam_cc_ife_2_main_fast_ahb_clk.clkr, + [CAM_CC_IFE_2_PCP_CLK] = &cam_cc_ife_2_pcp_clk.clkr, + [CAM_CC_IFE_2_PCP_FAST_AHB_CLK] = &cam_cc_ife_2_pcp_fast_ahb_clk.clkr, + [CAM_CC_IFE_2_SCALAR_CLK] = &cam_cc_ife_2_scalar_clk.clkr, + [CAM_CC_IFE_2_SCALAR_FAST_AHB_CLK] = &cam_cc_ife_2_scalar_fast_ahb_clk.clkr, + [CAM_CC_IFE_2_TMC_CLK] = &cam_cc_ife_2_tmc_clk.clkr, + [CAM_CC_IFE_2_TMC_FAST_AHB_CLK] = &cam_cc_ife_2_tmc_fast_ahb_clk.clkr, + [CAM_CC_IFE_LITE_AHB_CLK] = &cam_cc_ife_lite_ahb_clk.clkr, + [CAM_CC_IFE_LITE_CLK] = &cam_cc_ife_lite_clk.clkr, + [CAM_CC_IFE_LITE_CLK_SRC] = &cam_cc_ife_lite_clk_src.clkr, + [CAM_CC_IFE_LITE_CPHY_RX_CLK] = &cam_cc_ife_lite_cphy_rx_clk.clkr, + [CAM_CC_IFE_LITE_CSID_CLK] = &cam_cc_ife_lite_csid_clk.clkr, + [CAM_CC_IFE_LITE_CSID_CLK_SRC] = &cam_cc_ife_lite_csid_clk_src.clkr, + [CAM_CC_IPE_0_AHB_CLK] = &cam_cc_ipe_0_ahb_clk.clkr, + [CAM_CC_IPE_0_CLK] = &cam_cc_ipe_0_clk.clkr, + [CAM_CC_IPE_0_CLK_SRC] = &cam_cc_ipe_0_clk_src.clkr, + [CAM_CC_IPE_0_FAST_AHB_CLK] = &cam_cc_ipe_0_fast_ahb_clk.clkr, + [CAM_CC_IPE_1_AHB_CLK] = &cam_cc_ipe_1_ahb_clk.clkr, + [CAM_CC_IPE_1_CLK] = &cam_cc_ipe_1_clk.clkr, + [CAM_CC_IPE_1_CLK_SRC] = &cam_cc_ipe_1_clk_src.clkr, + [CAM_CC_IPE_1_FAST_AHB_CLK] = &cam_cc_ipe_1_fast_ahb_clk.clkr, + [CAM_CC_PLL0] = &cam_cc_pll0.clkr, + [CAM_CC_PLL0_OUT_EVEN] = &cam_cc_pll0_out_even.clkr, + [CAM_CC_PLL0_OUT_ODD] = &cam_cc_pll0_out_odd.clkr, + [CAM_CC_PLL2] = &cam_cc_pll2.clkr, + [CAM_CC_PLL2_OUT_EVEN] = &cam_cc_pll2_out_even.clkr, + [CAM_CC_PLL3] = &cam_cc_pll3.clkr, + [CAM_CC_PLL3_OUT_EVEN] = &cam_cc_pll3_out_even.clkr, + [CAM_CC_PLL4] = &cam_cc_pll4.clkr, + [CAM_CC_PLL4_OUT_EVEN] = &cam_cc_pll4_out_even.clkr, + [CAM_CC_PLL5] = &cam_cc_pll5.clkr, + [CAM_CC_PLL5_OUT_EVEN] = &cam_cc_pll5_out_even.clkr, + [CAM_CC_PLL6] = &cam_cc_pll6.clkr, + [CAM_CC_PLL6_OUT_EVEN] = &cam_cc_pll6_out_even.clkr, + [CAM_CC_QDSS_DEBUG_CLK] = &cam_cc_qdss_debug_clk.clkr, + [CAM_CC_QDSS_DEBUG_CLK_SRC] = &cam_cc_qdss_debug_clk_src.clkr, + [CAM_CC_QDSS_DEBUG_XO_CLK] = &cam_cc_qdss_debug_xo_clk.clkr, + [CAM_CC_QUP_AHBM_CLK] = &cam_cc_qup_ahbm_clk.clkr, + [CAM_CC_QUP_AHBM_CLK_SRC] = &cam_cc_qup_ahbm_clk_src.clkr, + [CAM_CC_QUP_CORE_2X_CLK] = &cam_cc_qup_core_2x_clk.clkr, + [CAM_CC_QUP_CORE_2X_CLK_SRC] = &cam_cc_qup_core_2x_clk_src.clkr, + [CAM_CC_QUP_CORE_2X_DIV_CLK_SRC] = &cam_cc_qup_core_2x_div_clk_src.clkr, + [CAM_CC_QUP_CORE_CLK] = &cam_cc_qup_core_clk.clkr, + [CAM_CC_QUP_SE_CLK] = &cam_cc_qup_se_clk.clkr, + [CAM_CC_QUP_SE_CLK_SRC] = &cam_cc_qup_se_clk_src.clkr, + [CAM_CC_SFE_LITE_0_CLK] = &cam_cc_sfe_lite_0_clk.clkr, + [CAM_CC_SFE_LITE_0_FAST_AHB_CLK] = &cam_cc_sfe_lite_0_fast_ahb_clk.clkr, + [CAM_CC_SFE_LITE_1_CLK] = &cam_cc_sfe_lite_1_clk.clkr, + [CAM_CC_SFE_LITE_1_FAST_AHB_CLK] = &cam_cc_sfe_lite_1_fast_ahb_clk.clkr, + [CAM_CC_SFE_LITE_2_CLK] = &cam_cc_sfe_lite_2_clk.clkr, + [CAM_CC_SFE_LITE_2_FAST_AHB_CLK] = &cam_cc_sfe_lite_2_fast_ahb_clk.clkr, + [CAM_CC_SLEEP_CLK_SRC] = &cam_cc_sleep_clk_src.clkr, + [CAM_CC_SLOW_AHB_CLK_SRC] = &cam_cc_slow_ahb_clk_src.clkr, + [CAM_CC_SM_OBS_CLK] = &cam_cc_sm_obs_clk.clkr, + [CAM_CC_TOP_AHB_CLK] = &cam_cc_top_ahb_clk.clkr, + [CAM_CC_TOP_FAST_AHB_CLK] = &cam_cc_top_fast_ahb_clk.clkr, + [CAM_CC_TOP_IFE_0_CLK] = &cam_cc_top_ife_0_clk.clkr, + [CAM_CC_TOP_IFE_1_CLK] = &cam_cc_top_ife_1_clk.clkr, + [CAM_CC_TOP_IFE_2_CLK] = &cam_cc_top_ife_2_clk.clkr, + [CAM_CC_TOP_IFE_LITE_CLK] = &cam_cc_top_ife_lite_clk.clkr, + [CAM_CC_TOP_IPE_0_CLK] = &cam_cc_top_ipe_0_clk.clkr, + [CAM_CC_TOP_IPE_1_CLK] = &cam_cc_top_ipe_1_clk.clkr, + [CAM_CC_TOP_QUP_AHBM_CLK] = &cam_cc_top_qup_ahbm_clk.clkr, + [CAM_CC_TOP_SFE_LITE_0_CLK] = &cam_cc_top_sfe_lite_0_clk.clkr, + [CAM_CC_TOP_SFE_LITE_1_CLK] = &cam_cc_top_sfe_lite_1_clk.clkr, + [CAM_CC_TOP_SFE_LITE_2_CLK] = &cam_cc_top_sfe_lite_2_clk.clkr, + [CAM_CC_TPG_CSIPHY_RX_CLK] = &cam_cc_tpg_csiphy_rx_clk.clkr, + [CAM_CC_XO_CLK_SRC] = &cam_cc_xo_clk_src.clkr, +}; + +static struct gdsc *cam_cc_nord_gdscs[] = { + [CAM_CC_TITAN_TOP_GDSC] = &cam_cc_titan_top_gdsc, + [CAM_CC_IFE_0_GDSC] = &cam_cc_ife_0_gdsc, + [CAM_CC_IFE_1_GDSC] = &cam_cc_ife_1_gdsc, + [CAM_CC_IFE_2_GDSC] = &cam_cc_ife_2_gdsc, + [CAM_CC_IPE_0_GDSC] = &cam_cc_ipe_0_gdsc, + [CAM_CC_IPE_1_GDSC] = &cam_cc_ipe_1_gdsc, +}; + +static const struct qcom_reset_map cam_cc_nord_resets[] = { + [CAM_CC_CCU_BCR] = { 0x13298 }, + [CAM_CC_ICP_0_BCR] = { 0x130a0 }, + [CAM_CC_ICP_1_BCR] = { 0x130d8 }, + [CAM_CC_IFE_0_BCR] = { 0x12000 }, + [CAM_CC_IFE_1_BCR] = { 0x120c4 }, + [CAM_CC_IFE_2_BCR] = { 0x12170 }, + [CAM_CC_IPE_0_BCR] = { 0x11000 }, + [CAM_CC_IPE_1_BCR] = { 0x1105c }, + [CAM_CC_QDSS_DEBUG_BCR] = { 0x1332c }, + [CAM_CC_SFE_LITE_0_BCR] = { 0x13058 }, + [CAM_CC_SFE_LITE_1_BCR] = { 0x13070 }, + [CAM_CC_SFE_LITE_2_BCR] = { 0x13088 }, +}; + +static struct clk_alpha_pll *cam_cc_nord_plls[] = { + &cam_cc_pll0, + &cam_cc_pll2, + &cam_cc_pll3, + &cam_cc_pll4, + &cam_cc_pll5, + &cam_cc_pll6, +}; + +static const u32 cam_cc_nord_critical_cbcrs[] = { + 0x13294, /* CAM_CC_CAMNOC_XO_CLK */ + 0x13364, /* CAM_CC_CORE_AHB_CLK */ + 0x13380, /* CAM_CC_GDSC_CLK */ + 0x132e4, /* CAM_CC_QUP_SLEEP_CLK */ + 0x1339c, /* CAM_CC_SLEEP_CLK */ +}; + +static const struct regmap_config cam_cc_nord_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x17000, + .fast_io = true, +}; + +static const struct qcom_cc_driver_data cam_cc_nord_driver_data = { + .alpha_plls = cam_cc_nord_plls, + .num_alpha_plls = ARRAY_SIZE(cam_cc_nord_plls), + .clk_cbcrs = cam_cc_nord_critical_cbcrs, + .num_clk_cbcrs = ARRAY_SIZE(cam_cc_nord_critical_cbcrs), +}; + +static const struct qcom_cc_desc cam_cc_nord_desc = { + .config = &cam_cc_nord_regmap_config, + .clks = cam_cc_nord_clocks, + .num_clks = ARRAY_SIZE(cam_cc_nord_clocks), + .resets = cam_cc_nord_resets, + .num_resets = ARRAY_SIZE(cam_cc_nord_resets), + .gdscs = cam_cc_nord_gdscs, + .num_gdscs = ARRAY_SIZE(cam_cc_nord_gdscs), + .use_rpm = true, + .driver_data = &cam_cc_nord_driver_data, +}; + +static const struct of_device_id cam_cc_nord_match_table[] = { + { .compatible = "qcom,nord-camcc" }, + { } +}; +MODULE_DEVICE_TABLE(of, cam_cc_nord_match_table); + +static int cam_cc_nord_probe(struct platform_device *pdev) +{ + return qcom_cc_probe(pdev, &cam_cc_nord_desc); +} + +static struct platform_driver cam_cc_nord_driver = { + .probe = cam_cc_nord_probe, + .driver = { + .name = "camcc-nord", + .of_match_table = cam_cc_nord_match_table, + }, +}; + +module_platform_driver(cam_cc_nord_driver); + +MODULE_DESCRIPTION("QTI CAMCC Nord Driver"); +MODULE_LICENSE("GPL"); From 3a46c2241ec0a32e19df52d3d21548c49bd5894a Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Thu, 9 Jul 2026 11:27:26 +0530 Subject: [PATCH 554/562] arm64: dts: qcom: nord: Add clock controller nodes Add the GPU, camera, video, and display clock controller nodes for the Qualcomm Nord SoC, along with their required dt-bindings includes. Signed-off-by: Taniya Das --- arch/arm64/boot/dts/qcom/nord-embedded.dtsi | 108 ++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/nord-embedded.dtsi b/arch/arm64/boot/dts/qcom/nord-embedded.dtsi index 619025011b56..7212d87915c7 100644 --- a/arch/arm64/boot/dts/qcom/nord-embedded.dtsi +++ b/arch/arm64/boot/dts/qcom/nord-embedded.dtsi @@ -3,11 +3,15 @@ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ +#include +#include #include +#include #include #include #include #include +#include #include #include #include @@ -696,6 +700,17 @@ qcom,bcm-voters = <&apps_bcm_voter>; }; + gpucc: clock-controller@3d90000 { + compatible = "qcom,nord-gpucc"; + reg = <0x0 0x03d90000 0x0 0x9800>; + clocks = <&rpmhcc RPMH_CXO_CLK>, + <&nwgcc NW_GCC_GPU_GPLL0_CLK_SRC>, + <&nwgcc NW_GCC_GPU_GPLL0_DIV_CLK_SRC>; + #clock-cells = <1>; + #reset-cells = <1>; + #power-domain-cells = <1>; + }; + hpass_ag_noc: interconnect@5fc0000 { compatible = "qcom,nord-hpass-ag-noc"; reg = <0x0 0x05fc0000 0x0 0x37080>; @@ -738,6 +753,99 @@ #power-domain-cells = <1>; }; + dispcc1: clock-controller@8d00000 { + compatible = "qcom,nord-dispcc1"; + reg = <0x0 0x08d00000 0x0 0x20000>; + clocks = <&bi_tcxo_div2>, + <&bi_tcxo_ao_div2>, + <&nwgcc NW_GCC_DISP_1_AHB_CLK>, + <&sleep_clk>, + <0>, + <0>, + <0>, + <0>, + <0>, + <0>, + <0>, + <0>, + <0>, + <0>, + <0>, + <0>; + power-domains = <&rpmhpd RPMHPD_MMCX>; + required-opps = <&rpmhpd_opp_min_svs>; + #clock-cells = <1>; + #reset-cells = <1>; + #power-domain-cells = <1>; + }; + + gpu2cc: clock-controller@9890000 { + compatible = "qcom,nord-gpu2cc"; + reg = <0x0 0x09890000 0x0 0xa000>; + clocks = <&bi_tcxo_div2>, + <&negcc NE_GCC_GPU_2_GPLL0_CLK_SRC>, + <&negcc NE_GCC_GPU_2_GPLL0_DIV_CLK_SRC>; + #clock-cells = <1>; + #reset-cells = <1>; + #power-domain-cells = <1>; + }; + + camcc: clock-controller@9ce0000 { + compatible = "qcom,nord-camcc"; + reg = <0x0 0x09ce0000 0x0 0x20000>; + power-domains = <&rpmhpd RPMHPD_MMCX>, + <&rpmhpd RPMHPD_MXC>; + required-opps = <&rpmhpd_opp_low_svs>, + <&rpmhpd_opp_low_svs>; + clocks = <&nwgcc NW_GCC_CAMERA_AHB_CLK>, + <&bi_tcxo_div2>, + <&bi_tcxo_ao_div2>, + <&sleep_clk>; + #clock-cells = <1>; + #reset-cells = <1>; + #power-domain-cells = <1>; + }; + + videocc: clock-controller@aaf0000 { + compatible = "qcom,nord-videocc"; + reg = <0x0 0x0aaf0000 0x0 0x10000>; + clocks = <&bi_tcxo_div2>, + <&nwgcc NW_GCC_VIDEO_AHB_CLK>; + power-domains = <&rpmhpd RPMHPD_MMCX>, + <&rpmhpd RPMHPD_MXC>; + required-opps = <&rpmhpd_opp_low_svs>, + <&rpmhpd_opp_low_svs>; + #clock-cells = <1>; + #reset-cells = <1>; + #power-domain-cells = <1>; + }; + + dispcc0: clock-controller@af00000 { + compatible = "qcom,nord-dispcc0"; + reg = <0x0 0x0af00000 0x0 0x20000>; + clocks = <&bi_tcxo_div2>, + <&bi_tcxo_ao_div2>, + <&nwgcc NW_GCC_DISP_0_AHB_CLK>, + <&sleep_clk>, + <0>, + <0>, + <0>, + <0>, + <0>, + <0>, + <0>, + <0>, + <0>, + <0>, + <0>, + <0>; + power-domains = <&rpmhpd RPMHPD_MMCX>; + required-opps = <&rpmhpd_opp_min_svs>; + #clock-cells = <1>; + #reset-cells = <1>; + #power-domain-cells = <1>; + }; + tcsrcc: clock-controller@f1d9000 { compatible = "qcom,nord-tcsrcc", "syscon"; From 5968a35e6ec3812f3331865986d556a05fb2921f Mon Sep 17 00:00:00 2001 From: Dhruvin Rajpura Date: Mon, 20 Jul 2026 17:34:18 +0530 Subject: [PATCH 555/562] pmdomain: qcom: rpmhd: Remove the RPMHPD driver changes Remove the RPMHPD driver changes for Nord. Signed-off-by: Dhruvin Rajpura --- drivers/pmdomain/qcom/rpmhpd.c | 35 ---------------------------------- 1 file changed, 35 deletions(-) diff --git a/drivers/pmdomain/qcom/rpmhpd.c b/drivers/pmdomain/qcom/rpmhpd.c index 63120e703923..ba0cf4694435 100644 --- a/drivers/pmdomain/qcom/rpmhpd.c +++ b/drivers/pmdomain/qcom/rpmhpd.c @@ -122,11 +122,6 @@ static struct rpmhpd gfx = { .res_name = "gfx.lvl", }; -static struct rpmhpd gfx1 = { - .pd = { .name = "gfx1", }, - .res_name = "gfx1.lvl", -}; - static struct rpmhpd lcx = { .pd = { .name = "lcx", }, .res_name = "lcx.lvl", @@ -222,11 +217,6 @@ static struct rpmhpd nsp2 = { .res_name = "nsp2.lvl", }; -static struct rpmhpd nsp3 = { - .pd = { .name = "nsp3", }, - .res_name = "nsp3.lvl", -}; - static struct rpmhpd qphy = { .pd = { .name = "qphy", }, .res_name = "qphy.lvl", @@ -318,30 +308,6 @@ static const struct rpmhpd_desc sa8775p_desc = { .num_pds = ARRAY_SIZE(sa8775p_rpmhpds), }; -/* Nord RPMH powerdomains */ -static struct rpmhpd *nord_rpmhpds[] = { - [RPMHPD_CX] = &cx, - [RPMHPD_CX_AO] = &cx_ao, - [RPMHPD_EBI] = &ebi, - [RPMHPD_GFX] = &gfx, - [RPMHPD_GFX1] = &gfx1, - [RPMHPD_MX] = &mx, - [RPMHPD_MX_AO] = &mx_ao, - [RPMHPD_MMCX] = &mmcx, - [RPMHPD_MMCX_AO] = &mmcx_ao, - [RPMHPD_MXC] = &mxc, - [RPMHPD_MXC_AO] = &mxc_ao, - [RPMHPD_NSP0] = &nsp0, - [RPMHPD_NSP1] = &nsp1, - [RPMHPD_NSP2] = &nsp2, - [RPMHPD_NSP3] = &nsp3, -}; - -static const struct rpmhpd_desc nord_desc = { - .rpmhpds = nord_rpmhpds, - .num_pds = ARRAY_SIZE(nord_rpmhpds), -}; - /* SAR2130P RPMH powerdomains */ static struct rpmhpd *sar2130p_rpmhpds[] = { [RPMHPD_CX] = &cx, @@ -890,7 +856,6 @@ static const struct of_device_id rpmhpd_match_table[] = { { .compatible = "qcom,hawi-rpmhpd", .data = &hawi_desc }, { .compatible = "qcom,kaanapali-rpmhpd", .data = &kaanapali_desc }, { .compatible = "qcom,milos-rpmhpd", .data = &milos_desc }, - { .compatible = "qcom,nord-rpmhpd", .data = &nord_desc }, { .compatible = "qcom,qcs615-rpmhpd", .data = &qcs615_desc }, { .compatible = "qcom,qcs8300-rpmhpd", .data = &qcs8300_desc }, { .compatible = "qcom,qdu1000-rpmhpd", .data = &qdu1000_desc }, From 7e83435cb0684d387e0fb5385362592e79ba0528 Mon Sep 17 00:00:00 2001 From: Dhruvin Rajpura Date: Mon, 20 Jul 2026 17:35:54 +0530 Subject: [PATCH 556/562] regulator: Remove the RPMh regulator changes Remove the RPMh regulator changes for Nord. Signed-off-by: Dhruvin Rajpura Date: Mon, 20 Jul 2026 17:36:52 +0530 Subject: [PATCH 557/562] arm64: dts: qcom: iq10: Remove RPMHPD DT changes Remove RPMHPD DT changes for Nord. Signed-off-by: Dhruvin Rajpura --- arch/arm64/boot/dts/qcom/nord-embedded.dtsi | 49 --------------------- 1 file changed, 49 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/nord-embedded.dtsi b/arch/arm64/boot/dts/qcom/nord-embedded.dtsi index 7212d87915c7..aee39cda889c 100644 --- a/arch/arm64/boot/dts/qcom/nord-embedded.dtsi +++ b/arch/arm64/boot/dts/qcom/nord-embedded.dtsi @@ -886,55 +886,6 @@ #clock-cells = <1>; }; - rpmhpd: power-controller { - compatible = "qcom,nord-rpmhpd"; - #power-domain-cells = <1>; - operating-points-v2 = <&rpmhpd_opp_table>; - - rpmhpd_opp_table: opp-table { - compatible = "operating-points-v2"; - - rpmhpd_opp_ret: opp-0 { - opp-level = ; - }; - - rpmhpd_opp_min_svs: opp-1 { - opp-level = ; - }; - - rpmhpd_opp_low_svs: opp2 { - opp-level = ; - }; - - rpmhpd_opp_svs: opp3 { - opp-level = ; - }; - - rpmhpd_opp_svs_l1: opp-4 { - opp-level = ; - }; - - rpmhpd_opp_nom: opp-5 { - opp-level = ; - }; - - rpmhpd_opp_nom_l1: opp-6 { - opp-level = ; - }; - - rpmhpd_opp_nom_l2: opp-7 { - opp-level = ; - }; - - rpmhpd_opp_turbo: opp-8 { - opp-level = ; - }; - - rpmhpd_opp_turbo_l1: opp-9 { - opp-level = ; - }; - }; - }; }; nsp_data_noc_0: interconnect@1f200000 { From af0c0f6e44c02d54b2944fee2f056e53695c7185 Mon Sep 17 00:00:00 2001 From: Dhruvin Rajpura Date: Mon, 20 Jul 2026 17:38:00 +0530 Subject: [PATCH 558/562] arm64: dts: qcom: iq10: Remove RPMH regulator DT changes Remove RPMH regulator DT changes for Nord. Signed-off-by: Dhruvin Rajpura --- arch/arm64/boot/dts/qcom/iq10-rrd.dts | 461 -------------------------- 1 file changed, 461 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/iq10-rrd.dts b/arch/arm64/boot/dts/qcom/iq10-rrd.dts index 39d254b49c14..0c0acfe19729 100644 --- a/arch/arm64/boot/dts/qcom/iq10-rrd.dts +++ b/arch/arm64/boot/dts/qcom/iq10-rrd.dts @@ -69,467 +69,6 @@ }; }; -&apps_rsc { - /* PMIC A - Kobra_MM (PMM8650AU) - SID 0x0, Bus E0 */ - regulators-0 { - compatible = "qcom,pmm8654au-rpmh-regulators"; - qcom,pmic-id = "A_E0"; - - /* LDO Regulators */ - vreg_l4a_1p2: ldo4 { - regulator-name = "vreg_l4a_1p2"; - regulator-min-microvolt = <1200000>; - regulator-max-microvolt = <1200000>; - regulator-initial-mode = ; - regulator-allow-set-load; - regulator-allowed-modes = ; - }; - - vreg_l6a_1p2: ldo6 { - regulator-name = "vreg_l6a_1p2"; - regulator-min-microvolt = <1200000>; - regulator-max-microvolt = <1200000>; - regulator-initial-mode = ; - regulator-allow-set-load; - regulator-allowed-modes = ; - }; - - vreg_l7a_1p2: ldo7 { - regulator-name = "vreg_l7a_1p2"; - regulator-min-microvolt = <1200000>; - regulator-max-microvolt = <1200000>; - regulator-initial-mode = ; - regulator-always-on; - regulator-boot-on; - regulator-allow-set-load; - regulator-allowed-modes = ; - }; - - vreg_l8a_1p8: ldo8 { - regulator-name = "vreg_l8a_1p8"; - regulator-min-microvolt = <1800000>; - regulator-max-microvolt = <1800000>; - regulator-initial-mode = ; - regulator-always-on; - regulator-boot-on; - regulator-allow-set-load; - regulator-allowed-modes = ; - }; - - /* SMPS Regulators */ - vreg_s1a_vdd2h_l: smps1 { - regulator-name = "vreg_s1a_vdd2h_l"; - regulator-min-microvolt = <900000>; - regulator-max-microvolt = <1100000>; - regulator-initial-mode = ; - regulator-always-on; - regulator-boot-on; - }; - - vreg_s3a_1p8: smps3 { - regulator-name = "vreg_s3a_1p8"; - regulator-min-microvolt = <1800000>; - regulator-max-microvolt = <1800000>; - regulator-initial-mode = ; - regulator-always-on; - regulator-boot-on; - }; - - vreg_s5a_mv: smps5 { - regulator-name = "vreg_s5a_mv"; - regulator-min-microvolt = <1328000>; - regulator-max-microvolt = <1370000>; - regulator-initial-mode = ; - regulator-always-on; - regulator-boot-on; - }; - - vreg_s6a_vddq_l: smps6 { - regulator-name = "vreg_s6a_vddq_l"; - regulator-min-microvolt = <500000>; - regulator-max-microvolt = <570000>; - regulator-initial-mode = ; - regulator-allow-set-load; - regulator-allowed-modes = ; - }; - - vreg_s8a_vdda_ebi: smps8 { - regulator-name = "vreg_s8a_vdda_ebi"; - regulator-min-microvolt = <300000>; - regulator-max-microvolt = <1200000>; - regulator-initial-mode = ; - regulator-always-on; - regulator-boot-on; - }; - }; - - /* PMIC E - Kai_MV - SID 0x4, Bus E0 */ - regulators-1 { - compatible = "qcom,pmau0102-rpmh-regulators"; - qcom,pmic-id = "E_E0"; - - /* LDO Regulators */ - vreg_l1e_0p9: ldo1 { - regulator-name = "vreg_l1e_0p9"; - regulator-min-microvolt = <936000>; - regulator-max-microvolt = <936000>; - regulator-initial-mode = ; - regulator-allow-set-load; - regulator-allowed-modes = ; - }; - - vreg_l2e_0p9: ldo2 { - regulator-name = "vreg_l2e_0p9"; - regulator-min-microvolt = <936000>; - regulator-max-microvolt = <936000>; - regulator-initial-mode = ; - regulator-allow-set-load; - regulator-allowed-modes = ; - }; - - vreg_l3e_1p8: ldo3 { - regulator-name = "vreg_l3e_1p8"; - regulator-min-microvolt = <1800000>; - regulator-max-microvolt = <1800000>; - regulator-initial-mode = ; - regulator-always-on; - regulator-boot-on; - regulator-allow-set-load; - regulator-allowed-modes = ; - }; - - /* SMPS Regulators */ - vreg_s1e_nsp3: smps1 { - regulator-name = "vreg_s1e_nsp3"; - regulator-min-microvolt = <300000>; - regulator-max-microvolt = <1200000>; - regulator-initial-mode = ; - regulator-allow-set-load; - regulator-allowed-modes = ; - }; - - vreg_s7e_mxa: smps7 { - regulator-name = "vreg_s7e_mxa"; - regulator-min-microvolt = <300000>; - regulator-max-microvolt = <1200000>; - regulator-initial-mode = ; - regulator-always-on; - regulator-boot-on; - }; - }; - - /* PMIC F - Kai_MV - SID 0x5, Bus E0 */ - regulators-2 { - compatible = "qcom,pmau0102-rpmh-regulators"; - qcom,pmic-id = "F_E0"; - - /* LDO Regulators */ - vreg_l1f_vdd2l: ldo1 { - regulator-name = "vreg_l1f_vdd2l"; - regulator-min-microvolt = <904000>; - regulator-max-microvolt = <904000>; - regulator-initial-mode = ; - regulator-always-on; - regulator-boot-on; - regulator-allow-set-load; - regulator-allowed-modes = ; - }; - - vreg_l3f_vdd1: ldo3 { - regulator-name = "vreg_l3f_vdd1"; - regulator-min-microvolt = <1800000>; - regulator-max-microvolt = <1800000>; - regulator-initial-mode = ; - regulator-always-on; - regulator-boot-on; - regulator-allow-set-load; - regulator-allowed-modes = ; - }; - - /* SMPS Regulators */ - vreg_s1f_nsp1: smps1 { - regulator-name = "vreg_s1f_nsp1"; - regulator-min-microvolt = <300000>; - regulator-max-microvolt = <1200000>; - regulator-initial-mode = ; - regulator-allow-set-load; - regulator-allowed-modes = ; - }; - - vreg_s7f_lv_sub: smps7 { - regulator-name = "vreg_s7f_lv_sub"; - regulator-min-microvolt = <1036000>; - regulator-max-microvolt = <1136000>; - regulator-initial-mode = ; - regulator-always-on; - regulator-boot-on; - }; - - vreg_s8f_vddq_h: smps8 { - regulator-name = "vreg_s8f_vddq_h"; - regulator-min-microvolt = <500000>; - regulator-max-microvolt = <570000>; - regulator-initial-mode = ; - regulator-allow-set-load; - regulator-allowed-modes = ; - }; - }; - - /* PMIC G - Kai_MV - SID 0x6, Bus E0 */ - regulators-3 { - compatible = "qcom,pmau0102-rpmh-regulators"; - qcom,pmic-id = "G_E0"; - - /* LDO Regulators */ - vreg_l2g_0p7: ldo2 { - regulator-name = "vreg_l2g_0p7"; - regulator-min-microvolt = <752000>; - regulator-max-microvolt = <752000>; - regulator-initial-mode = ; - regulator-allow-set-load; - regulator-allowed-modes = ; - }; - - /* SMPS Regulators */ - vreg_s1g_nsp0: smps1 { - regulator-name = "vreg_s1g_nsp0"; - regulator-min-microvolt = <300000>; - regulator-max-microvolt = <1200000>; - regulator-initial-mode = ; - regulator-allow-set-load; - regulator-allowed-modes = ; - }; - - vreg_s5g_vdd2h_h: smps5 { - regulator-name = "vreg_s5g_vdd2h_h"; - regulator-min-microvolt = <1080000>; - regulator-max-microvolt = <1150000>; - regulator-initial-mode = ; - regulator-always-on; - regulator-boot-on; - }; - }; - - /* PMIC H - Kai_MV - SID 0x7, Bus E0 */ - regulators-4 { - compatible = "qcom,pmau0102-rpmh-regulators"; - qcom,pmic-id = "H_E0"; - - /* LDO Regulators */ - vreg_l1h_0p9: ldo1 { - regulator-name = "vreg_l1h_0p9"; - regulator-min-microvolt = <904000>; - regulator-max-microvolt = <904000>; - regulator-initial-mode = ; - regulator-allow-set-load; - regulator-allowed-modes = ; - }; - - vreg_l2h_1p2: ldo2 { - regulator-name = "vreg_l2h_1p2"; - regulator-min-microvolt = <1200000>; - regulator-max-microvolt = <1200000>; - regulator-initial-mode = ; - regulator-allow-set-load; - regulator-allowed-modes = ; - }; - - /* SMPS Regulators */ - vreg_s1h_nsp2: smps1 { - regulator-name = "vreg_s1h_nsp2"; - regulator-min-microvolt = <300000>; - regulator-max-microvolt = <1200000>; - regulator-initial-mode = ; - regulator-allow-set-load; - regulator-allowed-modes = ; - }; - - vreg_s5h_mxc: smps5 { - regulator-name = "vreg_s5h_mxc"; - regulator-min-microvolt = <300000>; - regulator-max-microvolt = <1200000>; - regulator-initial-mode = ; - regulator-allow-set-load; - regulator-allowed-modes = ; - }; - }; - - /* PMIC I - Kai_MV - SID 0x8, Bus E0 */ - regulators-5 { - compatible = "qcom,pmau0102-rpmh-regulators"; - qcom,pmic-id = "I_E0"; - - /* LDO Regulators */ - vreg_l1i_0p9: ldo1 { - regulator-name = "vreg_l1i_0p9"; - regulator-min-microvolt = <912000>; - regulator-max-microvolt = <912000>; - regulator-initial-mode = ; - regulator-allow-set-load; - regulator-allowed-modes = ; - }; - - vreg_l2i_1p2: ldo2 { - regulator-name = "vreg_l2i_1p2"; - regulator-min-microvolt = <1200000>; - regulator-max-microvolt = <1200000>; - regulator-initial-mode = ; - regulator-always-on; - regulator-boot-on; - regulator-allow-set-load; - regulator-allowed-modes = ; - }; - - vreg_l3i_2p5: ldo3 { - regulator-name = "vreg_l3i_2p5"; - regulator-min-microvolt = <2504000>; - regulator-max-microvolt = <2504000>; - regulator-initial-mode = ; - regulator-allow-set-load; - regulator-allowed-modes = ; - }; - - /* SMPS Regulators */ - vreg_s2i_gfx0: smps2 { - regulator-name = "vreg_s2i_gfx0"; - regulator-min-microvolt = <300000>; - regulator-max-microvolt = <1200000>; - regulator-initial-mode = ; - regulator-allow-set-load; - regulator-allowed-modes = ; - }; - - vreg_s7i_mm: smps7 { - regulator-name = "vreg_s7i_mm"; - regulator-min-microvolt = <300000>; - regulator-max-microvolt = <1200000>; - regulator-initial-mode = ; - regulator-allow-set-load; - regulator-allowed-modes = ; - }; - }; - - /* PMIC J - Kai_MV - SID 0x9, Bus E0 */ - regulators-6 { - compatible = "qcom,pmau0102-rpmh-regulators"; - qcom,pmic-id = "J_E0"; - - /* SMPS Regulators */ - vreg_s7j_gfx1: smps7 { - regulator-name = "vreg_s7j_gfx1"; - regulator-min-microvolt = <300000>; - regulator-max-microvolt = <1200000>; - regulator-initial-mode = ; - regulator-allow-set-load; - regulator-allowed-modes = ; - }; - }; - - /* PMIC K - Kai_MV - SID 0xA, Bus E0 */ - regulators-7 { - compatible = "qcom,pmau0102-rpmh-regulators"; - qcom,pmic-id = "K_E0"; - - /* LDO Regulators */ - vreg_l1k_vdd2l: ldo1 { - regulator-name = "vreg_l1k_vdd2l"; - regulator-min-microvolt = <904000>; - regulator-max-microvolt = <904000>; - regulator-initial-mode = ; - regulator-always-on; - regulator-boot-on; - regulator-allow-set-load; - regulator-allowed-modes = ; - }; - - vreg_l2k_0p9: ldo2 { - regulator-name = "vreg_l2k_0p9"; - regulator-min-microvolt = <880000>; - regulator-max-microvolt = <880000>; - regulator-initial-mode = ; - regulator-allow-set-load; - regulator-allowed-modes = ; - }; - - vreg_l3k_vdd1: ldo3 { - regulator-name = "vreg_l3k_vdd1"; - regulator-min-microvolt = <1800000>; - regulator-max-microvolt = <1800000>; - regulator-initial-mode = ; - regulator-always-on; - regulator-boot-on; - regulator-allow-set-load; - regulator-allowed-modes = ; - }; - }; - - /* PMIC L - Kai_MV - SID 0xB, Bus E0 */ - regulators-8 { - compatible = "qcom,pmau0102-rpmh-regulators"; - qcom,pmic-id = "L_E0"; - - /* LDO Regulators */ - vreg_l3l_1p8: ldo3 { - regulator-name = "vreg_l3l_1p8"; - regulator-min-microvolt = <1800000>; - regulator-max-microvolt = <1800000>; - regulator-initial-mode = ; - regulator-allow-set-load; - regulator-allowed-modes = ; - }; - - /* SMPS Regulators */ - vreg_s1l_nsp_mxc: smps1 { - regulator-name = "vreg_s1l_nsp_mxc"; - regulator-min-microvolt = <300000>; - regulator-max-microvolt = <1200000>; - regulator-initial-mode = ; - regulator-allow-set-load; - regulator-allowed-modes = ; - }; - - vreg_s2l_cx: smps2 { - regulator-name = "vreg_s2l_cx"; - regulator-min-microvolt = <300000>; - regulator-max-microvolt = <1200000>; - regulator-initial-mode = ; - regulator-allow-set-load; - regulator-allowed-modes = ; - }; - }; -}; - &i2c7 { clock-frequency = <400000>; status = "okay"; From fd2bb8c415d59577708c57eb3c8834dca027a81b Mon Sep 17 00:00:00 2001 From: Dhruvin Rajpura Date: Mon, 20 Jul 2026 20:52:03 +0530 Subject: [PATCH 559/562] pmdomain: qcom: rpmhpd: Add RPMHPD driver support Add RPMHPD driver support for Nord. Signed-off-by: Dhruvin Rajpura --- .../devicetree/bindings/power/qcom,rpmpd.yaml | 1 + drivers/pmdomain/qcom/rpmhpd.c | 39 +++++++++++++++++++ include/dt-bindings/power/qcom,rpmhpd.h | 1 + 3 files changed, 41 insertions(+) diff --git a/Documentation/devicetree/bindings/power/qcom,rpmpd.yaml b/Documentation/devicetree/bindings/power/qcom,rpmpd.yaml index 0744a867c79e..2eadffe9ae14 100644 --- a/Documentation/devicetree/bindings/power/qcom,rpmpd.yaml +++ b/Documentation/devicetree/bindings/power/qcom,rpmpd.yaml @@ -36,6 +36,7 @@ properties: - qcom,msm8996-rpmpd - qcom,msm8998-rpmpd - qcom,nord-rpmhpd + # - qcom,nordau-rpmhpd (driver support only, binding entry not yet added) - qcom,qcm2290-rpmpd - qcom,qcs404-rpmpd - qcom,qcs615-rpmhpd diff --git a/drivers/pmdomain/qcom/rpmhpd.c b/drivers/pmdomain/qcom/rpmhpd.c index ba0cf4694435..e77116001e72 100644 --- a/drivers/pmdomain/qcom/rpmhpd.c +++ b/drivers/pmdomain/qcom/rpmhpd.c @@ -122,6 +122,16 @@ static struct rpmhpd gfx = { .res_name = "gfx.lvl", }; +static struct rpmhpd gfx0 = { + .pd = { .name = "gfx0", }, + .res_name = "gfx0.lvl", +}; + +static struct rpmhpd gfx1 = { + .pd = { .name = "gfx1", }, + .res_name = "gfx1.lvl", +}; + static struct rpmhpd lcx = { .pd = { .name = "lcx", }, .res_name = "lcx.lvl", @@ -217,6 +227,11 @@ static struct rpmhpd nsp2 = { .res_name = "nsp2.lvl", }; +static struct rpmhpd nsp3 = { + .pd = { .name = "nsp3", }, + .res_name = "nsp3.lvl", +}; + static struct rpmhpd qphy = { .pd = { .name = "qphy", }, .res_name = "qphy.lvl", @@ -264,6 +279,29 @@ static const struct rpmhpd_desc milos_desc = { .num_pds = ARRAY_SIZE(milos_rpmhpds), }; +/* NordAU RPMH powerdomains */ +static struct rpmhpd *nordau_rpmhpds[] = { + [RPMHPD_CX] = &cx, + [RPMHPD_CX_AO] = &cx_ao, + [RPMHPD_GFX0] = &gfx0, + [RPMHPD_GFX1] = &gfx1, + [RPMHPD_MMCX] = &mmcx, + [RPMHPD_MMCX_AO] = &mmcx_ao, + [RPMHPD_MX] = &mx, + [RPMHPD_MX_AO] = &mx_ao, + [RPMHPD_MXC] = &mxc, + [RPMHPD_MXC_AO] = &mxc_ao, + [RPMHPD_NSP0] = &nsp0, + [RPMHPD_NSP1] = &nsp1, + [RPMHPD_NSP2] = &nsp2, + [RPMHPD_NSP3] = &nsp3, +}; + +static const struct rpmhpd_desc nordau_desc = { + .rpmhpds = nordau_rpmhpds, + .num_pds = ARRAY_SIZE(nordau_rpmhpds), +}; + /* SA8540P RPMH powerdomains */ static struct rpmhpd *sa8540p_rpmhpds[] = { [SC8280XP_CX] = &cx, @@ -856,6 +894,7 @@ static const struct of_device_id rpmhpd_match_table[] = { { .compatible = "qcom,hawi-rpmhpd", .data = &hawi_desc }, { .compatible = "qcom,kaanapali-rpmhpd", .data = &kaanapali_desc }, { .compatible = "qcom,milos-rpmhpd", .data = &milos_desc }, + { .compatible = "qcom,nordau-rpmhpd", .data = &nordau_desc }, { .compatible = "qcom,qcs615-rpmhpd", .data = &qcs615_desc }, { .compatible = "qcom,qcs8300-rpmhpd", .data = &qcs8300_desc }, { .compatible = "qcom,qdu1000-rpmhpd", .data = &qdu1000_desc }, diff --git a/include/dt-bindings/power/qcom,rpmhpd.h b/include/dt-bindings/power/qcom,rpmhpd.h index 07bd2a7b0150..6c9653264bb7 100644 --- a/include/dt-bindings/power/qcom,rpmhpd.h +++ b/include/dt-bindings/power/qcom,rpmhpd.h @@ -32,6 +32,7 @@ #define RPMHPD_GBX 22 #define RPMHPD_NSP3 23 #define RPMHPD_GFX1 24 +#define RPMHPD_GFX0 25 /* RPMh Power Domain performance levels */ #define RPMH_REGULATOR_LEVEL_RETENTION 16 From 0d204098c8903b1586a8fbd6889507e6c9e5e933 Mon Sep 17 00:00:00 2001 From: Dhruvin Rajpura Date: Mon, 20 Jul 2026 20:54:21 +0530 Subject: [PATCH 560/562] regulator: Add RPMH Regulator driver changes Add RPMH Regulator driver changes for Nord. Signed-off-by: Dhruvin Rajpura --- .../regulator/qcom,rpmh-regulator.yaml | 2 + drivers/regulator/qcom-rpmh-regulator.c | 42 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/Documentation/devicetree/bindings/regulator/qcom,rpmh-regulator.yaml b/Documentation/devicetree/bindings/regulator/qcom,rpmh-regulator.yaml index d93594304651..de1815ed0ab3 100644 --- a/Documentation/devicetree/bindings/regulator/qcom,rpmh-regulator.yaml +++ b/Documentation/devicetree/bindings/regulator/qcom,rpmh-regulator.yaml @@ -68,6 +68,7 @@ description: | properties: compatible: enum: + # - qcom,kai_mv-rpmh-regulators (driver support only, binding entry not yet added) - qcom,pm6150-rpmh-regulators - qcom,pm6150l-rpmh-regulators - qcom,pm6350-rpmh-regulators @@ -99,6 +100,7 @@ properties: - qcom,pmh0110-rpmh-regulators - qcom,pmi8998-rpmh-regulators - qcom,pmm8155au-rpmh-regulators + # - qcom,pmm8650au-rpmh-regulators (driver support only, binding entry not yet added) - qcom,pmm8654au-rpmh-regulators - qcom,pmr735a-rpmh-regulators - qcom,pmr735b-rpmh-regulators diff --git a/drivers/regulator/qcom-rpmh-regulator.c b/drivers/regulator/qcom-rpmh-regulator.c index 12a9cdd3e57b..7c6ce9ec6a8f 100644 --- a/drivers/regulator/qcom-rpmh-regulator.c +++ b/drivers/regulator/qcom-rpmh-regulator.c @@ -1206,6 +1206,25 @@ static const struct rpmh_vreg_init_data pmm8155au_vreg_data[] = { {} }; +static const struct rpmh_vreg_init_data pmm8650au_vreg_data[] = { + RPMH_VREG("smps1", SMPS, 1, &pmic5_ftsmps527, "vdd-s1"), + RPMH_VREG("smps2", SMPS, 2, &pmic5_ftsmps527, "vdd-s2"), + RPMH_VREG("smps3", SMPS, 3, &pmic5_ftsmps527, "vdd-s3"), + RPMH_VREG("smps5", SMPS, 5, &pmic5_ftsmps527, "vdd-s5"), + RPMH_VREG("smps6", SMPS, 6, &pmic5_ftsmps527, "vdd-s6"), + RPMH_VREG("smps8", SMPS, 8, &pmic5_ftsmps527, "vdd-s8"), + RPMH_VREG("ldo1", LDO, 1, &pmic5_nldo515, "vdd-l1"), + RPMH_VREG("ldo2", LDO, 2, &pmic5_nldo515, "vdd-l2"), + RPMH_VREG("ldo3", LDO, 3, &pmic5_nldo515, "vdd-l3"), + RPMH_VREG("ldo4", LDO, 4, &pmic5_nldo515, "vdd-l4"), + RPMH_VREG("ldo5", LDO, 5, &pmic5_nldo515, "vdd-l5"), + RPMH_VREG("ldo6", LDO, 6, &pmic5_nldo515, "vdd-l6"), + RPMH_VREG("ldo7", LDO, 7, &pmic5_nldo515, "vdd-l7"), + RPMH_VREG("ldo8", LDO, 8, &pmic5_pldo515_mv, "vdd-l8"), + RPMH_VREG("ldo9", LDO, 9, &pmic5_pldo, "vdd-l9"), + {} +}; + static const struct rpmh_vreg_init_data pm8350_vreg_data[] = { RPMH_VREG("smps1", SMPS, 1, &pmic5_ftsmps510, "vdd-s1"), RPMH_VREG("smps2", SMPS, 2, &pmic5_ftsmps510, "vdd-s2"), @@ -1376,6 +1395,21 @@ static const struct rpmh_vreg_init_data pm8010_vreg_data[] = { RPMH_VREG("ldo7", LDO, 7, &pmic5_pldo502, "vdd-l7"), }; +static const struct rpmh_vreg_init_data kai_mv_vreg_data[] = { + RPMH_VREG("smps1", SMPS, 1, &pmic5_ftsmps527, "vdd-s1"), + RPMH_VREG("smps2", SMPS, 2, &pmic5_ftsmps527, "vdd-s2"), + RPMH_VREG("smps3", SMPS, 3, &pmic5_ftsmps527, "vdd-s3"), + RPMH_VREG("smps4", SMPS, 4, &pmic5_ftsmps527, "vdd-s4"), + RPMH_VREG("smps5", SMPS, 5, &pmic5_ftsmps527, "vdd-s5"), + RPMH_VREG("smps6", SMPS, 6, &pmic5_ftsmps527, "vdd-s6"), + RPMH_VREG("smps7", SMPS, 7, &pmic5_ftsmps527, "vdd-s7"), + RPMH_VREG("smps8", SMPS, 8, &pmic5_ftsmps527, "vdd-s8"), + RPMH_VREG("ldo1", LDO, 1, &pmic5_nldo515, "vdd-l1"), + RPMH_VREG("ldo2", LDO, 2, &pmic5_nldo515, "vdd-l2"), + RPMH_VREG("ldo3", LDO, 3, &pmic5_pldo515_mv, "vdd-l3"), + {} +}; + static const struct rpmh_vreg_init_data pm6150_vreg_data[] = { RPMH_VREG("smps1", SMPS, 1, &pmic5_ftsmps510, "vdd-s1"), RPMH_VREG("smps2", SMPS, 2, &pmic5_ftsmps510, "vdd-s2"), @@ -1863,6 +1897,10 @@ static const struct of_device_id __maybe_unused rpmh_regulator_match_table[] = { .compatible = "qcom,pmi8998-rpmh-regulators", .data = pmi8998_vreg_data, }, + { + .compatible = "qcom,kai_mv-rpmh-regulators", + .data = kai_mv_vreg_data, + }, { .compatible = "qcom,pm6150-rpmh-regulators", .data = pm6150_vreg_data, @@ -1903,6 +1941,10 @@ static const struct of_device_id __maybe_unused rpmh_regulator_match_table[] = { .compatible = "qcom,pmh0110-rpmh-regulators", .data = pmh0110_vreg_data, }, + { + .compatible = "qcom,pmm8650au-rpmh-regulators", + .data = pmm8650au_vreg_data, + }, { .compatible = "qcom,pmm8155au-rpmh-regulators", .data = pmm8155au_vreg_data, From 361011475d53c44f32bb836c61e062712b9b3598 Mon Sep 17 00:00:00 2001 From: Dhruvin Rajpura Date: Mon, 20 Jul 2026 20:55:21 +0530 Subject: [PATCH 561/562] arm64: dts: qcom: iq10: Add RPMHPD DT changes Add RPMHPD DT change for Nord. Signed-off-by: Dhruvin Rajpura --- arch/arm64/boot/dts/qcom/nord-embedded.dtsi | 115 ++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/nord-embedded.dtsi b/arch/arm64/boot/dts/qcom/nord-embedded.dtsi index aee39cda889c..c76080afef12 100644 --- a/arch/arm64/boot/dts/qcom/nord-embedded.dtsi +++ b/arch/arm64/boot/dts/qcom/nord-embedded.dtsi @@ -886,6 +886,121 @@ #clock-cells = <1>; }; + rpmhpd: power-controller { + compatible = "qcom,nordau-rpmhpd"; + + operating-points-v2 = <&rpmhpd_opp_table>; + + #power-domain-cells = <1>; + + rpmhpd_opp_table: opp-table { + compatible = "operating-points-v2"; + + rpmhpd_opp_ret: opp-ret { + opp-level = ; + }; + + rpmhpd_opp_low_svs_d3: opp-low-svs-d3 { + opp-level = ; + }; + + rpmhpd_opp_low_svs_d2_1: opp-low-svs-d2-1 { + opp-level = ; + }; + + rpmhpd_opp_low_svs_d2: opp-low-svs-d2 { + opp-level = ; + }; + + rpmhpd_opp_low_svs_d1_1: opp-low-svs-d1-1 { + opp-level = ; + }; + + rpmhpd_opp_low_svs_d1: opp-low-svs-d1 { + opp-level = ; + }; + + rpmhpd_opp_low_svs_d0: opp-low-svs-d0 { + opp-level = ; + }; + + rpmhpd_opp_low_svs: opp-low-svs { + opp-level = ; + }; + + rpmhpd_opp_low_svs_l0: opp-low-svs-l0 { + opp-level = ; + }; + + rpmhpd_opp_low_svs_l1: opp-low-svs-l1 { + opp-level = ; + }; + + rpmhpd_opp_low_svs_l2: opp-low-svs-l2 { + opp-level = ; + }; + + rpmhpd_opp_svs: opp-svs { + opp-level = ; + }; + + rpmhpd_opp_svs_l0: opp-svs-l0 { + opp-level = ; + }; + + rpmhpd_opp_svs_l1: opp-svs-l1 { + opp-level = ; + }; + + rpmhpd_opp_svs_l2: opp-svs-l2 { + opp-level = ; + }; + + rpmhpd_opp_nom: opp-nom { + opp-level = ; + }; + + rpmhpd_opp_nom_l1: opp-nom-l1 { + opp-level = ; + }; + + rpmhpd_opp_nom_l2: opp-nom-l2 { + opp-level = ; + }; + + rpmhpd_opp_turbo: opp-turbo { + opp-level = ; + }; + + rpmhpd_opp_turbo_l0: opp-turbo-l0 { + opp-level = ; + }; + + rpmhpd_opp_turbo_l1: opp-turbo-l1 { + opp-level = ; + }; + + rpmhpd_opp_turbo_l2: opp-turbo-l2 { + opp-level = ; + }; + + rpmhpd_opp_turbo_l3: opp-turbo-l3 { + opp-level = ; + }; + + rpmhpd_opp_turbo_l4: opp-turbo-l4 { + opp-level = ; + }; + + rpmhpd_opp_turbo_l5: opp-turbo-l5 { + opp-level = ; + }; + + rpmhpd_opp_super_turbo_no_cpr: opp-super-turbo-no-cpr { + opp-level = ; + }; + }; + }; }; nsp_data_noc_0: interconnect@1f200000 { From 6f22083fa0b676a0c63e06388a0978093c138173 Mon Sep 17 00:00:00 2001 From: Dhruvin Rajpura Date: Mon, 20 Jul 2026 20:56:46 +0530 Subject: [PATCH 562/562] arm64: dts: qcom: iq10: Add RPMh regulator DT changes Add RPMh regulator DT changes for Nord. Signed-off-by: Dhruvin Rajpura --- arch/arm64/boot/dts/qcom/iq10-rrd.dts | 259 ++++++++++++++++++++++++++ 1 file changed, 259 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/iq10-rrd.dts b/arch/arm64/boot/dts/qcom/iq10-rrd.dts index 0c0acfe19729..ac27b01026d3 100644 --- a/arch/arm64/boot/dts/qcom/iq10-rrd.dts +++ b/arch/arm64/boot/dts/qcom/iq10-rrd.dts @@ -69,6 +69,265 @@ }; }; +&apps_rsc { + regulators-0 { + compatible = "qcom,pmm8650au-rpmh-regulators"; + qcom,pmic-id = "A_E0"; + + vreg_s1a_1p01: smps1 { + regulator-name = "vreg_s1a_1p01"; + regulator-min-microvolt = <1010000>; + regulator-max-microvolt = <1170000>; + regulator-initial-mode = ; + }; + + vreg_s3a_1p65: smps3 { + regulator-name = "vreg_s3a_1p65"; + regulator-min-microvolt = <1650000>; + regulator-max-microvolt = <1950000>; + regulator-initial-mode = ; + }; + + vreg_s5a_1p17: smps5 { + regulator-name = "vreg_s5a_1p17"; + regulator-min-microvolt = <1170000>; + regulator-max-microvolt = <1370000>; + regulator-initial-mode = ; + }; + + vreg_s6a_0p3: smps6 { + regulator-name = "vreg_s6a_0p3"; + regulator-min-microvolt = <300000>; + regulator-max-microvolt = <570000>; + regulator-initial-mode = ; + }; + + vreg_s8a_0p64: smps8 { + regulator-name = "vreg_s8a_0p64"; + regulator-min-microvolt = <640000>; + regulator-max-microvolt = <1000000>; + regulator-initial-mode = ; + }; + + vreg_l4a_1p14: ldo4 { + regulator-name = "vreg_l4a_1p14"; + regulator-min-microvolt = <1140000>; + regulator-max-microvolt = <1300000>; + regulator-initial-mode = ; + }; + + vreg_l6a_1p14: ldo6 { + regulator-name = "vreg_l6a_1p14"; + regulator-min-microvolt = <1140000>; + regulator-max-microvolt = <1300000>; + regulator-initial-mode = ; + }; + + vreg_l7a_1p1: ldo7 { + regulator-name = "vreg_l7a_1p1"; + regulator-min-microvolt = <1100000>; + regulator-max-microvolt = <1300000>; + regulator-initial-mode = ; + }; + + vreg_l8a_1p8: ldo8 { + regulator-name = "vreg_l8a_1p8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1100000>; + regulator-initial-mode = ; + }; + }; + + regulators-1 { + compatible = "qcom,kai_mv-rpmh-regulators"; + qcom,pmic-id = "E_E0"; + + vreg_s7e_0p67: smps7 { + regulator-name = "vreg_s7e_0p67"; + regulator-min-microvolt = <675000>; + regulator-max-microvolt = <750000>; + regulator-initial-mode = ; + }; + + vreg_l1e_0p89: ldo1 { + regulator-name = "vreg_l1e_0p89"; + regulator-min-microvolt = <890000>; + regulator-max-microvolt = <980000>; + regulator-initial-mode = ; + }; + + vreg_l2e_0p89: ldo2 { + regulator-name = "vreg_l2e_0p89"; + regulator-min-microvolt = <890000>; + regulator-max-microvolt = <980000>; + regulator-initial-mode = ; + }; + + vreg_l3e_1p8: ldo3 { + regulator-name = "vreg_l3e_1p8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1890000>; + regulator-initial-mode = ; + }; + }; + + regulators-2 { + compatible = "qcom,kai_mv-rpmh-regulators"; + qcom,pmic-id = "F_E0"; + + vreg_s1f_0p68: smps1 { + regulator-name = "vreg_s1f_0p68"; + regulator-min-microvolt = <684000>; + regulator-max-microvolt = <888000>; + regulator-initial-mode = ; + }; + + vreg_s7f_0p74: smps7 { + regulator-name = "vreg_s7f_0p74"; + regulator-min-microvolt = <745000>; + regulator-max-microvolt = <1050000>; + regulator-initial-mode = ; + }; + + vreg_s8f_0p3: smps8 { + regulator-name = "vreg_s8f_0p3"; + regulator-min-microvolt = <300000>; + regulator-max-microvolt = <570000>; + regulator-initial-mode = ; + }; + + vreg_l1f_0p87: ldo1 { + regulator-name = "vreg_l1f_0p87"; + regulator-min-microvolt = <870000>; + regulator-max-microvolt = <970000>; + regulator-initial-mode = ; + }; + + vreg_l3f_1p8: ldo3 { + regulator-name = "vreg_l3f_1p8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1950000>; + regulator-initial-mode = ; + }; + }; + + regulators-3 { + compatible = "qcom,kai_mv-rpmh-regulators"; + qcom,pmic-id = "G_E0"; + + vreg_s1g_0p68: smps1 { + regulator-name = "vreg_s1g_0p68"; + regulator-min-microvolt = <684000>; + regulator-max-microvolt = <888000>; + regulator-initial-mode = ; + }; + + vreg_s5g_1p01: smps5 { + regulator-name = "vreg_s5g_1p01"; + regulator-min-microvolt = <1010000>; + regulator-max-microvolt = <1120000>; + regulator-initial-mode = ; + }; + + vreg_l2g_0p71: ldo2 { + regulator-name = "vreg_l2g_0p71"; + regulator-min-microvolt = <713000>; + regulator-max-microvolt = <825000>; + regulator-initial-mode = ; + }; + }; + + regulators-4 { + compatible = "qcom,kai_mv-rpmh-regulators"; + qcom,pmic-id = "H_E0"; + + vreg_l1h_0p67: ldo1 { + regulator-name = "vreg_l1h_0p67"; + regulator-min-microvolt = <675000>; + regulator-max-microvolt = <960000>; + regulator-initial-mode = ; + }; + + vreg_l2h_1p14: ldo2 { + regulator-name = "vreg_l2h_1p14"; + regulator-min-microvolt = <1140000>; + regulator-max-microvolt = <1260000>; + regulator-initial-mode = ; + }; + }; + + regulators-5 { + compatible = "qcom,kai_mv-rpmh-regulators"; + qcom,pmic-id = "I_E0"; + + vreg_s2i_0p5: smps2 { + regulator-name = "vreg_s2i_0p5"; + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <850000>; + regulator-initial-mode = ; + }; + + vreg_l1i_0p86: ldo1 { + regulator-name = "vreg_l1i_0p86"; + regulator-min-microvolt = <866000>; + regulator-max-microvolt = <958000>; + regulator-initial-mode = ; + }; + + vreg_l2i_1p1: ldo2 { + regulator-name = "vreg_l2i_1p1"; + regulator-min-microvolt = <1100000>; + regulator-max-microvolt = <1300000>; + regulator-initial-mode = ; + }; + + vreg_l3i_2p4: ldo3 { + regulator-name = "vreg_l3i_2p4"; + regulator-min-microvolt = <2400000>; + regulator-max-microvolt = <3300000>; + regulator-initial-mode = ; + }; + }; + + regulators-6 { + compatible = "qcom,kai_mv-rpmh-regulators"; + qcom,pmic-id = "K_E0"; + + vreg_l1k_0p87: ldo1 { + regulator-name = "vreg_l1k_0p87"; + regulator-min-microvolt = <870000>; + regulator-max-microvolt = <970000>; + regulator-initial-mode = ; + }; + + vreg_l2k_0p83: ldo2 { + regulator-name = "vreg_l2k_0p83"; + regulator-min-microvolt = <830000>; + regulator-max-microvolt = <920000>; + regulator-initial-mode = ; + }; + + vreg_l3k_1p8: ldo3 { + regulator-name = "vreg_l3k_1p8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1950000>; + regulator-initial-mode = ; + }; + }; + + regulators-7 { + compatible = "qcom,kai_mv-rpmh-regulators"; + qcom,pmic-id = "L_E0"; + + vreg_l3l_1p8: ldo3 { + regulator-name = "vreg_l3l_1p8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1980000>; + regulator-initial-mode = ; + }; + }; +}; + &i2c7 { clock-frequency = <400000>; status = "okay";