/*
 * domain_addr.c: helper APIs for managing domain device addresses
 *
 * Copyright (C) 2006-2016 Red Hat, Inc.
 * Copyright (C) 2006 Daniel P. Berrange
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see
 * <http://www.gnu.org/licenses/>.
 */

#include <config.h>

#include "viralloc.h"
#include "virlog.h"
#include "virstring.h"
#include "domain_addr.h"

#define VIR_FROM_THIS VIR_FROM_DOMAIN

VIR_LOG_INIT("conf.domain_addr");

static int
virDomainZPCIAddressReserveId(GHashTable *set,
                              virZPCIDeviceAddressID *id,
                              const char *name)
{
    int *idval;

    if (!id->isSet) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("No zPCI %s to reserve"),
                       name);
        return -1;
    }

    if (g_hash_table_lookup(set, &id->value)) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("zPCI %s %o is already reserved"),
                       name, id->value);
        return -1;
    }

    idval = g_new0(int, 1);
    *idval = (int) id->value;

    g_hash_table_add(set, idval);

    return 0;
}


static int
virDomainZPCIAddressReserveUid(GHashTable *set,
                               virZPCIDeviceAddressPtr addr)
{
    return virDomainZPCIAddressReserveId(set, &addr->uid, "uid");
}


static int
virDomainZPCIAddressReserveFid(GHashTable *set,
                               virZPCIDeviceAddressPtr addr)
{
    return virDomainZPCIAddressReserveId(set, &addr->fid, "fid");
}


static int
virDomainZPCIAddressAssignId(GHashTable *set,
                             virZPCIDeviceAddressID *id,
                             unsigned int min,
                             unsigned int max,
                             const char *name)
{
    if (id->isSet)
        return 0;

    while (g_hash_table_lookup(set, &min)) {
        if (min == max) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("There is no more free %s."),
                           name);
            return -1;
        }
        ++min;
    }

    id->value = min;
    id->isSet = true;

    return 0;
}


static int
virDomainZPCIAddressAssignUid(GHashTable *set,
                              virZPCIDeviceAddressPtr addr)
{
    return virDomainZPCIAddressAssignId(set, &addr->uid, 1,
                                        VIR_DOMAIN_DEVICE_ZPCI_MAX_UID, "uid");
}


static int
virDomainZPCIAddressAssignFid(GHashTable *set,
                              virZPCIDeviceAddressPtr addr)
{
    return virDomainZPCIAddressAssignId(set, &addr->fid, 0,
                                        VIR_DOMAIN_DEVICE_ZPCI_MAX_FID, "fid");
}


static void
virDomainZPCIAddressReleaseId(GHashTable *set,
                              virZPCIDeviceAddressID *id)
{
    if (!id->isSet)
        return;

    g_hash_table_remove(set, &id->value);

    id->value = 0;
    id->isSet = false;
}


static void
virDomainZPCIAddressReleaseIds(virDomainZPCIAddressIdsPtr zpciIds,
                               virZPCIDeviceAddressPtr addr)
{
    if (!zpciIds)
        return;

    virDomainZPCIAddressReleaseId(zpciIds->uids, &addr->uid);
    virDomainZPCIAddressReleaseId(zpciIds->fids, &addr->fid);
}


static int
virDomainZPCIAddressEnsureAddr(virDomainZPCIAddressIdsPtr zpciIds,
                                virZPCIDeviceAddressPtr addr)
{
    if (virDomainZPCIAddressAssignUid(zpciIds->uids, addr) < 0)
        return -1;

    if (virDomainZPCIAddressAssignFid(zpciIds->fids, addr) < 0)
        return -1;

    if (virDomainZPCIAddressReserveUid(zpciIds->uids, addr) < 0)
        return -1;

    if (virDomainZPCIAddressReserveFid(zpciIds->fids, addr) < 0) {
        virDomainZPCIAddressReleaseId(zpciIds->uids, &addr->uid);
        return -1;
    }

    return 0;
}


int
virDomainPCIAddressExtensionReserveAddr(virDomainPCIAddressSetPtr addrs,
                                        virPCIDeviceAddressPtr addr)
{
    if (addr->extFlags & VIR_PCI_ADDRESS_EXTENSION_ZPCI) {
        /* Reserve uid/fid to ZPCI device which has defined uid/fid
         * in the domain.
         */
        return virDomainZPCIAddressEnsureAddr(addrs->zpciIds, &addr->zpci);
    }

    return 0;
}


int
virDomainPCIAddressExtensionReserveNextAddr(virDomainPCIAddressSetPtr addrs,
                                            virPCIDeviceAddressPtr addr)
{
    if (addr->extFlags & VIR_PCI_ADDRESS_EXTENSION_ZPCI) {
        virZPCIDeviceAddress zpci = addr->zpci;

        if (virDomainZPCIAddressEnsureAddr(addrs->zpciIds, &zpci) < 0)
            return -1;

        if (!addrs->dryRun)
            addr->zpci = zpci;
    }

    return 0;
}


static int
virDomainPCIAddressExtensionEnsureAddr(virDomainPCIAddressSetPtr addrs,
                                       virPCIDeviceAddressPtr addr)
{
    if (addr->extFlags & VIR_PCI_ADDRESS_EXTENSION_ZPCI) {
        virZPCIDeviceAddressPtr zpci = &addr->zpci;

        if (virDomainZPCIAddressEnsureAddr(addrs->zpciIds, zpci) < 0)
            return -1;
    }

    return 0;
}


virDomainPCIConnectFlags
virDomainPCIControllerModelToConnectType(virDomainControllerModelPCI model)
{
    /* given a VIR_DOMAIN_CONTROLLER_MODEL_PCI*, return
     * the equivalent VIR_PCI_CONNECT_TYPE_*.
     */
    switch (model) {
    case VIR_DOMAIN_CONTROLLER_MODEL_PCI_DEFAULT:
    case VIR_DOMAIN_CONTROLLER_MODEL_PCI_LAST:
    case VIR_DOMAIN_CONTROLLER_MODEL_PCI_ROOT:
    case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT:
        /* pci-root and pcie-root are implicit in the machine,
         * and have no upstream connection, "last" will never actually
         * happen, it's just there so that all possible cases are
         * covered in the switch (keeps the compiler happy).
         */
        return 0;

    case VIR_DOMAIN_CONTROLLER_MODEL_PCI_BRIDGE:
        return VIR_PCI_CONNECT_TYPE_PCI_BRIDGE;

    case VIR_DOMAIN_CONTROLLER_MODEL_PCI_EXPANDER_BUS:
        return VIR_PCI_CONNECT_TYPE_PCI_EXPANDER_BUS;

    case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_EXPANDER_BUS:
        return VIR_PCI_CONNECT_TYPE_PCIE_EXPANDER_BUS;

    case VIR_DOMAIN_CONTROLLER_MODEL_DMI_TO_PCI_BRIDGE:
        return VIR_PCI_CONNECT_TYPE_DMI_TO_PCI_BRIDGE;

    case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_TO_PCI_BRIDGE:
        return VIR_PCI_CONNECT_TYPE_PCIE_TO_PCI_BRIDGE;

    case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT_PORT:
        return VIR_PCI_CONNECT_TYPE_PCIE_ROOT_PORT | VIR_PCI_CONNECT_AGGREGATE_SLOT;

    case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_SWITCH_UPSTREAM_PORT:
        return VIR_PCI_CONNECT_TYPE_PCIE_SWITCH_UPSTREAM_PORT;

    case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_SWITCH_DOWNSTREAM_PORT:
        return VIR_PCI_CONNECT_TYPE_PCIE_SWITCH_DOWNSTREAM_PORT;
    }
    return 0;
}


static int
virDomainPCIControllerConnectTypeToModel(virDomainPCIConnectFlags flags)
{
    if (flags & VIR_PCI_CONNECT_TYPE_PCIE_ROOT_PORT)
        return VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT_PORT;

    if (flags & VIR_PCI_CONNECT_TYPE_PCIE_SWITCH_UPSTREAM_PORT)
        return VIR_DOMAIN_CONTROLLER_MODEL_PCIE_SWITCH_UPSTREAM_PORT;

    if (flags & VIR_PCI_CONNECT_TYPE_PCIE_SWITCH_DOWNSTREAM_PORT)
        return VIR_DOMAIN_CONTROLLER_MODEL_PCIE_SWITCH_DOWNSTREAM_PORT;

    if (flags & VIR_PCI_CONNECT_TYPE_DMI_TO_PCI_BRIDGE)
        return VIR_DOMAIN_CONTROLLER_MODEL_DMI_TO_PCI_BRIDGE;

    if (flags & VIR_PCI_CONNECT_TYPE_PCI_EXPANDER_BUS)
        return VIR_DOMAIN_CONTROLLER_MODEL_PCI_EXPANDER_BUS;

    if (flags & VIR_PCI_CONNECT_TYPE_PCIE_EXPANDER_BUS)
        return VIR_DOMAIN_CONTROLLER_MODEL_PCIE_EXPANDER_BUS;

    if (flags & VIR_PCI_CONNECT_TYPE_PCI_BRIDGE)
        return VIR_DOMAIN_CONTROLLER_MODEL_PCI_BRIDGE;

    /* some connect types don't correspond to a controller model */
    return -1;
}


static bool
virDomainPCIAddressFlagsCompatible(virPCIDeviceAddressPtr addr,
                                   const char *addrStr,
                                   virDomainPCIConnectFlags busFlags,
                                   virDomainPCIConnectFlags devFlags,
                                   bool reportError,
                                   bool fromConfig)
{
    virErrorNumber errType = (fromConfig
                              ? VIR_ERR_XML_ERROR : VIR_ERR_INTERNAL_ERROR);

    if (fromConfig) {
        /* If the requested connection was manually specified in
         * config, allow a PCI device to connect to a PCIe slot, or
         * vice versa. In order to do so, we add *both* the PCI_DEVICE
         * and the PCIE_DEVICE flags to the bus if it already has either
         * of them, using the ENDPOINT mask.
         */
        if (busFlags & VIR_PCI_CONNECT_TYPES_ENDPOINT)
            busFlags |= VIR_PCI_CONNECT_TYPES_ENDPOINT;
        /* if the device is a pci-bridge, allow manually
         * assigning to any bus that would also accept a
         * standard PCI device.
         */
        if (devFlags & VIR_PCI_CONNECT_TYPE_PCI_BRIDGE)
            devFlags |= VIR_PCI_CONNECT_TYPE_PCI_DEVICE;
    } else if ((devFlags & VIR_PCI_CONNECT_AUTOASSIGN) &&
               !(busFlags & VIR_PCI_CONNECT_AUTOASSIGN)) {
        if (reportError) {
            virReportError(errType,
                           _("The device at PCI address %s was auto-assigned "
                             "this address, but the PCI controller "
                             "with index='%d' doesn't allow auto-assignment"),
                           addrStr, addr->bus);
        }
        return false;
    }

    if ((devFlags & VIR_PCI_CONNECT_HOTPLUGGABLE) &&
        !(busFlags & VIR_PCI_CONNECT_HOTPLUGGABLE)) {
        if (reportError) {
            virReportError(errType,
                           _("The device at PCI address %s requires "
                             "hotplug capability, but the PCI controller "
                             "with index='%d' doesn't support hotplug"),
                           addrStr, addr->bus);
        }
        return false;
    }

    /* If this bus doesn't allow the type of connection (PCI
     * vs. PCIe) required by the device, or if the device requires
     * hot-plug and this bus doesn't have it, return false.
     */
    if (!(devFlags & busFlags & VIR_PCI_CONNECT_TYPES_MASK)) {
        const char *connectStr;

        if (!reportError)
            return false;

        if (devFlags & VIR_PCI_CONNECT_TYPE_PCI_DEVICE) {
            connectStr = "standard PCI device";
        } else if (devFlags & VIR_PCI_CONNECT_TYPE_PCIE_DEVICE) {
            connectStr = "PCI Express device";
        } else if (devFlags & VIR_PCI_CONNECT_TYPE_PCIE_ROOT_PORT) {
            connectStr = "pcie-root-port";
        } else if (devFlags & VIR_PCI_CONNECT_TYPE_PCIE_SWITCH_UPSTREAM_PORT) {
            connectStr = "pcie-switch-upstream-port";
        } else if (devFlags & VIR_PCI_CONNECT_TYPE_PCIE_SWITCH_DOWNSTREAM_PORT) {
            connectStr = "pcie-switch-downstream-port";
        } else if (devFlags & VIR_PCI_CONNECT_TYPE_DMI_TO_PCI_BRIDGE) {
            connectStr = "dmi-to-pci-bridge";
        } else if (devFlags & VIR_PCI_CONNECT_TYPE_PCIE_TO_PCI_BRIDGE) {
            connectStr = "pcie-to-pci-bridge";
        } else if (devFlags & VIR_PCI_CONNECT_TYPE_PCI_EXPANDER_BUS) {
            connectStr = "pci-expander-bus";
        } else if (devFlags & VIR_PCI_CONNECT_TYPE_PCIE_EXPANDER_BUS) {
            connectStr = "pcie-expander-bus";
        } else if (devFlags & VIR_PCI_CONNECT_TYPE_PCI_BRIDGE) {
            connectStr = "pci-bridge";
        } else {
            /* this should never happen. If it does, there is a
             * bug in the code that sets the flag bits for devices.
             */
            virReportError(errType,
                           _("The device at PCI address %s has "
                             "unrecognized connection type flags 0x%.2x"),
                           addrStr, devFlags & VIR_PCI_CONNECT_TYPES_MASK);
            return false;
        }
        virReportError(errType,
                       _("The device at PCI address %s cannot be "
                         "plugged into the PCI controller with index='%d'. "
                         "It requires a controller that accepts a %s."),
                       addrStr, addr->bus, connectStr);
        return false;
    }
    return true;
}


/* Verify that the address is in bounds for the chosen bus, and
 * that the bus is of the correct type for the device (via
 * comparing the flags).
 */
bool
virDomainPCIAddressValidate(virDomainPCIAddressSetPtr addrs,
                            virPCIDeviceAddressPtr addr,
                            const char *addrStr,
                            virDomainPCIConnectFlags flags,
                            bool fromConfig)
{
    virDomainPCIAddressBusPtr bus;
    virErrorNumber errType = (fromConfig
                              ? VIR_ERR_XML_ERROR : VIR_ERR_INTERNAL_ERROR);

    if (addrs->nbuses == 0) {
        virReportError(errType, "%s", _("No PCI buses available"));
        return false;
    }
    if (addr->domain != 0) {
        virReportError(errType,
                       _("Invalid PCI address %s. "
                         "Only PCI domain 0 is available"),
                       addrStr);
        return false;
    }
    if (addr->bus >= addrs->nbuses) {
        virReportError(errType,
                       _("Invalid PCI address %s. "
                         "Only PCI buses up to %zu are available"),
                       addrStr, addrs->nbuses - 1);
        return false;
    }

    bus = &addrs->buses[addr->bus];

    /* assure that at least one of the requested connection types is
     * provided by this bus
     */
    if (!virDomainPCIAddressFlagsCompatible(addr, addrStr, bus->flags,
                                            flags, true, fromConfig))
        return false;

    /* some "buses" are really just a single port */
    if (bus->minSlot && addr->slot < bus->minSlot) {
        virReportError(errType,
                       _("Invalid PCI address %s. slot must be >= %zu"),
                       addrStr, bus->minSlot);
        return false;
    }
    if (addr->slot > bus->maxSlot) {
        virReportError(errType,
                       _("Invalid PCI address %s. slot must be <= %zu"),
                       addrStr, bus->maxSlot);
        return false;
    }
    if (addr->function > VIR_PCI_ADDRESS_FUNCTION_LAST) {
        virReportError(errType,
                       _("Invalid PCI address %s. function must be <= %u"),
                       addrStr, VIR_PCI_ADDRESS_FUNCTION_LAST);
        return false;
    }
    return true;
}


int
virDomainPCIAddressBusSetModel(virDomainPCIAddressBusPtr bus,
                               virDomainControllerModelPCI model,
                               bool allowHotplug)
{
    /* set flags for what can be connected *downstream* from each
     * bus.
     */
    virDomainPCIConnectFlags hotplugFlag = 0;

    if (allowHotplug)
        hotplugFlag = VIR_PCI_CONNECT_HOTPLUGGABLE;

    switch (model) {
    case VIR_DOMAIN_CONTROLLER_MODEL_PCI_ROOT:
        bus->flags = (VIR_PCI_CONNECT_AUTOASSIGN |
                      VIR_PCI_CONNECT_TYPE_PCI_DEVICE |
                      VIR_PCI_CONNECT_TYPE_PCI_BRIDGE |
                      VIR_PCI_CONNECT_TYPE_PCI_EXPANDER_BUS |
                      hotplugFlag);
        bus->minSlot = 1;
        bus->maxSlot = VIR_PCI_ADDRESS_SLOT_LAST;
        break;
    case VIR_DOMAIN_CONTROLLER_MODEL_PCI_BRIDGE:
        bus->flags = (VIR_PCI_CONNECT_AUTOASSIGN |
                      VIR_PCI_CONNECT_TYPE_PCI_DEVICE |
                      VIR_PCI_CONNECT_TYPE_PCI_BRIDGE |
                      hotplugFlag);
        bus->minSlot = 1;
        bus->maxSlot = VIR_PCI_ADDRESS_SLOT_LAST;
        break;
    case VIR_DOMAIN_CONTROLLER_MODEL_PCI_EXPANDER_BUS:
        bus->flags = (VIR_PCI_CONNECT_AUTOASSIGN |
                      VIR_PCI_CONNECT_TYPE_PCI_DEVICE |
                      VIR_PCI_CONNECT_TYPE_PCI_BRIDGE |
                      hotplugFlag);
        bus->minSlot = 0;
        bus->maxSlot = VIR_PCI_ADDRESS_SLOT_LAST;
        break;

    case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT:
        /* slots 1 - 31, no hotplug, PCIe endpoint device or
         * pcie-root-port only, unless the address was specified in
         * user config *and* the particular device being attached also
         * allows it.
         */
        bus->flags = (VIR_PCI_CONNECT_TYPE_PCIE_DEVICE |
                      VIR_PCI_CONNECT_TYPE_PCIE_ROOT_PORT |
                      VIR_PCI_CONNECT_TYPE_DMI_TO_PCI_BRIDGE |
                      VIR_PCI_CONNECT_TYPE_PCIE_EXPANDER_BUS);
        bus->minSlot = 1;
        bus->maxSlot = VIR_PCI_ADDRESS_SLOT_LAST;
        break;
    case VIR_DOMAIN_CONTROLLER_MODEL_DMI_TO_PCI_BRIDGE:
        /* slots 0 - 31, standard PCI slots,
         * but *not* hot-pluggable */
        bus->flags = (VIR_PCI_CONNECT_TYPE_PCI_DEVICE |
                      VIR_PCI_CONNECT_TYPE_PCI_BRIDGE);
        bus->minSlot = 0;
        bus->maxSlot = VIR_PCI_ADDRESS_SLOT_LAST;
        break;
    case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_TO_PCI_BRIDGE:
        /* Same as pci-bridge: 32 hotpluggable traditional PCI slots (0-31),
         * the first of which is not usable because of the SHPC */
        bus->flags = (VIR_PCI_CONNECT_AUTOASSIGN |
                      VIR_PCI_CONNECT_TYPE_PCI_DEVICE |
                      VIR_PCI_CONNECT_TYPE_PCI_BRIDGE |
                      hotplugFlag);
        bus->minSlot = 1;
        bus->maxSlot = VIR_PCI_ADDRESS_SLOT_LAST;
        break;
    case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT_PORT:
    case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_SWITCH_DOWNSTREAM_PORT:
        /* provides one slot which is pcie, can be used by endpoint
         * devices, pcie-switch-upstream-ports or pcie-to-pci-bridges,
         * and is hotpluggable */
        bus->flags = (VIR_PCI_CONNECT_AUTOASSIGN |
                      VIR_PCI_CONNECT_TYPE_PCIE_DEVICE |
                      VIR_PCI_CONNECT_TYPE_PCIE_SWITCH_UPSTREAM_PORT |
                      VIR_PCI_CONNECT_TYPE_PCIE_TO_PCI_BRIDGE |
                      hotplugFlag);
        bus->minSlot = 0;
        bus->maxSlot = 0;
        break;
    case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_SWITCH_UPSTREAM_PORT:
        /* 32 slots, can only accept pcie-switch-downstream-ports,
         * no hotplug
         */
        bus->flags = VIR_PCI_CONNECT_TYPE_PCIE_SWITCH_DOWNSTREAM_PORT;
        bus->minSlot = 0;
        bus->maxSlot = VIR_PCI_ADDRESS_SLOT_LAST;
        break;
    case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_EXPANDER_BUS:
        /* 32 slots, no hotplug, only accepts pcie-root-port or
         * dmi-to-pci-bridge
         */
        bus->flags = (VIR_PCI_CONNECT_TYPE_PCIE_ROOT_PORT |
                      VIR_PCI_CONNECT_TYPE_DMI_TO_PCI_BRIDGE);
        bus->minSlot = 0;
        bus->maxSlot = VIR_PCI_ADDRESS_SLOT_LAST;
        break;

    case VIR_DOMAIN_CONTROLLER_MODEL_PCI_DEFAULT:
    case VIR_DOMAIN_CONTROLLER_MODEL_PCI_LAST:
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       "%s", _("PCI controller model was not set correctly"));
        return -1;
    }

    bus->model = model;
    return 0;
}


bool
virDomainPCIAddressBusIsFullyReserved(virDomainPCIAddressBusPtr bus)
{
    size_t i;

    for (i = bus->minSlot; i <= bus->maxSlot; i++) {
        if (!bus->slot[i].functions)
            return false;
    }

    return true;
}


static bool ATTRIBUTE_NONNULL(1)
virDomainPCIAddressBusIsEmpty(virDomainPCIAddressBusPtr bus)
{
    size_t i;

    for (i = bus->minSlot; i <= bus->maxSlot; i++) {
        if (bus->slot[i].functions)
            return false;
    }

    return true;
}


/* Ensure addr fits in the address set, by expanding it if needed
 *
 * Return value:
 * -1 = OOM
 *  0 = no action performed
 * >0 = number of buses added
 */
static int
virDomainPCIAddressSetGrow(virDomainPCIAddressSetPtr addrs,
                           virPCIDeviceAddressPtr addr,
                           virDomainPCIConnectFlags flags)
{
    int add;
    size_t i;
    int model;
    bool needDMIToPCIBridge = false;
    bool needPCIeToPCIBridge = false;

    add = addr->bus - addrs->nbuses + 1;
    if (add <= 0)
        return 0;

    /* remember that the flags aren't for the type of controller that
     * we want to add, they are the type of *device* that we want to
     * plug in, and this function must decide on the appropriate
     * controller to add in order to give us a slot for that device.
     */

    if (flags & VIR_PCI_CONNECT_TYPE_PCI_DEVICE) {
        if (addrs->areMultipleRootsSupported) {
            /* Use a pci-root controller to expand the guest's PCI
             * topology if it supports having more than one */
            model = VIR_DOMAIN_CONTROLLER_MODEL_PCI_ROOT;
        } else {
            model = VIR_DOMAIN_CONTROLLER_MODEL_PCI_BRIDGE;

            /* if there aren't yet any buses that will accept a
             * pci-bridge, but we need one for the device's PCI address
             * to make sense, it means the guest only has a PCIe topology
             * configured so far, and we need to create a traditional PCI
             * topology to accommodate the new device.
             */
            needDMIToPCIBridge = true;
            needPCIeToPCIBridge = true;
            for (i = 0; i < addrs->nbuses; i++) {
                if (addrs->buses[i].flags & VIR_PCI_CONNECT_TYPE_PCI_BRIDGE) {
                    needDMIToPCIBridge = false;
                    needPCIeToPCIBridge = false;
                    break;
                }
            }

            /* Prefer pcie-to-pci-bridge, fall back to dmi-to-pci-bridge */
            if (addrs->isPCIeToPCIBridgeSupported)
                needDMIToPCIBridge = false;
            else
                needPCIeToPCIBridge = false;

            if ((needDMIToPCIBridge || needPCIeToPCIBridge) && add == 1) {
                /* We need to add a single pci-bridge to provide the bus
                 * our legacy PCI device will be plugged into; however, we
                 * have also determined that there isn't yet any proper
                 * place to connect that pci-bridge we're about to add,
                 * which means we're dealing with a pure PCIe guest. We
                 * need to create a traditional PCI topology, and for that
                 * we have two options: dmi-to-pci-bridge + pci-bridge or
                 * pcie-root-port + pcie-to-pci-bridge (the latter of which
                 * is pretty much a pci-bridge as far as devices attached
                 * to it are concerned and as such makes the pci-bridge
                 * unnecessary). Either way, there's going to be one more
                 * controller than initially expected, and the 'bus' part
                 * of the device's address will need to be bumped.
                 */
                add++;
                addr->bus++;
            }
        }
    } else if (flags & (VIR_PCI_CONNECT_TYPE_PCIE_DEVICE |
                        VIR_PCI_CONNECT_TYPE_PCIE_SWITCH_UPSTREAM_PORT)) {
        model = VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT_PORT;
    } else {
        /* The types of devices that we can't auto-add a controller for:
         *
         * VIR_CONNECT_TYPE_DMI_TO_PCI_BRIDGE &
         * VIR_PCI_CONNECT_TYPE_ROOT_PORT - these can only plug into
         *  pcie-root or pcie-expander-bus. By definition there is
         *  only 1 pcie-root, and we don't support auto-adding
         *  pcie-expander-bus (because it is intended for NUMA usage,
         *  and we can't automatically decide which numa node to
         *  associate it with)
         *
         * VIR_CONNECT_TYPE_PCIE_SWITCH_DOWNSTREAM_PORT - we ndon't
         *  support this, because it can only plug into an
         *  upstream-port, and the upstream port might need a
         *  root-port; supporting this extra layer needlessly
         *  complicates the code, and upstream/downstream ports are
         *  outside the scope of our "automatic-bus-expansion" model
         *  anyway.
         *
         * VIR_CONNECT_TYPE_PCI[E]_EXPANDER_BUS - these were created
         *  to support guest awareness of the NUMA node placement of
         *  devices on the host, and are also outside the scope of our
         *  "automatic-bus-expansion".
         *
         * VIR_PCI_CONNECT_TYPE_PCI_BRIDGE (when the root bus is
         *  pci-root) - see the comment above in the case that handles
         *  adding a slot for pci-bridge to a guest with pcie-root.
         *
         */
        int existingContModel = virDomainPCIControllerConnectTypeToModel(flags);

        if (existingContModel >= 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("a PCI slot is needed to connect a PCI controller "
                             "model='%s', but none is available, and it "
                             "cannot be automatically added"),
                           virDomainControllerModelPCITypeToString(existingContModel));
        } else {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("Cannot automatically add a new PCI bus for a "
                             "device with connect flags %.2x"), flags);
        }
        return -1;
    }

    i = addrs->nbuses;

    if (VIR_EXPAND_N(addrs->buses, addrs->nbuses, add) < 0)
        return -1;

    if (needDMIToPCIBridge) {
        /* first of the new buses is dmi-to-pci-bridge, the
         * rest are of the requested type
         */
        if (virDomainPCIAddressBusSetModel(&addrs->buses[i++],
                                           VIR_DOMAIN_CONTROLLER_MODEL_DMI_TO_PCI_BRIDGE, false) < 0) {
            return -1;
        }
    }

    if (needPCIeToPCIBridge) {
        /* We need a pcie-root-port to plug pcie-to-pci-bridge into; however,
         * qemuDomainAssignPCIAddresses() will, in some cases, create a dummy
         * PCIe device and reserve an address for it in order to leave the
         * user with an empty pcie-root-port ready for hotplugging, and if
         * we didn't do anything other than adding the pcie-root-port here
         * it would be used for that, which we don't want. So we change the
         * connect flags to make sure only the pcie-to-pci-bridge will be
         * connected to the pcie-root-port we just added, and another one
         * will be allocated for the dummy PCIe device later on.
         */
        if (virDomainPCIAddressBusSetModel(&addrs->buses[i],
                                           VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT_PORT, true) < 0) {
            return -1;
        }
        addrs->buses[i].flags = VIR_PCI_CONNECT_TYPE_PCIE_TO_PCI_BRIDGE;
        i++;

        if (virDomainPCIAddressBusSetModel(&addrs->buses[i++],
                                           VIR_DOMAIN_CONTROLLER_MODEL_PCIE_TO_PCI_BRIDGE, true) < 0) {
            return -1;
        }
    }

    for (; i < addrs->nbuses; i++) {
        if (virDomainPCIAddressBusSetModel(&addrs->buses[i], model, true) < 0)
            return -1;
    }

    return add;
}


/*
 * Check if the PCI slot is used by another device.
 */
bool
virDomainPCIAddressSlotInUse(virDomainPCIAddressSetPtr addrs,
                             virPCIDeviceAddressPtr addr)
{
    return !!addrs->buses[addr->bus].slot[addr->slot].functions;
}


/*
 * Reserve a function in a slot. If fromConfig is true, the address
 * being requested came directly from the config and errors should be
 * worded appropriately. If fromConfig is false, the address was
 * automatically created by libvirt, so it is an internal error (not
 * XML).
 */
static int ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2)
virDomainPCIAddressReserveAddrInternal(virDomainPCIAddressSetPtr addrs,
                                       virPCIDeviceAddressPtr addr,
                                       virDomainPCIConnectFlags flags,
                                       unsigned int isolationGroup,
                                       bool fromConfig)
{
    g_autofree char *addrStr = NULL;
    virDomainPCIAddressBusPtr bus;
    virErrorNumber errType = (fromConfig
                              ? VIR_ERR_XML_ERROR : VIR_ERR_INTERNAL_ERROR);

    if (!(addrStr = virPCIDeviceAddressAsString(addr)))
        return -1;

    /* Add an extra bus if necessary */
    if (addrs->dryRun && virDomainPCIAddressSetGrow(addrs, addr, flags) < 0)
        return -1;
    /* Check that the requested bus exists, is the correct type, and we
     * are asking for a valid slot
     */
    if (!virDomainPCIAddressValidate(addrs, addr, addrStr, flags, fromConfig))
        return -1;

    bus = &addrs->buses[addr->bus];

    if (bus->slot[addr->slot].functions & (1 << addr->function)) {
        virReportError(errType,
                       _("Attempted double use of PCI Address %s"), addrStr);
        return -1;
    }

    /* if this is the first function to be reserved on this slot, and
     * the device it's being reserved for can aggregate multiples on a
     * slot, set the slot's aggregate flag.
    */
    if (!bus->slot[addr->slot].functions &&
        flags & VIR_PCI_CONNECT_AGGREGATE_SLOT) {
        bus->slot[addr->slot].aggregate = true;
    }

    if (virDomainPCIAddressBusIsEmpty(bus) && !bus->isolationGroupLocked) {
        /* The first device decides the isolation group for the
         * entire bus */
        bus->isolationGroup = isolationGroup;
        VIR_DEBUG("PCI bus %04x:%02x assigned isolation group %u because of "
                  "first device %s",
                  addr->domain, addr->bus, isolationGroup, addrStr);
    } else if (bus->isolationGroup != isolationGroup && fromConfig) {
        /* If this is not the first function and its isolation group
         * doesn't match the bus', then it should not be using this
         * address. However, if the address comes from the user then
         * we comply with the request and change the isolation group
         * back to the default (because at that point isolation can't
         * be guaranteed anymore) */
        bus->isolationGroup = 0;
        VIR_DEBUG("PCI bus %04x:%02x assigned isolation group %u because of "
                  "user assigned address %s",
                  addr->domain, addr->bus, isolationGroup, addrStr);
    }

    /* mark the requested function as reserved */
    bus->slot[addr->slot].functions |= (1 << addr->function);
    VIR_DEBUG("Reserving PCI address %s (aggregate='%s')", addrStr,
              bus->slot[addr->slot].aggregate ? "true" : "false");

    return 0;
}


int
virDomainPCIAddressReserveAddr(virDomainPCIAddressSetPtr addrs,
                               virPCIDeviceAddressPtr addr,
                               virDomainPCIConnectFlags flags,
                               unsigned int isolationGroup)
{
    return virDomainPCIAddressReserveAddrInternal(addrs, addr, flags,
                                                  isolationGroup, true);
}

int
virDomainPCIAddressEnsureAddr(virDomainPCIAddressSetPtr addrs,
                              virDomainDeviceInfoPtr dev,
                              virDomainPCIConnectFlags flags)
{
    g_autofree char *addrStr = NULL;

    /* if flags is 0, the particular model of this device on this
     * machinetype doesn't need a PCI address, so we're done.
     */
    if (!flags)
       return 0;

    /* This function is only called during hotplug, so we require hotplug
     * support from the controller.
     */
    flags |= VIR_PCI_CONNECT_HOTPLUGGABLE;

    if (!(addrStr = virPCIDeviceAddressAsString(&dev->addr.pci)))
        return -1;

    if (virDeviceInfoPCIAddressIsPresent(dev)) {
        /* We do not support hotplug multi-function PCI device now, so we should
         * reserve the whole slot. The function of the PCI device must be 0.
         */
        if (dev->addr.pci.function != 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("Only PCI device addresses with function=0"
                             " are supported"));
            return -1;
        }

        if (!virDomainPCIAddressValidate(addrs, &dev->addr.pci,
                                         addrStr, flags, true))
            return -1;

        if (virDomainPCIAddressReserveAddrInternal(addrs, &dev->addr.pci,
                                                   flags, dev->isolationGroup,
                                                   true) < 0) {
            return -1;
        }
    } else {
        if (virDomainPCIAddressReserveNextAddr(addrs, dev, flags, -1) < 0)
            return -1;
    }

    dev->addr.pci.extFlags = dev->pciAddrExtFlags;
    if (virDomainPCIAddressExtensionEnsureAddr(addrs, &dev->addr.pci) < 0)
        return -1;

    return 0;
}


void
virDomainPCIAddressExtensionReleaseAddr(virDomainPCIAddressSetPtr addrs,
                                        virPCIDeviceAddressPtr addr)
{
    if (addr->extFlags & VIR_PCI_ADDRESS_EXTENSION_ZPCI)
        virDomainZPCIAddressReleaseIds(addrs->zpciIds, &addr->zpci);
}


void
virDomainPCIAddressReleaseAddr(virDomainPCIAddressSetPtr addrs,
                               virPCIDeviceAddressPtr addr)
{
    addrs->buses[addr->bus].slot[addr->slot].functions &= ~(1 << addr->function);
}


static void
virDomainPCIAddressSetExtensionFree(virDomainPCIAddressSetPtr addrs)
{
    if (!addrs || !addrs->zpciIds)
        return;

    g_clear_pointer(&addrs->zpciIds->uids, g_hash_table_unref);
    g_clear_pointer(&addrs->zpciIds->fids, g_hash_table_unref);

    VIR_FREE(addrs->zpciIds);
}


static int
virDomainPCIAddressSetExtensionAlloc(virDomainPCIAddressSetPtr addrs,
                                     virPCIDeviceAddressExtensionFlags extFlags)
{
    if (extFlags & VIR_PCI_ADDRESS_EXTENSION_ZPCI) {
        if (addrs->zpciIds)
            return 0;

        addrs->zpciIds = g_new0(virDomainZPCIAddressIds, 1);

        addrs->zpciIds->uids = g_hash_table_new_full(g_int_hash, g_int_equal, g_free, NULL);
        addrs->zpciIds->fids = g_hash_table_new_full(g_int_hash, g_int_equal, g_free, NULL);
    }

    return 0;
}


virDomainPCIAddressSetPtr
virDomainPCIAddressSetAlloc(unsigned int nbuses,
                            virPCIDeviceAddressExtensionFlags extFlags)
{
    virDomainPCIAddressSetPtr addrs;

    addrs = g_new0(virDomainPCIAddressSet, 1);
    addrs->buses = g_new0(virDomainPCIAddressBus, nbuses);
    addrs->nbuses = nbuses;

    if (virDomainPCIAddressSetExtensionAlloc(addrs, extFlags) < 0)
        goto error;

    return addrs;

 error:
    virDomainPCIAddressSetFree(addrs);
    return NULL;
}


void
virDomainPCIAddressSetFree(virDomainPCIAddressSetPtr addrs)
{
    if (!addrs)
        return;

    virDomainPCIAddressSetExtensionFree(addrs);
    VIR_FREE(addrs->buses);
    VIR_FREE(addrs);
}


static int
virDomainPCIAddressFindUnusedFunctionOnBus(virDomainPCIAddressBusPtr bus,
                                           virPCIDeviceAddressPtr searchAddr,
                                           int function,
                                           virDomainPCIConnectFlags flags,
                                           bool *found)
{
    g_autofree char *addrStr = NULL;

    *found = false;

    if (!(addrStr = virPCIDeviceAddressAsString(searchAddr)))
        return -1;

    if (!virDomainPCIAddressFlagsCompatible(searchAddr, addrStr, bus->flags,
                                            flags, false, false)) {
        VIR_DEBUG("PCI bus %04x:%02x is not compatible with the device",
                  searchAddr->domain, searchAddr->bus);
    } else {
        while (searchAddr->slot <= bus->maxSlot) {
            if (bus->slot[searchAddr->slot].functions == 0) {
                *found = true;
                break;
            }

            if (flags & VIR_PCI_CONNECT_AGGREGATE_SLOT &&
                bus->slot[searchAddr->slot].aggregate) {
                /* slot and device are okay with aggregating devices */
                if ((bus->slot[searchAddr->slot].functions &
                     (1 << searchAddr->function)) == 0) {
                    *found = true;
                    break;
                }

                /* also check for *any* unused function if caller
                 * sent function = -1
                 */
                if (function == -1) {
                    while (searchAddr->function < 8) {
                        if ((bus->slot[searchAddr->slot].functions &
                             (1 << searchAddr->function)) == 0) {
                            *found = true;
                            break; /* out of inner while */
                        }
                        searchAddr->function++;
                    }
                    if (*found)
                       break; /* out of outer while */
                    searchAddr->function = 0; /* reset for next try */
                }
            }

            VIR_DEBUG("PCI slot %04x:%02x:%02x already in use",
                      searchAddr->domain, searchAddr->bus, searchAddr->slot);
            searchAddr->slot++;
        }
    }

    return 0;
}


static int ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2)
virDomainPCIAddressGetNextAddr(virDomainPCIAddressSetPtr addrs,
                               virPCIDeviceAddressPtr next_addr,
                               virDomainPCIConnectFlags flags,
                               unsigned int isolationGroup,
                               int function)
{
    virPCIDeviceAddress a = { 0 };

    if (addrs->nbuses == 0) {
        virReportError(VIR_ERR_XML_ERROR, "%s", _("No PCI buses available"));
        return -1;
    }

    /* if the caller asks for "any function", give them function 0 */
    if (function == -1)
        a.function = 0;
    else
        a.function = function;

    /* When looking for a suitable bus for the device, start by being
     * very strict and ignoring all those where the isolation groups
     * don't match. This ensures all devices sharing the same isolation
     * group will end up on the same bus */
    for (a.bus = 0; a.bus < addrs->nbuses; a.bus++) {
        virDomainPCIAddressBusPtr bus = &addrs->buses[a.bus];
        bool found = false;

        if (bus->isolationGroup != isolationGroup)
            continue;

        a.slot = bus->minSlot;

        if (virDomainPCIAddressFindUnusedFunctionOnBus(bus, &a, function,
                                                       flags, &found) < 0) {
            return -1;
        }

        if (found)
            goto success;
    }

    /* We haven't been able to find a perfectly matching bus, but we
     * might still be able to make this work by altering the isolation
     * group for a bus that's currently empty. So let's try that */
    for (a.bus = 0; a.bus < addrs->nbuses; a.bus++) {
        virDomainPCIAddressBusPtr bus = &addrs->buses[a.bus];
        bool found = false;

        /* We can only change the isolation group for a bus when
         * plugging in the first device; moreover, some buses are
         * prevented from ever changing it */
        if (!virDomainPCIAddressBusIsEmpty(bus) || bus->isolationGroupLocked)
            continue;

        a.slot = bus->minSlot;

        if (virDomainPCIAddressFindUnusedFunctionOnBus(bus, &a, function,
                                                       flags, &found) < 0) {
            return -1;
        }

        /* The isolation group for the bus will actually be changed
         * later, in virDomainPCIAddressReserveAddrInternal() */
        if (found)
            goto success;
    }

    /* There were no free slots after the last used one */
    if (addrs->dryRun) {
        /* a is already set to the first new bus */
        if (virDomainPCIAddressSetGrow(addrs, &a, flags) < 0)
            return -1;
        /* this device will use the first slot of the new bus */
        a.slot = addrs->buses[a.bus].minSlot;
        goto success;
    }

    virReportError(VIR_ERR_INTERNAL_ERROR,
                   "%s", _("No more available PCI slots"));
    return -1;

 success:
    VIR_DEBUG("Found free PCI slot %04x:%02x:%02x",
              a.domain, a.bus, a.slot);
    *next_addr = a;
    return 0;
}


/**
 * virDomainPCIAddressReserveNextAddr:
 *
 * @addrs: a set of PCI addresses.
 * @dev: virDomainDeviceInfo that should get the new address.
 * @flags: CONNECT_TYPE flags for the device that needs an address.
 * @function: which function on the slot to mark as reserved
 *
 * Find the next *completely unreserved* slot with compatible
 * connection @flags, mark one function of the slot as in-use
 * (according to @function), then set @dev->addr.pci with this newly
 * reserved address. If @function is -1, then the lowest unused
 * function of the slot will be reserved (and since we only look for
 * completely unused slots, that means "0").
 *
 * returns 0 on success, or -1 on failure.
 */
int
virDomainPCIAddressReserveNextAddr(virDomainPCIAddressSetPtr addrs,
                                   virDomainDeviceInfoPtr dev,
                                   virDomainPCIConnectFlags flags,
                                   int function)
{
    virPCIDeviceAddress addr;

    if (virDomainPCIAddressGetNextAddr(addrs, &addr, flags,
                                       dev->isolationGroup, function) < 0)
        return -1;

    if (virDomainPCIAddressReserveAddrInternal(addrs, &addr, flags,
                                               dev->isolationGroup, false) < 0)
        return -1;

    addr.extFlags = dev->addr.pci.extFlags;
    addr.zpci = dev->addr.pci.zpci;

    if (!addrs->dryRun) {
        dev->type = VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI;
        dev->addr.pci = addr;
    }

    return 0;
}


static int
virDomainPCIAddressSetMultiIter(virDomainDefPtr def G_GNUC_UNUSED,
                                virDomainDeviceDefPtr dev G_GNUC_UNUSED,
                                virDomainDeviceInfoPtr info,
                                void *data)
{
    virPCIDeviceAddressPtr testAddr = data;
    virPCIDeviceAddressPtr thisAddr;

    if (!info || info->type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI)
       return 0;

    thisAddr = &info->addr.pci;

    if (thisAddr->domain == testAddr->domain &&
        thisAddr->bus == testAddr->bus &&
        thisAddr->slot == testAddr->slot &&
        thisAddr->function == 0) {

        /* only set to ON if it wasn't previously set
         * (assuming that the user must have better information
         * than us if they explicitly set it OFF)
         */
        if (thisAddr->multi == VIR_TRISTATE_SWITCH_ABSENT)
            thisAddr->multi = VIR_TRISTATE_SWITCH_ON;

        return -1; /* finish early, *NOT* an error */
    }

    return 0;
}


static int
virDomainPCIAddressSetAllMultiIter(virDomainDefPtr def,
                                   virDomainDeviceDefPtr dev G_GNUC_UNUSED,
                                   virDomainDeviceInfoPtr info,
                                   void *data G_GNUC_UNUSED)
{
    virPCIDeviceAddressPtr testAddr;

    if (!info || info->type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI)
       return 0;

    testAddr = &info->addr.pci;

    if (testAddr->function != 0) {
        ignore_value(virDomainDeviceInfoIterate(def,
                                                virDomainPCIAddressSetMultiIter,
                                                testAddr));
    }

    return 0;
}


/**
 * virDomainPCIAddressSetAllMulti():
 *
 * @def: the domain definition whose devices may need adjusting
 * @addrs: address set keeping track of all addresses in use.
 *
 * Look for any PCI slots that have multiple functions assigned, and
 * set multi to ON in the address for the device at function 0
 * (unless it has been explicitly set to OFF).
 *
 * No return code, since there is no possibility of failure.
 */
void
virDomainPCIAddressSetAllMulti(virDomainDefPtr def)
{
    /* Use nested iterators over all the devices - the outer iterator
     * scans through all the devices looking for those whose address
     * has a non-0 function; when one is found, the inner iterator looks
     * for the device that uses function 0 on the same slot and marks
     * it as multi = ON
     */
    ignore_value(virDomainDeviceInfoIterate(def,
                                            virDomainPCIAddressSetAllMultiIter,
                                            NULL));
}


char*
virDomainCCWAddressAsString(virDomainDeviceCCWAddressPtr addr)
{
    return g_strdup_printf("%x.%x.%04x", addr->cssid, addr->ssid, addr->devno);
}

static int
virDomainCCWAddressIncrement(virDomainDeviceCCWAddressPtr addr)
{
    virDomainDeviceCCWAddress ccwaddr = *addr;

    /* We are not touching subchannel sets and channel subsystems */
    if (++ccwaddr.devno > VIR_DOMAIN_DEVICE_CCW_MAX_DEVNO)
        return -1;

    *addr = ccwaddr;
    return 0;
}


int
virDomainCCWAddressAssign(virDomainDeviceInfoPtr dev,
                          virDomainCCWAddressSetPtr addrs,
                          bool autoassign)
{
    g_autofree char *addr = NULL;

    if (dev->type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_CCW)
        return 0;

    if (!autoassign && dev->addr.ccw.assigned) {
        if (!(addr = virDomainCCWAddressAsString(&dev->addr.ccw)))
            return -1;

        if (virHashLookup(addrs->defined, addr)) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("The CCW devno '%s' is in use already"),
                           addr);
            return -1;
        }
    } else if (autoassign && !dev->addr.ccw.assigned) {
        if (!(addr = virDomainCCWAddressAsString(&addrs->next)))
            return -1;

        while (virHashLookup(addrs->defined, addr)) {
            if (virDomainCCWAddressIncrement(&addrs->next) < 0) {
                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                               _("There are no more free CCW devnos."));
                return -1;
            }
            VIR_FREE(addr);
            if (!(addr = virDomainCCWAddressAsString(&addrs->next)))
                return -1;
        }
        dev->addr.ccw = addrs->next;
        dev->addr.ccw.assigned = true;
    } else {
        return 0;
    }

    if (virHashAddEntry(addrs->defined, addr, addr) < 0)
        return -1;
    else
        addr = NULL; /* memory will be freed by hash table */

    return 0;
}

static int ATTRIBUTE_NONNULL(3) ATTRIBUTE_NONNULL(4)
virDomainCCWAddressAllocate(virDomainDefPtr def G_GNUC_UNUSED,
                            virDomainDeviceDefPtr dev G_GNUC_UNUSED,
                            virDomainDeviceInfoPtr info,
                            void *data)
{
    return virDomainCCWAddressAssign(info, data, true);
}

static int ATTRIBUTE_NONNULL(3) ATTRIBUTE_NONNULL(4)
virDomainCCWAddressValidate(virDomainDefPtr def G_GNUC_UNUSED,
                            virDomainDeviceDefPtr dev G_GNUC_UNUSED,
                            virDomainDeviceInfoPtr info,
                            void *data)
{
    return virDomainCCWAddressAssign(info, data, false);
}

void virDomainCCWAddressSetFree(virDomainCCWAddressSetPtr addrs)
{
    if (!addrs)
        return;

    virHashFree(addrs->defined);
    VIR_FREE(addrs);
}

static virDomainCCWAddressSetPtr
virDomainCCWAddressSetCreate(void)
{
    virDomainCCWAddressSetPtr addrs = NULL;

    addrs = g_new0(virDomainCCWAddressSet, 1);

    if (!(addrs->defined = virHashNew(g_free)))
        goto error;

    /* must use cssid = 0xfe (254) for virtio-ccw devices */
    addrs->next.cssid = 254;
    addrs->next.ssid = 0;
    addrs->next.devno = 0;
    addrs->next.assigned = 0;
    return addrs;

 error:
    virDomainCCWAddressSetFree(addrs);
    return NULL;
}


virDomainCCWAddressSetPtr
virDomainCCWAddressSetCreateFromDomain(virDomainDefPtr def)
{
    virDomainCCWAddressSetPtr addrs = NULL;

    if (!(addrs = virDomainCCWAddressSetCreate()))
        goto error;

    if (virDomainDeviceInfoIterate(def, virDomainCCWAddressValidate,
                                   addrs) < 0)
        goto error;

    if (virDomainDeviceInfoIterate(def, virDomainCCWAddressAllocate,
                                   addrs) < 0)
        goto error;

    return addrs;

 error:
    virDomainCCWAddressSetFree(addrs);
    return NULL;
}


#define VIR_DOMAIN_DEFAULT_VIRTIO_SERIAL_PORTS 31


/* virDomainVirtioSerialAddrSetCreate
 *
 * Allocates an address set for virtio serial addresses
 */
static virDomainVirtioSerialAddrSetPtr
virDomainVirtioSerialAddrSetCreate(void)
{
    virDomainVirtioSerialAddrSetPtr ret = NULL;

    ret = g_new0(virDomainVirtioSerialAddrSet, 1);

    return ret;
}

static void
virDomainVirtioSerialControllerFree(virDomainVirtioSerialControllerPtr cont)
{
    if (cont) {
        virBitmapFree(cont->ports);
        VIR_FREE(cont);
    }
}

static ssize_t
virDomainVirtioSerialAddrPlaceController(virDomainVirtioSerialAddrSetPtr addrs,
                                         virDomainVirtioSerialControllerPtr cont)
{
    size_t i;

    for (i = 0; i < addrs->ncontrollers; i++) {
        if (addrs->controllers[i]->idx == cont->idx) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("virtio serial controller with index %u already exists"
                             " in the address set"),
                           cont->idx);
            return -2;
        }
        if (addrs->controllers[i]->idx > cont->idx)
            return i;
    }
    return -1;
}

static ssize_t
virDomainVirtioSerialAddrFindController(virDomainVirtioSerialAddrSetPtr addrs,
                                        unsigned int idx)
{
    size_t i;

    for (i = 0; i < addrs->ncontrollers; i++) {
        if (addrs->controllers[i]->idx == idx)
            return i;
    }
    return -1;
}

/* virDomainVirtioSerialAddrSetAddController
 *
 * Adds virtio serial ports of the existing controller
 * to the address set.
 */
static int
virDomainVirtioSerialAddrSetAddController(virDomainVirtioSerialAddrSetPtr addrs,
                                          virDomainControllerDefPtr cont)
{
    int ret = -1;
    int ports;
    virDomainVirtioSerialControllerPtr cnt = NULL;
    ssize_t insertAt;

    if (cont->type != VIR_DOMAIN_CONTROLLER_TYPE_VIRTIO_SERIAL)
        return 0;

    ports = cont->opts.vioserial.ports;
    if (ports == -1)
        ports = VIR_DOMAIN_DEFAULT_VIRTIO_SERIAL_PORTS;

    VIR_DEBUG("Adding virtio serial controller index %u with %d"
              " ports to the address set", cont->idx, ports);

    cnt = g_new0(virDomainVirtioSerialController, 1);

    cnt->ports = virBitmapNew(ports);
    cnt->idx = cont->idx;

    if ((insertAt = virDomainVirtioSerialAddrPlaceController(addrs, cnt)) < -1)
        goto cleanup;
    if (VIR_INSERT_ELEMENT(addrs->controllers, insertAt,
                           addrs->ncontrollers, cnt) < 0)
        goto cleanup;

    ret = 0;

 cleanup:
    virDomainVirtioSerialControllerFree(cnt);
    return ret;
}

/* virDomainVirtioSerialAddrSetAddControllers
 *
 * Adds virtio serial ports of controllers present in the domain definition
 * to the address set.
 */
static int ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2)
virDomainVirtioSerialAddrSetAddControllers(virDomainVirtioSerialAddrSetPtr addrs,
                                           virDomainDefPtr def)
{
    size_t i;

    for (i = 0; i < def->ncontrollers; i++) {
        if (virDomainVirtioSerialAddrSetAddController(addrs,
                                                      def->controllers[i]) < 0)
            return -1;
    }

    return 0;
}


void
virDomainVirtioSerialAddrSetFree(virDomainVirtioSerialAddrSetPtr addrs)
{
    size_t i;
    if (addrs) {
        for (i = 0; i < addrs->ncontrollers; i++)
            virDomainVirtioSerialControllerFree(addrs->controllers[i]);
        VIR_FREE(addrs->controllers);
        VIR_FREE(addrs);
    }
}

/* virDomainVirtioSerialAddrReserve
 *
 * Reserve the virtio serial address of the device
 *
 * For use with virDomainDeviceInfoIterate,
 * opaque should be the address set
 */
static int ATTRIBUTE_NONNULL(3) ATTRIBUTE_NONNULL(4)
virDomainVirtioSerialAddrReserve(virDomainDefPtr def G_GNUC_UNUSED,
                                 virDomainDeviceDefPtr dev G_GNUC_UNUSED,
                                 virDomainDeviceInfoPtr info,
                                 void *data)
{
    virDomainVirtioSerialAddrSetPtr addrs = data;
    virBitmapPtr map = NULL;
    bool b;
    ssize_t i;

    if (!virDomainVirtioSerialAddrIsComplete(info))
        return 0;

    VIR_DEBUG("Reserving virtio serial %u %u", info->addr.vioserial.controller,
              info->addr.vioserial.port);

    i = virDomainVirtioSerialAddrFindController(addrs, info->addr.vioserial.controller);
    if (i < 0) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("virtio serial controller %u is missing"),
                       info->addr.vioserial.controller);
        return -1;
    }

    map = addrs->controllers[i]->ports;
    if (virBitmapGetBit(map, info->addr.vioserial.port, &b) < 0) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("virtio serial controller %u does not have port %u"),
                       info->addr.vioserial.controller,
                       info->addr.vioserial.port);
        return -1;
    }

    if (b) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("virtio serial port %u on controller %u is already occupied"),
                       info->addr.vioserial.port,
                       info->addr.vioserial.controller);
        return -1;
    }

    ignore_value(virBitmapSetBit(map, info->addr.vioserial.port));

    return 0;
}

/* virDomainVirtioSerialAddrSetCreateFromDomain
 *
 * @def: Domain def to introspect
 *
 * Inspect the domain definition and return an address set containing
 * every virtio serial address we find
 */
virDomainVirtioSerialAddrSetPtr
virDomainVirtioSerialAddrSetCreateFromDomain(virDomainDefPtr def)
{
    virDomainVirtioSerialAddrSetPtr addrs = NULL;
    virDomainVirtioSerialAddrSetPtr ret = NULL;

    if (!(addrs = virDomainVirtioSerialAddrSetCreate()))
        goto cleanup;

    if (virDomainVirtioSerialAddrSetAddControllers(addrs, def) < 0)
        goto cleanup;

    if (virDomainDeviceInfoIterate(def, virDomainVirtioSerialAddrReserve,
                                   addrs) < 0)
        goto cleanup;

    ret = g_steal_pointer(&addrs);
 cleanup:
    virDomainVirtioSerialAddrSetFree(addrs);
    return ret;
}

static int
virDomainVirtioSerialAddrSetAutoaddController(virDomainDefPtr def,
                                              virDomainVirtioSerialAddrSetPtr addrs,
                                              unsigned int idx)
{
    int contidx;

    if (virDomainDefMaybeAddController(def,
                                       VIR_DOMAIN_CONTROLLER_TYPE_VIRTIO_SERIAL,
                                       idx, -1) < 0)
        return -1;

    contidx = virDomainControllerFind(def, VIR_DOMAIN_CONTROLLER_TYPE_VIRTIO_SERIAL, idx);

    if (virDomainVirtioSerialAddrSetAddController(addrs, def->controllers[contidx]) < 0)
        return -1;

    return 0;
}

static int
virDomainVirtioSerialAddrNext(virDomainDefPtr def,
                              virDomainVirtioSerialAddrSetPtr addrs,
                              virDomainDeviceVirtioSerialAddress *addr,
                              bool allowZero)
{
    ssize_t port, startPort = 0;
    ssize_t i;
    unsigned int controller;

    /* port number 0 is reserved for virtconsoles */
    if (allowZero)
        startPort = -1;

    if (addrs->ncontrollers == 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("no virtio-serial controllers are available"));
        return -1;
    }

    for (i = 0; i < addrs->ncontrollers; i++) {
        virBitmapPtr map = addrs->controllers[i]->ports;
        if ((port = virBitmapNextClearBit(map, startPort)) >= 0) {
            controller = addrs->controllers[i]->idx;
            goto success;
        }
    }

    if (def) {
        for (i = 0; i < INT_MAX; i++) {
            int idx = virDomainControllerFind(def, VIR_DOMAIN_CONTROLLER_TYPE_VIRTIO_SERIAL, i);

            if (idx == -1) {
                if (virDomainVirtioSerialAddrSetAutoaddController(def, addrs, i) < 0)
                    return -1;
                controller = i;
                port = startPort + 1;
                goto success;
            }
        }
    }

    virReportError(VIR_ERR_XML_ERROR, "%s",
                   _("Unable to find a free virtio-serial port"));

    return -1;

 success:
    addr->bus = 0;
    addr->port = port;
    addr->controller = controller;
    VIR_DEBUG("Found free virtio serial controller %u port %u", addr->controller,
              addr->port);
    return 0;
}

static int
virDomainVirtioSerialAddrNextFromController(virDomainVirtioSerialAddrSetPtr addrs,
                                            virDomainDeviceVirtioSerialAddress *addr)
{
    ssize_t port;
    ssize_t i;
    virBitmapPtr map;

    i = virDomainVirtioSerialAddrFindController(addrs, addr->controller);
    if (i < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("virtio-serial controller %u not available"),
                       addr->controller);
        return -1;
    }

    map = addrs->controllers[i]->ports;
    if ((port = virBitmapNextClearBit(map, 0)) <= 0) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("Unable to find a free port on virtio-serial controller %u"),
                       addr->controller);
        return -1;
    }

    addr->bus = 0;
    addr->port = port;
    VIR_DEBUG("Found free virtio serial controller %u port %u", addr->controller,
              addr->port);
    return 0;
}

static int ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3)
virDomainVirtioSerialAddrAssign(virDomainDefPtr def,
                                virDomainVirtioSerialAddrSetPtr addrs,
                                virDomainDeviceInfoPtr info,
                                bool allowZero,
                                bool portOnly)
{
    virDomainDeviceInfo nfo = { 0 };
    virDomainDeviceInfoPtr ptr = allowZero ? &nfo : info;

    ptr->type = VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_SERIAL;

    if (portOnly) {
        if (virDomainVirtioSerialAddrNextFromController(addrs,
                                                        &ptr->addr.vioserial) < 0)
            return -1;
    } else {
        if (virDomainVirtioSerialAddrNext(def, addrs, &ptr->addr.vioserial,
                                          allowZero) < 0)
            return -1;
    }

    if (virDomainVirtioSerialAddrReserve(NULL, NULL, ptr, addrs) < 0)
        return -1;

    return 0;
}

/* virDomainVirtioSerialAddrAutoAssign
 *
 * reserve a virtio serial address of the device (if it has one)
 * or assign a virtio serial address to the device
 */
int
virDomainVirtioSerialAddrAutoAssignFromCache(virDomainDefPtr def,
                                             virDomainVirtioSerialAddrSetPtr addrs,
                                             virDomainDeviceInfoPtr info,
                                             bool allowZero)
{
    bool portOnly = info->type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_SERIAL;
    if (info->type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_SERIAL &&
        info->addr.vioserial.port)
        return virDomainVirtioSerialAddrReserve(NULL, NULL, info, addrs);
    else
        return virDomainVirtioSerialAddrAssign(def, addrs, info, allowZero, portOnly);
}

int
virDomainVirtioSerialAddrAutoAssign(virDomainDefPtr def,
                                    virDomainDeviceInfoPtr info,
                                    bool allowZero)
{
    virDomainVirtioSerialAddrSetPtr addrs = NULL;
    int ret = -1;

    if (!(addrs = virDomainVirtioSerialAddrSetCreateFromDomain(def)))
        goto cleanup;

    if (virDomainVirtioSerialAddrAutoAssignFromCache(def, addrs, info, allowZero) < 0)
        goto cleanup;

    ret = 0;

 cleanup:
    virDomainVirtioSerialAddrSetFree(addrs);
    return ret;
}


/* virDomainVirtioSerialAddrIsComplete
 *
 * Check if the address is complete, or it needs auto-assignment
 */
bool
virDomainVirtioSerialAddrIsComplete(virDomainDeviceInfoPtr info)
{
    return info->type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_SERIAL &&
        info->addr.vioserial.port != 0;
}


bool
virDomainUSBAddressPortIsValid(unsigned int *port)
{
    return port[0] != 0;
}


void
virDomainUSBAddressPortFormatBuf(virBufferPtr buf,
                                 unsigned int *port)
{
    size_t i;

    for (i = 0; i < VIR_DOMAIN_DEVICE_USB_MAX_PORT_DEPTH; i++) {
        if (port[i] == 0)
            break;
        virBufferAsprintf(buf, "%u.", port[i]);
    }
    virBufferTrim(buf, ".");
}


static char * ATTRIBUTE_NONNULL(1)
virDomainUSBAddressPortFormat(unsigned int *port)
{
    g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
    virDomainUSBAddressPortFormatBuf(&buf, port);
    return virBufferContentAndReset(&buf);
}


virDomainUSBAddressSetPtr
virDomainUSBAddressSetCreate(void)
{
    virDomainUSBAddressSetPtr addrs;

    addrs = g_new0(virDomainUSBAddressSet, 1);

    return addrs;
}


static void
virDomainUSBAddressHubFree(virDomainUSBAddressHubPtr hub)
{
    size_t i;

    if (!hub)
        return;

    for (i = 0; i < hub->nports; i++)
        virDomainUSBAddressHubFree(hub->ports[i]);
    VIR_FREE(hub->ports);
    virBitmapFree(hub->portmap);
    VIR_FREE(hub);
}


void
virDomainUSBAddressSetFree(virDomainUSBAddressSetPtr addrs)
{
    size_t i;

    if (!addrs)
        return;

    for (i = 0; i < addrs->nbuses; i++)
        virDomainUSBAddressHubFree(addrs->buses[i]);
    VIR_FREE(addrs->buses);
    VIR_FREE(addrs);
}


static size_t
virDomainUSBAddressControllerModelToPorts(virDomainControllerDefPtr cont)
{
    switch ((virDomainControllerModelUSB) cont->model) {
    case VIR_DOMAIN_CONTROLLER_MODEL_USB_DEFAULT:
    case VIR_DOMAIN_CONTROLLER_MODEL_USB_PIIX3_UHCI:
    case VIR_DOMAIN_CONTROLLER_MODEL_USB_PIIX4_UHCI:
    case VIR_DOMAIN_CONTROLLER_MODEL_USB_VT82C686B_UHCI:
        return 2;

    case VIR_DOMAIN_CONTROLLER_MODEL_USB_EHCI:
    case VIR_DOMAIN_CONTROLLER_MODEL_USB_ICH9_EHCI1:
        return 6;

    case VIR_DOMAIN_CONTROLLER_MODEL_USB_ICH9_UHCI1:
    case VIR_DOMAIN_CONTROLLER_MODEL_USB_ICH9_UHCI2:
    case VIR_DOMAIN_CONTROLLER_MODEL_USB_ICH9_UHCI3:
        /* These have two ports each and are used to provide USB1.1
         * ports while ICH9_EHCI1 provides 6 USB2.0 ports.
         * Ignore these since we will add the EHCI1 too. */
        return 0;

    case VIR_DOMAIN_CONTROLLER_MODEL_USB_PCI_OHCI:
        return 3;

    case VIR_DOMAIN_CONTROLLER_MODEL_USB_NEC_XHCI:
    case VIR_DOMAIN_CONTROLLER_MODEL_USB_QEMU_XHCI:
        if (cont->opts.usbopts.ports != -1)
            return cont->opts.usbopts.ports;
        return 4;

    case VIR_DOMAIN_CONTROLLER_MODEL_USB_QUSB1:
    case VIR_DOMAIN_CONTROLLER_MODEL_USB_QUSB2:
        if (cont->opts.usbopts.ports != -1)
            return cont->opts.usbopts.ports;
        return 8;

    case VIR_DOMAIN_CONTROLLER_MODEL_USB_NONE:
    case VIR_DOMAIN_CONTROLLER_MODEL_USB_LAST:
        break;
    }
    return 0;
}


static virDomainUSBAddressHubPtr
virDomainUSBAddressHubNew(size_t nports)
{
    virDomainUSBAddressHubPtr hub;

    hub = g_new0(virDomainUSBAddressHub, 1);

    hub->portmap = virBitmapNew(nports);

    hub->ports = g_new0(virDomainUSBAddressHubPtr, nports);
    hub->nports = nports;

    return hub;
}


static int
virDomainUSBAddressSetAddController(virDomainUSBAddressSetPtr addrs,
                                    virDomainControllerDefPtr cont)
{
    size_t nports = virDomainUSBAddressControllerModelToPorts(cont);
    virDomainUSBAddressHubPtr hub = NULL;
    int ret = -1;

    VIR_DEBUG("Adding a USB controller model=%s with %zu ports",
              virDomainControllerModelUSBTypeToString(cont->model),
              nports);

    /* Skip UHCI{1,2,3} companions; only add the EHCI1 */
    if (nports == 0)
        return 0;

    if (addrs->nbuses <= cont->idx) {
        if (VIR_EXPAND_N(addrs->buses, addrs->nbuses, cont->idx - addrs->nbuses + 1) < 0)
            goto cleanup;
    } else if (addrs->buses[cont->idx]) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("Duplicate USB controllers with index %u"),
                       cont->idx);
        goto cleanup;
    }

    if (!(hub = virDomainUSBAddressHubNew(nports)))
        goto cleanup;

    addrs->buses[cont->idx] = hub;
    hub = NULL;

    ret = 0;
 cleanup:
    virDomainUSBAddressHubFree(hub);
    return ret;
}


static ssize_t
virDomainUSBAddressGetLastIdx(virDomainDeviceInfoPtr info)
{
    ssize_t i;
    for (i = VIR_DOMAIN_DEVICE_USB_MAX_PORT_DEPTH - 1; i > 0; i--) {
        if (info->addr.usb.port[i] != 0)
            break;
    }
    return i;
}


/* Find the USBAddressHub structure representing the hub/controller
 * that corresponds to the bus/port path specified by info.
 * Returns the index of the requested port in targetIdx.
 */
static virDomainUSBAddressHubPtr
virDomainUSBAddressFindPort(virDomainUSBAddressSetPtr addrs,
                            virDomainDeviceInfoPtr info,
                            int *targetIdx,
                            const char *portStr)
{
    virDomainUSBAddressHubPtr hub = NULL;
    ssize_t i, lastIdx, targetPort;

    if (info->addr.usb.bus >= addrs->nbuses ||
        !addrs->buses[info->addr.usb.bus]) {
        virReportError(VIR_ERR_XML_ERROR, _("Missing USB bus %u"),
                       info->addr.usb.bus);
        return NULL;
    }
    hub = addrs->buses[info->addr.usb.bus];

    lastIdx = virDomainUSBAddressGetLastIdx(info);

    for (i = 0; i < lastIdx; i++) {
        /* ports are numbered from 1 */
        int portIdx = info->addr.usb.port[i] - 1;

        if (hub->nports <= portIdx) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("port %u out of range in USB address bus: %u port: %s"),
                           info->addr.usb.port[i],
                           info->addr.usb.bus,
                           portStr);
            return NULL;
        }
        hub = hub->ports[portIdx];
        if (!hub) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("there is no hub at port %u in USB address bus: %u port: %s"),
                           info->addr.usb.port[i],
                           info->addr.usb.bus,
                           portStr);
            return NULL;
        }
    }

    targetPort = info->addr.usb.port[lastIdx] - 1;
    if (targetPort >= virBitmapSize(hub->portmap)) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("requested USB port %s not present on USB bus %u"),
                       portStr, info->addr.usb.bus);
        return NULL;
    }

    *targetIdx = targetPort;
    return hub;
}


int
virDomainUSBAddressSetAddHub(virDomainUSBAddressSetPtr addrs,
                             virDomainHubDefPtr hub)
{
    virDomainUSBAddressHubPtr targetHub = NULL, newHub = NULL;
    int ret = -1;
    int targetPort;
    g_autofree char *portStr = NULL;

    if (hub->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_USB) {
        virReportError(VIR_ERR_XML_ERROR, "%s",
                       _("Wrong address type for USB hub"));
        goto cleanup;
    }

    if (!(portStr = virDomainUSBAddressPortFormat(hub->info.addr.usb.port)))
        goto cleanup;

    VIR_DEBUG("Adding a USB hub with 8 ports on bus=%u port=%s",
              hub->info.addr.usb.bus, portStr);

    if (!(newHub = virDomainUSBAddressHubNew(VIR_DOMAIN_USB_HUB_PORTS)))
        goto cleanup;

    if (!(targetHub = virDomainUSBAddressFindPort(addrs, &(hub->info), &targetPort,
                                                  portStr)))
        goto cleanup;

    if (targetHub->ports[targetPort]) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("Duplicate USB hub on bus %u port %s"),
                       hub->info.addr.usb.bus, portStr);
        goto cleanup;
    }
    ignore_value(virBitmapSetBit(targetHub->portmap, targetPort));
    targetHub->ports[targetPort] = newHub;
    newHub = NULL;

    ret = 0;
 cleanup:
    virDomainUSBAddressHubFree(newHub);
    return ret;
}


int
virDomainUSBAddressSetAddControllers(virDomainUSBAddressSetPtr addrs,
                                     virDomainDefPtr def)
{
    size_t i;

    for (i = 0; i < def->ncontrollers; i++) {
        virDomainControllerDefPtr cont = def->controllers[i];
        if (cont->type == VIR_DOMAIN_CONTROLLER_TYPE_USB) {
            if (virDomainUSBAddressSetAddController(addrs, cont) < 0)
                return -1;
        }
    }

    for (i = 0; i < def->nhubs; i++) {
        virDomainHubDefPtr hub = def->hubs[i];
        if (hub->type == VIR_DOMAIN_HUB_TYPE_USB &&
            hub->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_USB &&
            virDomainUSBAddressPortIsValid(hub->info.addr.usb.port)) {
            /* USB hubs that do not yet have an USB address have to be
             * dealt with later */
            if (virDomainUSBAddressSetAddHub(addrs, hub) < 0)
                return -1;
        }
    }
    return 0;
}


static int
virDomainUSBAddressFindFreePort(virDomainUSBAddressHubPtr hub,
                                unsigned int *portpath,
                                unsigned int level)
{
    unsigned int port;
    ssize_t portIdx;
    size_t i;

    /* Look for free ports on the current hub */
    if ((portIdx = virBitmapNextClearBit(hub->portmap, -1)) >= 0) {
        port = portIdx + 1;
        VIR_DEBUG("Found a free port %u at level %u", port, level);
        portpath[level] = port;
        return 0;
    }

    VIR_DEBUG("No ports found on hub %p, trying the hubs on it", hub);

    if (level >= VIR_DOMAIN_DEVICE_USB_MAX_PORT_DEPTH - 1)
        return -1;

    /* Recursively search through the ports that contain another hub */
    for (i = 0; i < hub->nports; i++) {
        if (!hub->ports[i])
            continue;

        port = i + 1;
        VIR_DEBUG("Looking at USB hub at level: %u port: %u", level, port);
        if (virDomainUSBAddressFindFreePort(hub->ports[i], portpath,
                                            level + 1) < 0)
            continue;

        portpath[level] = port;
        return 0;
    }
    return -1;
}


size_t
virDomainUSBAddressCountAllPorts(virDomainDefPtr def)
{
    size_t i, ret = 0;

    for (i = 0; i < def->ncontrollers; i++) {
        virDomainControllerDefPtr cont = def->controllers[i];
        if (cont->type == VIR_DOMAIN_CONTROLLER_TYPE_USB)
            ret += virDomainUSBAddressControllerModelToPorts(cont);
    }

    for (i = 0; i < def->nhubs; i++) {
        virDomainHubDefPtr hub = def->hubs[i];
        if (hub->type == VIR_DOMAIN_HUB_TYPE_USB)
            ret += VIR_DOMAIN_USB_HUB_PORTS;
    }
    return ret;
}


/* Try to find a free port on bus @bus.
 *
 * Returns  0 on success
 *         -1 on fatal error (OOM)
 *         -2 if there is no bus at @bus or no free port on this bus
 */
static int
virDomainUSBAddressAssignFromBus(virDomainUSBAddressSetPtr addrs,
                                 virDomainDeviceInfoPtr info,
                                 size_t bus)
{
    unsigned int portpath[VIR_DOMAIN_DEVICE_USB_MAX_PORT_DEPTH] = { 0 };
    virDomainUSBAddressHubPtr hub = addrs->buses[bus];
    g_autofree char *portStr = NULL;

    if (!hub)
        return -2;

    if (virDomainUSBAddressFindFreePort(hub, portpath, 0) < 0)
        return -2;

    /* we found a free port */
    if (!(portStr = virDomainUSBAddressPortFormat(portpath)))
        return -1;

    info->type = VIR_DOMAIN_DEVICE_ADDRESS_TYPE_USB;
    info->addr.usb.bus = bus;
    memcpy(info->addr.usb.port, portpath, sizeof(portpath));
    VIR_DEBUG("Assigning USB addr bus=%u port=%s",
              info->addr.usb.bus, portStr);
    if (virDomainUSBAddressReserve(info, addrs) < 0)
        return -1;

    return 0;
}


int
virDomainUSBAddressAssign(virDomainUSBAddressSetPtr addrs,
                          virDomainDeviceInfoPtr info)
{
    size_t i;
    int rc;

    if (info->type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_USB) {
        VIR_DEBUG("A USB port on bus %u was requested", info->addr.usb.bus);
        if (info->addr.usb.bus >= addrs->nbuses ||
            !addrs->buses[info->addr.usb.bus]) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("USB bus %u requested but no controller "
                             "with that index is present"), info->addr.usb.bus);
            return -1;
        }
        rc = virDomainUSBAddressAssignFromBus(addrs, info, info->addr.usb.bus);
        if (rc >= -1)
            return rc;
    } else {
        VIR_DEBUG("Looking for a free USB port on all the buses");
        for (i = 0; i < addrs->nbuses; i++) {
            rc = virDomainUSBAddressAssignFromBus(addrs, info, i);
            if (rc >= -1)
                return rc;
        }
    }

    virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("No free USB ports"));
    return -1;
}


int
virDomainUSBAddressPresent(virDomainDeviceInfoPtr info,
                           void *data G_GNUC_UNUSED)
{
    if (info->type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_USB &&
        virDomainUSBAddressPortIsValid(info->addr.usb.port))
        return 0;

    return -1;
}


int
virDomainUSBAddressReserve(virDomainDeviceInfoPtr info,
                           void *data)
{
    virDomainUSBAddressSetPtr addrs = data;
    virDomainUSBAddressHubPtr targetHub = NULL;
    g_autofree char *portStr = NULL;
    int targetPort;

    if (info->type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_USB)
        return 0;

    if (!virDomainUSBAddressPortIsValid(info->addr.usb.port))
        return 0;

    portStr = virDomainUSBAddressPortFormat(info->addr.usb.port);
    if (!portStr)
        return -1;
    VIR_DEBUG("Reserving USB address bus=%u port=%s", info->addr.usb.bus, portStr);

    if (!(targetHub = virDomainUSBAddressFindPort(addrs, info, &targetPort,
                                                  portStr)))
        return -1;

    if (virBitmapIsBitSet(targetHub->portmap, targetPort)) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("Duplicate USB address bus %u port %s"),
                       info->addr.usb.bus, portStr);
        return -1;
    }

    ignore_value(virBitmapSetBit(targetHub->portmap, targetPort));

    return 0;
}


int
virDomainUSBAddressEnsure(virDomainUSBAddressSetPtr addrs,
                          virDomainDeviceInfoPtr info)
{
    if (!addrs)
        return 0;

    if (info->type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE ||
        (info->type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_USB &&
         !virDomainUSBAddressPortIsValid(info->addr.usb.port))) {
        if (virDomainUSBAddressAssign(addrs, info) < 0)
            return -1;
    } else if (info->type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_USB) {
        if (virDomainUSBAddressReserve(info, addrs) < 0)
            return -1;
    }

    return 0;
}


int
virDomainUSBAddressRelease(virDomainUSBAddressSetPtr addrs,
                           virDomainDeviceInfoPtr info)
{
    virDomainUSBAddressHubPtr targetHub = NULL;
    g_autofree char *portStr = NULL;
    int targetPort;

    if (!addrs || info->type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_USB ||
        !virDomainUSBAddressPortIsValid(info->addr.usb.port))
        return 0;

    portStr = virDomainUSBAddressPortFormat(info->addr.usb.port);
    VIR_DEBUG("Releasing USB addr bus=%u port=%s", info->addr.usb.bus, portStr);

    if (!(targetHub = virDomainUSBAddressFindPort(addrs, info, &targetPort,
                                                  portStr)))
        return -1;

    ignore_value(virBitmapClearBit(targetHub->portmap, targetPort));

    return 0;
}
