The SWIOTLB allocator relies on two runtime invariants across all pool initialization paths: 1. pool->nareas must always be a power of two so that a slot's area can be located efficiently via bitwise masking (index & (nareas - 1)) instead of integer division. 2. pool->nslabs must be a multiple of nareas * IO_TLB_SEGSIZE so that each area contains an integer multiple of IO_TLB_SEGSIZE (default 128) slots, preventing contiguous allocations from crossing area boundaries. Enforce these invariants consistently during early boot, pool initialization (swiotlb_init_io_tlb_pool), and restricted DMA pool setup. Fixes: 8ac04063354a ("swiotlb: reduce the number of areas to match actual memory pool size") Signed-off-by: Luigi Rizzo --- kernel/dma/swiotlb.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/kernel/dma/swiotlb.c b/kernel/dma/swiotlb.c index 1abd3e6146f45..8e4bd9d47735a 100644 --- a/kernel/dma/swiotlb.c +++ b/kernel/dma/swiotlb.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -176,7 +177,7 @@ static void swiotlb_adjust_nareas(unsigned int nareas) static unsigned int limit_nareas(unsigned int nareas, unsigned long nslots) { if (nslots < nareas * IO_TLB_SEGSIZE) - return nslots / IO_TLB_SEGSIZE; + return rounddown_pow_of_two(nslots / IO_TLB_SEGSIZE); return nareas; } @@ -269,7 +270,16 @@ static void swiotlb_init_io_tlb_pool(struct io_tlb_pool *mem, phys_addr_t start, unsigned long nslabs, bool late_alloc, unsigned int nareas) { void *vaddr = phys_to_virt(start); - unsigned long bytes = nslabs << IO_TLB_SHIFT, i; + unsigned long bytes, i; + + /* + * If we have multiple areas, ensure each area's size is a multiple of + * IO_TLB_SEGSIZE slots by aligning the total pool size down. + */ + if (nareas > 1) + nslabs = ALIGN_DOWN(nslabs, nareas * IO_TLB_SEGSIZE); + + bytes = nslabs << IO_TLB_SHIFT; mem->nslabs = nslabs; mem->start = start; @@ -1813,7 +1823,10 @@ static int rmem_swiotlb_device_init(struct reserved_mem *rmem, struct device *dev) { struct io_tlb_mem *mem = rmem->priv; - unsigned long nslabs = rmem->size >> IO_TLB_SHIFT; + unsigned long nslabs = round_down(rmem->size >> IO_TLB_SHIFT, IO_TLB_SEGSIZE); + + if (!nslabs) + return -EINVAL; /* Set Per-device io tlb area to one */ unsigned int nareas = 1; -- 2.55.0.766.g2966f0265a-goog