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 */
vhost_iova_tree_new(hwaddr iova_first,hwaddr iova_last)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 */
vhost_iova_tree_delete(VhostIOVATree * iova_tree)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 */
vhost_iova_tree_find_iova(const VhostIOVATree * tree,const DMAMap * map)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 */
vhost_iova_tree_map_alloc(VhostIOVATree * tree,DMAMap * map,hwaddr taddr)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 */
vhost_iova_tree_remove(VhostIOVATree * iova_tree,DMAMap map)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 */
vhost_iova_tree_find_gpa(const VhostIOVATree * tree,const DMAMap * map)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 */
vhost_iova_tree_map_alloc_gpa(VhostIOVATree * tree,DMAMap * map,hwaddr taddr)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 */
vhost_iova_tree_remove_gpa(VhostIOVATree * iova_tree,DMAMap map)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