// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2021-2022, NVIDIA CORPORATION & AFFILIATES.
*
* The io_pagetable is the top of datastructure that maps IOVA's to PFNs. The
* PFNs can be placed into an iommu_domain, or returned to the caller as a page
* list for access by an in-kernel user.
*
* The datastructure uses the iopt_pages to optimize the storage of the PFNs
* between the domains and xarray.
*/
#include <linux/iommufd.h>
#include <linux/lockdep.h>
#include <linux/iommu.h>
#include <linux/sched/mm.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include "io_pagetable.h"
#include "double_span.h"
struct iopt_pages_list {
struct iopt_pages *pages;
struct iopt_area *area;
struct list_head next;
unsigned long start_byte;
unsigned long length;
};
struct iopt_area *iopt_area_contig_init(struct iopt_area_contig_iter *iter,
struct io_pagetable *iopt,
unsigned long iova,
unsigned long last_iova)
{
lockdep_assert_held(&iopt->iova_rwsem);
iter->cur_iova = iova;
iter->last_iova = last_iova;
iter->area = iopt_area_iter_first(iopt, iova, iova);
if (!iter->area)
return NULL;
if (!iter->area->pages) {
iter->area = NULL;
return NULL;
}
return iter->area;
}
struct iopt_area *iopt_area_contig_next(struct iopt_area_contig_iter *iter)
{
unsigned long last_iova;
if (!iter->area)
return NULL;
last_iova = iopt_area_last_iova(iter->area);
if (iter->last_iova <= last_iova)
return NULL;
iter->cur_iova = last_iova + 1;
iter->area = iopt_area_iter_next(iter->area, iter->cur_iova,
iter->last_iova);
if (!iter->area)
return NULL;
if (iter->cur_iova != iopt_area_iova(iter->area) ||
!iter->area->pages) {
iter->area = NULL;
return NULL;
}
return iter->area;
}
static bool __alloc_iova_check_hole(struct interval_tree_double_span_iter *span,
unsigned long length,
unsigned long iova_alignment,
unsigned long page_offset)
{
if (span->is_used || span->last_hole - span->start_hole < length - 1)
return false;
span->start_hole = ALIGN(span->start_hole, iova_alignment) |
page_offset;
if (span->start_hole > span->last_hole ||
span->last_hole - span->start_hole < length - 1)
return false;
return true;
}
static bool __alloc_iova_check_used(struct interval_tree_span_iter *span,
unsigned long length,
unsigned long iova_alignment,
unsigned long page_offset)
{
if (span->is_hole || span->last_used - span->start_used < length - 1)
return false;
span->start_used = ALIGN(span->start_used, iova_alignment) |
page_offset;
if (span->start_used > span->last_used ||
span->last_used - span->start_used < length - 1)
return false;
return true;
}
/*
* Automatically find a block of IOVA that is not being used and not reserved.
* Does not return a 0 IOVA even if it is valid.
*/
static int iopt_alloc_iova(struct io_pagetable *iopt, unsigned long *iova,