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