xref: /qemu/hw/virtio/vhost-iova-tree.c (revision f07a5674cf97b8473e5d06d7b1df9b51e97d553f)
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