1ec6122d8SEugenio Pérez /*
2ec6122d8SEugenio Pérez * vhost software live migration iova tree
3ec6122d8SEugenio Pérez *
4ec6122d8SEugenio Pérez * SPDX-FileCopyrightText: Red Hat, Inc. 2021
5ec6122d8SEugenio Pérez * SPDX-FileContributor: Author: Eugenio Pérez <eperezma@redhat.com>
6ec6122d8SEugenio Pérez *
7ec6122d8SEugenio Pérez * SPDX-License-Identifier: GPL-2.0-or-later
8ec6122d8SEugenio Pérez */
9ec6122d8SEugenio Pérez
10ec6122d8SEugenio Pérez #include "qemu/osdep.h"
11ec6122d8SEugenio Pérez #include "qemu/iova-tree.h"
12ec6122d8SEugenio Pérez #include "vhost-iova-tree.h"
13ec6122d8SEugenio Pérez
148e3b0cbbSMarc-André Lureau #define iova_min_addr qemu_real_host_page_size()
15ec6122d8SEugenio Pérez
16ec6122d8SEugenio Pérez /**
17ec6122d8SEugenio Pérez * VhostIOVATree, able to:
18ec6122d8SEugenio Pérez * - Translate iova address
19ec6122d8SEugenio Pérez * - Reverse translate iova address (from translated to iova)
20ec6122d8SEugenio Pérez * - Allocate IOVA regions for translated range (linear operation)
21ec6122d8SEugenio Pérez */
22ec6122d8SEugenio Pérez struct VhostIOVATree {
23ec6122d8SEugenio Pérez /* First addressable iova address in the device */
24ec6122d8SEugenio Pérez uint64_t iova_first;
25ec6122d8SEugenio Pérez
26ec6122d8SEugenio Pérez /* Last addressable iova address in the device */
27ec6122d8SEugenio Pérez uint64_t iova_last;
28ec6122d8SEugenio Pérez
29ec6122d8SEugenio Pérez /* IOVA address to qemu memory maps. */
30ec6122d8SEugenio Pérez IOVATree *iova_taddr_map;
3192cf61e7SJonah Palmer
3292cf61e7SJonah Palmer /* Allocated IOVA addresses */
3392cf61e7SJonah Palmer IOVATree *iova_map;
3405063f55SJonah Palmer
3505063f55SJonah Palmer /* GPA->IOVA address memory maps */
3605063f55SJonah Palmer IOVATree *gpa_iova_map;
37ec6122d8SEugenio Pérez };
38ec6122d8SEugenio Pérez
39ec6122d8SEugenio Pérez /**
40*332859ddSJonah Palmer * Create a new VhostIOVATree
41ec6122d8SEugenio Pérez *
42*332859ddSJonah Palmer * Returns the new VhostIOVATree.
43ec6122d8SEugenio Pérez */
vhost_iova_tree_new(hwaddr iova_first,hwaddr iova_last)44ec6122d8SEugenio Pérez VhostIOVATree *vhost_iova_tree_new(hwaddr iova_first, hwaddr iova_last)
45ec6122d8SEugenio Pérez {
46ec6122d8SEugenio Pérez VhostIOVATree *tree = g_new(VhostIOVATree, 1);
47ec6122d8SEugenio Pérez
48ec6122d8SEugenio Pérez /* Some devices do not like 0 addresses */
49ec6122d8SEugenio Pérez tree->iova_first = MAX(iova_first, iova_min_addr);
50ec6122d8SEugenio Pérez tree->iova_last = iova_last;
51ec6122d8SEugenio Pérez
52ec6122d8SEugenio Pérez tree->iova_taddr_map = iova_tree_new();
5392cf61e7SJonah Palmer tree->iova_map = iova_tree_new();
5405063f55SJonah Palmer tree->gpa_iova_map = gpa_tree_new();
55ec6122d8SEugenio Pérez return tree;
56ec6122d8SEugenio Pérez }
57ec6122d8SEugenio Pérez
58ec6122d8SEugenio Pérez /**
59*332859ddSJonah Palmer * Delete a VhostIOVATree
60ec6122d8SEugenio Pérez */
vhost_iova_tree_delete(VhostIOVATree * iova_tree)61ec6122d8SEugenio Pérez void vhost_iova_tree_delete(VhostIOVATree *iova_tree)
62ec6122d8SEugenio Pérez {
63ec6122d8SEugenio Pérez iova_tree_destroy(iova_tree->iova_taddr_map);
6492cf61e7SJonah Palmer iova_tree_destroy(iova_tree->iova_map);
6505063f55SJonah Palmer iova_tree_destroy(iova_tree->gpa_iova_map);
66ec6122d8SEugenio Pérez g_free(iova_tree);
67ec6122d8SEugenio Pérez }
68ec6122d8SEugenio Pérez
69ec6122d8SEugenio Pérez /**
70ec6122d8SEugenio Pérez * Find the IOVA address stored from a memory address
71ec6122d8SEugenio Pérez *
72*332859ddSJonah Palmer * @tree: The VhostIOVATree
73ec6122d8SEugenio Pérez * @map: The map with the memory address
74ec6122d8SEugenio Pérez *
75*332859ddSJonah Palmer * Returns the stored IOVA->HVA mapping, or NULL if not found.
76ec6122d8SEugenio Pérez */
vhost_iova_tree_find_iova(const VhostIOVATree * tree,const DMAMap * map)77ec6122d8SEugenio Pérez const DMAMap *vhost_iova_tree_find_iova(const VhostIOVATree *tree,
78ec6122d8SEugenio Pérez const DMAMap *map)
79ec6122d8SEugenio Pérez {
80ec6122d8SEugenio Pérez return iova_tree_find_iova(tree->iova_taddr_map, map);
81ec6122d8SEugenio Pérez }
82ec6122d8SEugenio Pérez
83ec6122d8SEugenio Pérez /**
84*332859ddSJonah Palmer * Allocate a new IOVA range and add the mapping to the IOVA->HVA tree
85ec6122d8SEugenio Pérez *
86*332859ddSJonah Palmer * @tree: The VhostIOVATree
87*332859ddSJonah Palmer * @map: The IOVA mapping
8892cf61e7SJonah Palmer * @taddr: The translated address (HVA)
89ec6122d8SEugenio Pérez *
90ec6122d8SEugenio Pérez * Returns:
91ec6122d8SEugenio Pérez * - IOVA_OK if the map fits in the container
92ec6122d8SEugenio Pérez * - IOVA_ERR_INVALID if the map does not make sense (like size overflow)
93ec6122d8SEugenio Pérez * - IOVA_ERR_NOMEM if tree cannot allocate more space.
94ec6122d8SEugenio Pérez *
95*332859ddSJonah Palmer * It returns an assigned IOVA in map->iova if the return value is IOVA_OK.
96ec6122d8SEugenio Pérez */
vhost_iova_tree_map_alloc(VhostIOVATree * tree,DMAMap * map,hwaddr taddr)9792cf61e7SJonah Palmer int vhost_iova_tree_map_alloc(VhostIOVATree *tree, DMAMap *map, hwaddr taddr)
98ec6122d8SEugenio Pérez {
9992cf61e7SJonah Palmer int ret;
10092cf61e7SJonah Palmer
101ec6122d8SEugenio Pérez /* Some vhost devices do not like addr 0. Skip first page */
1028e3b0cbbSMarc-André Lureau hwaddr iova_first = tree->iova_first ?: qemu_real_host_page_size();
103ec6122d8SEugenio Pérez
10492cf61e7SJonah Palmer if (taddr + map->size < taddr || map->perm == IOMMU_NONE) {
105ec6122d8SEugenio Pérez return IOVA_ERR_INVALID;
106ec6122d8SEugenio Pérez }
107ec6122d8SEugenio Pérez
10892cf61e7SJonah Palmer /* Allocate a node in the IOVA-only tree */
10992cf61e7SJonah Palmer ret = iova_tree_alloc_map(tree->iova_map, map, iova_first, tree->iova_last);
11092cf61e7SJonah Palmer if (unlikely(ret != IOVA_OK)) {
11192cf61e7SJonah Palmer return ret;
11292cf61e7SJonah Palmer }
11392cf61e7SJonah Palmer
11492cf61e7SJonah Palmer /* Insert a node in the IOVA->HVA tree */
11592cf61e7SJonah Palmer map->translated_addr = taddr;
11692cf61e7SJonah Palmer return iova_tree_insert(tree->iova_taddr_map, map);
117ec6122d8SEugenio Pérez }
118ec6122d8SEugenio Pérez
119ec6122d8SEugenio Pérez /**
120*332859ddSJonah Palmer * Remove existing mappings from the IOVA-only and IOVA->HVA trees
121ec6122d8SEugenio Pérez *
122*332859ddSJonah Palmer * @iova_tree: The VhostIOVATree
123ec6122d8SEugenio Pérez * @map: The map to remove
124ec6122d8SEugenio Pérez */
vhost_iova_tree_remove(VhostIOVATree * iova_tree,DMAMap map)12569292a8eSEugenio Pérez void vhost_iova_tree_remove(VhostIOVATree *iova_tree, DMAMap map)
126ec6122d8SEugenio Pérez {
127ec6122d8SEugenio Pérez iova_tree_remove(iova_tree->iova_taddr_map, map);
12892cf61e7SJonah Palmer iova_tree_remove(iova_tree->iova_map, map);
129ec6122d8SEugenio Pérez }
13005063f55SJonah Palmer
13105063f55SJonah Palmer /**
13205063f55SJonah Palmer * Find the IOVA address stored from a guest memory address (GPA)
13305063f55SJonah Palmer *
13405063f55SJonah Palmer * @tree: The VhostIOVATree
13505063f55SJonah Palmer * @map: The map with the guest memory address
13605063f55SJonah Palmer *
13705063f55SJonah Palmer * Returns the stored GPA->IOVA mapping, or NULL if not found.
13805063f55SJonah Palmer */
vhost_iova_tree_find_gpa(const VhostIOVATree * tree,const DMAMap * map)13905063f55SJonah Palmer const DMAMap *vhost_iova_tree_find_gpa(const VhostIOVATree *tree,
14005063f55SJonah Palmer const DMAMap *map)
14105063f55SJonah Palmer {
14205063f55SJonah Palmer return iova_tree_find_iova(tree->gpa_iova_map, map);
14305063f55SJonah Palmer }
14405063f55SJonah Palmer
14505063f55SJonah Palmer /**
14605063f55SJonah Palmer * Allocate a new IOVA range and add the mapping to the GPA->IOVA tree
14705063f55SJonah Palmer *
14805063f55SJonah Palmer * @tree: The VhostIOVATree
14905063f55SJonah Palmer * @map: The IOVA mapping
15005063f55SJonah Palmer * @taddr: The translated address (GPA)
15105063f55SJonah Palmer *
15205063f55SJonah Palmer * Returns:
15305063f55SJonah Palmer * - IOVA_OK if the map fits both containers
15405063f55SJonah Palmer * - IOVA_ERR_INVALID if the map does not make sense (like size overflow)
15505063f55SJonah Palmer * - IOVA_ERR_NOMEM if the IOVA-only tree cannot allocate more space
15605063f55SJonah Palmer *
15705063f55SJonah Palmer * It returns an assigned IOVA in map->iova if the return value is IOVA_OK.
15805063f55SJonah Palmer */
vhost_iova_tree_map_alloc_gpa(VhostIOVATree * tree,DMAMap * map,hwaddr taddr)15905063f55SJonah Palmer int vhost_iova_tree_map_alloc_gpa(VhostIOVATree *tree, DMAMap *map, hwaddr taddr)
16005063f55SJonah Palmer {
16105063f55SJonah Palmer int ret;
16205063f55SJonah Palmer
16305063f55SJonah Palmer /* Some vhost devices don't like addr 0. Skip first page */
16405063f55SJonah Palmer hwaddr iova_first = tree->iova_first ?: qemu_real_host_page_size();
16505063f55SJonah Palmer
16605063f55SJonah Palmer if (taddr + map->size < taddr || map->perm == IOMMU_NONE) {
16705063f55SJonah Palmer return IOVA_ERR_INVALID;
16805063f55SJonah Palmer }
16905063f55SJonah Palmer
17005063f55SJonah Palmer /* Allocate a node in the IOVA-only tree */
17105063f55SJonah Palmer ret = iova_tree_alloc_map(tree->iova_map, map, iova_first, tree->iova_last);
17205063f55SJonah Palmer if (unlikely(ret != IOVA_OK)) {
17305063f55SJonah Palmer return ret;
17405063f55SJonah Palmer }
17505063f55SJonah Palmer
17605063f55SJonah Palmer /* Insert a node in the GPA->IOVA tree */
17705063f55SJonah Palmer map->translated_addr = taddr;
17805063f55SJonah Palmer return gpa_tree_insert(tree->gpa_iova_map, map);
17905063f55SJonah Palmer }
18005063f55SJonah Palmer
18105063f55SJonah Palmer /**
18205063f55SJonah Palmer * Remove existing mappings from the IOVA-only and GPA->IOVA trees
18305063f55SJonah Palmer *
18405063f55SJonah Palmer * @tree: The VhostIOVATree
18505063f55SJonah Palmer * @map: The map to remove
18605063f55SJonah Palmer */
vhost_iova_tree_remove_gpa(VhostIOVATree * iova_tree,DMAMap map)18705063f55SJonah Palmer void vhost_iova_tree_remove_gpa(VhostIOVATree *iova_tree, DMAMap map)
18805063f55SJonah Palmer {
18905063f55SJonah Palmer iova_tree_remove(iova_tree->gpa_iova_map, map);
19005063f55SJonah Palmer iova_tree_remove(iova_tree->iova_map, map);
19105063f55SJonah Palmer }
192