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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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