1466ae697SMostafa Saleh // SPDX-License-Identifier: GPL-2.0-only 2466ae697SMostafa Saleh /* 3466ae697SMostafa Saleh * Copyright (C) 2025 - Google Inc 4466ae697SMostafa Saleh * Author: Mostafa Saleh <smostafa@google.com> 5466ae697SMostafa Saleh * IOMMU API debug page alloc sanitizer 6466ae697SMostafa Saleh */ 7466ae697SMostafa Saleh #include <linux/atomic.h> 8ccc21213SMostafa Saleh #include <linux/iommu.h> 9466ae697SMostafa Saleh #include <linux/iommu-debug-pagealloc.h> 10466ae697SMostafa Saleh #include <linux/kernel.h> 11466ae697SMostafa Saleh #include <linux/page_ext.h> 12*a8258ffeSMostafa Saleh #include <linux/page_owner.h> 13466ae697SMostafa Saleh 14ccc21213SMostafa Saleh #include "iommu-priv.h" 15ccc21213SMostafa Saleh 16466ae697SMostafa Saleh static bool needed; 17ccc21213SMostafa Saleh DEFINE_STATIC_KEY_FALSE(iommu_debug_initialized); 18466ae697SMostafa Saleh 19466ae697SMostafa Saleh struct iommu_debug_metadata { 20466ae697SMostafa Saleh atomic_t ref; 21466ae697SMostafa Saleh }; 22466ae697SMostafa Saleh 23466ae697SMostafa Saleh static __init bool need_iommu_debug(void) 24466ae697SMostafa Saleh { 25466ae697SMostafa Saleh return needed; 26466ae697SMostafa Saleh } 27466ae697SMostafa Saleh 28466ae697SMostafa Saleh struct page_ext_operations page_iommu_debug_ops = { 29466ae697SMostafa Saleh .size = sizeof(struct iommu_debug_metadata), 30466ae697SMostafa Saleh .need = need_iommu_debug, 31466ae697SMostafa Saleh }; 32466ae697SMostafa Saleh 337e845937SMostafa Saleh static struct page_ext *get_iommu_page_ext(phys_addr_t phys) 347e845937SMostafa Saleh { 357e845937SMostafa Saleh struct page *page = phys_to_page(phys); 367e845937SMostafa Saleh struct page_ext *page_ext = page_ext_get(page); 377e845937SMostafa Saleh 387e845937SMostafa Saleh return page_ext; 397e845937SMostafa Saleh } 407e845937SMostafa Saleh 417e845937SMostafa Saleh static struct iommu_debug_metadata *get_iommu_data(struct page_ext *page_ext) 427e845937SMostafa Saleh { 437e845937SMostafa Saleh return page_ext_data(page_ext, &page_iommu_debug_ops); 447e845937SMostafa Saleh } 457e845937SMostafa Saleh 467e845937SMostafa Saleh static void iommu_debug_inc_page(phys_addr_t phys) 477e845937SMostafa Saleh { 487e845937SMostafa Saleh struct page_ext *page_ext = get_iommu_page_ext(phys); 497e845937SMostafa Saleh struct iommu_debug_metadata *d = get_iommu_data(page_ext); 507e845937SMostafa Saleh 517e845937SMostafa Saleh WARN_ON(atomic_inc_return_relaxed(&d->ref) <= 0); 527e845937SMostafa Saleh page_ext_put(page_ext); 537e845937SMostafa Saleh } 547e845937SMostafa Saleh 557e845937SMostafa Saleh static void iommu_debug_dec_page(phys_addr_t phys) 567e845937SMostafa Saleh { 577e845937SMostafa Saleh struct page_ext *page_ext = get_iommu_page_ext(phys); 587e845937SMostafa Saleh struct iommu_debug_metadata *d = get_iommu_data(page_ext); 597e845937SMostafa Saleh 607e845937SMostafa Saleh WARN_ON(atomic_dec_return_relaxed(&d->ref) < 0); 617e845937SMostafa Saleh page_ext_put(page_ext); 627e845937SMostafa Saleh } 637e845937SMostafa Saleh 647e845937SMostafa Saleh /* 657e845937SMostafa Saleh * IOMMU page size doesn't have to match the CPU page size. So, we use 667e845937SMostafa Saleh * the smallest IOMMU page size to refcount the pages in the vmemmap. 677e845937SMostafa Saleh * That is important as both map and unmap has to use the same page size 687e845937SMostafa Saleh * to update the refcount to avoid double counting the same page. 697e845937SMostafa Saleh * And as we can't know from iommu_unmap() what was the original page size 707e845937SMostafa Saleh * used for map, we just use the minimum supported one for both. 717e845937SMostafa Saleh */ 727e845937SMostafa Saleh static size_t iommu_debug_page_size(struct iommu_domain *domain) 737e845937SMostafa Saleh { 747e845937SMostafa Saleh return 1UL << __ffs(domain->pgsize_bitmap); 757e845937SMostafa Saleh } 767e845937SMostafa Saleh 77*a8258ffeSMostafa Saleh static bool iommu_debug_page_count(const struct page *page) 78*a8258ffeSMostafa Saleh { 79*a8258ffeSMostafa Saleh unsigned int ref; 80*a8258ffeSMostafa Saleh struct page_ext *page_ext = page_ext_get(page); 81*a8258ffeSMostafa Saleh struct iommu_debug_metadata *d = get_iommu_data(page_ext); 82*a8258ffeSMostafa Saleh 83*a8258ffeSMostafa Saleh ref = atomic_read(&d->ref); 84*a8258ffeSMostafa Saleh page_ext_put(page_ext); 85*a8258ffeSMostafa Saleh return ref != 0; 86*a8258ffeSMostafa Saleh } 87*a8258ffeSMostafa Saleh 88*a8258ffeSMostafa Saleh void __iommu_debug_check_unmapped(const struct page *page, int numpages) 89*a8258ffeSMostafa Saleh { 90*a8258ffeSMostafa Saleh while (numpages--) { 91*a8258ffeSMostafa Saleh if (WARN_ON(iommu_debug_page_count(page))) { 92*a8258ffeSMostafa Saleh pr_warn("iommu: Detected page leak!\n"); 93*a8258ffeSMostafa Saleh dump_page_owner(page); 94*a8258ffeSMostafa Saleh } 95*a8258ffeSMostafa Saleh page++; 96*a8258ffeSMostafa Saleh } 97*a8258ffeSMostafa Saleh } 98*a8258ffeSMostafa Saleh 99ccc21213SMostafa Saleh void __iommu_debug_map(struct iommu_domain *domain, phys_addr_t phys, size_t size) 100ccc21213SMostafa Saleh { 1017e845937SMostafa Saleh size_t off, end; 1027e845937SMostafa Saleh size_t page_size = iommu_debug_page_size(domain); 1037e845937SMostafa Saleh 1047e845937SMostafa Saleh if (WARN_ON(!phys || check_add_overflow(phys, size, &end))) 1057e845937SMostafa Saleh return; 1067e845937SMostafa Saleh 1077e845937SMostafa Saleh for (off = 0 ; off < size ; off += page_size) { 1087e845937SMostafa Saleh if (!pfn_valid(__phys_to_pfn(phys + off))) 1097e845937SMostafa Saleh continue; 1107e845937SMostafa Saleh iommu_debug_inc_page(phys + off); 1117e845937SMostafa Saleh } 1127e845937SMostafa Saleh } 1137e845937SMostafa Saleh 1147e845937SMostafa Saleh static void __iommu_debug_update_iova(struct iommu_domain *domain, 1157e845937SMostafa Saleh unsigned long iova, size_t size, bool inc) 1167e845937SMostafa Saleh { 1177e845937SMostafa Saleh size_t off, end; 1187e845937SMostafa Saleh size_t page_size = iommu_debug_page_size(domain); 1197e845937SMostafa Saleh 1207e845937SMostafa Saleh if (WARN_ON(check_add_overflow(iova, size, &end))) 1217e845937SMostafa Saleh return; 1227e845937SMostafa Saleh 1237e845937SMostafa Saleh for (off = 0 ; off < size ; off += page_size) { 1247e845937SMostafa Saleh phys_addr_t phys = iommu_iova_to_phys(domain, iova + off); 1257e845937SMostafa Saleh 1267e845937SMostafa Saleh if (!phys || !pfn_valid(__phys_to_pfn(phys))) 1277e845937SMostafa Saleh continue; 1287e845937SMostafa Saleh 1297e845937SMostafa Saleh if (inc) 1307e845937SMostafa Saleh iommu_debug_inc_page(phys); 1317e845937SMostafa Saleh else 1327e845937SMostafa Saleh iommu_debug_dec_page(phys); 1337e845937SMostafa Saleh } 134ccc21213SMostafa Saleh } 135ccc21213SMostafa Saleh 136ccc21213SMostafa Saleh void __iommu_debug_unmap_begin(struct iommu_domain *domain, 137ccc21213SMostafa Saleh unsigned long iova, size_t size) 138ccc21213SMostafa Saleh { 1397e845937SMostafa Saleh __iommu_debug_update_iova(domain, iova, size, false); 140ccc21213SMostafa Saleh } 141ccc21213SMostafa Saleh 142ccc21213SMostafa Saleh void __iommu_debug_unmap_end(struct iommu_domain *domain, 143ccc21213SMostafa Saleh unsigned long iova, size_t size, 144ccc21213SMostafa Saleh size_t unmapped) 145ccc21213SMostafa Saleh { 1467e845937SMostafa Saleh if ((unmapped == size) || WARN_ON_ONCE(unmapped > size)) 1477e845937SMostafa Saleh return; 1487e845937SMostafa Saleh 1497e845937SMostafa Saleh /* If unmap failed, re-increment the refcount. */ 1507e845937SMostafa Saleh __iommu_debug_update_iova(domain, iova + unmapped, 1517e845937SMostafa Saleh size - unmapped, true); 152ccc21213SMostafa Saleh } 153ccc21213SMostafa Saleh 154ccc21213SMostafa Saleh void iommu_debug_init(void) 155ccc21213SMostafa Saleh { 156ccc21213SMostafa Saleh if (!needed) 157ccc21213SMostafa Saleh return; 158ccc21213SMostafa Saleh 159ccc21213SMostafa Saleh pr_info("iommu: Debugging page allocations, expect overhead or disable iommu.debug_pagealloc"); 160ccc21213SMostafa Saleh static_branch_enable(&iommu_debug_initialized); 161ccc21213SMostafa Saleh } 162ccc21213SMostafa Saleh 163466ae697SMostafa Saleh static int __init iommu_debug_pagealloc(char *str) 164466ae697SMostafa Saleh { 165466ae697SMostafa Saleh return kstrtobool(str, &needed); 166466ae697SMostafa Saleh } 167466ae697SMostafa Saleh early_param("iommu.debug_pagealloc", iommu_debug_pagealloc); 168