xref: /linux/drivers/iommu/iommu-debug-pagealloc.c (revision a8258ffed2efdf533bdc756141eeb7bc5301ad4f)
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