From fdba62342c3dcd5b965ce42c018e2ced033ddcd8 Mon Sep 17 00:00:00 2001 From: Suren Baghdasaryan Date: Mon, 27 Apr 2026 23:26:25 +0000 Subject: [PATCH] Reapply "ANDROID: arm64/mm: Add command line option to make ZONE_DMA32 empty" This reverts commit 98de91adb1ce171425446a0b84d721870a04b669. Bug: 372371001 Change-Id: I94d473d8f430a2f0364d30105f9ee3e78184686f Signed-off-by: Suren Baghdasaryan --- .../admin-guide/kernel-parameters.txt | 4 +++ arch/arm64/mm/init.c | 27 ++++++++++++++++--- arch/x86/mm/init.c | 20 +++++++++++++- include/linux/dma-direct.h | 27 +++++++++++++++++++ kernel/dma/direct.c | 6 +++-- kernel/dma/pool.c | 8 +++--- 6 files changed, 82 insertions(+), 10 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 77270537be96..0f8f3159fc43 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -1302,6 +1302,10 @@ Kernel parameters can be useful when debugging issues that require an SLB miss to occur. + disable_dma32= [KNL] + Dynamically disable ZONE_DMA32 on kernels compiled with + CONFIG_ZONE_DMA32=y. + disable= [IPV6] See Documentation/networking/ipv6.rst. diff --git a/arch/arm64/mm/init.c b/arch/arm64/mm/init.c index 96711b8578fd..51b602e60e6c 100644 --- a/arch/arm64/mm/init.c +++ b/arch/arm64/mm/init.c @@ -67,6 +67,12 @@ EXPORT_SYMBOL(memstart_addr); */ phys_addr_t __ro_after_init arm64_dma_phys_limit; +/* + * Provide a run-time mean of disabling ZONE_DMA32 if it is enabled via + * CONFIG_ZONE_DMA32. + */ +static bool disable_dma32 __ro_after_init; + /* * To make optimal use of block mappings when laying out the linear * mapping, round down the base of physical memory to a size that can @@ -127,7 +133,8 @@ void __init arch_zone_limits_init(unsigned long *max_zone_pfns) max_zone_pfns[ZONE_DMA] = PFN_DOWN(max_zone_phys(zone_dma_limit)); #endif #ifdef CONFIG_ZONE_DMA32 - max_zone_pfns[ZONE_DMA32] = PFN_DOWN(dma32_phys_limit); + if (!disable_dma32) + max_zone_pfns[ZONE_DMA32] = PFN_DOWN(dma32_phys_limit); #endif max_zone_pfns[ZONE_NORMAL] = max_pfn; } @@ -154,13 +161,27 @@ static void __init dma_limits_init(void) arm64_dma_phys_limit = max_zone_phys(zone_dma_limit); #endif #ifdef CONFIG_ZONE_DMA32 - if (!arm64_dma_phys_limit) - arm64_dma_phys_limit = dma32_phys_limit; + if (!disable_dma32) { + if (!arm64_dma_phys_limit) + arm64_dma_phys_limit = dma32_phys_limit; + } #endif if (!arm64_dma_phys_limit) arm64_dma_phys_limit = PHYS_MASK + 1; } +static int __init early_disable_dma32(char *buf) +{ + if (!buf) + return -EINVAL; + + if (!strcmp(buf, "on")) + disable_dma32 = true; + + return 0; +} +early_param("disable_dma32", early_disable_dma32); + int pfn_is_map_memory(unsigned long pfn) { phys_addr_t addr = PFN_PHYS(pfn); diff --git a/arch/x86/mm/init.c b/arch/x86/mm/init.c index fb67217fddcd..1d320f358341 100644 --- a/arch/x86/mm/init.c +++ b/arch/x86/mm/init.c @@ -114,6 +114,12 @@ static unsigned long min_pfn_mapped; static bool __initdata can_use_brk_pgt = true; +/* + * Provide a run-time mean of disabling ZONE_DMA32 if it is enabled via + * CONFIG_ZONE_DMA32. + */ +static bool disable_dma32 __ro_after_init; + /* * Pages returned are already directly mapped. * @@ -1002,7 +1008,7 @@ void __init arch_zone_limits_init(unsigned long *max_zone_pfns) max_zone_pfns[ZONE_DMA] = min(MAX_DMA_PFN, max_low_pfn); #endif #ifdef CONFIG_ZONE_DMA32 - max_zone_pfns[ZONE_DMA32] = min(MAX_DMA32_PFN, max_low_pfn); + max_zone_pfns[ZONE_DMA32] = disable_dma32 ? 0 : min(MAX_DMA32_PFN, max_low_pfn); #endif max_zone_pfns[ZONE_NORMAL] = max_low_pfn; #ifdef CONFIG_HIGHMEM @@ -1010,6 +1016,18 @@ void __init arch_zone_limits_init(unsigned long *max_zone_pfns) #endif } +static int __init early_disable_dma32(char *buf) +{ + if (!buf) + return -EINVAL; + + if (!strcmp(buf, "on")) + disable_dma32 = true; + + return 0; +} +early_param("disable_dma32", early_disable_dma32); + __visible DEFINE_PER_CPU_ALIGNED(struct tlb_state, cpu_tlbstate) = { .loaded_mm = &init_mm, .next_asid = 1, diff --git a/include/linux/dma-direct.h b/include/linux/dma-direct.h index c249912456f9..6263d6f2fddc 100644 --- a/include/linux/dma-direct.h +++ b/include/linux/dma-direct.h @@ -23,6 +23,33 @@ struct bus_dma_region { u64 size; }; +static inline bool zone_dma32_is_empty(int node) +{ +#ifdef CONFIG_ZONE_DMA32 + pg_data_t *pgdat = NODE_DATA(node); + + return zone_is_empty(&pgdat->node_zones[ZONE_DMA32]); +#else + return true; +#endif +} + +static inline bool zone_dma32_are_empty(void) +{ +#ifdef CONFIG_NUMA + int node; + + for_each_node(node) + if (!zone_dma32_is_empty(node)) + return false; +#else + if (!zone_dma32_is_empty(numa_node_id())) + return false; +#endif + + return true; +} + static inline dma_addr_t translate_phys_to_dma(struct device *dev, phys_addr_t paddr) { diff --git a/kernel/dma/direct.c b/kernel/dma/direct.c index 8f43a930716d..a52dbb501754 100644 --- a/kernel/dma/direct.c +++ b/kernel/dma/direct.c @@ -62,7 +62,8 @@ static gfp_t dma_direct_optimal_gfp_mask(struct device *dev, u64 *phys_limit) *phys_limit = dma_to_phys(dev, dma_limit); if (*phys_limit <= zone_dma_limit) return GFP_DMA; - if (*phys_limit <= DMA_BIT_MASK(32)) + if (*phys_limit <= DMA_BIT_MASK(32) && + !zone_dma32_are_empty()) return GFP_DMA32; return 0; } @@ -144,7 +145,8 @@ static struct page *__dma_direct_alloc_pages(struct device *dev, size_t size, if (IS_ENABLED(CONFIG_ZONE_DMA32) && phys_limit < DMA_BIT_MASK(64) && - !(gfp & (GFP_DMA32 | GFP_DMA))) + !(gfp & (GFP_DMA32 | GFP_DMA)) && + !zone_dma32_are_empty()) gfp |= GFP_DMA32; else if (IS_ENABLED(CONFIG_ZONE_DMA) && !(gfp & GFP_DMA)) gfp = (gfp & ~GFP_DMA32) | GFP_DMA; diff --git a/kernel/dma/pool.c b/kernel/dma/pool.c index 2b2fbb709242..50b31758e310 100644 --- a/kernel/dma/pool.c +++ b/kernel/dma/pool.c @@ -71,7 +71,7 @@ static bool cma_in_zone(gfp_t gfp) end = cma_get_base(cma) + size - 1; if (IS_ENABLED(CONFIG_ZONE_DMA) && (gfp & GFP_DMA)) return end <= zone_dma_limit; - if (IS_ENABLED(CONFIG_ZONE_DMA32) && (gfp & GFP_DMA32)) + if (IS_ENABLED(CONFIG_ZONE_DMA32) && (gfp & GFP_DMA32) && !zone_dma32_are_empty()) return end <= max(DMA_BIT_MASK(32), zone_dma_limit); return true; } @@ -153,7 +153,7 @@ static void atomic_pool_work_fn(struct work_struct *work) if (IS_ENABLED(CONFIG_ZONE_DMA)) atomic_pool_resize(atomic_pool_dma, GFP_KERNEL | GFP_DMA); - if (IS_ENABLED(CONFIG_ZONE_DMA32)) + if (IS_ENABLED(CONFIG_ZONE_DMA32) && !zone_dma32_are_empty()) atomic_pool_resize(atomic_pool_dma32, GFP_KERNEL | GFP_DMA32); atomic_pool_resize(atomic_pool_kernel, GFP_KERNEL); @@ -218,7 +218,7 @@ static int __init dma_atomic_pool_init(void) if (!atomic_pool_dma) ret = -ENOMEM; } - if (has_managed_dma32) { + if (has_managed_dma32 && !zone_dma32_are_empty()) { atomic_pool_dma32 = __dma_atomic_pool_init(atomic_pool_size, GFP_KERNEL | GFP_DMA32); if (!atomic_pool_dma32) @@ -235,7 +235,7 @@ static inline struct gen_pool *dma_guess_pool(struct gen_pool *prev, gfp_t gfp) if (prev == NULL) { if (gfp & GFP_DMA) return atomic_pool_dma ?: atomic_pool_dma32 ?: atomic_pool_kernel; - if (gfp & GFP_DMA32) + if ((gfp & GFP_DMA32) && !zone_dma32_are_empty()) return atomic_pool_dma32 ?: atomic_pool_dma ?: atomic_pool_kernel; return atomic_pool_kernel ?: atomic_pool_dma32 ?: atomic_pool_dma; }