| 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 | } |