virDomainControllerDefParseXML(virDomainXMLOptionPtr xmlopt,
                               xmlNodePtr node,
                               xmlXPathContextPtr ctxt,
                               unsigned int flags)
{
    virDomainControllerDefPtr def = NULL;
    int type = 0;
    xmlNodePtr cur = NULL;
    bool processedModel = false;
    bool processedTarget = false;
    int numaNode = -1;
    int ports = -1;
    VIR_XPATH_NODE_AUTORESTORE(ctxt);
    int rc;
    g_autofree char *typeStr = NULL;
    g_autofree char *idx = NULL;
    g_autofree char *model = NULL;
    g_autofree char *queues = NULL;
    g_autofree char *cmd_per_lun = NULL;
    g_autofree char *max_sectors = NULL;
    g_autofree char *modelName = NULL;
    g_autofree char *chassisNr = NULL;
    g_autofree char *chassis = NULL;
    g_autofree char *port = NULL;
    g_autofree char *busNr = NULL;
    g_autofree char *targetIndex = NULL;
    g_autofree char *hotplug = NULL;
    g_autofree char *ioeventfd = NULL;
    g_autofree char *portsStr = NULL;
    g_autofree char *iothread = NULL;

    ctxt->node = node;

    typeStr = virXMLPropString(node, "type");
    if (typeStr) {
        if ((type = virDomainControllerTypeFromString(typeStr)) < 0) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                           _("Unknown controller type '%s'"), typeStr);
            goto error;
        }
    }

    if (!(def = virDomainControllerDefNew(type)))
        goto error;

    model = virXMLPropString(node, "model");
    if (model) {
        if ((def->model = virDomainControllerModelTypeFromString(def, model)) < 0) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                           _("Unknown model type '%s'"), model);
            goto error;
        }
    }

    idx = virXMLPropString(node, "index");
    if (idx) {
        unsigned int idxVal;
        if (virStrToLong_ui(idx, NULL, 10, &idxVal) < 0 ||
            idxVal > INT_MAX) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("Cannot parse controller index %s"), idx);
            goto error;
        }
        def->idx = idxVal;
    }

    cur = node->children;
    while (cur != NULL) {
        if (cur->type == XML_ELEMENT_NODE) {
            if (virXMLNodeNameEqual(cur, "driver")) {
                queues = virXMLPropString(cur, "queues");
                cmd_per_lun = virXMLPropString(cur, "cmd_per_lun");
                max_sectors = virXMLPropString(cur, "max_sectors");
                ioeventfd = virXMLPropString(cur, "ioeventfd");
                iothread = virXMLPropString(cur, "iothread");

                if (virDomainVirtioOptionsParseXML(cur, &def->virtio) < 0)
                    goto error;
            } else if (virXMLNodeNameEqual(cur, "model")) {
                if (processedModel) {
                    virReportError(VIR_ERR_XML_ERROR, "%s",
                                   _("Multiple <model> elements in "
                                     "controller definition not allowed"));
                    goto error;
                }
                modelName = virXMLPropString(cur, "name");
                processedModel = true;
            } else if (virXMLNodeNameEqual(cur, "target")) {
                if (processedTarget) {
                    virReportError(VIR_ERR_XML_ERROR, "%s",
                                   _("Multiple <target> elements in "
                                     "controller definition not allowed"));
                    goto error;
                }
                chassisNr = virXMLPropString(cur, "chassisNr");
                chassis = virXMLPropString(cur, "chassis");
                port = virXMLPropString(cur, "port");
                busNr = virXMLPropString(cur, "busNr");
                hotplug = virXMLPropString(cur, "hotplug");
                targetIndex = virXMLPropString(cur, "index");
                processedTarget = true;
            }
        }
        cur = cur->next;
    }

    /* node is parsed differently from target attributes because
     * someone thought it should be a subelement instead...
     */
    rc = virXPathInt("string(./target/node)", ctxt, &numaNode);
    if (rc == -2 || (rc == 0 && numaNode < 0)) {
        virReportError(VIR_ERR_XML_ERROR, "%s",
                       _("invalid NUMA node in target"));
        goto error;
    }

    if (queues && virStrToLong_ui(queues, NULL, 10, &def->queues) < 0) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("Malformed 'queues' value '%s'"), queues);
        goto error;
    }

    if (cmd_per_lun && virStrToLong_ui(cmd_per_lun, NULL, 10, &def->cmd_per_lun) < 0) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("Malformed 'cmd_per_lun' value '%s'"), cmd_per_lun);
        goto error;
    }

    if (max_sectors && virStrToLong_ui(max_sectors, NULL, 10, &def->max_sectors) < 0) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("Malformed 'max_sectors' value %s"), max_sectors);
        goto error;
    }

    if (ioeventfd &&
        (def->ioeventfd = virTristateSwitchTypeFromString(ioeventfd)) < 0) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("Malformed 'ioeventfd' value %s"), ioeventfd);
        goto error;
    }

    if (iothread) {
        if (virStrToLong_uip(iothread, NULL, 10, &def->iothread) < 0) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("Invalid 'iothread' value '%s'"), iothread);
            goto error;
        }
    }

    if (def->type == VIR_DOMAIN_CONTROLLER_TYPE_USB &&
        def->model == VIR_DOMAIN_CONTROLLER_MODEL_USB_NONE) {
        VIR_DEBUG("Ignoring device address for none model usb controller");
    } else if (virDomainDeviceInfoParseXML(xmlopt, node,
                                           &def->info, flags) < 0) {
        goto error;
    }

    portsStr = virXMLPropString(node, "ports");
    if (portsStr) {
        int r = virStrToLong_i(portsStr, NULL, 10, &ports);
        if (r != 0 || ports < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("Invalid ports: %s"), portsStr);
            goto error;
        }
    }

    switch (def->type) {
    case VIR_DOMAIN_CONTROLLER_TYPE_VIRTIO_SERIAL: {
        g_autofree char *vectors = virXMLPropString(node, "vectors");

        def->opts.vioserial.ports = ports;
        if (vectors) {
            int r = virStrToLong_i(vectors, NULL, 10,
                                   &def->opts.vioserial.vectors);
            if (r != 0 || def->opts.vioserial.vectors < 0) {
                virReportError(VIR_ERR_INTERNAL_ERROR,
                               _("Invalid vectors: %s"), vectors);
                goto error;
            }
        }
        break;
    }
    case VIR_DOMAIN_CONTROLLER_TYPE_USB: {
        /* If the XML has a uhci1, uhci2, uhci3 controller and no
         * master port was given, we should set a sensible one */
        int masterPort = -1;
        switch (def->model) {
        case VIR_DOMAIN_CONTROLLER_MODEL_USB_ICH9_UHCI1:
            masterPort = 0;
            break;
        case VIR_DOMAIN_CONTROLLER_MODEL_USB_ICH9_UHCI2:
            masterPort = 2;
            break;
        case VIR_DOMAIN_CONTROLLER_MODEL_USB_ICH9_UHCI3:
            masterPort = 4;
            break;
        }
        if (masterPort != -1 &&
            def->info.mastertype == VIR_DOMAIN_CONTROLLER_MASTER_NONE) {
            def->info.mastertype = VIR_DOMAIN_CONTROLLER_MASTER_USB;
            def->info.master.usb.startport = masterPort;
        }

        def->opts.usbopts.ports = ports;
        break;
    }
    case VIR_DOMAIN_CONTROLLER_TYPE_PCI:
        switch ((virDomainControllerModelPCI) def->model) {
        case VIR_DOMAIN_CONTROLLER_MODEL_PCI_ROOT:
        case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT: {
            unsigned long long bytes;
            if (def->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE) {
                virReportError(VIR_ERR_XML_ERROR, "%s",
                               _("pci-root and pcie-root controllers should not "
                                 "have an address"));
                goto error;
            }
            if ((rc = virDomainParseScaledValue("./pcihole64", NULL,
                                                ctxt, &bytes, 1024,
                                                1024ULL * ULONG_MAX, false)) < 0)
                goto error;

            if (rc == 1)
                def->opts.pciopts.pcihole64 = true;
            def->opts.pciopts.pcihole64size = VIR_DIV_UP(bytes, 1024);
        }
        case VIR_DOMAIN_CONTROLLER_MODEL_PCI_BRIDGE:
        case VIR_DOMAIN_CONTROLLER_MODEL_DMI_TO_PCI_BRIDGE:
        case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_TO_PCI_BRIDGE:
        case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT_PORT:
        case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_SWITCH_UPSTREAM_PORT:
        case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_SWITCH_DOWNSTREAM_PORT:
        case VIR_DOMAIN_CONTROLLER_MODEL_PCI_EXPANDER_BUS:
        case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_EXPANDER_BUS:
        case VIR_DOMAIN_CONTROLLER_MODEL_PCI_DEFAULT:
        case VIR_DOMAIN_CONTROLLER_MODEL_PCI_LAST:
            /* Other controller models don't require extra checks */
            break;
        }
        if (modelName &&
            (def->opts.pciopts.modelName
             = virDomainControllerPCIModelNameTypeFromString(modelName)) <= 0) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                           _("Unknown PCI controller model name '%s'"),
                           modelName);
            goto error;
        }
        if (chassisNr) {
            if (virStrToLong_i(chassisNr, NULL, 0,
                               &def->opts.pciopts.chassisNr) < 0) {
                virReportError(VIR_ERR_XML_ERROR,
                               _("Invalid chassisNr '%s' in PCI controller"),
                               chassisNr);
                goto error;
            }
            if (def->opts.pciopts.chassisNr < 1 ||
                def->opts.pciopts.chassisNr > 255) {
                virReportError(VIR_ERR_XML_ERROR,
                               _("PCI controller chassisNr '%s' out of range "
                                 "- must be 1-255"),
                               chassisNr);
                goto error;
            }
        }
        if (chassis) {
            if (virStrToLong_i(chassis, NULL, 0,
                               &def->opts.pciopts.chassis) < 0) {
                virReportError(VIR_ERR_XML_ERROR,
                               _("Invalid chassis '%s' in PCI controller"),
                               chassis);
                goto error;
            }
            if (def->opts.pciopts.chassis < 0 ||
                def->opts.pciopts.chassis > 255) {
                virReportError(VIR_ERR_XML_ERROR,
                               _("PCI controller chassis '%s' out of range "
                                 "- must be 0-255"),
                               chassis);
                goto error;
            }
        }
        if (port) {
            if (virStrToLong_i(port, NULL, 0,
                               &def->opts.pciopts.port) < 0) {
                virReportError(VIR_ERR_XML_ERROR,
                               _("Invalid port '%s' in PCI controller"),
                               port);
                goto error;
            }
            if (def->opts.pciopts.port < 0 ||
                def->opts.pciopts.port > 255) {
                virReportError(VIR_ERR_XML_ERROR,
                               _("PCI controller port '%s' out of range "
                                 "- must be 0-255"),
                               port);
                goto error;
            }
        }
        if (busNr) {
            if (virStrToLong_i(busNr, NULL, 0,
                               &def->opts.pciopts.busNr) < 0) {
                virReportError(VIR_ERR_XML_ERROR,
                               _("Invalid busNr '%s' in PCI controller"),
                               busNr);
                goto error;
            }
            if (def->opts.pciopts.busNr < 1 ||
                def->opts.pciopts.busNr > 254) {
                virReportError(VIR_ERR_XML_ERROR,
                               _("PCI controller busNr '%s' out of range "
                                 "- must be 1-254"),
                               busNr);
                goto error;
            }
        }
        if (targetIndex) {
            if (virStrToLong_i(targetIndex, NULL, 0,
                               &def->opts.pciopts.targetIndex) < 0 ||
                def->opts.pciopts.targetIndex == -1) {
                virReportError(VIR_ERR_XML_ERROR,
                               _("Invalid target index '%s' in PCI controller"),
                               targetIndex);
                goto error;
            }
        }
        if (numaNode >= 0) {
            if (def->idx == 0) {
                virReportError(VIR_ERR_XML_ERROR, "%s",
                               _("The PCI controller with index=0 can't "
                                 "be associated with a NUMA node"));
                goto error;
            }
            def->opts.pciopts.numaNode = numaNode;
        }
        if (hotplug) {
            int val = virTristateSwitchTypeFromString(hotplug);

            if (val <= 0) {
                virReportError(VIR_ERR_XML_ERROR,
                               _("PCI controller unrecognized hotplug setting '%s'"),
                               hotplug);
                goto error;
            }
            def->opts.pciopts.hotplug = val;
        }
        break;
    case VIR_DOMAIN_CONTROLLER_TYPE_XENBUS: {
        g_autofree char *gntframes = virXMLPropString(node, "maxGrantFrames");
        g_autofree char *eventchannels = virXMLPropString(node, "maxEventChannels");

        if (gntframes) {
            int r = virStrToLong_i(gntframes, NULL, 10,
                                   &def->opts.xenbusopts.maxGrantFrames);
            if (r != 0 || def->opts.xenbusopts.maxGrantFrames < 0) {
                virReportError(VIR_ERR_INTERNAL_ERROR,
                               _("Invalid maxGrantFrames: %s"), gntframes);
                goto error;
            }
        }
        if (eventchannels) {
            int r = virStrToLong_i(eventchannels, NULL, 10,
                                   &def->opts.xenbusopts.maxEventChannels);
            if (r != 0 || def->opts.xenbusopts.maxEventChannels < 0) {
                virReportError(VIR_ERR_INTERNAL_ERROR,
                               _("Invalid maxEventChannels: %s"), eventchannels);
                goto error;
            }
        }
        break;
    }

    default:
        break;
    }

    return def;

 error:
    virDomainControllerDefFree(def);
    return NULL;
}