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> 12a8258ffeSMostafa 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 iommu_debug_metadata *get_iommu_data(struct page_ext *page_ext) 347e845937SMostafa Saleh { 357e845937SMostafa Saleh return page_ext_data(page_ext, &page_iommu_debug_ops); 367e845937SMostafa Saleh } 377e845937SMostafa Saleh 387e845937SMostafa Saleh static void iommu_debug_inc_page(phys_addr_t phys) 397e845937SMostafa Saleh { 40*a7f1bc23SMostafa Saleh struct page_ext *page_ext = page_ext_from_phys(phys); 41*a7f1bc23SMostafa Saleh struct iommu_debug_metadata *d; 427e845937SMostafa Saleh 43*a7f1bc23SMostafa Saleh if (!page_ext) 44*a7f1bc23SMostafa Saleh return; 45*a7f1bc23SMostafa Saleh 46*a7f1bc23SMostafa Saleh d = get_iommu_data(page_ext); 477e845937SMostafa Saleh WARN_ON(atomic_inc_return_relaxed(&d->ref) <= 0); 487e845937SMostafa Saleh page_ext_put(page_ext); 497e845937SMostafa Saleh } 507e845937SMostafa Saleh 517e845937SMostafa Saleh static void iommu_debug_dec_page(phys_addr_t phys) 527e845937SMostafa Saleh { 53*a7f1bc23SMostafa Saleh struct page_ext *page_ext = page_ext_from_phys(phys); 54*a7f1bc23SMostafa Saleh struct iommu_debug_metadata *d; 557e845937SMostafa Saleh 56*a7f1bc23SMostafa Saleh if (!page_ext) 57*a7f1bc23SMostafa Saleh return; 58*a7f1bc23SMostafa Saleh 59*a7f1bc23SMostafa Saleh d = get_iommu_data(page_ext); 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 77a8258ffeSMostafa Saleh static bool iommu_debug_page_count(const struct page *page) 78a8258ffeSMostafa Saleh { 79a8258ffeSMostafa Saleh unsigned int ref; 80a8258ffeSMostafa Saleh struct page_ext *page_ext = page_ext_get(page); 81a8258ffeSMostafa Saleh struct iommu_debug_metadata *d = get_iommu_data(page_ext); 82a8258ffeSMostafa Saleh 83a8258ffeSMostafa Saleh ref = atomic_read(&d->ref); 84a8258ffeSMostafa Saleh page_ext_put(page_ext); 85a8258ffeSMostafa Saleh return ref != 0; 86a8258ffeSMostafa Saleh } 87a8258ffeSMostafa Saleh 88a8258ffeSMostafa Saleh void __iommu_debug_check_unmapped(const struct page *page, int numpages) 89a8258ffeSMostafa Saleh { 90a8258ffeSMostafa Saleh while (numpages--) { 91a8258ffeSMostafa Saleh if (WARN_ON(iommu_debug_page_count(page))) { 92a8258ffeSMostafa Saleh pr_warn("iommu: Detected page leak!\n"); 93a8258ffeSMostafa Saleh dump_page_owner(page); 94a8258ffeSMostafa Saleh } 95a8258ffeSMostafa Saleh page++; 96a8258ffeSMostafa Saleh } 97a8258ffeSMostafa Saleh } 98a8258ffeSMostafa 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 107*a7f1bc23SMostafa Saleh for (off = 0 ; off < size ; off += page_size) 1087e845937SMostafa Saleh iommu_debug_inc_page(phys + off); 1097e845937SMostafa Saleh } 1107e845937SMostafa Saleh 1117e845937SMostafa Saleh static void __iommu_debug_update_iova(struct iommu_domain *domain, 1127e845937SMostafa Saleh unsigned long iova, size_t size, bool inc) 1137e845937SMostafa Saleh { 1147e845937SMostafa Saleh size_t off, end; 1157e845937SMostafa Saleh size_t page_size = iommu_debug_page_size(domain); 1167e845937SMostafa Saleh 1177e845937SMostafa Saleh if (WARN_ON(check_add_overflow(iova, size, &end))) 1187e845937SMostafa Saleh return; 1197e845937SMostafa Saleh 1207e845937SMostafa Saleh for (off = 0 ; off < size ; off += page_size) { 1217e845937SMostafa Saleh phys_addr_t phys = iommu_iova_to_phys(domain, iova + off); 1227e845937SMostafa Saleh 123*a7f1bc23SMostafa Saleh if (!phys) 1247e845937SMostafa Saleh continue; 1257e845937SMostafa Saleh 1267e845937SMostafa Saleh if (inc) 1277e845937SMostafa Saleh iommu_debug_inc_page(phys); 1287e845937SMostafa Saleh else 1297e845937SMostafa Saleh iommu_debug_dec_page(phys); 1307e845937SMostafa Saleh } 131ccc21213SMostafa Saleh } 132ccc21213SMostafa Saleh 133ccc21213SMostafa Saleh void __iommu_debug_unmap_begin(struct iommu_domain *domain, 134ccc21213SMostafa Saleh unsigned long iova, size_t size) 135ccc21213SMostafa Saleh { 1367e845937SMostafa Saleh __iommu_debug_update_iova(domain, iova, size, false); 137ccc21213SMostafa Saleh } 138ccc21213SMostafa Saleh 139ccc21213SMostafa Saleh void __iommu_debug_unmap_end(struct iommu_domain *domain, 140ccc21213SMostafa Saleh unsigned long iova, size_t size, 141ccc21213SMostafa Saleh size_t unmapped) 142ccc21213SMostafa Saleh { 1437e845937SMostafa Saleh if ((unmapped == size) || WARN_ON_ONCE(unmapped > size)) 1447e845937SMostafa Saleh return; 1457e845937SMostafa Saleh 1467e845937SMostafa Saleh /* If unmap failed, re-increment the refcount. */ 1477e845937SMostafa Saleh __iommu_debug_update_iova(domain, iova + unmapped, 1487e845937SMostafa Saleh size - unmapped, true); 149ccc21213SMostafa Saleh } 150ccc21213SMostafa Saleh 151ccc21213SMostafa Saleh void iommu_debug_init(void) 152ccc21213SMostafa Saleh { 153ccc21213SMostafa Saleh if (!needed) 154ccc21213SMostafa Saleh return; 155ccc21213SMostafa Saleh 156ccc21213SMostafa Saleh pr_info("iommu: Debugging page allocations, expect overhead or disable iommu.debug_pagealloc"); 157ccc21213SMostafa Saleh static_branch_enable(&iommu_debug_initialized); 158ccc21213SMostafa Saleh } 159ccc21213SMostafa Saleh 160466ae697SMostafa Saleh static int __init iommu_debug_pagealloc(char *str) 161466ae697SMostafa Saleh { 162466ae697SMostafa Saleh return kstrtobool(str, &needed); 163466ae697SMostafa Saleh } 164466ae697SMostafa Saleh early_param("iommu.debug_pagealloc", iommu_debug_pagealloc); 165