mirror of
https://github.com/torvalds/linux.git
synced 2026-03-08 01:04:41 +01:00
Instead of calling pfn_valid() and then getting the page, call the newly added function page_ext_get_from_phys(), which would also check for MMIO and offline memory and return NULL in that case. Signed-off-by: Mostafa Saleh <smostafa@google.com> Signed-off-by: Joerg Roedel <joerg.roedel@amd.com>
164 lines
4 KiB
C
164 lines
4 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (C) 2025 - Google Inc
|
|
* Author: Mostafa Saleh <smostafa@google.com>
|
|
* IOMMU API debug page alloc sanitizer
|
|
*/
|
|
#include <linux/atomic.h>
|
|
#include <linux/iommu.h>
|
|
#include <linux/iommu-debug-pagealloc.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/page_ext.h>
|
|
#include <linux/page_owner.h>
|
|
|
|
#include "iommu-priv.h"
|
|
|
|
static bool needed;
|
|
DEFINE_STATIC_KEY_FALSE(iommu_debug_initialized);
|
|
|
|
struct iommu_debug_metadata {
|
|
atomic_t ref;
|
|
};
|
|
|
|
static __init bool need_iommu_debug(void)
|
|
{
|
|
return needed;
|
|
}
|
|
|
|
struct page_ext_operations page_iommu_debug_ops = {
|
|
.size = sizeof(struct iommu_debug_metadata),
|
|
.need = need_iommu_debug,
|
|
};
|
|
|
|
static struct iommu_debug_metadata *get_iommu_data(struct page_ext *page_ext)
|
|
{
|
|
return page_ext_data(page_ext, &page_iommu_debug_ops);
|
|
}
|
|
|
|
static void iommu_debug_inc_page(phys_addr_t phys)
|
|
{
|
|
struct page_ext *page_ext = page_ext_from_phys(phys);
|
|
struct iommu_debug_metadata *d;
|
|
|
|
if (!page_ext)
|
|
return;
|
|
|
|
d = get_iommu_data(page_ext);
|
|
WARN_ON(atomic_inc_return_relaxed(&d->ref) <= 0);
|
|
page_ext_put(page_ext);
|
|
}
|
|
|
|
static void iommu_debug_dec_page(phys_addr_t phys)
|
|
{
|
|
struct page_ext *page_ext = page_ext_from_phys(phys);
|
|
struct iommu_debug_metadata *d;
|
|
|
|
if (!page_ext)
|
|
return;
|
|
|
|
d = get_iommu_data(page_ext);
|
|
WARN_ON(atomic_dec_return_relaxed(&d->ref) < 0);
|
|
page_ext_put(page_ext);
|
|
}
|
|
|
|
/*
|
|
* IOMMU page size doesn't have to match the CPU page size. So, we use
|
|
* the smallest IOMMU page size to refcount the pages in the vmemmap.
|
|
* That is important as both map and unmap has to use the same page size
|
|
* to update the refcount to avoid double counting the same page.
|
|
* And as we can't know from iommu_unmap() what was the original page size
|
|
* used for map, we just use the minimum supported one for both.
|
|
*/
|
|
static size_t iommu_debug_page_size(struct iommu_domain *domain)
|
|
{
|
|
return 1UL << __ffs(domain->pgsize_bitmap);
|
|
}
|
|
|
|
static bool iommu_debug_page_count(const struct page *page)
|
|
{
|
|
unsigned int ref;
|
|
struct page_ext *page_ext = page_ext_get(page);
|
|
struct iommu_debug_metadata *d = get_iommu_data(page_ext);
|
|
|
|
ref = atomic_read(&d->ref);
|
|
page_ext_put(page_ext);
|
|
return ref != 0;
|
|
}
|
|
|
|
void __iommu_debug_check_unmapped(const struct page *page, int numpages)
|
|
{
|
|
while (numpages--) {
|
|
if (WARN_ON(iommu_debug_page_count(page))) {
|
|
pr_warn("iommu: Detected page leak!\n");
|
|
dump_page_owner(page);
|
|
}
|
|
page++;
|
|
}
|
|
}
|
|
|
|
void __iommu_debug_map(struct iommu_domain *domain, phys_addr_t phys, size_t size)
|
|
{
|
|
size_t off, end;
|
|
size_t page_size = iommu_debug_page_size(domain);
|
|
|
|
if (WARN_ON(!phys || check_add_overflow(phys, size, &end)))
|
|
return;
|
|
|
|
for (off = 0 ; off < size ; off += page_size)
|
|
iommu_debug_inc_page(phys + off);
|
|
}
|
|
|
|
static void __iommu_debug_update_iova(struct iommu_domain *domain,
|
|
unsigned long iova, size_t size, bool inc)
|
|
{
|
|
size_t off, end;
|
|
size_t page_size = iommu_debug_page_size(domain);
|
|
|
|
if (WARN_ON(check_add_overflow(iova, size, &end)))
|
|
return;
|
|
|
|
for (off = 0 ; off < size ; off += page_size) {
|
|
phys_addr_t phys = iommu_iova_to_phys(domain, iova + off);
|
|
|
|
if (!phys)
|
|
continue;
|
|
|
|
if (inc)
|
|
iommu_debug_inc_page(phys);
|
|
else
|
|
iommu_debug_dec_page(phys);
|
|
}
|
|
}
|
|
|
|
void __iommu_debug_unmap_begin(struct iommu_domain *domain,
|
|
unsigned long iova, size_t size)
|
|
{
|
|
__iommu_debug_update_iova(domain, iova, size, false);
|
|
}
|
|
|
|
void __iommu_debug_unmap_end(struct iommu_domain *domain,
|
|
unsigned long iova, size_t size,
|
|
size_t unmapped)
|
|
{
|
|
if ((unmapped == size) || WARN_ON_ONCE(unmapped > size))
|
|
return;
|
|
|
|
/* If unmap failed, re-increment the refcount. */
|
|
__iommu_debug_update_iova(domain, iova + unmapped,
|
|
size - unmapped, true);
|
|
}
|
|
|
|
void iommu_debug_init(void)
|
|
{
|
|
if (!needed)
|
|
return;
|
|
|
|
pr_info("iommu: Debugging page allocations, expect overhead or disable iommu.debug_pagealloc");
|
|
static_branch_enable(&iommu_debug_initialized);
|
|
}
|
|
|
|
static int __init iommu_debug_pagealloc(char *str)
|
|
{
|
|
return kstrtobool(str, &needed);
|
|
}
|
|
early_param("iommu.debug_pagealloc", iommu_debug_pagealloc);
|