1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
|
/*
* vhost software live migration iova tree
*
* SPDX-FileCopyrightText: Red Hat, Inc. 2021
* SPDX-FileContributor: Author: Eugenio Pérez <eperezma@redhat.com>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "qemu/osdep.h"
#include "qemu/iova-tree.h"
#include "vhost-iova-tree.h"
#define iova_min_addr qemu_real_host_page_size()
/**
* VhostIOVATree, able to:
* - Translate iova address
* - Reverse translate iova address (from translated to iova)
* - Allocate IOVA regions for translated range (linear operation)
*/
struct VhostIOVATree {
/* First addressable iova address in the device */
uint64_t iova_first;
/* Last addressable iova address in the device */
uint64_t iova_last;
/* IOVA address to qemu memory maps. */
IOVATree *iova_taddr_map;
/* Allocated IOVA addresses */
IOVATree *iova_map;
/* GPA->IOVA address memory maps */
IOVATree *gpa_iova_map;
};
/**
* Create a new VhostIOVATree
*
* Returns the new VhostIOVATree.
*/
VhostIOVATree *vhost_iova_tree_new(hwaddr iova_first, hwaddr iova_last)
{
VhostIOVATree *tree = g_new(VhostIOVATree, 1);
/* Some devices do not like 0 addresses */
tree->iova_first = MAX(iova_first, iova_min_addr);
tree->iova_last = iova_last;
tree->iova_taddr_map = iova_tree_new();
tree->iova_map = iova_tree_new();
tree->gpa_iova_map = gpa_tree_new();
return tree;
}
/**
* Delete a VhostIOVATree
*/
void vhost_iova_tree_delete(VhostIOVATree *iova_tree)
{
iova_tree_destroy(iova_tree->iova_taddr_map);
iova_tree_destroy(iova_tree->iova_map);
iova_tree_destroy(iova_tree->gpa_iova_map);
g_free(iova_tree);
}
/**
* Find the IOVA address stored from a memory address
*
* @tree: The VhostIOVATree
* @map: The map with the memory address
*
* Returns the stored IOVA->HVA mapping, or NULL if not found.
*/
const DMAMap *vhost_iova_tree_find_iova(const VhostIOVATree *tree,
const DMAMap *map)
{
return iova_tree_find_iova(tree->iova_taddr_map, map);
}
/**
* Allocate a new IOVA range and add the mapping to the IOVA->HVA tree
*
* @tree: The VhostIOVATree
* @map: The IOVA mapping
* @taddr: The translated address (HVA)
*
* Returns:
* - IOVA_OK if the map fits in the container
* - IOVA_ERR_INVALID if the map does not make sense (like size overflow)
* - IOVA_ERR_NOMEM if tree cannot allocate more space.
*
* It returns an assigned IOVA in map->iova if the return value is IOVA_OK.
*/
int vhost_iova_tree_map_alloc(VhostIOVATree *tree, DMAMap *map, hwaddr taddr)
{
int ret;
/* Some vhost devices do not like addr 0. Skip first page */
hwaddr iova_first = tree->iova_first ?: qemu_real_host_page_size();
if (taddr + map->size < taddr || map->perm == IOMMU_NONE) {
return IOVA_ERR_INVALID;
}
/* Allocate a node in the IOVA-only tree */
ret = iova_tree_alloc_map(tree->iova_map, map, iova_first, tree->iova_last);
if (unlikely(ret != IOVA_OK)) {
return ret;
}
/* Insert a node in the IOVA->HVA tree */
map->translated_addr = taddr;
return iova_tree_insert(tree->iova_taddr_map, map);
}
/**
* Remove existing mappings from the IOVA-only and IOVA->HVA trees
*
* @iova_tree: The VhostIOVATree
* @map: The map to remove
*/
void vhost_iova_tree_remove(VhostIOVATree *iova_tree, DMAMap map)
{
iova_tree_remove(iova_tree->iova_taddr_map, map);
iova_tree_remove(iova_tree->iova_map, map);
}
/**
* Find the IOVA address stored from a guest memory address (GPA)
*
* @tree: The VhostIOVATree
* @map: The map with the guest memory address
*
* Returns the stored GPA->IOVA mapping, or NULL if not found.
*/
const DMAMap *vhost_iova_tree_find_gpa(const VhostIOVATree *tree,
const DMAMap *map)
{
return iova_tree_find_iova(tree->gpa_iova_map, map);
}
/**
* Allocate a new IOVA range and add the mapping to the GPA->IOVA tree
*
* @tree: The VhostIOVATree
* @map: The IOVA mapping
* @taddr: The translated address (GPA)
*
* Returns:
* - IOVA_OK if the map fits both containers
* - IOVA_ERR_INVALID if the map does not make sense (like size overflow)
* - IOVA_ERR_NOMEM if the IOVA-only tree cannot allocate more space
*
* It returns an assigned IOVA in map->iova if the return value is IOVA_OK.
*/
int vhost_iova_tree_map_alloc_gpa(VhostIOVATree *tree, DMAMap *map, hwaddr taddr)
{
int ret;
/* Some vhost devices don't like addr 0. Skip first page */
hwaddr iova_first = tree->iova_first ?: qemu_real_host_page_size();
if (taddr + map->size < taddr || map->perm == IOMMU_NONE) {
return IOVA_ERR_INVALID;
}
/* Allocate a node in the IOVA-only tree */
ret = iova_tree_alloc_map(tree->iova_map, map, iova_first, tree->iova_last);
if (unlikely(ret != IOVA_OK)) {
return ret;
}
/* Insert a node in the GPA->IOVA tree */
map->translated_addr = taddr;
return gpa_tree_insert(tree->gpa_iova_map, map);
}
/**
* Remove existing mappings from the IOVA-only and GPA->IOVA trees
*
* @tree: The VhostIOVATree
* @map: The map to remove
*/
void vhost_iova_tree_remove_gpa(VhostIOVATree *iova_tree, DMAMap map)
{
iova_tree_remove(iova_tree->gpa_iova_map, map);
iova_tree_remove(iova_tree->iova_map, map);
}
|