/*
 * vhost software live migration iova tree
 *
 * SPDX-FileCopyrightText: Red Hat, Inc. 2021
 * SPDX-FileContributor: Author: Eugenio Pérez <eperezma@redhat.com>
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "qemu/osdep.h"
#include "qemu/iova-tree.h"
#include "vhost-iova-tree.h"

#define iova_min_addr qemu_real_host_page_size()

/**
 * VhostIOVATree, able to:
 * - Translate iova address
 * - Reverse translate iova address (from translated to iova)
 * - Allocate IOVA regions for translated range (linear operation)
 */
struct VhostIOVATree {
    /* First addressable iova address in the device */
    uint64_t iova_first;

    /* Last addressable iova address in the device */
    uint64_t iova_last;

    /* IOVA address to qemu memory maps. */
    IOVATree *iova_taddr_map;
};

/**
 * Create a new IOVA tree
 *
 * Returns the new IOVA tree
 */
VhostIOVATree *vhost_iova_tree_new(hwaddr iova_first, hwaddr iova_last)
{
    VhostIOVATree *tree = g_new(VhostIOVATree, 1);

    /* Some devices do not like 0 addresses */
    tree->iova_first = MAX(iova_first, iova_min_addr);
    tree->iova_last = iova_last;

    tree->iova_taddr_map = iova_tree_new();
    return tree;
}

/**
 * Delete an iova tree
 */
void vhost_iova_tree_delete(VhostIOVATree *iova_tree)
{
    iova_tree_destroy(iova_tree->iova_taddr_map);
    g_free(iova_tree);
}

/**
 * Find the IOVA address stored from a memory address
 *
 * @tree: The iova tree
 * @map: The map with the memory address
 *
 * Return the stored mapping, or NULL if not found.
 */
const DMAMap *vhost_iova_tree_find_iova(const VhostIOVATree *tree,
                                        const DMAMap *map)
{
    return iova_tree_find_iova(tree->iova_taddr_map, map);
}

/**
 * Allocate a new mapping
 *
 * @tree: The iova tree
 * @map: The iova map
 *
 * Returns:
 * - IOVA_OK if the map fits in the container
 * - IOVA_ERR_INVALID if the map does not make sense (like size overflow)
 * - IOVA_ERR_NOMEM if tree cannot allocate more space.
 *
 * It returns assignated iova in map->iova if return value is VHOST_DMA_MAP_OK.
 */
int vhost_iova_tree_map_alloc(VhostIOVATree *tree, DMAMap *map)
{
    /* Some vhost devices do not like addr 0. Skip first page */
    hwaddr iova_first = tree->iova_first ?: qemu_real_host_page_size();

    if (map->translated_addr + map->size < map->translated_addr ||
        map->perm == IOMMU_NONE) {
        return IOVA_ERR_INVALID;
    }

    /* Allocate a node in IOVA address */
    return iova_tree_alloc_map(tree->iova_taddr_map, map, iova_first,
                               tree->iova_last);
}

/**
 * Remove existing mappings from iova tree
 *
 * @iova_tree: The vhost iova tree
 * @map: The map to remove
 */
void vhost_iova_tree_remove(VhostIOVATree *iova_tree, DMAMap map)
{
    iova_tree_remove(iova_tree->iova_taddr_map, map);
}
